Bug 1776197 - mozannotation_server crate implementation r=afranchuk

This crate contains the logic used to find the annotations within another
process and pull them out.

Depends on D173697

Differential Revision: https://phabricator.services.mozilla.com/D173698
This commit is contained in:
Gabriele Svelto 2023-06-07 12:34:31 +00:00
parent 73a3085a6b
commit 4c87f445a6
14 changed files with 1102 additions and 0 deletions

16
Cargo.lock generated
View File

@ -2108,6 +2108,7 @@ dependencies = [
"mio 0.8.0",
"moz_asserts",
"mozannotation_client",
"mozannotation_server",
"mozglue-static",
"mozurl",
"mp4parse_capi",
@ -3337,6 +3338,21 @@ dependencies = [
"nsstring",
]
[[package]]
name = "mozannotation_server"
version = "0.1.0"
dependencies = [
"goblin",
"libc",
"mach2",
"memoffset",
"mozannotation_client",
"nsstring",
"thin-vec",
"thiserror",
"winapi",
]
[[package]]
name = "mozbuild"
version = "0.1.0"

View File

@ -64,6 +64,7 @@ if CONFIG["MOZ_CRASHREPORTER"]:
DIRS += [
"client",
"mozannotation_client",
"mozannotation_server",
]
if CONFIG["MOZ_CRASHREPORTER_INJECTOR"]:

View File

@ -0,0 +1,25 @@
[package]
name = "mozannotation_server"
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"
mozannotation_client = { path = "../mozannotation_client/" }
nsstring = { path = "../../../xpcom/rust/nsstring/" }
thin-vec = { version = "0.2.7", features = ["gecko-ffi"] }
thiserror = "1.0.38"
[target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies]
libc = "0.2"
[target."cfg(target_os = \"windows\")".dependencies]
winapi = { version = "0.3", features = ["minwindef", "memoryapi", "psapi"] }
[target."cfg(target_os = \"macos\")".dependencies]
mach2 = { version = "0.4" }

View File

@ -0,0 +1,15 @@
header = """/* 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/. */"""
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
"""
include_version = true
braces = "SameLine"
line_length = 100
tab_width = 2
language = "C++"
include_guard = "mozannotation_server_ffi_generated_h"
includes = ["nsString.h", "nsTArrayForwardDeclare.h"]
[export.rename]
"ThinVec" = "nsTArray"

View File

@ -0,0 +1,17 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
if CONFIG["COMPILE_ENVIRONMENT"]:
# This tells mach to run cbindgen and that this header-file should be created
CbindgenHeader(
"mozannotation_server_ffi_generated.h",
inputs=["/toolkit/crashreporter/mozannotation_server"],
)
# This tells mach to copy that generated file to obj/dist/include/mozilla/toolkit/crashreporter
EXPORTS.mozilla.toolkit.crashreporter += [
"!mozannotation_server_ffi_generated.h",
]

View File

@ -0,0 +1,79 @@
/* 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 RetrievalError {
#[error("The process handle/PID was invalid")]
InvalidProcessHandle,
#[error("Could not find the address of the annotations vector")]
AnnotationVectorNotFound(#[from] FindAnnotationsAddressError),
#[error("The data read from the target process is invalid")]
InvalidData,
#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("Could not attach to the target process")]
AttachError(#[from] PtraceError),
#[error("Could not read from the target process address space")]
ReadFromProcessError(#[from] ReadError),
#[error("waitpid() failed when attaching to the process")]
WaitPidError,
}
#[derive(Debug, Error)]
pub enum FindAnnotationsAddressError {
#[error("Could not convert address {0}")]
ConvertAddressError(#[from] std::num::TryFromIntError),
#[error("goblin failed to parse a module")]
GoblinError(#[from] goblin::error::Error),
#[error("Address was out of bounds")]
InvalidAddress,
#[error("IO error for file {0}")]
IOError(#[from] std::io::Error),
#[error("Could not find the address of the annotations vector")]
NotFound,
#[error("Could not parse address {0}")]
ParseAddressError(#[from] std::num::ParseIntError),
#[error("Could not parse a line in /proc/<pid>/maps")]
ProcMapsParseError,
#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("Program header was not found")]
ProgramHeaderNotFound,
#[cfg(target_os = "windows")]
#[error("Section was not found")]
SectionNotFound,
#[cfg(target_os = "windows")]
#[error("Cannot enumerate the target process's modules")]
EnumProcessModulesError,
#[error("Could not read memory from the target process")]
ReadError(#[from] ReadError),
#[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(any(target_os = "linux", target_os = "android"))]
#[error("ptrace-specific error")]
PtraceError(#[from] PtraceError),
#[cfg(target_os = "windows")]
#[error("ReadProcessMemory failed")]
ReadProcessMemoryError,
#[cfg(target_os = "macos")]
#[error("mach call failed")]
MachError,
}
#[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),
}

View File

@ -0,0 +1,208 @@
/* 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/. */
mod errors;
mod process_reader;
use crate::errors::*;
use process_reader::ProcessReader;
use mozannotation_client::{Annotation, AnnotationContents, AnnotationMutex};
use nsstring::nsCString;
use std::iter::FromIterator;
use std::mem::size_of;
use std::ptr::null_mut;
use thin_vec::ThinVec;
#[repr(C)]
pub enum AnnotationData {
Empty,
UsizeData(usize),
NSCStringData(nsCString),
ByteBuffer(ThinVec<u8>),
}
#[repr(C)]
pub struct CAnnotation {
id: u32,
data: AnnotationData,
}
#[cfg(target_os = "windows")]
type ProcessHandle = winapi::shared::ntdef::HANDLE;
#[cfg(any(target_os = "linux", target_os = "android"))]
type ProcessHandle = libc::pid_t;
#[cfg(any(target_os = "macos"))]
type ProcessHandle = mach2::mach_types::task_t;
/// Return the annotations of a given process.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_retrieve(process: usize) -> *mut ThinVec<CAnnotation> {
let result = retrieve_annotations(process as _);
match result {
// Leak the object as it will be owned by the C++ code from now on
Ok(annotations) => Box::into_raw(annotations) as *mut _,
Err(_) => null_mut(),
}
}
/// Free the annotations returned by `mozannotation_retrieve()`.
///
/// # Safety
///
/// `ptr` must contain the value returned by a call to
/// `mozannotation_retrieve()` and be called only once.
#[no_mangle]
pub unsafe extern "C" fn mozannotation_free(ptr: *mut ThinVec<CAnnotation>) {
// The annotation vector will be automatically destroyed when the contents
// of this box are automatically dropped at the end of the function.
let _box = Box::from_raw(ptr);
}
pub fn retrieve_annotations(
process: ProcessHandle,
) -> Result<Box<ThinVec<CAnnotation>>, RetrievalError> {
let reader = ProcessReader::new(process)?;
let address = reader.find_annotations()?;
let mut mutex = reader.copy_object_shallow::<AnnotationMutex>(address)?;
let mutex = unsafe { mutex.assume_init_mut() };
// TODO: we should clear the poison value here before getting the mutex
// contents. Right now we have to fail if the mutex was poisoned.
let contents = mutex.get_mut().map_err(|_e| RetrievalError::InvalidData)?;
let vec_pointer = contents.as_ptr();
let length = contents.len();
let mut annotations = ThinVec::<CAnnotation>::with_capacity(length);
for i in 0..length {
let annotation_address = unsafe { vec_pointer.add(i) };
if let Ok(annotation) = read_annotation(&reader, annotation_address as usize) {
annotations.push(annotation);
}
}
Ok(Box::new(annotations))
}
// Read an annotation from the given address
fn read_annotation(reader: &ProcessReader, address: usize) -> Result<CAnnotation, ReadError> {
let raw_annotation = reader.copy_object::<Annotation>(address)?;
let mut annotation = CAnnotation {
id: raw_annotation.id,
data: AnnotationData::Empty,
};
match raw_annotation.contents {
AnnotationContents::Empty => {}
AnnotationContents::NSCString => {
let string = copy_nscstring(reader, raw_annotation.address)?;
annotation.data = AnnotationData::NSCStringData(string);
}
AnnotationContents::CString => {
let string = copy_null_terminated_string_pointer(reader, raw_annotation.address)?;
annotation.data = AnnotationData::NSCStringData(string);
}
AnnotationContents::CharBuffer => {
let string = copy_null_terminated_string(reader, raw_annotation.address)?;
annotation.data = AnnotationData::NSCStringData(string);
}
AnnotationContents::USize => {
let value = reader.copy_object::<usize>(raw_annotation.address)?;
annotation.data = AnnotationData::UsizeData(value);
}
AnnotationContents::ByteBuffer(size) => {
let value = copy_bytebuffer(reader, raw_annotation.address, size)?;
annotation.data = AnnotationData::ByteBuffer(value);
}
};
Ok(annotation)
}
fn copy_null_terminated_string_pointer(
reader: &ProcessReader,
address: usize,
) -> Result<nsCString, ReadError> {
let buffer_address = reader.copy_object::<usize>(address)?;
copy_null_terminated_string(reader, buffer_address)
}
fn copy_null_terminated_string(
reader: &ProcessReader,
address: usize,
) -> Result<nsCString, ReadError> {
// Try copying the string word-by-word first, this is considerably faster
// than one byte at a time.
if let Ok(string) = copy_null_terminated_string_word_by_word(reader, 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 = reader.copy_object::<u8>(address + length)?;
length += 1;
string.push(char);
if char == 0 {
break;
}
}
Ok(nsCString::from(&string[..length]))
}
fn copy_null_terminated_string_word_by_word(
reader: &ProcessReader,
address: usize,
) -> Result<nsCString, ReadError> {
const WORD_SIZE: usize = size_of::<usize>();
let mut length = 0;
let mut string = Vec::<u8>::new();
loop {
let mut array = reader.copy_array::<u8>(address + length, WORD_SIZE)?;
let null_terminator = array.iter().position(|&e| e == 0);
length += null_terminator.unwrap_or(WORD_SIZE);
string.append(&mut array);
if null_terminator.is_some() {
break;
}
}
Ok(nsCString::from(&string[..length]))
}
fn copy_nscstring(reader: &ProcessReader, address: usize) -> Result<nsCString, ReadError> {
// HACK: This assumes the layout of the string
let length_address = address + size_of::<usize>();
let length = reader.copy_object::<u32>(length_address)?;
if length > 0 {
let data_address = reader.copy_object::<usize>(address)?;
reader
.copy_array::<u8>(data_address, length as _)
.map(nsCString::from)
} else {
Ok(nsCString::new())
}
}
fn copy_bytebuffer(
reader: &ProcessReader,
address: usize,
size: u32,
) -> Result<ThinVec<u8>, ReadError> {
let value = reader.copy_array::<u8>(address, size as _)?;
Ok(ThinVec::<u8>::from_iter(value.into_iter()))
}

View File

@ -0,0 +1,18 @@
/* 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 crate::ProcessHandle;
pub struct ProcessReader {
process: ProcessHandle,
}
#[cfg(target_os = "windows")]
mod windows;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod linux;
#[cfg(target_os = "macos")]
mod macos;

View File

@ -0,0 +1,312 @@
/* 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 mozannotation_client::MozAnnotationNote;
use std::{
cmp::min,
fs::File,
io::{BufRead, BufReader, Error},
mem::{size_of, MaybeUninit},
ptr::null_mut,
slice,
};
use crate::{
errors::{FindAnnotationsAddressError, PtraceError, ReadError, RetrievalError},
ProcessHandle,
};
use super::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,
};
use memoffset::offset_of;
use mozannotation_client::ANNOTATION_TYPE;
impl ProcessReader {
pub fn new(process: ProcessHandle) -> Result<ProcessReader, RetrievalError> {
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(RetrievalError::WaitPidError);
}
}
} else {
break;
}
}
Ok(ProcessReader { process: pid })
}
pub fn find_annotations(&self) -> Result<usize, FindAnnotationsAddressError> {
let maps_file = File::open(format!("/proc/{}/maps", self.process))?;
BufReader::new(maps_file)
.lines()
.flatten()
.find_map(|line| self.find_annotations_in_module(&line).ok())
.ok_or(FindAnnotationsAddressError::NotFound)
}
fn find_annotations_in_module(&self, line: &str) -> Result<usize, FindAnnotationsAddressError> {
parse_proc_maps_line(line).and_then(|module_address| {
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,
)?;
let (note_address, desc) = self
.find_mozannotation_note(module_address, &elf)
.ok_or(FindAnnotationsAddressError::ProgramHeaderNotFound)?;
usize::checked_add(note_address, desc)
.ok_or(FindAnnotationsAddressError::InvalidAddress)
})
}
// Looks through the program headers for the note contained in the
// mozannotation_client crate. If the note is found return the address of the
// note's desc field as well as its contents.
fn find_mozannotation_note(&self, module_address: usize, elf: &Elf) -> Option<(usize, usize)> {
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
// MozAnnotationNote structure.
if (program_header.p_type == PT_NOTE)
&& ((program_header.p_flags & PF_R) != 0
&& (program_header.p_memsz as usize >= size_of::<MozAnnotationNote>()))
{
// Iterate over the notes
let notes_address = module_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 == ANNOTATION_TYPE {
if let Ok(note) = self.copy_object::<MozAnnotationNote>(note_address) {
return Some((
note_address + offset_of!(MozAnnotationNote, desc),
note.desc,
));
}
}
notes_offset += size_of::<goblin::elf::note::Nhdr32>()
+ (note.n_descsz as usize)
+ (note.n_namesz as usize);
} else {
break;
}
}
}
}
None
}
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<usize, FindAnnotationsAddressError> {
let mut splits = line.trim().splitn(6, ' ');
let address_str = splits
.next()
.ok_or(FindAnnotationsAddressError::ProcMapsParseError)?;
let _perms_str = splits
.next()
.ok_or(FindAnnotationsAddressError::ProcMapsParseError)?;
let _offset_str = splits
.next()
.ok_or(FindAnnotationsAddressError::ProcMapsParseError)?;
let _dev_str = splits
.next()
.ok_or(FindAnnotationsAddressError::ProcMapsParseError)?;
let _inode_str = splits
.next()
.ok_or(FindAnnotationsAddressError::ProcMapsParseError)?;
let _path_str = splits
.next()
.ok_or(FindAnnotationsAddressError::ProcMapsParseError)?;
let address = get_proc_maps_address(address_str)?;
Ok(address)
}
fn get_proc_maps_address(addresses: &str) -> Result<usize, FindAnnotationsAddressError> {
let begin = addresses
.split('-')
.next()
.ok_or(FindAnnotationsAddressError::ProcMapsParseError)?;
usize::from_str_radix(begin, 16).map_err(FindAnnotationsAddressError::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(target_os = "linux")]
type PTraceOperationNative = libc::c_uint;
#[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)
}
}

View File

@ -0,0 +1,214 @@
/* 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::{
errors::{FindAnnotationsAddressError, ReadError, RetrievalError},
ProcessHandle,
};
use super::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";
const MOZANNOTATION_SECTION: &[u8; 16] = b"mozannotation\0\0\0";
impl ProcessReader {
pub fn new(process: ProcessHandle) -> Result<ProcessReader, RetrievalError> {
Ok(ProcessReader { process })
}
pub fn find_annotations(&self) -> Result<usize, FindAnnotationsAddressError> {
let dyld_info = self.task_info()?;
if (dyld_info.all_image_info_format as u32) != TASK_DYLD_ALL_IMAGE_INFO_64 {
return Err(FindAnnotationsAddressError::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(FindAnnotationsAddressError::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_map(|image| self.find_annotations_in_image(image))
.ok_or(FindAnnotationsAddressError::NotFound)
}
fn task_info(&self) -> Result<task_dyld_info, FindAnnotationsAddressError> {
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(FindAnnotationsAddressError::TaskInfoError)
}
}
fn find_annotations_in_image(&self, image: &ImageInfo) -> Option<usize> {
self.copy_object::<Header64>(image.load_address as _)
.map_err(FindAnnotationsAddressError::from)
.and_then(|header| {
let image_address = image.load_address as usize;
let mut address = image_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_annotations_in_segment(address) {
return image_address
.checked_add(offset)
.ok_or(FindAnnotationsAddressError::InvalidAddress);
}
}
address += command.cmdsize as usize;
}
}
Err(FindAnnotationsAddressError::NotFound)
})
.ok()
}
fn find_annotations_in_segment(
&self,
segment_address: usize,
) -> Result<usize, FindAnnotationsAddressError> {
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(MOZANNOTATION_SECTION) {
return Ok(section.offset as usize);
}
}
}
Err(FindAnnotationsAddressError::InvalidAddress)
}
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) {}
}

View File

@ -0,0 +1,193 @@
/* 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,
mem::{size_of, MaybeUninit},
ptr::null_mut,
};
use winapi::{
shared::{
basetsd::SIZE_T,
minwindef::{DWORD, FALSE, HMODULE},
},
um::{
memoryapi::ReadProcessMemory,
psapi::{K32EnumProcessModules, K32GetModuleInformation, MODULEINFO},
},
};
use crate::{
errors::{FindAnnotationsAddressError, ReadError, RetrievalError},
ProcessHandle,
};
use super::ProcessReader;
impl ProcessReader {
pub fn new(process: ProcessHandle) -> Result<ProcessReader, RetrievalError> {
Ok(ProcessReader { process })
}
pub fn find_annotations(&self) -> Result<usize, FindAnnotationsAddressError> {
let modules = self.get_module_list()?;
modules
.iter()
.find_map(|&module| {
self.get_module_info(module).and_then(|info| {
self.find_annotations_in_module(
info.lpBaseOfDll as usize,
info.SizeOfImage as usize,
)
.ok()
})
})
.ok_or(FindAnnotationsAddressError::InvalidAddress)
}
fn get_module_list(&self) -> Result<Vec<HMODULE>, FindAnnotationsAddressError> {
let mut module_num: usize = 100;
let mut required_buffer_size: DWORD = 0;
let mut module_array = Vec::<MaybeUninit<HMODULE>>::with_capacity(module_num);
loop {
let buffer_size: DWORD = (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::<MaybeUninit<HMODULE>>::with_capacity(module_num);
} else {
return Err(FindAnnotationsAddressError::EnumProcessModulesError);
}
} else {
break;
}
}
// SAFETY: module_array has been filled by K32EnumProcessModules()
let module_array: Vec<HMODULE> = unsafe {
module_array.set_len(module_num);
std::mem::transmute(module_array)
};
Ok(module_array)
}
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 DWORD,
)
};
if res == 0 {
None
} else {
let info = unsafe { info.assume_init() };
Some(info)
}
}
fn find_annotations_in_module(
&self,
module_address: usize,
size: usize,
) -> Result<usize, FindAnnotationsAddressError> {
// 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(FindAnnotationsAddressError::ReadError(
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(mozannotation_client::ANNOTATION_SECTION) {
let address = module_address.checked_add(section.virtual_address as usize);
return address.ok_or(FindAnnotationsAddressError::InvalidAddress);
}
}
Err(FindAnnotationsAddressError::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>() as SIZE_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<MaybeUninit<T>> = Vec::with_capacity(num);
let res = unsafe {
ReadProcessMemory(
self.process,
src as _,
array.as_mut_ptr() as _,
num_of_bytes as SIZE_T,
null_mut(),
)
};
if res != FALSE {
unsafe {
array.set_len(num);
Ok(std::mem::transmute(array))
}
} else {
Err(ReadError::ReadProcessMemoryError)
}
}
}

View File

@ -57,6 +57,7 @@ fluent-langneg-ffi = { path = "../../../../intl/locale/rust/fluent-langneg-ffi"
rure = "0.2.2"
rust_minidump_writer_linux = { path = "../../../crashreporter/rust_minidump_writer_linux", optional = true }
mozannotation_client = { path = "../../../crashreporter/mozannotation_client" }
mozannotation_server = { path = "../../../crashreporter/mozannotation_server" }
gecko-profiler = { path = "../../../../tools/profiler/rust-api"}
midir_impl = { path = "../../../../dom/midi/midir_impl", optional = true }
dom = { path = "../../../../dom/base/rust" }

View File

@ -92,6 +92,8 @@ extern crate gecko_logger;
extern crate rust_minidump_writer_linux;
extern crate mozannotation_client;
extern crate mozannotation_server;
#[cfg(feature = "webmidi_midir_impl")]
extern crate midir_impl;

View File

@ -34,6 +34,7 @@ clippy:
- third_party/rust/mp4parse/
- third_party/rust/mp4parse_capi/
- toolkit/crashreporter/mozannotation_client/
- toolkit/crashreporter/mozannotation_server/
- toolkit/components/kvstore/
- toolkit/components/glean/
- toolkit/library/rust/