summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/process_reader
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /toolkit/crashreporter/process_reader
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/process_reader')
-rw-r--r--toolkit/crashreporter/process_reader/Cargo.toml14
-rw-r--r--toolkit/crashreporter/process_reader/src/error.rs42
-rw-r--r--toolkit/crashreporter/process_reader/src/lib.rs72
-rw-r--r--toolkit/crashreporter/process_reader/src/platform.rs (renamed from toolkit/crashreporter/process_reader/src/process_reader.rs)6
-rw-r--r--toolkit/crashreporter/process_reader/src/platform/linux.rs322
-rw-r--r--toolkit/crashreporter/process_reader/src/platform/macos.rs225
-rw-r--r--toolkit/crashreporter/process_reader/src/platform/windows.rs (renamed from toolkit/crashreporter/process_reader/src/process_reader/windows.rs)113
7 files changed, 720 insertions, 74 deletions
diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml
index f84a00a29f..5fb20cf95a 100644
--- a/toolkit/crashreporter/process_reader/Cargo.toml
+++ b/toolkit/crashreporter/process_reader/Cargo.toml
@@ -10,14 +10,18 @@ license = "MPL-2.0"
[dependencies]
goblin = { version = "0.7", features = ["elf32", "elf64", "pe32", "pe64"] }
memoffset = "0.9"
-mozilla-central-workspace-hack = { version = "0.1", optional = true }
thiserror = "1.0"
+[target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies]
+libc = "0.2"
+
[target."cfg(target_os = \"windows\")".dependencies]
-[dependencies.windows-sys]
-version = "0.52"
-features = [
+windows-sys = { version = "0.52", features = [
"Win32_Foundation",
"Win32_System_Diagnostics_Debug",
"Win32_System_ProcessStatus",
-]
+] }
+mozilla-central-workspace-hack = { version = "0.1", optional = true }
+
+[target."cfg(target_os = \"macos\")".dependencies]
+mach2 = { version = "0.4" }
diff --git a/toolkit/crashreporter/process_reader/src/error.rs b/toolkit/crashreporter/process_reader/src/error.rs
index af4d803a7a..2c681f78fb 100644
--- a/toolkit/crashreporter/process_reader/src/error.rs
+++ b/toolkit/crashreporter/process_reader/src/error.rs
@@ -8,6 +8,8 @@ use thiserror::Error;
pub enum ProcessReaderError {
#[error("Could not convert address {0}")]
ConvertAddressError(#[from] std::num::TryFromIntError),
+ #[error("Could not parse address {0}")]
+ ParseAddressError(#[from] std::num::ParseIntError),
#[cfg(target_os = "windows")]
#[error("Cannot enumerate the target process's modules")]
EnumProcessModulesError,
@@ -17,14 +19,52 @@ pub enum ProcessReaderError {
InvalidAddress,
#[error("Could not read from the target process address space")]
ReadFromProcessError(#[from] ReadError),
- #[cfg(target_os = "windows")]
+ #[cfg(any(target_os = "windows", target_os = "macos"))]
#[error("Section was not found")]
SectionNotFound,
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[error("Could not attach to the target process")]
+ AttachError(#[from] PtraceError),
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[error("Note not found")]
+ NoteNotFound,
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[error("waitpid() failed when attaching to the process")]
+ WaitPidError,
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[error("Could not parse a line in /proc/<pid>/maps")]
+ ProcMapsParseError,
+ #[error("Module not found")]
+ ModuleNotFound,
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[error("IO error for file {0}")]
+ IOError(#[from] std::io::Error),
+ #[cfg(target_os = "macos")]
+ #[error("Failure when requesting the task information")]
+ TaskInfoError,
+ #[cfg(target_os = "macos")]
+ #[error("The task dyld information format is unknown or invalid")]
+ ImageFormatError,
}
#[derive(Debug, Error)]
pub enum ReadError {
+ #[cfg(target_os = "macos")]
+ #[error("mach call failed")]
+ MachError,
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ #[error("ptrace-specific error")]
+ PtraceError(#[from] PtraceError),
#[cfg(target_os = "windows")]
#[error("ReadProcessMemory failed")]
ReadProcessMemoryError,
}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[derive(Debug, Error)]
+pub enum PtraceError {
+ #[error("Could not read from the target process address space")]
+ ReadError(#[source] std::io::Error),
+ #[error("Could not trace the process")]
+ TraceError(#[source] std::io::Error),
+}
diff --git a/toolkit/crashreporter/process_reader/src/lib.rs b/toolkit/crashreporter/process_reader/src/lib.rs
index 1189ff144e..09936e46f1 100644
--- a/toolkit/crashreporter/process_reader/src/lib.rs
+++ b/toolkit/crashreporter/process_reader/src/lib.rs
@@ -2,12 +2,78 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use std::{ffi::CString, mem::size_of};
+
+use error::ReadError;
+
+pub mod error;
+mod platform;
+
#[cfg(target_os = "windows")]
-type ProcessHandle = windows_sys::Win32::Foundation::HANDLE;
+pub type ProcessHandle = windows_sys::Win32::Foundation::HANDLE;
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub type ProcessHandle = libc::pid_t;
+
+#[cfg(any(target_os = "macos"))]
+pub type ProcessHandle = mach2::mach_types::task_t;
pub struct ProcessReader {
process: ProcessHandle,
}
-mod error;
-mod process_reader;
+impl ProcessReader {
+ pub fn copy_null_terminated_string(&self, address: usize) -> Result<CString, ReadError> {
+ // Try copying the string word-by-word first, this is considerably
+ // faster than one byte at a time.
+ if let Ok(string) = self.copy_null_terminated_string_word_by_word(address) {
+ return Ok(string);
+ }
+
+ // Reading the string one word at a time failed, let's try again one
+ // byte at a time. It's slow but it might work in situations where the
+ // string alignment causes word-by-word access to straddle page
+ // boundaries.
+ let mut length = 0;
+ let mut string = Vec::<u8>::new();
+
+ loop {
+ let char = self.copy_object::<u8>(address + length)?;
+ length += 1;
+ string.push(char);
+
+ if char == 0 {
+ break;
+ }
+ }
+
+ // SAFETY: If we reach this point we've read at least one byte and we
+ // know that the last one we read is nul.
+ Ok(unsafe { CString::from_vec_with_nul_unchecked(string) })
+ }
+
+ fn copy_null_terminated_string_word_by_word(
+ &self,
+ address: usize,
+ ) -> Result<CString, ReadError> {
+ const WORD_SIZE: usize = size_of::<usize>();
+ let mut length = 0;
+ let mut string = Vec::<u8>::new();
+
+ loop {
+ let array = self.copy_array::<u8>(address + length, WORD_SIZE)?;
+ let null_terminator = array.iter().position(|&e| e == 0);
+ length += null_terminator.unwrap_or(WORD_SIZE);
+ string.extend(array.into_iter());
+
+ if null_terminator.is_some() {
+ string.truncate(length + 1);
+ break;
+ }
+ }
+
+ // SAFETY: If we reach this point we've read at least one byte and we
+ // know that the last one we read is nul.
+ Ok(unsafe { CString::from_vec_with_nul_unchecked(string) })
+ }
+}
diff --git a/toolkit/crashreporter/process_reader/src/process_reader.rs b/toolkit/crashreporter/process_reader/src/platform.rs
index 1473aafa09..0ac354cd66 100644
--- a/toolkit/crashreporter/process_reader/src/process_reader.rs
+++ b/toolkit/crashreporter/process_reader/src/platform.rs
@@ -4,3 +4,9 @@
#[cfg(target_os = "windows")]
mod windows;
+
+#[cfg(any(target_os = "android", target_os = "linux"))]
+mod linux;
+
+#[cfg(target_os = "macos")]
+mod macos;
diff --git a/toolkit/crashreporter/process_reader/src/platform/linux.rs b/toolkit/crashreporter/process_reader/src/platform/linux.rs
new file mode 100644
index 0000000000..4a87e5480b
--- /dev/null
+++ b/toolkit/crashreporter/process_reader/src/platform/linux.rs
@@ -0,0 +1,322 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::{
+ cmp::min,
+ fs::File,
+ io::{BufRead, BufReader, Error},
+ mem::{size_of, MaybeUninit},
+ ptr::null_mut,
+ slice,
+};
+
+use crate::{
+ error::{ProcessReaderError, PtraceError, ReadError},
+ ProcessHandle, ProcessReader,
+};
+
+use goblin::elf::{
+ self,
+ program_header::{PF_R, PT_NOTE},
+ Elf, ProgramHeader,
+};
+use libc::{
+ c_int, c_long, c_void, pid_t, ptrace, waitpid, EINTR, PTRACE_ATTACH, PTRACE_DETACH,
+ PTRACE_PEEKDATA, __WALL,
+};
+
+impl ProcessReader {
+ pub fn new(process: ProcessHandle) -> Result<ProcessReader, ProcessReaderError> {
+ let pid: pid_t = process;
+
+ ptrace_attach(pid)?;
+
+ let mut status: i32 = 0;
+
+ loop {
+ let res = unsafe { waitpid(pid, &mut status as *mut _, __WALL) };
+ if res < 0 {
+ match get_errno() {
+ EINTR => continue,
+ _ => {
+ ptrace_detach(pid)?;
+ return Err(ProcessReaderError::WaitPidError);
+ }
+ }
+ } else {
+ break;
+ }
+ }
+
+ Ok(ProcessReader { process: pid })
+ }
+
+ pub fn find_module(&self, module_name: &str) -> Result<usize, ProcessReaderError> {
+ let maps_file = File::open(format!("/proc/{}/maps", self.process))?;
+
+ BufReader::new(maps_file)
+ .lines()
+ .flatten()
+ .map(|line| parse_proc_maps_line(&line))
+ .filter_map(Result::ok)
+ .find_map(|(name, address)| {
+ if name.is_some_and(|name| name.eq(module_name)) {
+ Some(address)
+ } else {
+ None
+ }
+ })
+ .ok_or(ProcessReaderError::ModuleNotFound)
+ }
+
+ pub fn find_program_note(
+ &self,
+ module_address: usize,
+ note_type: u32,
+ note_size: usize,
+ ) -> Result<usize, ProcessReaderError> {
+ let header_bytes = self.copy_array(module_address, size_of::<elf::Header>())?;
+ let elf_header = Elf::parse_header(&header_bytes)?;
+
+ let program_header_bytes = self.copy_array(
+ module_address + (elf_header.e_phoff as usize),
+ (elf_header.e_phnum as usize) * (elf_header.e_phentsize as usize),
+ )?;
+
+ let mut elf = Elf::lazy_parse(elf_header)?;
+ let context = goblin::container::Ctx {
+ container: elf.header.container()?,
+ le: elf.header.endianness()?,
+ };
+
+ elf.program_headers = ProgramHeader::parse(
+ &program_header_bytes,
+ 0,
+ elf_header.e_phnum as usize,
+ context,
+ )?;
+
+ self.find_note_in_headers(&elf, module_address, note_type, note_size)
+ }
+
+ fn find_note_in_headers(
+ &self,
+ elf: &Elf,
+ address: usize,
+ note_type: u32,
+ note_size: usize,
+ ) -> Result<usize, ProcessReaderError> {
+ for program_header in elf.program_headers.iter() {
+ // We're looking for a note in the program headers, it needs to be
+ // readable and it needs to be at least as large as the
+ // requested size.
+ if (program_header.p_type == PT_NOTE)
+ && ((program_header.p_flags & PF_R) != 0
+ && (program_header.p_memsz as usize >= note_size))
+ {
+ // Iterate over the notes
+ let notes_address = address + program_header.p_offset as usize;
+ let mut notes_offset = 0;
+ let notes_size = program_header.p_memsz as usize;
+ while notes_offset < notes_size {
+ let note_address = notes_address + notes_offset;
+ if let Ok(note) = self.copy_object::<goblin::elf::note::Nhdr32>(note_address) {
+ if note.n_type == note_type {
+ return Ok(note_address);
+ }
+
+ notes_offset += size_of::<goblin::elf::note::Nhdr32>()
+ + (note.n_descsz as usize)
+ + (note.n_namesz as usize);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ Err(ProcessReaderError::NoteNotFound)
+ }
+
+ pub fn copy_object_shallow<T>(&self, src: usize) -> Result<MaybeUninit<T>, ReadError> {
+ let data = self.copy_array(src, size_of::<T>())?;
+ let mut object = MaybeUninit::<T>::uninit();
+ let uninitialized_object = uninit_as_bytes_mut(&mut object);
+
+ for (index, &value) in data.iter().enumerate() {
+ uninitialized_object[index].write(value);
+ }
+
+ Ok(object)
+ }
+
+ pub fn copy_object<T>(&self, src: usize) -> Result<T, ReadError> {
+ self.copy_object_shallow(src)
+ .map(|object| unsafe { object.assume_init() })
+ }
+
+ pub fn copy_array<T>(&self, src: usize, num: usize) -> Result<Vec<T>, ReadError> {
+ let mut array = Vec::<MaybeUninit<T>>::with_capacity(num);
+ let num_bytes = num * size_of::<T>();
+ let mut array_buffer = array.as_mut_ptr() as *mut u8;
+ let mut index = 0;
+
+ while index < num_bytes {
+ let word = ptrace_read(self.process, src + index)?;
+ let len = min(size_of::<c_long>(), num_bytes - index);
+ let word_as_bytes = word.to_ne_bytes();
+ for &byte in word_as_bytes.iter().take(len) {
+ unsafe {
+ array_buffer.write(byte);
+ array_buffer = array_buffer.add(1);
+ }
+ }
+
+ index += size_of::<c_long>();
+ }
+
+ unsafe {
+ array.set_len(num);
+ Ok(std::mem::transmute(array))
+ }
+ }
+}
+
+impl Drop for ProcessReader {
+ fn drop(&mut self) {
+ let _ignored = ptrace_detach(self.process);
+ }
+}
+
+fn parse_proc_maps_line(line: &str) -> Result<(Option<String>, usize), ProcessReaderError> {
+ let mut splits = line.trim().splitn(6, ' ');
+ let address_str = splits
+ .next()
+ .ok_or(ProcessReaderError::ProcMapsParseError)?;
+ let _perms_str = splits
+ .next()
+ .ok_or(ProcessReaderError::ProcMapsParseError)?;
+ let _offset_str = splits
+ .next()
+ .ok_or(ProcessReaderError::ProcMapsParseError)?;
+ let _dev_str = splits
+ .next()
+ .ok_or(ProcessReaderError::ProcMapsParseError)?;
+ let _inode_str = splits
+ .next()
+ .ok_or(ProcessReaderError::ProcMapsParseError)?;
+ let path_str = splits
+ .next()
+ .ok_or(ProcessReaderError::ProcMapsParseError)?;
+
+ let address = get_proc_maps_address(address_str)?;
+
+ // Note that we don't care if the mapped file has been deleted because
+ // we're reading everything from memory.
+ let name = path_str
+ .trim_end_matches(" (deleted)")
+ .rsplit('/')
+ .next()
+ .map(String::from);
+
+ Ok((name, address))
+}
+
+fn get_proc_maps_address(addresses: &str) -> Result<usize, ProcessReaderError> {
+ let begin = addresses
+ .split('-')
+ .next()
+ .ok_or(ProcessReaderError::ProcMapsParseError)?;
+ usize::from_str_radix(begin, 16).map_err(ProcessReaderError::from)
+}
+
+fn uninit_as_bytes_mut<T>(elem: &mut MaybeUninit<T>) -> &mut [MaybeUninit<u8>] {
+ // SAFETY: MaybeUninit<u8> is always valid, even for padding bytes
+ unsafe { slice::from_raw_parts_mut(elem.as_mut_ptr() as *mut MaybeUninit<u8>, size_of::<T>()) }
+}
+
+/***********************************************************************
+ ***** libc helpers *****
+ ***********************************************************************/
+
+fn get_errno() -> c_int {
+ #[cfg(target_os = "linux")]
+ unsafe {
+ *libc::__errno_location()
+ }
+ #[cfg(target_os = "android")]
+ unsafe {
+ *libc::__errno()
+ }
+}
+
+fn clear_errno() {
+ #[cfg(target_os = "linux")]
+ unsafe {
+ *libc::__errno_location() = 0;
+ }
+ #[cfg(target_os = "android")]
+ unsafe {
+ *libc::__errno() = 0;
+ }
+}
+
+#[derive(Clone, Copy)]
+enum PTraceOperation {
+ Attach,
+ Detach,
+ PeekData,
+}
+
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
+type PTraceOperationNative = libc::c_uint;
+#[cfg(all(target_os = "linux", target_env = "musl"))]
+type PTraceOperationNative = libc::c_int;
+#[cfg(target_os = "android")]
+type PTraceOperationNative = c_int;
+
+impl From<PTraceOperation> for PTraceOperationNative {
+ fn from(val: PTraceOperation) -> Self {
+ match val {
+ PTraceOperation::Attach => PTRACE_ATTACH,
+ PTraceOperation::Detach => PTRACE_DETACH,
+ PTraceOperation::PeekData => PTRACE_PEEKDATA,
+ }
+ }
+}
+
+fn ptrace_attach(pid: pid_t) -> Result<(), PtraceError> {
+ ptrace_helper(pid, PTraceOperation::Attach, 0).map(|_r| ())
+}
+
+fn ptrace_detach(pid: pid_t) -> Result<(), PtraceError> {
+ ptrace_helper(pid, PTraceOperation::Detach, 0).map(|_r| ())
+}
+
+fn ptrace_read(pid: libc::pid_t, addr: usize) -> Result<c_long, PtraceError> {
+ ptrace_helper(pid, PTraceOperation::PeekData, addr)
+}
+
+fn ptrace_helper(pid: pid_t, op: PTraceOperation, addr: usize) -> Result<c_long, PtraceError> {
+ clear_errno();
+ let result = unsafe { ptrace(op.into(), pid, addr, null_mut::<c_void>()) };
+
+ if result == -1 {
+ let errno = get_errno();
+ if errno != 0 {
+ let error = match op {
+ PTraceOperation::Attach => PtraceError::TraceError(Error::from_raw_os_error(errno)),
+ PTraceOperation::Detach => PtraceError::TraceError(Error::from_raw_os_error(errno)),
+ PTraceOperation::PeekData => {
+ PtraceError::ReadError(Error::from_raw_os_error(errno))
+ }
+ };
+ Err(error)
+ } else {
+ Ok(result)
+ }
+ } else {
+ Ok(result)
+ }
+}
diff --git a/toolkit/crashreporter/process_reader/src/platform/macos.rs b/toolkit/crashreporter/process_reader/src/platform/macos.rs
new file mode 100644
index 0000000000..a0dfa2b8fd
--- /dev/null
+++ b/toolkit/crashreporter/process_reader/src/platform/macos.rs
@@ -0,0 +1,225 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use goblin::mach::{
+ header::{Header64, MH_DYLIB, MH_EXECUTE, MH_MAGIC_64},
+ load_command::{LoadCommandHeader, Section64, SegmentCommand64, LC_SEGMENT_64},
+};
+use mach2::{
+ kern_return::KERN_SUCCESS,
+ task::task_info,
+ task_info::{task_dyld_info, TASK_DYLD_ALL_IMAGE_INFO_64, TASK_DYLD_INFO},
+ vm::mach_vm_read_overwrite,
+};
+use std::mem::{size_of, MaybeUninit};
+
+use crate::{
+ error::{ProcessReaderError, ReadError},
+ ProcessHandle, ProcessReader,
+};
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+struct AllImagesInfo {
+ // VERSION 1
+ pub version: u32,
+ /// The number of [`ImageInfo`] structs at that following address
+ info_array_count: u32,
+ /// The address in the process where the array of [`ImageInfo`] structs is
+ info_array_addr: u64,
+ /// A function pointer, unused
+ _notification: u64,
+ /// Unused
+ _process_detached_from_shared_region: bool,
+ // VERSION 2
+ lib_system_initialized: bool,
+ // Note that crashpad adds a 32-bit int here to get proper alignment when
+ // building on 32-bit targets...but we explicitly don't care about 32-bit
+ // targets since Apple doesn't
+ pub dyld_image_load_address: u64,
+}
+
+/// `dyld_image_info` from <usr/include/mach-o/dyld_images.h>
+#[repr(C)]
+#[derive(Debug, Clone, Copy)]
+struct ImageInfo {
+ /// The address in the process where the image is loaded
+ pub load_address: u64,
+ /// The address in the process where the image's file path can be read
+ pub file_path: u64,
+ /// Timestamp for when the image's file was last modified
+ pub file_mod_date: u64,
+}
+
+const DATA_SEGMENT: &[u8; 16] = b"__DATA\0\0\0\0\0\0\0\0\0\0";
+
+impl ProcessReader {
+ pub fn new(process: ProcessHandle) -> Result<ProcessReader, ProcessReaderError> {
+ Ok(ProcessReader { process })
+ }
+
+ pub fn find_module(&self, module_name: &str) -> Result<usize, ProcessReaderError> {
+ let dyld_info = self.task_info()?;
+ if (dyld_info.all_image_info_format as u32) != TASK_DYLD_ALL_IMAGE_INFO_64 {
+ return Err(ProcessReaderError::ImageFormatError);
+ }
+
+ let all_image_info_size = dyld_info.all_image_info_size;
+ let all_image_info_addr = dyld_info.all_image_info_addr;
+ if (all_image_info_size as usize) < size_of::<AllImagesInfo>() {
+ return Err(ProcessReaderError::ImageFormatError);
+ }
+
+ let all_images_info = self.copy_object::<AllImagesInfo>(all_image_info_addr as _)?;
+
+ // Load the images
+ let images = self.copy_array::<ImageInfo>(
+ all_images_info.info_array_addr as _,
+ all_images_info.info_array_count as _,
+ )?;
+
+ images
+ .iter()
+ .find(|&image| {
+ let image_path = self.copy_null_terminated_string(image.file_path as usize);
+
+ if let Ok(image_path) = image_path {
+ if let Some(image_name) = image_path.into_bytes().rsplit(|&b| b == b'/').next()
+ {
+ image_name.eq(module_name.as_bytes())
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ })
+ .map(|image| image.load_address as usize)
+ .ok_or(ProcessReaderError::ModuleNotFound)
+ }
+
+ pub fn find_section(
+ &self,
+ module_address: usize,
+ section_name: &[u8; 16],
+ ) -> Result<usize, ProcessReaderError> {
+ let header = self.copy_object::<Header64>(module_address)?;
+ let mut address = module_address + size_of::<Header64>();
+
+ if header.magic == MH_MAGIC_64
+ && (header.filetype == MH_EXECUTE || header.filetype == MH_DYLIB)
+ {
+ let end_of_commands = address + (header.sizeofcmds as usize);
+
+ while address < end_of_commands {
+ let command = self.copy_object::<LoadCommandHeader>(address)?;
+
+ if command.cmd == LC_SEGMENT_64 {
+ if let Ok(offset) = self.find_section_in_segment(address, section_name) {
+ return module_address
+ .checked_add(offset)
+ .ok_or(ProcessReaderError::InvalidAddress);
+ }
+ }
+
+ address += command.cmdsize as usize;
+ }
+ }
+
+ Err(ProcessReaderError::SectionNotFound)
+ }
+
+ fn find_section_in_segment(
+ &self,
+ segment_address: usize,
+ section_name: &[u8; 16],
+ ) -> Result<usize, ProcessReaderError> {
+ let segment = self.copy_object::<SegmentCommand64>(segment_address)?;
+
+ if segment.segname.eq(DATA_SEGMENT) {
+ let sections_addr = segment_address + size_of::<SegmentCommand64>();
+ let sections = self.copy_array::<Section64>(sections_addr, segment.nsects as usize)?;
+ for section in &sections {
+ if section.sectname.eq(section_name) {
+ return Ok(section.offset as usize);
+ }
+ }
+ }
+
+ Err(ProcessReaderError::SectionNotFound)
+ }
+
+ fn task_info(&self) -> Result<task_dyld_info, ProcessReaderError> {
+ let mut info = std::mem::MaybeUninit::<task_dyld_info>::uninit();
+ let mut count = (std::mem::size_of::<task_dyld_info>() / std::mem::size_of::<u32>()) as u32;
+
+ let res = unsafe {
+ task_info(
+ self.process,
+ TASK_DYLD_INFO,
+ info.as_mut_ptr().cast(),
+ &mut count,
+ )
+ };
+
+ if res == KERN_SUCCESS {
+ // SAFETY: this will be initialized if the call succeeded
+ unsafe { Ok(info.assume_init()) }
+ } else {
+ Err(ProcessReaderError::TaskInfoError)
+ }
+ }
+
+ pub fn copy_object_shallow<T>(&self, src: usize) -> Result<MaybeUninit<T>, ReadError> {
+ let mut object = MaybeUninit::<T>::uninit();
+ let mut size: u64 = 0;
+ let res = unsafe {
+ mach_vm_read_overwrite(
+ self.process,
+ src as u64,
+ size_of::<T>() as u64,
+ object.as_mut_ptr() as _,
+ &mut size as _,
+ )
+ };
+
+ if res == KERN_SUCCESS {
+ Ok(object)
+ } else {
+ Err(ReadError::MachError)
+ }
+ }
+
+ pub fn copy_object<T>(&self, src: usize) -> Result<T, ReadError> {
+ let object = self.copy_object_shallow(src)?;
+ Ok(unsafe { object.assume_init() })
+ }
+
+ pub fn copy_array<T>(&self, src: usize, num: usize) -> Result<Vec<T>, ReadError> {
+ let mut array: Vec<MaybeUninit<T>> = Vec::with_capacity(num);
+ let mut size: u64 = 0;
+ let res = unsafe {
+ mach_vm_read_overwrite(
+ self.process,
+ src as u64,
+ (num * size_of::<T>()) as u64,
+ array.as_mut_ptr() as _,
+ &mut size as _,
+ )
+ };
+
+ if res == KERN_SUCCESS {
+ unsafe {
+ array.set_len(num);
+ Ok(std::mem::transmute(array))
+ }
+ } else {
+ Err(ReadError::MachError)
+ }
+ }
+}
+
+impl Drop for ProcessReader {
+ fn drop(&mut self) {}
+}
diff --git a/toolkit/crashreporter/process_reader/src/process_reader/windows.rs b/toolkit/crashreporter/process_reader/src/platform/windows.rs
index 1dd1a13049..2d8c63444e 100644
--- a/toolkit/crashreporter/process_reader/src/process_reader/windows.rs
+++ b/toolkit/crashreporter/process_reader/src/platform/windows.rs
@@ -15,7 +15,7 @@ use windows_sys::Win32::{
System::{
Diagnostics::Debug::ReadProcessMemory,
ProcessStatus::{
- GetModuleBaseNameW, K32EnumProcessModules, K32GetModuleInformation, MODULEINFO,
+ K32EnumProcessModules, K32GetModuleBaseNameW, K32GetModuleInformation, MODULEINFO,
},
},
};
@@ -30,32 +30,54 @@ impl ProcessReader {
Ok(ProcessReader { process })
}
+ pub fn find_module(&self, module_name: &str) -> Result<usize, ProcessReaderError> {
+ let modules = self.get_module_list()?;
+
+ let module = modules.iter().find_map(|&module| {
+ let name = self.get_module_name(module);
+ // Crude way of mimicking Windows lower-case comparisons but
+ // sufficient for our use-cases.
+ if name.is_some_and(|name| name.eq_ignore_ascii_case(module_name)) {
+ self.get_module_info(module)
+ .map(|module| module.lpBaseOfDll as usize)
+ } else {
+ None
+ }
+ });
+
+ module.ok_or(ProcessReaderError::ModuleNotFound)
+ }
+
pub fn find_section(
&self,
- module_name: &str,
- section_name: &str,
+ module_address: usize,
+ section_name: &[u8; 8],
) -> Result<usize, ProcessReaderError> {
- let modules = self.get_module_list()?;
+ // We read only the first page from the module, this should be more than
+ // enough to read the header and section list. In the future we might do
+ // this incrementally but for now goblin requires an array to parse
+ // so we can't do it just yet.
+ const PAGE_SIZE: usize = 4096;
+ let bytes = self.copy_array(module_address as _, PAGE_SIZE)?;
+ let header = goblin::pe::header::Header::parse(&bytes)?;
- modules
- .iter()
- .filter(|&&module| {
- let name = self.get_module_name(module);
- // Crude way of mimicking Windows lower-case comparisons but
- // sufficient for our use-cases.
- name.is_some_and(|name| name.eq_ignore_ascii_case(module_name))
- })
- .find_map(|&module| {
- self.get_module_info(module).and_then(|info| {
- self.find_section_in_module(
- section_name,
- info.lpBaseOfDll as usize,
- info.SizeOfImage as usize,
- )
- .ok()
- })
- })
- .ok_or(ProcessReaderError::InvalidAddress)
+ // Skip the PE header so we can parse the sections
+ let optional_header_offset = header.dos_header.pe_pointer as usize
+ + goblin::pe::header::SIZEOF_PE_MAGIC
+ + goblin::pe::header::SIZEOF_COFF_HEADER;
+ let offset =
+ &mut (optional_header_offset + header.coff_header.size_of_optional_header as usize);
+
+ let sections = header.coff_header.sections(&bytes, offset)?;
+
+ for section in sections {
+ if section.name.eq(section_name) {
+ let address = module_address.checked_add(section.virtual_address as usize);
+ return address.ok_or(ProcessReaderError::InvalidAddress);
+ }
+ }
+
+ Err(ProcessReaderError::SectionNotFound)
}
fn get_module_list(&self) -> Result<Vec<HMODULE>, ProcessReaderError> {
@@ -97,8 +119,9 @@ impl ProcessReader {
fn get_module_name(&self, module: HMODULE) -> Option<String> {
let mut path: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
- let res =
- unsafe { GetModuleBaseNameW(self.process, module, (&mut path).as_mut_ptr(), MAX_PATH) };
+ let res = unsafe {
+ K32GetModuleBaseNameW(self.process, module, (&mut path).as_mut_ptr(), MAX_PATH)
+ };
if res == 0 {
None
@@ -128,46 +151,6 @@ impl ProcessReader {
}
}
- fn find_section_in_module(
- &self,
- section_name: &str,
- module_address: usize,
- size: usize,
- ) -> Result<usize, ProcessReaderError> {
- // We read only the first page from the module, this should be more than
- // enough to read the header and section list. In the future we might do
- // this incrementally but for now goblin requires an array to parse
- // so we can't do it just yet.
- let page_size = 4096;
- if size < page_size {
- // Don't try to read from the target module if it's too small
- return Err(ProcessReaderError::ReadFromProcessError(
- ReadError::ReadProcessMemoryError,
- ));
- }
-
- let bytes = self.copy_array(module_address as _, 4096)?;
- let header = goblin::pe::header::Header::parse(&bytes)?;
-
- // Skip the PE header so we can parse the sections
- let optional_header_offset = header.dos_header.pe_pointer as usize
- + goblin::pe::header::SIZEOF_PE_MAGIC
- + goblin::pe::header::SIZEOF_COFF_HEADER;
- let offset =
- &mut (optional_header_offset + header.coff_header.size_of_optional_header as usize);
-
- let sections = header.coff_header.sections(&bytes, offset)?;
-
- for section in sections {
- if section.name.eq(section_name.as_bytes()) {
- let address = module_address.checked_add(section.virtual_address as usize);
- return address.ok_or(ProcessReaderError::InvalidAddress);
- }
- }
-
- Err(ProcessReaderError::SectionNotFound)
- }
-
pub fn copy_object_shallow<T>(&self, src: usize) -> Result<MaybeUninit<T>, ReadError> {
let mut object = MaybeUninit::<T>::uninit();
let res = unsafe {