summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs
blob: 52a3957ca9ed15152fc637497cdd46c5135a6fea (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
/* 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) {}
}