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