mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
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:
parent
73a3085a6b
commit
4c87f445a6
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -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"
|
||||
|
@ -64,6 +64,7 @@ if CONFIG["MOZ_CRASHREPORTER"]:
|
||||
DIRS += [
|
||||
"client",
|
||||
"mozannotation_client",
|
||||
"mozannotation_server",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_CRASHREPORTER_INJECTOR"]:
|
||||
|
25
toolkit/crashreporter/mozannotation_server/Cargo.toml
Normal file
25
toolkit/crashreporter/mozannotation_server/Cargo.toml
Normal 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" }
|
15
toolkit/crashreporter/mozannotation_server/cbindgen.toml
Normal file
15
toolkit/crashreporter/mozannotation_server/cbindgen.toml
Normal 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"
|
17
toolkit/crashreporter/mozannotation_server/moz.build
Normal file
17
toolkit/crashreporter/mozannotation_server/moz.build
Normal 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",
|
||||
]
|
79
toolkit/crashreporter/mozannotation_server/src/errors.rs
Normal file
79
toolkit/crashreporter/mozannotation_server/src/errors.rs
Normal 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),
|
||||
}
|
208
toolkit/crashreporter/mozannotation_server/src/lib.rs
Normal file
208
toolkit/crashreporter/mozannotation_server/src/lib.rs
Normal 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()))
|
||||
}
|
@ -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;
|
@ -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)
|
||||
}
|
||||
}
|
@ -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 §ions {
|
||||
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) {}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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" }
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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/
|
||||
|
Loading…
Reference in New Issue
Block a user