Bug 1689358 - Generate minidumps for child process crashes using the minidump-writer...
[gecko.git] / third_party / rust / minidump-writer / src / linux / sections / thread_list_stream.rs
blob648aef9869ada6d6f64111b2553041dc635c00a5
1 use std::cmp::min;
3 use super::*;
4 use crate::{minidump_cpu::RawContextCPU, minidump_writer::CrashingThreadContext};
6 // The following kLimit* constants are for when minidump_size_limit_ is set
7 // and the minidump size might exceed it.
8 //
9 // Estimate for how big each thread's stack will be (in bytes).
10 const LIMIT_AVERAGE_THREAD_STACK_LENGTH: usize = 8 * 1024;
11 // Number of threads whose stack size we don't want to limit.  These base
12 // threads will simply be the first N threads returned by the dumper (although
13 // the crashing thread will never be limited).  Threads beyond this count are
14 // the extra threads.
15 const LIMIT_BASE_THREAD_COUNT: usize = 20;
16 // Maximum stack size to dump for any extra thread (in bytes).
17 const LIMIT_MAX_EXTRA_THREAD_STACK_LEN: usize = 2 * 1024;
18 // Make sure this number of additional bytes can fit in the minidump
19 // (exclude the stack data).
20 const LIMIT_MINIDUMP_FUDGE_FACTOR: u64 = 64 * 1024;
22 #[derive(Debug, Clone, Copy)]
23 enum MaxStackLen {
24     None,
25     Len(usize),
28 pub fn write(
29     config: &mut MinidumpWriter,
30     buffer: &mut DumpBuf,
31     dumper: &PtraceDumper,
32 ) -> Result<MDRawDirectory, errors::SectionThreadListError> {
33     let num_threads = dumper.threads.len();
34     // Memory looks like this:
35     // <num_threads><thread_1><thread_2>...
37     let list_header = MemoryWriter::<u32>::alloc_with_val(buffer, num_threads as u32)?;
39     let mut dirent = MDRawDirectory {
40         stream_type: MDStreamType::ThreadListStream as u32,
41         location: list_header.location(),
42     };
44     let mut thread_list = MemoryArrayWriter::<MDRawThread>::alloc_array(buffer, num_threads)?;
45     dirent.location.data_size += thread_list.location().data_size;
46     // If there's a minidump size limit, check if it might be exceeded.  Since
47     // most of the space is filled with stack data, just check against that.
48     // If this expects to exceed the limit, set extra_thread_stack_len such
49     // that any thread beyond the first kLimitBaseThreadCount threads will
50     // have only kLimitMaxExtraThreadStackLen bytes dumped.
51     let mut extra_thread_stack_len = MaxStackLen::None; // default to no maximum
52     if let Some(minidump_size_limit) = config.minidump_size_limit {
53         let estimated_total_stack_size = (num_threads * LIMIT_AVERAGE_THREAD_STACK_LENGTH) as u64;
54         let curr_pos = buffer.position();
55         let estimated_minidump_size =
56             curr_pos + estimated_total_stack_size + LIMIT_MINIDUMP_FUDGE_FACTOR;
57         if estimated_minidump_size > minidump_size_limit {
58             extra_thread_stack_len = MaxStackLen::Len(LIMIT_MAX_EXTRA_THREAD_STACK_LEN);
59         }
60     }
62     for (idx, item) in dumper.threads.clone().iter().enumerate() {
63         let mut thread = MDRawThread {
64             thread_id: item.tid.try_into()?,
65             suspend_count: 0,
66             priority_class: 0,
67             priority: 0,
68             teb: 0,
69             stack: MDMemoryDescriptor::default(),
70             thread_context: MDLocationDescriptor::default(),
71         };
73         // We have a different source of information for the crashing thread. If
74         // we used the actual state of the thread we would find it running in the
75         // signal handler with the alternative stack, which would be deeply
76         // unhelpful.
77         if config.crash_context.is_some() && thread.thread_id == config.blamed_thread as u32 {
78             let crash_context = config.crash_context.as_ref().unwrap();
79             let instruction_ptr = crash_context.get_instruction_pointer();
80             let stack_pointer = crash_context.get_stack_pointer();
81             fill_thread_stack(
82                 config,
83                 buffer,
84                 dumper,
85                 &mut thread,
86                 instruction_ptr,
87                 stack_pointer,
88                 MaxStackLen::None,
89             )?;
90             // Copy 256 bytes around crashing instruction pointer to minidump.
91             let ip_memory_size: usize = 256;
92             // Bound it to the upper and lower bounds of the memory map
93             // it's contained within. If it's not in mapped memory,
94             // don't bother trying to write it.
95             for mapping in &dumper.mappings {
96                 if instruction_ptr < mapping.start_address
97                     || instruction_ptr >= mapping.start_address + mapping.size
98                 {
99                     continue;
100                 }
101                 // Try to get 128 bytes before and after the IP, but
102                 // settle for whatever's available.
103                 let mut ip_memory_d = MDMemoryDescriptor {
104                     start_of_memory_range: std::cmp::max(
105                         mapping.start_address,
106                         instruction_ptr - ip_memory_size / 2,
107                     ) as u64,
108                     ..Default::default()
109                 };
111                 let end_of_range = std::cmp::min(
112                     mapping.start_address + mapping.size,
113                     instruction_ptr + ip_memory_size / 2,
114                 ) as u64;
115                 ip_memory_d.memory.data_size =
116                     (end_of_range - ip_memory_d.start_of_memory_range) as u32;
118                 let memory_copy = PtraceDumper::copy_from_process(
119                     thread.thread_id as i32,
120                     ip_memory_d.start_of_memory_range as *mut libc::c_void,
121                     ip_memory_d.memory.data_size as usize,
122                 )?;
124                 let mem_section = MemoryArrayWriter::alloc_from_array(buffer, &memory_copy)?;
125                 ip_memory_d.memory = mem_section.location();
126                 config.memory_blocks.push(ip_memory_d);
128                 break;
129             }
130             // let cpu = MemoryWriter::alloc(buffer, &memory_copy)?;
131             let mut cpu: RawContextCPU = Default::default();
132             let crash_context = config.crash_context.as_ref().unwrap();
133             crash_context.fill_cpu_context(&mut cpu);
134             let cpu_section = MemoryWriter::alloc_with_val(buffer, cpu)?;
135             thread.thread_context = cpu_section.location();
137             config.crashing_thread_context =
138                 CrashingThreadContext::CrashContext(cpu_section.location());
139         } else {
140             let info = dumper.get_thread_info_by_index(idx)?;
141             let max_stack_len =
142                 if config.minidump_size_limit.is_some() && idx >= LIMIT_BASE_THREAD_COUNT {
143                     extra_thread_stack_len
144                 } else {
145                     MaxStackLen::None // default to no maximum for this thread
146                 };
147             let instruction_ptr = info.get_instruction_pointer();
148             fill_thread_stack(
149                 config,
150                 buffer,
151                 dumper,
152                 &mut thread,
153                 instruction_ptr,
154                 info.stack_pointer,
155                 max_stack_len,
156             )?;
158             let mut cpu = RawContextCPU::default();
159             info.fill_cpu_context(&mut cpu);
160             let cpu_section = MemoryWriter::<RawContextCPU>::alloc_with_val(buffer, cpu)?;
161             thread.thread_context = cpu_section.location();
162             if item.tid == config.blamed_thread {
163                 // This is the crashing thread of a live process, but
164                 // no context was provided, so set the crash address
165                 // while the instruction pointer is already here.
166                 config.crashing_thread_context = CrashingThreadContext::CrashContextPlusAddress((
167                     cpu_section.location(),
168                     instruction_ptr,
169                 ));
170             }
171         }
172         thread_list.set_value_at(buffer, thread, idx)?;
173     }
174     Ok(dirent)
177 fn fill_thread_stack(
178     config: &mut MinidumpWriter,
179     buffer: &mut DumpBuf,
180     dumper: &PtraceDumper,
181     thread: &mut MDRawThread,
182     instruction_ptr: usize,
183     stack_ptr: usize,
184     max_stack_len: MaxStackLen,
185 ) -> Result<(), errors::SectionThreadListError> {
186     thread.stack.start_of_memory_range = stack_ptr.try_into()?;
187     thread.stack.memory.data_size = 0;
188     thread.stack.memory.rva = buffer.position() as u32;
190     if let Ok((valid_stack_ptr, stack_len)) = dumper.get_stack_info(stack_ptr) {
191         let stack_len = if let MaxStackLen::Len(max_stack_len) = max_stack_len {
192             min(stack_len, max_stack_len)
193         } else {
194             stack_len
195         };
197         let mut stack_bytes = PtraceDumper::copy_from_process(
198             thread.thread_id.try_into()?,
199             valid_stack_ptr as *mut libc::c_void,
200             stack_len,
201         )?;
202         let stack_pointer_offset = stack_ptr.saturating_sub(valid_stack_ptr);
203         if config.skip_stacks_if_mapping_unreferenced {
204             if let Some(principal_mapping) = &config.principal_mapping {
205                 let low_addr = principal_mapping.system_mapping_info.start_address;
206                 let high_addr = principal_mapping.system_mapping_info.end_address;
207                 if (instruction_ptr < low_addr || instruction_ptr > high_addr)
208                     && !principal_mapping
209                         .stack_has_pointer_to_mapping(&stack_bytes, stack_pointer_offset)
210                 {
211                     return Ok(());
212                 }
213             } else {
214                 return Ok(());
215             }
216         }
218         if config.sanitize_stack {
219             dumper.sanitize_stack_copy(&mut stack_bytes, stack_ptr, stack_pointer_offset)?;
220         }
222         let stack_location = MDLocationDescriptor {
223             data_size: stack_bytes.len() as u32,
224             rva: buffer.position() as u32,
225         };
226         buffer.write_all(&stack_bytes);
227         thread.stack.start_of_memory_range = valid_stack_ptr as u64;
228         thread.stack.memory = stack_location;
229         config.memory_blocks.push(thread.stack);
230     }
231     Ok(())