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.
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
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)]
29 config: &mut MinidumpWriter,
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(),
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);
62 for (idx, item) in dumper.threads.clone().iter().enumerate() {
63 let mut thread = MDRawThread {
64 thread_id: item.tid.try_into()?,
69 stack: MDMemoryDescriptor::default(),
70 thread_context: MDLocationDescriptor::default(),
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
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();
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
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,
111 let end_of_range = std::cmp::min(
112 mapping.start_address + mapping.size,
113 instruction_ptr + ip_memory_size / 2,
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,
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);
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());
140 let info = dumper.get_thread_info_by_index(idx)?;
142 if config.minidump_size_limit.is_some() && idx >= LIMIT_BASE_THREAD_COUNT {
143 extra_thread_stack_len
145 MaxStackLen::None // default to no maximum for this thread
147 let instruction_ptr = info.get_instruction_pointer();
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(),
172 thread_list.set_value_at(buffer, thread, idx)?;
177 fn fill_thread_stack(
178 config: &mut MinidumpWriter,
179 buffer: &mut DumpBuf,
180 dumper: &PtraceDumper,
181 thread: &mut MDRawThread,
182 instruction_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)
197 let mut stack_bytes = PtraceDumper::copy_from_process(
198 thread.thread_id.try_into()?,
199 valid_stack_ptr as *mut libc::c_void,
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)
218 if config.sanitize_stack {
219 dumper.sanitize_stack_copy(&mut stack_bytes, stack_ptr, stack_pointer_offset)?;
222 let stack_location = MDLocationDescriptor {
223 data_size: stack_bytes.len() as u32,
224 rva: buffer.position() as u32,
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);