mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
Bug 1872920 - Introduce the process_reader crate a=diannaS
Original Revision: https://phabricator.services.mozilla.com/D201590 Differential Revision: https://phabricator.services.mozilla.com/D202915
This commit is contained in:
parent
fde823c7b7
commit
bce95d658f
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -4184,6 +4184,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "process_reader"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"goblin",
|
||||
"memoffset",
|
||||
"mozilla-central-workspace-hack",
|
||||
"thiserror",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "processtools"
|
||||
version = "0.1.0"
|
||||
|
18
toolkit/crashreporter/process_reader/Cargo.toml
Normal file
18
toolkit/crashreporter/process_reader/Cargo.toml
Normal file
@ -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"
|
30
toolkit/crashreporter/process_reader/src/error.rs
Normal file
30
toolkit/crashreporter/process_reader/src/error.rs
Normal file
@ -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,
|
||||
}
|
13
toolkit/crashreporter/process_reader/src/lib.rs
Normal file
13
toolkit/crashreporter/process_reader/src/lib.rs
Normal file
@ -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;
|
@ -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;
|
@ -0,0 +1,222 @@
|
||||
/* 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.
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user