8000 linux: optionally get module data from the debugger rendez-vous by schopin-mozilla · Pull Request #234 · rust-minidump/minidump-writer · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ cfg_if::cfg_if! {
StackPointerMapping,
ThreadStackCopy,
CrashingThreadIpCopy,
EnumerateMappingsFromProc,
}
}

Expand Down
12 changes: 11 additions & 1 deletion src/linux/minidump_writer/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use {
thread_list_stream::SectionThreadListError,
thread_names_stream::SectionThreadNamesError,
},
module_list::ModuleResolveError,
module_list::{FromRendezvousError, ModuleResolveError},
module_reader::ModuleReaderError,
process_inspection,
serializers::*,
Expand Down Expand Up @@ -185,6 +185,16 @@ pub enum InitError {
SuspendNoThreadsLeft(usize),
#[error("Crash thread does not reference principal mapping")]
PrincipalMappingNotReferenced,
#[error("no program header table address in auxiliary vector")]
MissingProgramHeaderTableAddress,
#[error("no program header count in auxiliary vector")]
MissingProgramHeaderCount,
#[error("failed to obtain the module list from the debugger rendez-vous")]
ModuleListFromDebuggerRendezvousFailed(#[source] FromRendezvousError),
#[error("Errors occurred while walking the debugger rendez-vous")]
DebuggerRendezvousErrors(#[source] ErrorList<FromRendezvousError>),
#[error("no module could be derived from the process mappings")]
NoModulesInProcessMappings,
}

#[derive(Debug, thiserror::Error, serde::Serialize)]
Expand Down
118 changes: 103 additions & 15 deletions src/linux/minidump_writer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use {
app_memory::AppMemoryList,
auxv::AuxvDumpInfo,
crash_context_ext::CrashContextExt,
dso_debug,
dso_debug::{elf_addr_to_usize, write_dso_debug_stream},
dumper_cpu_info::CpuInfoError,
maps_reader::{MappingInfo, MappingList},
module_list,
module_list::{self, ModuleCandidate},
process_inspection::{self, ProcessInspector, process_reader::CopyFromProcessError},
remote_process_inspection,
serializers::*,
Expand Down Expand Up @@ -67,6 +67,16 @@ pub const AT_SYSINFO_EHDR: u32 = 33;
#[cfg(target_pointer_width = "64")]
pub const AT_SYSINFO_EHDR: u64 = 33;

/// Where the module list written to the `ModuleListStream` is sourced from.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ModuleListSource {
#[default]
/// Derive the module list from `/proc/<pid>/maps`.
ProcMaps,
/// Use the DT_DEBUG debugger rendezvous protocol to inspect ELF objects
DebuggerRendezvous,
}

#[derive(Debug)]
pub struct MinidumpWriterConfig {
process_id: Pid,
Expand All @@ -84,6 +94,7 @@ pub struct MinidumpWriterConfig {
stop_timeout: Duration,
direct_auxv_dump_info: Option<DirectAuxvDumpInfo>,
process_inspector: Box<dyn ProcessInspector>,
module_list_source: ModuleListSource,
}

#[derive(Debug)]
Expand All @@ -94,6 +105,7 @@ pub struct MinidumpWriter {
pub auxv: AuxvDumpInfo,
pub mappings: Vec<MappingInfo>,
pub modules: Vec<ModuleInfo>,
module_list_source: ModuleListSource,
pub page_size: usize,
pub sanitize_stack: bool,
pub minidump_size_limit: Option<u64>,
Expand Down Expand Up @@ -142,6 +154,7 @@ impl MinidumpWriterConfig {
stop_timeout: STOP_TIMEOUT,
direct_auxv_dump_info: Default::default(),
process_inspector: process_inspection::local(process_id),
module_list_source: Default::default(),
}
}

Expand Down Expand Up @@ -216,6 +229,12 @@ impl MinidumpWriterConfig {
self
}

/// Chooses where the `ModuleListStream` is sourced from.
pub fn set_module_list_source(&mut self, module_list_source: ModuleListSource) -> &mut Self {
self.module_list_source = module_list_source;
self
}

/// Generates a minidump and writes to the destination provided. Returns the in-memory
/// version of the minidump as well.
pub fn write(self, destination: &mut (impl Write + Seek)) -> Result<Vec<u8>, WriterError> {
Expand Down Expand Up @@ -250,6 +269,7 @@ impl MinidumpWriterConfig {
auxv,
mappings: Default::default(),
modules: Default::default(),
module_list_source: self.module_list_source,
page_size: Default::default(),
sanitize_stack: self.sanitize_stack,
minidump_size_limit: self.minidump_size_limit,
Expand Down Expand Up @@ -456,17 +476,14 @@ impl MinidumpWriter {
file_entry!("auxv", LinuxAuxv, WriteEnvironmentFailed);
file_entry!("maps", LinuxMaps, WriteMapsFailed);

let dirent = match dso_debug::write_dso_debug_stream(
self.process_inspector.as_ref(),
buffer,
&self.auxv,
) {
Ok(dirent) => dirent,
Err(e) => {
soft_errors.push(WriterError::WriteDSODebugStreamFailed(e));
Default::default()
}
};
let dirent =
match write_dso_debug_stream(self.process_inspector.as_ref(), buffer, &self.auxv) {
Ok(dirent) => dirent,
Err(e) => {
soft_errors.push(WriterError::WriteDSODebugStreamFailed(e));
Default::default()
}
};
dir_section.write_to_file(buffer, Some(dirent))?;

file_entry!("limits", MozLinuxLimits, WriteLimitsFailed);
Expand Down Expand Up @@ -720,6 +737,12 @@ impl MinidumpWriter {
}

fn enumerate_mappings(&mut self) -> Result<(), InitError> {
if failspot!(EnumerateMappingsFromProc) {
return Err(InitError::AggregateMappingsFailed(
std::io::Error::other("fake I/O error reading maps file").into(),
));
}

// linux_gate_loc is the beginning of the kernel's mapping of
// linux-gate.so in the process. It doesn't actually show up in the
// maps list as a filename, but it can be found using the AT_SYSINFO_EHDR
Expand All @@ -745,8 +768,18 @@ impl MinidumpWriter {
&mut self,
mut soft_errors: impl WriteErrorList<InitError>,
) -> Result<(), InitError> {
let mut candidates =
module_list::from_mappings(self.process_inspector.as_ref(), &self.mappings);
use ModuleListSource::*;
let (primary, fallback) = match self.module_list_source {
ProcMaps => (ProcMaps, DebuggerRendezvous),
DebuggerRendezvous => (DebuggerRendezvous, ProcMaps),
};
let mut candidates = self
.try_module_source(primary, &mut soft_errors)
.or_else(|e| {
soft_errors.push(e);
self.try_module_source(fallback, &mut soft_errors)
})?;

candidates.append(&mut module_list::from_user_mappings(
&self.user_mapping_list,
));
Expand All @@ -762,6 +795,61 @@ impl MinidumpWriter {
Ok(())
}

fn try_module_source(
&self,
source: ModuleListSource,
mut soft_errors: impl WriteErrorList<InitError>,
) -> Result<Vec<ModuleCandidate>, InitError> {
use ModuleListSource::*;
match source {
ProcMaps => {
let candidates =
module_list::from_mappings(self.process_inspector.as_ref(), &self.mappings);

// No detected module is probably because the initial mapping parsing failed.
// Error out so that we try the fallback.
if candidates.is_empty() {
return Err(InitError::NoModulesInProcessMappings);
}

Ok(candidates)
}
DebuggerRendezvous => self.modules_via_debugger_rendezvous(
soft_errors.subwriter(InitError::DebuggerRendezvousErrors),
),
}
}

fn modules_via_debugger_rendezvous(
&self,
soft_errors: impl WriteErrorList<module_list::FromRendezvousError>,
) -> Result<Vec<ModuleCandidate>, InitError> {
// `DT_DEBUG` only lives in the main executable's dynamic section, and
// AT_PHDR/AT_PHNUM are how we find that executable's program headers.
let Some(program_header_table_address) = self
.auxv
.get_program_header_address()
.map(elf_addr_to_usize)
else {
return Err(InitError::MissingProgramHeaderTableAddress);
};

let Some(program_header_count) =
self.auxv.get_program_header_count().map(elf_addr_to_usize)
else {
return Err(InitError::MissingProgramHeaderCount);
};

module_list::from_debugger_rendezvous(
self.process_inspector.as_ref(),
self.process_id,
program_header_table_address,
program_header_count,
soft_errors,
)
.map_err(InitError::ModuleListFromDebuggerRendezvousFailed)
}

/// Read thread info from /proc/$pid/status.
/// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
/// these members are set to -1. Returns true if all three members are
Expand Down
1 change: 0 additions & 1 deletion src/linux/module_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,6 @@ mod tests {
/// headers, hence the need for `process_inspector`, `program_header_table_address`
/// and `program_header_count`, while `pid` is there in order to get a name for the
/// main program's module, which isn't necessarily given in the DT_DEBUG data (glibc).
#[expect(unused)]
pub(crate) fn from_debugger_rendezvous(
process_inspector: &dyn ProcessInspector,
pid: Pid,
Expand Down
13 changes: 13 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,19 @@ where
);
}

#[allow(unused)]
/// Negative equivalent to [`assert_minidump_contains_soft_error`].
pub fn assert_minidump_lacks_soft_error<'a, T>(dump: &minidump::Minidump<'a, T>, variant: &str)
where
T: std::ops::Deref<Target = [u8]> + 'a,
{
let errors = read_minidump_soft_errors_or_panic(dump);
assert!(
!soft_errors_contain(&errors, variant),
"soft error list contains unexpected error `{variant}`\nError_list: {errors:#?}"
);
}

fn soft_errors_contain(value: &serde_json::Value, variant: &str) -> bool {
match value {
serde_json::Value::String(s) => s == variant,
Expand Down
Loading
Loading
0