use {super::*, crate::minidump_cpu::RawContextCPU, std::cmp::min};
const LIMIT_AVERAGE_THREAD_STACK_LENGTH: usize = 8 * 1024;
const LIMIT_BASE_THREAD_COUNT: usize = 20;
const LIMIT_MAX_EXTRA_THREAD_STACK_LEN: usize = 2 * 1024;
const LIMIT_MINIDUMP_FUDGE_FACTOR: u64 = 64 * 1024;
#[derive(Debug, Clone, Copy)]
enum MaxStackLen {
None,
Len(usize),
}
#[derive(Debug, Error, serde::Serialize)]
pub enum SectionThreadListError {
#[error("Failed to write to memory")]
MemoryWriterError(#[from] MemoryWriterError),
#[error("Failed integer conversion")]
TryFromIntError(
#[from]
#[serde(skip)]
std::num::TryFromIntError,
),
#[error("Failed to copy memory from process")]
CopyFromProcessError(#[from] CopyFromProcessError),
#[error("Failed to get thread info")]
ThreadInfoError(#[from] ThreadInfoError),
#[error("Failed to write to memory buffer")]
IOError(
#[from]
#[serde(serialize_with = "serialize_io_error")]
std::io::Error,
),
#[error("Failed to sanitize stack copy")]
SanitizeStackCopyFailed(#[source] Box<WriterError>),
}
impl MinidumpWriter {
pub fn write_thread_list_stream(
&mut self,
buffer: &mut DumpBuf,
) -> Result<MDRawDirectory, SectionThreadListError> {
let num_threads = self.threads.len();
let list_header = MemoryWriter::<u32>::alloc_with_val(buffer, num_threads as u32)?;
let mut dirent = MDRawDirectory {
stream_type: MDStreamType::ThreadListStream as u32,
location: list_header.location(),
};
let mut thread_list = MemoryArrayWriter::<MDRawThread>::alloc_array(buffer, num_threads)?;
dirent.location.data_size += thread_list.location().data_size;
let mut extra_thread_stack_len = MaxStackLen::None; if let Some(minidump_size_limit) = self.minidump_size_limit {
let estimated_total_stack_size =
(num_threads * LIMIT_AVERAGE_THREAD_STACK_LENGTH) as u64;
let curr_pos = buffer.position();
let estimated_minidump_size =
curr_pos + estimated_total_stack_size + LIMIT_MINIDUMP_FUDGE_FACTOR;
if estimated_minidump_size > minidump_size_limit {
extra_thread_stack_len = MaxStackLen::Len(LIMIT_MAX_EXTRA_THREAD_STACK_LEN);
}
}
for (idx, item) in self.threads.clone().iter().enumerate() {
let mut thread = MDRawThread {
thread_id: item.tid.try_into()?,
suspend_count: 0,
priority_class: 0,
priority: 0,
teb: 0,
stack: MDMemoryDescriptor::default(),
thread_context: MDLocationDescriptor::default(),
};
if let Some(crash_context) = &self.crash_context
&& thread.thread_id == self.blamed_thread as u32
{
let instruction_ptr = crash_context.get_instruction_pointer();
let stack_pointer = crash_context.get_stack_pointer();
self.fill_thread_stack(
buffer,
&mut thread,
instruction_ptr,
stack_pointer,
MaxStackLen::None,
)?;
let ip_memory_size: usize = 256;
for mapping in &self.mappings {
if instruction_ptr < mapping.start_address
|| instruction_ptr >= mapping.start_address + mapping.size
{
continue;
}
let mut ip_memory_d = MDMemoryDescriptor {
start_of_memory_range: std::cmp::max(
mapping.start_address,
instruction_ptr - ip_memory_size / 2,
) as u64,
..Default::default()
};
let end_of_range = std::cmp::min(
mapping.start_address + mapping.size,
instruction_ptr + ip_memory_size / 2,
) as u64;
ip_memory_d.memory.data_size =
(end_of_range - ip_memory_d.start_of_memory_range) as u32;
let memory_copy = MinidumpWriter::copy_from_process(
thread.thread_id as i32,
ip_memory_d.start_of_memory_range as _,
ip_memory_d.memory.data_size as usize,
)?;
let mem_section = MemoryArrayWriter::alloc_from_array(buffer, &memory_copy)?;
ip_memory_d.memory = mem_section.location();
self.memory_blocks.push(ip_memory_d);
break;
}
let mut cpu: RawContextCPU = Default::default();
let crash_context = self.crash_context.as_ref().unwrap();
crash_context.fill_cpu_context(&mut cpu);
let cpu_section = MemoryWriter::alloc_with_val(buffer, cpu)?;
thread.thread_context = cpu_section.location();
self.crashing_thread_context =
CrashingThreadContext::CrashContext(cpu_section.location());
} else {
let info = self.get_thread_info_by_index(idx)?;
let max_stack_len =
if self.minidump_size_limit.is_some() && idx >= LIMIT_BASE_THREAD_COUNT {
extra_thread_stack_len
} else {
MaxStackLen::None };
let instruction_ptr = info.get_instruction_pointer();
self.fill_thread_stack(
buffer,
&mut thread,
instruction_ptr,
info.stack_pointer,
max_stack_len,
)?;
let mut cpu = RawContextCPU::default();
info.fill_cpu_context(&mut cpu);
let cpu_section = MemoryWriter::<RawContextCPU>::alloc_with_val(buffer, cpu)?;
thread.thread_context = cpu_section.location();
if item.tid == self.blamed_thread {
self.crashing_thread_context = CrashingThreadContext::CrashContextPlusAddress(
(cpu_section.location(), instruction_ptr),
);
}
}
thread_list.set_value_at(buffer, thread, idx)?;
}
Ok(dirent)
}
fn fill_thread_stack(
&mut self,
buffer: &mut DumpBuf,
thread: &mut MDRawThread,
instruction_ptr: usize,
stack_ptr: usize,
max_stack_len: MaxStackLen,
) -> Result<(), SectionThreadListError> {
thread.stack.start_of_memory_range = stack_ptr.try_into()?;
thread.stack.memory.data_size = 0;
thread.stack.memory.rva = buffer.position() as u32;
if let Ok((valid_stack_ptr, stack_len)) = self.get_stack_info(stack_ptr) {
let stack_len = if let MaxStackLen::Len(max_stack_len) = max_stack_len {
min(stack_len, max_stack_len)
} else {
stack_len
};
let mut stack_bytes = MinidumpWriter::copy_from_process(
thread.thread_id.try_into()?,
valid_stack_ptr,
stack_len,
)?;
let stack_pointer_offset = stack_ptr.saturating_sub(valid_stack_ptr);
if self.skip_stacks_if_mapping_unreferenced {
if let Some(principal_mapping) = &self.principal_mapping {
let low_addr = principal_mapping.system_mapping_info.start_address;
let high_addr = principal_mapping.system_mapping_info.end_address;
if (instruction_ptr < low_addr || instruction_ptr > high_addr)
&& !principal_mapping
.stack_has_pointer_to_mapping(&stack_bytes, stack_pointer_offset)
{
return Ok(());
}
} else {
return Ok(());
}
}
if self.sanitize_stack {
self.sanitize_stack_copy(&mut stack_bytes, stack_ptr, stack_pointer_offset)
.map_err(|e| SectionThreadListError::SanitizeStackCopyFailed(Box::new(e)))?;
}
let stack_location = MDLocationDescriptor {
data_size: stack_bytes.len() as u32,
rva: buffer.position() as u32,
};
buffer.write_all(&stack_bytes);
thread.stack.start_of_memory_range = valid_stack_ptr as u64;
thread.stack.memory = stack_location;
self.memory_blocks.push(thread.stack);
}
Ok(())
}
}