summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/process_reader
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/process_reader')
-rw-r--r--toolkit/crashreporter/process_reader/Cargo.toml18
-rw-r--r--toolkit/crashreporter/process_reader/src/error.rs30
-rw-r--r--toolkit/crashreporter/process_reader/src/lib.rs13
-rw-r--r--toolkit/crashreporter/process_reader/src/process_reader.rs6
-rw-r--r--toolkit/crashreporter/process_reader/src/process_reader/windows.rs226
5 files changed, 293 insertions, 0 deletions
diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml
new file mode 100644
index 0000000000..ee85951d5b
--- /dev/null
+++ b/toolkit/crashreporter/process_reader/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "process_reader"
+version = "0.1.0"
+authors = ["Gabriele Svelto"]
+edition = "2018"
+license = "MPL-2.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+goblin = { version = "0.6", features = ["elf32", "elf64", "pe32", "pe64"] }
+memoffset = "0.8"
+mozilla-central-workspace-hack = { path = "../../../build/workspace-hack" }
+thiserror = "1.0"
+
+[target."cfg(target_os = \"windows\")".dependencies]
+[dependencies.winapi]
+version = "0.3"
diff --git a/toolkit/crashreporter/process_reader/src/error.rs b/toolkit/crashreporter/process_reader/src/error.rs
new file mode 100644
index 0000000000..af4d803a7a
--- /dev/null
+++ b/toolkit/crashreporter/process_reader/src/error.rs
@@ -0,0 +1,30 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum ProcessReaderError {
+ #[error("Could not convert address {0}")]
+ ConvertAddressError(#[from] std::num::TryFromIntError),
+ #[cfg(target_os = "windows")]
+ #[error("Cannot enumerate the target process's modules")]
+ EnumProcessModulesError,
+ #[error("goblin failed to parse a module")]
+ GoblinError(#[from] goblin::error::Error),
+ #[error("Address was out of bounds")]
+ InvalidAddress,
+ #[error("Could not read from the target process address space")]
+ ReadFromProcessError(#[from] ReadError),
+ #[cfg(target_os = "windows")]
+ #[error("Section was not found")]
+ SectionNotFound,
+}
+
+#[derive(Debug, Error)]
+pub enum ReadError {
+ #[cfg(target_os = "windows")]
+ #[error("ReadProcessMemory failed")]
+ ReadProcessMemoryError,
+}
diff --git a/toolkit/crashreporter/process_reader/src/lib.rs b/toolkit/crashreporter/process_reader/src/lib.rs
new file mode 100644
index 0000000000..20bdad5207
--- /dev/null
+++ b/toolkit/crashreporter/process_reader/src/lib.rs
@@ -0,0 +1,13 @@
+/* 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/. */
+
+#[cfg(target_os = "windows")]
+type ProcessHandle = winapi::um::winnt::HANDLE;
+
+pub struct ProcessReader {
+ process: ProcessHandle,
+}
+
+mod error;
+mod process_reader;
diff --git a/toolkit/crashreporter/process_reader/src/process_reader.rs b/toolkit/crashreporter/process_reader/src/process_reader.rs
new file mode 100644
index 0000000000..1473aafa09
--- /dev/null
+++ b/toolkit/crashreporter/process_reader/src/process_reader.rs
@@ -0,0 +1,6 @@
+/* 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/. */
+
+#[cfg(target_os = "windows")]
+mod windows;
diff --git a/toolkit/crashreporter/process_reader/src/process_reader/windows.rs b/toolkit/crashreporter/process_reader/src/process_reader/windows.rs
new file mode 100644
index 0000000000..c3d44c1a16
--- /dev/null
+++ b/toolkit/crashreporter/process_reader/src/process_reader/windows.rs
@@ -0,0 +1,226 @@
+/* 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::{
+ convert::TryInto,
+ ffi::OsString,
+ mem::{size_of, MaybeUninit},
+ os::windows::ffi::OsStringExt,
+ ptr::null_mut,
+};
+
+use winapi::{
+ shared::minwindef::{FALSE, HMODULE, MAX_PATH},
+ um::{
+ memoryapi::ReadProcessMemory,
+ psapi::{GetModuleBaseNameW, K32EnumProcessModules, K32GetModuleInformation, MODULEINFO},
+ },
+};
+
+use crate::{
+ error::{ProcessReaderError, ReadError},
+ ProcessHandle, ProcessReader,
+};
+
+impl ProcessReader {
+ pub fn new(process: ProcessHandle) -> Result<ProcessReader, ProcessReaderError> {
+ Ok(ProcessReader { process })
+ }
+
+ pub fn find_section(
+ &self,
+ module_name: &str,
+ section_name: &str,
+ ) -> Result<usize, ProcessReaderError> {
+ let modules = self.get_module_list()?;
+
+ 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.
+ if let Some(name) = name {
+ name.eq_ignore_ascii_case(module_name)
+ } else {
+ false
+ }
+ })
+ .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)
+ }
+
+ fn get_module_list(&self) -> Result<Vec<HMODULE>, ProcessReaderError> {
+ let mut module_num: usize = 100;
+ let mut required_buffer_size: u32 = 0;
+ let mut module_array = Vec::<HMODULE>::with_capacity(module_num);
+
+ loop {
+ let buffer_size: u32 = (module_num * size_of::<HMODULE>()).try_into()?;
+ let res = unsafe {
+ K32EnumProcessModules(
+ self.process,
+ module_array.as_mut_ptr() as *mut _,
+ buffer_size,
+ &mut required_buffer_size as *mut _,
+ )
+ };
+
+ module_num = required_buffer_size as usize / size_of::<HMODULE>();
+
+ if res == 0 {
+ if required_buffer_size > buffer_size {
+ module_array = Vec::<HMODULE>::with_capacity(module_num);
+ } else {
+ return Err(ProcessReaderError::EnumProcessModulesError);
+ }
+ } else {
+ break;
+ }
+ }
+
+ // SAFETY: module_array has been filled by K32EnumProcessModules()
+ unsafe {
+ module_array.set_len(module_num);
+ };
+
+ Ok(module_array)
+ }
+
+ 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.try_into().unwrap(),
+ )
+ };
+
+ if res == 0 {
+ None
+ } else {
+ let name = OsString::from_wide(&path[0..res as usize]);
+ let name = name.to_str()?;
+ Some(name.to_string())
+ }
+ }
+
+ fn get_module_info(&self, module: HMODULE) -> Option<MODULEINFO> {
+ let mut info: MaybeUninit<MODULEINFO> = MaybeUninit::uninit();
+ let res = unsafe {
+ K32GetModuleInformation(
+ self.process,
+ module,
+ info.as_mut_ptr(),
+ size_of::<MODULEINFO>() as u32,
+ )
+ };
+
+ if res == 0 {
+ None
+ } else {
+ let info = unsafe { info.assume_init() };
+ Some(info)
+ }
+ }
+
+ 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 {
+ ReadProcessMemory(
+ self.process,
+ src as _,
+ object.as_mut_ptr() as _,
+ size_of::<T>(),
+ null_mut(),
+ )
+ };
+
+ if res != FALSE {
+ Ok(object)
+ } else {
+ Err(ReadError::ReadProcessMemoryError)
+ }
+ }
+
+ 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 num_of_bytes = num * size_of::<T>();
+ let mut array: Vec<T> = Vec::with_capacity(num);
+ let res = unsafe {
+ ReadProcessMemory(
+ self.process,
+ src as _,
+ array.as_mut_ptr() as _,
+ num_of_bytes,
+ null_mut(),
+ )
+ };
+
+ if res != FALSE {
+ unsafe {
+ array.set_len(num);
+ }
+
+ Ok(array)
+ } else {
+ Err(ReadError::ReadProcessMemoryError)
+ }
+ }
+}