|
|
|
@ -0,0 +1,317 @@
|
|
|
|
|
/* 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 nsstring::nsCString;
|
|
|
|
|
use std::{ffi::c_void, sync::Mutex};
|
|
|
|
|
|
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
|
use std::arch::global_asm;
|
|
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
|
pub enum AnnotationContents {
|
|
|
|
|
Empty,
|
|
|
|
|
NSCString,
|
|
|
|
|
CString,
|
|
|
|
|
CharBuffer,
|
|
|
|
|
USize,
|
|
|
|
|
ByteBuffer(u32),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
pub struct Annotation {
|
|
|
|
|
pub id: u32,
|
|
|
|
|
pub contents: AnnotationContents,
|
|
|
|
|
pub address: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub type AnnotationTable = Vec<Annotation>;
|
|
|
|
|
pub type AnnotationMutex = Mutex<AnnotationTable>;
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
|
#[link_section = "mozannot"]
|
|
|
|
|
static MOZANNOTATIONS: AnnotationMutex = Mutex::new(Vec::new());
|
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
|
static MOZANNOTATIONS: AnnotationMutex = Mutex::new(Vec::new());
|
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
#[link_section = "__DATA,mozannotation"]
|
|
|
|
|
static MOZANNOTATIONS: AnnotationMutex = Mutex::new(Vec::new());
|
|
|
|
|
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
unsafe fn mozannotation_get() -> *const AnnotationMutex {
|
|
|
|
|
&MOZANNOTATIONS as _
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
|
pub static MOZANNOTATION_NOTE_REFERENCE: &'static u32;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
|
pub const ANNOTATION_SECTION: &'static [u8; 8] = b"mozannot";
|
|
|
|
|
|
|
|
|
|
// TODO: Use the following constants in the assembly below when constant
|
|
|
|
|
// expressions are stabilized: https://github.com/rust-lang/rust/issues/93332
|
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
|
const _ANNOTATION_NOTE_ALIGNMENT: u32 = 4;
|
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
|
pub const ANNOTATION_NOTE_NAME: &str = "mozannotation";
|
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
|
pub const ANNOTATION_TYPE: u32 = u32::from_le_bytes(*b"MOZA");
|
|
|
|
|
|
|
|
|
|
// We use the crashpad crash info trick here. We create a program note which
|
|
|
|
|
// we'll use to find the location of the MOZANNOTATIONS static. Since program
|
|
|
|
|
// headers are always available we'll always be able to find this note in the
|
|
|
|
|
// memory of the crashed program, even if it's stripped or the backing file on
|
|
|
|
|
// disk has been deleted.
|
|
|
|
|
//
|
|
|
|
|
// We'll set the note type and name so we can easily recognize it (see the
|
|
|
|
|
// constants above). In the note's desc field we'll have the linker store the
|
|
|
|
|
// offset between the address of the MOZANNOTATIONS static and the desc field
|
|
|
|
|
// itself.
|
|
|
|
|
//
|
|
|
|
|
// At runtime we'll localize the note in the target process' memory, find the
|
|
|
|
|
// address of the `desc` field, load its contents (that is the offset we stored
|
|
|
|
|
// at link time) and add them together. The resulting address is the location of
|
|
|
|
|
// the MOZANNOTATIONS static in memory.
|
|
|
|
|
#[cfg(all(
|
|
|
|
|
target_pointer_width = "64",
|
|
|
|
|
any(target_os = "linux", target_os = "android")
|
|
|
|
|
))]
|
|
|
|
|
global_asm!(
|
|
|
|
|
// The section holding the note will be called '.note.moz.annotation'. We
|
|
|
|
|
// create a program note that's allocated ('a' option) in the target binary
|
|
|
|
|
// so that it's loaded into memory.
|
|
|
|
|
" .section .note.moz.annotation,\"a\",%note",
|
|
|
|
|
// Note alignment must be 4 bytes because that's the default alignment for
|
|
|
|
|
// that section. If a different alignment is chosen the note will end up in
|
|
|
|
|
// its own section which we don't want.
|
|
|
|
|
" .balign 4", // TODO: _ANNOTATION_NOTE_ALIGNMENT
|
|
|
|
|
"MOZANNOTATION_NOTE:",
|
|
|
|
|
" .long name_end - name", // size in bytes of the note's name
|
|
|
|
|
" .long desc_end - desc", // size in bytes of the note's desc field
|
|
|
|
|
" .long 0x415a4f4d", // TODO: _ANNOTATION_TYPE, MOZA in reverse
|
|
|
|
|
"name:",
|
|
|
|
|
" .asciz \"mozannotation\"", // TODO: _ANNOTATION_NOTE_NAME
|
|
|
|
|
"name_end:",
|
|
|
|
|
" .balign 4", // TODO: _ANNOTATION_NOTE_ALIGNMENT
|
|
|
|
|
"desc:",
|
|
|
|
|
" .quad {mozannotation_symbol} - desc",
|
|
|
|
|
"desc_end:",
|
|
|
|
|
" .size MOZANNOTATION_NOTE, .-MOZANNOTATION_NOTE",
|
|
|
|
|
mozannotation_symbol = sym MOZANNOTATIONS
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// The following global_asm!() expressions for other targets because Rust's
|
|
|
|
|
// support for putting statements within expressions is still experimental-only.
|
|
|
|
|
// Once https://github.com/rust-lang/rust/issues/15701 is fixed this can be
|
|
|
|
|
// folded in the `global_asm!()` statement above.
|
|
|
|
|
|
|
|
|
|
#[cfg(all(
|
|
|
|
|
target_pointer_width = "32",
|
|
|
|
|
any(target_os = "linux", target_os = "android")
|
|
|
|
|
))]
|
|
|
|
|
global_asm!(
|
|
|
|
|
" .section .note.moz.annotation,\"a\",%note",
|
|
|
|
|
" .balign 4",
|
|
|
|
|
"MOZANNOTATION_NOTE:",
|
|
|
|
|
" .long name_end - name",
|
|
|
|
|
" .long desc_end - desc",
|
|
|
|
|
" .long 0x415a4f4d",
|
|
|
|
|
"name:",
|
|
|
|
|
" .asciz \"mozannotation\"",
|
|
|
|
|
"name_end:",
|
|
|
|
|
" .balign 4",
|
|
|
|
|
"desc:",
|
|
|
|
|
" .long {mozannotation_symbol} - desc",
|
|
|
|
|
"desc_end:",
|
|
|
|
|
" .size MOZANNOTATION_NOTE, .-MOZANNOTATION_NOTE",
|
|
|
|
|
mozannotation_symbol = sym MOZANNOTATIONS
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
#[cfg(all(
|
|
|
|
|
any(target_os = "linux", target_os = "android"),
|
|
|
|
|
not(target_arch = "arm")
|
|
|
|
|
))]
|
|
|
|
|
global_asm!(
|
|
|
|
|
// MOZANNOTATION_NOTE can't be referenced directly because the relocation
|
|
|
|
|
// used to make the reference may require that the address be 8-byte aligned
|
|
|
|
|
// and notes must have 4-byte alignment.
|
|
|
|
|
" .section .rodata,\"a\",%progbits",
|
|
|
|
|
" .balign 8",
|
|
|
|
|
// .globl indicates that it's available to link against other .o files.
|
|
|
|
|
// .hidden indicates that it will not appear in the executable's symbol
|
|
|
|
|
// table.
|
|
|
|
|
" .globl",
|
|
|
|
|
" .hidden MOZANNOTATION_NOTE_REFERENCE",
|
|
|
|
|
" .type MOZANNOTATION_NOTE_REFERENCE, %object",
|
|
|
|
|
"MOZANNOTATION_NOTE_REFERENCE:",
|
|
|
|
|
// The value of this quad isn't important. It exists to reference
|
|
|
|
|
// MOZANNOTATION_NOTE, causing the linker to include the note into the
|
|
|
|
|
// binary linking the mozannotation_client crate. The subtraction from name
|
|
|
|
|
// is a convenience to allow the value to be computed statically.
|
|
|
|
|
" .quad name - MOZANNOTATION_NOTE",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// In theory we could have used the statement above for ARM targets but for some
|
|
|
|
|
// reason the current rust compiler rejects the .quad directive. As with the other
|
|
|
|
|
// duplicate code above we could replace this with a single conditional line once
|
|
|
|
|
// once https://github.com/rust-lang/rust/issues/15701 is fixed.
|
|
|
|
|
|
|
|
|
|
#[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "arm"))]
|
|
|
|
|
global_asm!(
|
|
|
|
|
" .section .rodata,\"a\",%progbits",
|
|
|
|
|
" .balign 8",
|
|
|
|
|
" .globl",
|
|
|
|
|
" .hidden MOZANNOTATION_NOTE_REFERENCE",
|
|
|
|
|
" .type MOZANNOTATION_NOTE_REFERENCE, %object",
|
|
|
|
|
"MOZANNOTATION_NOTE_REFERENCE:",
|
|
|
|
|
" .long name - MOZANNOTATION_NOTE",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/// This structure mirrors the contents of the note declared above in the
|
|
|
|
|
/// assembly blocks. It is used to copy the contents of the note out of the
|
|
|
|
|
/// target process.
|
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
#[repr(C, packed(4))]
|
|
|
|
|
pub struct MozAnnotationNote {
|
|
|
|
|
pub namesz: u32,
|
|
|
|
|
pub descsz: u32,
|
|
|
|
|
pub note_type: u32,
|
|
|
|
|
pub name: [u8; 16], // "mozannotation" plus padding to next 4-bytes boundary
|
|
|
|
|
pub desc: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Register an annotation containing an nsCString.
|
|
|
|
|
/// Returns false if the annotation is already present (and doesn't change it).
|
|
|
|
|
///
|
|
|
|
|
/// This function will be exposed to C++
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
pub extern "C" fn mozannotation_register_nscstring(id: u32, address: *const nsCString) -> bool {
|
|
|
|
|
let mut annotations = MOZANNOTATIONS.lock().unwrap();
|
|
|
|
|
|
|
|
|
|
if annotations.iter().any(|e| e.id == id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
annotations.push(Annotation {
|
|
|
|
|
id,
|
|
|
|
|
contents: AnnotationContents::NSCString,
|
|
|
|
|
address: address as usize,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Register an annotation containing a null-terminated string.
|
|
|
|
|
/// Returns false if the annotation is already present (and doesn't change it).
|
|
|
|
|
///
|
|
|
|
|
/// This function will be exposed to C++
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
pub extern "C" fn mozannotation_register_cstring(
|
|
|
|
|
id: u32,
|
|
|
|
|
address: *const *const std::ffi::c_char,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let mut annotations = MOZANNOTATIONS.lock().unwrap();
|
|
|
|
|
|
|
|
|
|
if annotations.iter().any(|e| e.id == id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
annotations.push(Annotation {
|
|
|
|
|
id,
|
|
|
|
|
contents: AnnotationContents::CString,
|
|
|
|
|
address: address as usize,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Register an annotation pointing to a buffer holding a null-terminated string.
|
|
|
|
|
/// Returns false if the annotation is already present (and doesn't change it).
|
|
|
|
|
///
|
|
|
|
|
/// This function will be exposed to C++
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
pub extern "C" fn mozannotation_register_char_buffer(
|
|
|
|
|
id: u32,
|
|
|
|
|
address: *const std::ffi::c_char,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let mut annotations = MOZANNOTATIONS.lock().unwrap();
|
|
|
|
|
|
|
|
|
|
if annotations.iter().any(|e| e.id == id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
annotations.push(Annotation {
|
|
|
|
|
id,
|
|
|
|
|
contents: AnnotationContents::CharBuffer,
|
|
|
|
|
address: address as usize,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Register an annotation pointing to a variable of type usize.
|
|
|
|
|
/// Returns false if the annotation is already present (and doesn't change it).
|
|
|
|
|
///
|
|
|
|
|
/// This function will be exposed to C++
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
pub extern "C" fn mozannotation_register_usize(id: u32, address: *const usize) -> bool {
|
|
|
|
|
let mut annotations = MOZANNOTATIONS.lock().unwrap();
|
|
|
|
|
|
|
|
|
|
if annotations.iter().any(|e| e.id == id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
annotations.push(Annotation {
|
|
|
|
|
id,
|
|
|
|
|
contents: AnnotationContents::USize,
|
|
|
|
|
address: address as usize,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Register an annotation pointing to a fixed size buffer.
|
|
|
|
|
/// Returns false if the annotation is already present (and doesn't change it).
|
|
|
|
|
///
|
|
|
|
|
/// This function will be exposed to C++
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
pub extern "C" fn mozannotation_register_bytebuffer(
|
|
|
|
|
id: u32,
|
|
|
|
|
address: *const c_void,
|
|
|
|
|
size: u32,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let mut annotations = MOZANNOTATIONS.lock().unwrap();
|
|
|
|
|
|
|
|
|
|
if annotations.iter().any(|e| e.id == id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
annotations.push(Annotation {
|
|
|
|
|
id,
|
|
|
|
|
contents: AnnotationContents::ByteBuffer(size),
|
|
|
|
|
address: address as usize,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Unregister a crash annotation. Returns false if the annotation is not present.
|
|
|
|
|
///
|
|
|
|
|
/// This function will be exposed to C++
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
pub extern "C" fn mozannotation_unregister(id: u32) -> bool {
|
|
|
|
|
let mut annotations = MOZANNOTATIONS.lock().unwrap();
|
|
|
|
|
let index = annotations.iter().position(|e| e.id == id);
|
|
|
|
|
|
|
|
|
|
if let Some(index) = index {
|
|
|
|
|
annotations.remove(index);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|