summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/mozannotation_client/src/lib.rs
blob: df2bd5bf94c6443b8417d795459cde9cb375d9ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
/* 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::{
    alloc::{self, Layout},
    cmp::min,
    ffi::{c_char, c_void, CStr},
    ptr::{copy_nonoverlapping, null_mut},
    sync::Mutex,
};

#[cfg(any(target_os = "linux", target_os = "android"))]
use std::arch::global_asm;

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AnnotationContents {
    Empty,
    NSCStringPointer,
    CStringPointer,
    CString,
    ByteBuffer(u32),
    OwnedByteBuffer(u32),
}
#[repr(C)]
pub struct Annotation {
    pub id: u32,
    pub contents: AnnotationContents,
    pub address: usize,
}

impl Drop for Annotation {
    fn drop(&mut self) {
        match self.contents {
            AnnotationContents::OwnedByteBuffer(len) => {
                if (self.address != 0) && (len > 0) {
                    let align = min(usize::next_power_of_two(len as usize), 32);
                    unsafe {
                        let layout = Layout::from_size_align_unchecked(len as usize, align);
                        alloc::dealloc(self.address as *mut u8, layout);
                    }
                }
            }
            _ => {
                // Nothing to do
            }
        };
    }
}

pub struct AnnotationTable {
    data: Vec<Annotation>,
    magic_number: u32,
}

impl AnnotationTable {
    const fn new() -> AnnotationTable {
        AnnotationTable {
            data: Vec::new(),
            magic_number: ANNOTATION_TYPE,
        }
    }

    pub const fn verify(&self) -> bool {
        self.magic_number == ANNOTATION_TYPE
    }

    pub fn get_ptr(&self) -> *const Annotation {
        self.data.as_ptr()
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }
}

pub type AnnotationMutex = Mutex<AnnotationTable>;

#[cfg(target_os = "windows")]
#[link_section = "mozannot"]
static MOZANNOTATIONS: AnnotationMutex = Mutex::new(AnnotationTable::new());
#[cfg(any(target_os = "linux", target_os = "android"))]
static MOZANNOTATIONS: AnnotationMutex = Mutex::new(AnnotationTable::new());
#[cfg(target_os = "macos")]
#[link_section = "__DATA,mozannotation"]
static MOZANNOTATIONS: AnnotationMutex = Mutex::new(AnnotationTable::new());

#[no_mangle]
unsafe fn mozannotation_get() -> *const AnnotationMutex {
    &MOZANNOTATIONS as _
}

#[cfg(any(target_os = "linux", target_os = "android"))]
extern "C" {
    pub static MOZANNOTATION_NOTE_REFERENCE: &'static u32;
    pub static __ehdr_start: [u8; 0];
}

#[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";
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.
//
// When elfhack is used, the note might be moved after the aforementioned offset
// is calculated, without it being updated. To compensate for this we store the
// offset between the `ehdr` field and the ELF header. At runtime we can
// use this offset to adjust for the shift of the `desc` field.
#[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",
    "ehdr:",
    "  .quad {__ehdr_start} - ehdr",
    "desc_end:",
    "  .size MOZANNOTATION_NOTE, .-MOZANNOTATION_NOTE",
    mozannotation_symbol = sym MOZANNOTATIONS,
    __ehdr_start = sym __ehdr_start
);

// 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",
    "ehdr:",
    "  .long {__ehdr_start} - ehdr",
    "desc_end:",
    "  .size MOZANNOTATION_NOTE, .-MOZANNOTATION_NOTE",
    mozannotation_symbol = sym MOZANNOTATIONS,
    __ehdr_start = sym __ehdr_start
);

#[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,
    pub ehdr: isize,
}

fn store_annotation<T>(id: u32, contents: AnnotationContents, address: *const T) -> *const T {
    let address = match contents {
        AnnotationContents::OwnedByteBuffer(len) => {
            if !address.is_null() && (len > 0) {
                // Copy the contents of this annotation, we'll own the copy
                let len = len as usize;
                let align = min(usize::next_power_of_two(len), 32);
                unsafe {
                    let layout = Layout::from_size_align_unchecked(len as usize, align);
                    let src = address as *mut u8;
                    let dst = alloc::alloc(layout);
                    copy_nonoverlapping(src, dst, len);
                    dst
                }
            } else {
                null_mut()
            }
        }
        _ => address as *mut u8,
    };

    let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
    let old = if let Some(existing) = annotations.iter_mut().find(|e| e.id == id) {
        let old = match existing.contents {
            AnnotationContents::OwnedByteBuffer(len) => {
                // If we owned the previous value of this annotation we must free it.
                if (existing.address != 0) && (len > 0) {
                    let len = len as usize;
                    let align = min(usize::next_power_of_two(len), 32);
                    unsafe {
                        let layout = Layout::from_size_align_unchecked(len, align);
                        alloc::dealloc(existing.address as *mut u8, layout);
                    }
                }
                null_mut::<T>()
            }
            _ => existing.address as *mut T,
        };

        existing.contents = contents;
        existing.address = address as usize;
        old
    } else {
        annotations.push(Annotation {
            id,
            contents,
            address: address as usize,
        });
        null_mut::<T>()
    };

    old
}

/// Register a pointer to an nsCString string.
///
/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_register_nscstring(
    id: u32,
    address: *const nsCString,
) -> *const nsCString {
    store_annotation(id, AnnotationContents::NSCStringPointer, address)
}

/// Create a copy of the provided string with a specified size that will be
/// owned by the crate, and register a pointer to it.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_record_nscstring_from_raw_parts(
    id: u32,
    address: *const u8,
    size: usize,
) {
    store_annotation(
        id,
        AnnotationContents::OwnedByteBuffer(size as u32),
        address,
    );
}

/// Register a pointer to a pointer to a nul-terminated string.
///
/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_register_cstring_ptr(
    id: u32,
    address: *const *const c_char,
) -> *const *const c_char {
    store_annotation(id, AnnotationContents::CStringPointer, address)
}

/// Register a pointer to a nul-terminated string.
///
/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_register_cstring(id: u32, address: *const c_char) -> *const c_char {
    store_annotation(id, AnnotationContents::CString, address)
}

/// Create a copy of the provided nul-terminated string which will be owned by
/// the crate, and register a pointer to it.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_record_cstring(id: u32, address: *const c_char) {
    let len = unsafe { CStr::from_ptr(address).to_bytes().len() };
    store_annotation(id, AnnotationContents::OwnedByteBuffer(len as u32), address);
}

/// Register a pointer to a fixed size buffer.
///
/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_register_bytebuffer(
    id: u32,
    address: *const c_void,
    size: u32,
) -> *const c_void {
    store_annotation(id, AnnotationContents::ByteBuffer(size), address)
}

/// Create a copy of the provided buffer which will be owned by the crate, and
/// register a pointer to it.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_record_bytebuffer(id: u32, address: *const c_void, size: u32) {
    store_annotation(id, AnnotationContents::OwnedByteBuffer(size), address);
}

/// Unregister a crash annotation. Returns the previously registered pointer or
/// null if none was present. Return null also if the crate owned the
/// annotations' buffer.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_unregister(id: u32) -> *const c_void {
    store_annotation(id, AnnotationContents::Empty, null_mut())
}

/// Returns the raw address of an annotation if it has been registered or NULL
/// if it hasn't.
///
/// This function will be exposed to C++
#[no_mangle]
pub extern "C" fn mozannotation_get_contents(id: u32, contents: *mut AnnotationContents) -> usize {
    let annotations = &MOZANNOTATIONS.lock().unwrap().data;
    if let Some(annotation) = annotations.iter().find(|e| e.id == id) {
        if annotation.contents == AnnotationContents::Empty {
            return 0;
        }

        unsafe { *contents = annotation.contents };
        return annotation.address;
    }

    return 0;
}

#[no_mangle]
pub extern "C" fn mozannotation_clear_all() {
    let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
    annotations.clear();
}