1 use crate::auxv_reader::AuxvType;
2 use crate::errors::SectionDsoDebugError;
3 use crate::linux_ptrace_dumper::LinuxPtraceDumper;
4 use crate::minidump_format::*;
5 use crate::sections::{write_string_to_location, MemoryArrayWriter, MemoryWriter};
6 use libc;
7 use std::collections::HashMap;
8 use std::io::Cursor;
9
10 type Result<T> = std::result::Result<T, SectionDsoDebugError>;
11
12 #[cfg(all(target_pointer_width = "32"))]
13 use goblin::elf::program_header::program_header32::SIZEOF_PHDR;
14 #[cfg(all(target_pointer_width = "64"))]
15 use goblin::elf::program_header::program_header64::SIZEOF_PHDR;
16
17 #[cfg(all(target_pointer_width = "64", target_arch = "arm"))]
18 type ElfAddr = u64;
19 #[cfg(all(target_pointer_width = "64", not(target_arch = "arm")))]
20 type ElfAddr = libc::Elf64_Addr;
21 #[cfg(all(target_pointer_width = "32", target_arch = "arm"))]
22 type ElfAddr = u32;
23 #[cfg(all(target_pointer_width = "32", not(target_arch = "arm")))]
24 type ElfAddr = libc::Elf32_Addr;
25
26 // COPY from <link.h>
27 #[derive(Debug, Clone, Default)]
28 #[repr(C)]
29 pub struct LinkMap {
30 /* These first few members are part of the protocol with the debugger.
31 This is the same format used in SVR4. */
32 l_addr: ElfAddr, /* Difference between the address in the ELF
33 file and the addresses in memory. */
34 l_name: usize, /* Absolute file name object was found in. WAS: `char*` */
35 l_ld: usize, /* Dynamic section of the shared object. WAS: `ElfW(Dyn) *` */
36 l_next: usize, /* Chain of loaded objects. WAS: `struct link_map *` */
37 l_prev: usize, /* Chain of loaded objects. WAS: `struct link_map *` */
38 }
39
40 // COPY from <link.h>
41 #[derive(Debug, Clone)]
42 #[allow(non_camel_case_types, unused)]
43 #[repr(C)]
44 enum RState {
45 /* This state value describes the mapping change taking place when
46 the `r_brk' address is called. */
47 RT_CONSISTENT, /* Mapping change is complete. */
48 RT_ADD, /* Beginning to add a new object. */
49 RT_DELETE, /* Beginning to remove an object mapping. */
50 }
51 impl Default for RState {
default() -> Self52 fn default() -> Self {
53 RState::RT_CONSISTENT // RStates are not used anyway
54 }
55 }
56 // COPY from <link.h>
57 #[derive(Debug, Clone, Default)]
58 #[repr(C)]
59 pub struct RDebug {
60 r_version: libc::c_int, /* Version number for this protocol. */
61 r_map: usize, /* Head of the chain of loaded objects. WAS: `struct link_map *` */
62
63 /* This is the address of a function internal to the run-time linker,
64 that will always be called when the linker begins to map in a
65 library or unmap it, and again when the mapping change is complete.
66 The debugger can set a breakpoint at this address if it wants to
67 notice shared object mapping changes. */
68 r_brk: ElfAddr,
69 r_state: RState,
70 r_ldbase: ElfAddr, /* Base address the linker is loaded at. */
71 }
72
write_dso_debug_stream( buffer: &mut Cursor<Vec<u8>>, blamed_thread: i32, auxv: &HashMap<AuxvType, AuxvType>, ) -> Result<MDRawDirectory>73 pub fn write_dso_debug_stream(
74 buffer: &mut Cursor<Vec<u8>>,
75 blamed_thread: i32,
76 auxv: &HashMap<AuxvType, AuxvType>,
77 ) -> Result<MDRawDirectory> {
78 let at_phnum;
79 let at_phdr;
80 #[cfg(target_arch = "arm")]
81 {
82 at_phdr = 3;
83 at_phnum = 5;
84 }
85 #[cfg(not(target_arch = "arm"))]
86 {
87 at_phdr = libc::AT_PHDR;
88 at_phnum = libc::AT_PHNUM;
89 }
90 let phnum_max = *auxv
91 .get(&at_phnum)
92 .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))?
93 as usize;
94 let phdr = *auxv
95 .get(&at_phdr)
96 .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize;
97
98 let ph = LinuxPtraceDumper::copy_from_process(
99 blamed_thread,
100 phdr as *mut libc::c_void,
101 SIZEOF_PHDR * phnum_max,
102 )?;
103 let program_headers;
104 #[cfg(target_pointer_width = "64")]
105 {
106 program_headers = goblin::elf::program_header::program_header64::ProgramHeader::from_bytes(
107 &ph, phnum_max,
108 );
109 }
110 #[cfg(target_pointer_width = "32")]
111 {
112 program_headers = goblin::elf::program_header::program_header32::ProgramHeader::from_bytes(
113 &ph, phnum_max,
114 );
115 };
116
117 // Assume the program base is at the beginning of the same page as the PHDR
118 let mut base = phdr & !0xfff;
119 let mut dyn_addr = 0 as ElfAddr;
120 // Search for the program PT_DYNAMIC segment
121 for ph in program_headers {
122 // Adjust base address with the virtual address of the PT_LOAD segment
123 // corresponding to offset 0
124 if ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_offset == 0 {
125 base -= ph.p_vaddr as usize;
126 }
127 if ph.p_type == goblin::elf::program_header::PT_DYNAMIC {
128 dyn_addr = ph.p_vaddr;
129 }
130 }
131
132 if dyn_addr == 0 {
133 return Err(SectionDsoDebugError::CouldNotFind(
134 "dyn_addr in program headers",
135 ));
136 }
137
138 dyn_addr += base as ElfAddr;
139
140 let dyn_size = std::mem::size_of::<goblin::elf::Dyn>();
141 let mut r_debug = 0usize;
142 let mut dynamic_length = 0usize;
143
144 // The dynamic linker makes information available that helps gdb find all
145 // DSOs loaded into the program. If this information is indeed available,
146 // dump it to a MD_LINUX_DSO_DEBUG stream.
147 loop {
148 let dyn_data = LinuxPtraceDumper::copy_from_process(
149 blamed_thread,
150 (dyn_addr as usize + dynamic_length) as *mut libc::c_void,
151 dyn_size,
152 )?;
153 dynamic_length += dyn_size;
154
155 // goblin::elf::Dyn doesn't have padding bytes
156 let (head, body, _tail) = unsafe { dyn_data.align_to::<goblin::elf::Dyn>() };
157 assert!(head.is_empty(), "Data was not aligned");
158 let dyn_struct = &body[0];
159
160 // #ifdef __mips__
161 // const int32_t debug_tag = DT_MIPS_RLD_MAP;
162 // #else
163 // const int32_t debug_tag = DT_DEBUG;
164 // #endif
165 let debug_tag = goblin::elf::dynamic::DT_DEBUG;
166 if dyn_struct.d_tag == debug_tag {
167 r_debug = dyn_struct.d_val as usize;
168 } else if dyn_struct.d_tag == goblin::elf::dynamic::DT_NULL {
169 break;
170 }
171 }
172
173 // The "r_map" field of that r_debug struct contains a linked list of all
174 // loaded DSOs.
175 // Our list of DSOs potentially is different from the ones in the crashing
176 // process. So, we have to be careful to never dereference pointers
177 // directly. Instead, we use CopyFromProcess() everywhere.
178 // See <link.h> for a more detailed discussion of the how the dynamic
179 // loader communicates with debuggers.
180
181 let debug_entry_data = LinuxPtraceDumper::copy_from_process(
182 blamed_thread,
183 r_debug as *mut libc::c_void,
184 std::mem::size_of::<RDebug>(),
185 )?;
186
187 // goblin::elf::Dyn doesn't have padding bytes
188 let (head, body, _tail) = unsafe { debug_entry_data.align_to::<RDebug>() };
189 assert!(head.is_empty(), "Data was not aligned");
190 let debug_entry = &body[0];
191
192 // Count the number of loaded DSOs
193 let mut dso_vec = Vec::new();
194 let mut curr_map = debug_entry.r_map;
195 while curr_map != 0 {
196 let link_map_data = LinuxPtraceDumper::copy_from_process(
197 blamed_thread,
198 curr_map as *mut libc::c_void,
199 std::mem::size_of::<LinkMap>(),
200 )?;
201
202 // LinkMap is repr(C) and doesn't have padding bytes, so this should be safe
203 let (head, body, _tail) = unsafe { link_map_data.align_to::<LinkMap>() };
204 assert!(head.is_empty(), "Data was not aligned");
205 let map = &body[0];
206
207 curr_map = map.l_next;
208 dso_vec.push(map.clone());
209 }
210
211 let mut linkmap_rva = u32::MAX;
212 if dso_vec.len() > 0 {
213 // If we have at least one DSO, create an array of MDRawLinkMap
214 // entries in the minidump file.
215 let mut linkmap = MemoryArrayWriter::<MDRawLinkMap>::alloc_array(buffer, dso_vec.len())?;
216 linkmap_rva = linkmap.location().rva;
217
218 // Iterate over DSOs and write their information to mini dump
219 for (idx, map) in dso_vec.iter().enumerate() {
220 let mut filename = String::new();
221 if map.l_name > 0 {
222 let filename_data = LinuxPtraceDumper::copy_from_process(
223 blamed_thread,
224 map.l_name as *mut libc::c_void,
225 256,
226 )?;
227
228 // C - string is NULL-terminated
229 if let Some(name) = filename_data.splitn(2, |x| *x == b'\0').next() {
230 filename = String::from_utf8(name.to_vec())?;
231 }
232 }
233 let location = write_string_to_location(buffer, &filename)?;
234 let entry = MDRawLinkMap {
235 addr: map.l_addr,
236 name: location.rva,
237 ld: map.l_ld as ElfAddr,
238 };
239
240 linkmap.set_value_at(buffer, entry, idx)?;
241 }
242 }
243
244 // Write MD_LINUX_DSO_DEBUG record
245 let debug = MDRawDebug {
246 version: debug_entry.r_version as u32,
247 map: linkmap_rva,
248 dso_count: dso_vec.len() as u32,
249 brk: debug_entry.r_brk,
250 ldbase: debug_entry.r_ldbase,
251 dynamic: dyn_addr,
252 };
253 let debug_loc = MemoryWriter::<MDRawDebug>::alloc_with_val(buffer, debug)?;
254
255 let mut dirent = MDRawDirectory {
256 stream_type: MDStreamType::LinuxDsoDebug as u32,
257 location: debug_loc.location(),
258 };
259
260 dirent.location.data_size += dynamic_length as u32;
261 let dso_debug_data = LinuxPtraceDumper::copy_from_process(
262 blamed_thread,
263 dyn_addr as *mut libc::c_void,
264 dynamic_length,
265 )?;
266 MemoryArrayWriter::<u8>::alloc_from_array(buffer, &dso_debug_data)?;
267
268 Ok(dirent)
269 }
270