summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/mozannotation_server
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/crashreporter/mozannotation_server
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/mozannotation_server')
-rw-r--r--toolkit/crashreporter/mozannotation_server/Cargo.toml25
-rw-r--r--toolkit/crashreporter/mozannotation_server/cbindgen.toml15
-rw-r--r--toolkit/crashreporter/mozannotation_server/moz.build17
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/errors.rs81
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/lib.rs217
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/process_reader.rs18
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs312
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs214
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/process_reader/windows.rs193
9 files changed, 1092 insertions, 0 deletions
diff --git a/toolkit/crashreporter/mozannotation_server/Cargo.toml b/toolkit/crashreporter/mozannotation_server/Cargo.toml
new file mode 100644
index 0000000000..747aff791e
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/Cargo.toml
@@ -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.7", 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" }
diff --git a/toolkit/crashreporter/mozannotation_server/cbindgen.toml b/toolkit/crashreporter/mozannotation_server/cbindgen.toml
new file mode 100644
index 0000000000..5329196262
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/cbindgen.toml
@@ -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"
diff --git a/toolkit/crashreporter/mozannotation_server/moz.build b/toolkit/crashreporter/mozannotation_server/moz.build
new file mode 100644
index 0000000000..21762b95b1
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/moz.build
@@ -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",
+ ]
diff --git a/toolkit/crashreporter/mozannotation_server/src/errors.rs b/toolkit/crashreporter/mozannotation_server/src/errors.rs
new file mode 100644
index 0000000000..037d432e5e
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/src/errors.rs
@@ -0,0 +1,81 @@
+/* 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")]
+ AnnotationTableNotFound(#[from] FindAnnotationsAddressError),
+ #[error("Corrupt or wrong annotation table")]
+ InvalidAnnotationTable,
+ #[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),
+}
diff --git a/toolkit/crashreporter/mozannotation_server/src/lib.rs b/toolkit/crashreporter/mozannotation_server/src/lib.rs
new file mode 100644
index 0000000000..4ecb8ec919
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/src/lib.rs
@@ -0,0 +1,217 @@
+/* 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::cmp::min;
+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,
+ max_annotations: usize,
+) -> *mut ThinVec<CAnnotation> {
+ let result = retrieve_annotations(process as _, max_annotations);
+ 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,
+ max_annotations: usize,
+) -> 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 annotation_table = mutex.get_mut().map_err(|_e| RetrievalError::InvalidData)?;
+
+ if !annotation_table.verify() {
+ return Err(RetrievalError::InvalidAnnotationTable);
+ }
+
+ let vec_pointer = annotation_table.get_ptr();
+ let length = annotation_table.len();
+ let mut annotations = ThinVec::<CAnnotation>::with_capacity(min(max_annotations, 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()))
+}
diff --git a/toolkit/crashreporter/mozannotation_server/src/process_reader.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader.rs
new file mode 100644
index 0000000000..b405b4b725
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/src/process_reader.rs
@@ -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;
diff --git a/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs
new file mode 100644
index 0000000000..db6dbd3df2
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs
@@ -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,
+ )?;
+
+ self.find_mozannotation_note(module_address, &elf)
+ .ok_or(FindAnnotationsAddressError::ProgramHeaderNotFound)
+ })
+ }
+
+ // 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> {
+ 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) {
+ let desc = note.desc;
+ let ehdr = (-note.ehdr) as usize;
+ let offset = desc + ehdr
+ - (offset_of!(MozAnnotationNote, ehdr)
+ - offset_of!(MozAnnotationNote, desc));
+
+ return usize::checked_add(module_address, offset);
+ }
+ }
+
+ 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)
+ }
+}
diff --git a/toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs
new file mode 100644
index 0000000000..52a3957ca9
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs
@@ -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) {}
+}
diff --git a/toolkit/crashreporter/mozannotation_server/src/process_reader/windows.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader/windows.rs
new file mode 100644
index 0000000000..ffdcd95937
--- /dev/null
+++ b/toolkit/crashreporter/mozannotation_server/src/process_reader/windows.rs
@@ -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)
+ }
+ }
+}