summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/bin/test.rs
blob: 85b6fa6a93b499e4cadcc0111fe87b9e16d3808f (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
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
// This binary shouldn't be under /src, but under /tests, but that is
// currently not possible (https://github.com/rust-lang/cargo/issues/4356)

type Error = Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>;
pub type Result<T> = std::result::Result<T, Error>;

#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux {
    use super::*;
    use minidump_writer::{
        ptrace_dumper::{PtraceDumper, AT_SYSINFO_EHDR},
        LINUX_GATE_LIBRARY_NAME,
    };
    use nix::{
        sys::mman::{mmap, MapFlags, ProtFlags},
        unistd::getppid,
    };
    use std::os::fd::BorrowedFd;

    macro_rules! test {
        ($x:expr, $errmsg:expr) => {
            if $x {
                Ok(())
            } else {
                Err($errmsg)
            }
        };
    }

    fn test_setup() -> Result<()> {
        let ppid = getppid();
        PtraceDumper::new(ppid.as_raw())?;
        Ok(())
    }

    fn test_thread_list() -> Result<()> {
        let ppid = getppid();
        let dumper = PtraceDumper::new(ppid.as_raw())?;
        test!(!dumper.threads.is_empty(), "No threads")?;
        test!(
            dumper
                .threads
                .iter()
                .filter(|x| x.tid == ppid.as_raw())
                .count()
                == 1,
            "Thread found multiple times"
        )?;
        Ok(())
    }

    fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> {
        let ppid = getppid().as_raw();
        let mut dumper = PtraceDumper::new(ppid)?;
        dumper.suspend_threads()?;
        let stack_res = PtraceDumper::copy_from_process(ppid, stack_var as *mut libc::c_void, 1)?;

        let expected_stack: libc::c_long = 0x11223344;
        test!(
            stack_res == expected_stack.to_ne_bytes(),
            "stack var not correct"
        )?;

        let heap_res = PtraceDumper::copy_from_process(ppid, heap_var as *mut libc::c_void, 1)?;
        let expected_heap: libc::c_long = 0x55667788;
        test!(
            heap_res == expected_heap.to_ne_bytes(),
            "heap var not correct"
        )?;
        dumper.resume_threads()?;
        Ok(())
    }

    fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> {
        let ppid = getppid();
        let dumper = PtraceDumper::new(ppid.as_raw())?;
        dumper
            .find_mapping(addr1)
            .ok_or("No mapping for addr1 found")?;

        dumper
            .find_mapping(addr2)
            .ok_or("No mapping for addr2 found")?;

        test!(dumper.find_mapping(0).is_none(), "NULL found")?;
        Ok(())
    }

    fn test_file_id() -> Result<()> {
        let ppid = getppid().as_raw();
        let exe_link = format!("/proc/{}/exe", ppid);
        let exe_name = std::fs::read_link(exe_link)?.into_os_string();
        let mut dumper = PtraceDumper::new(getppid().as_raw())?;
        let mut found_exe = None;
        for (idx, mapping) in dumper.mappings.iter().enumerate() {
            if mapping.name.as_ref().map(|x| x.into()).as_ref() == Some(&exe_name) {
                found_exe = Some(idx);
                break;
            }
        }
        let idx = found_exe.unwrap();
        let id = dumper.elf_identifier_for_mapping_index(idx)?;
        assert!(!id.is_empty());
        assert!(id.iter().any(|&x| x > 0));
        Ok(())
    }

    fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> {
        // Now check that PtraceDumper interpreted the mappings properly.
        let dumper = PtraceDumper::new(getppid().as_raw())?;
        let mut mapping_count = 0;
        for map in &dumper.mappings {
            if map
                .name
                .as_ref()
                .map_or(false, |name| name.to_string_lossy().starts_with(&path))
            {
                mapping_count += 1;
                // This mapping should encompass the entire original mapped
                // range.
                assert_eq!(map.start_address, mapped_mem);
                assert_eq!(map.size, mem_size);
                assert_eq!(0, map.offset);
            }
        }
        assert_eq!(1, mapping_count);
        Ok(())
    }

    fn test_linux_gate_mapping_id() -> Result<()> {
        let ppid = getppid().as_raw();
        let mut dumper = PtraceDumper::new(ppid)?;
        let mut found_linux_gate = false;
        for mut mapping in dumper.mappings.clone() {
            if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
                found_linux_gate = true;
                dumper.suspend_threads()?;
                let id = PtraceDumper::elf_identifier_for_mapping(&mut mapping, ppid)?;
                test!(!id.is_empty(), "id-vec is empty")?;
                test!(id.iter().any(|&x| x > 0), "all id elements are 0")?;
                dumper.resume_threads()?;
                break;
            }
        }
        test!(found_linux_gate, "found no linux_gate")?;
        Ok(())
    }

    fn test_mappings_include_linux_gate() -> Result<()> {
        let ppid = getppid().as_raw();
        let dumper = PtraceDumper::new(ppid)?;
        let linux_gate_loc = dumper.auxv[&AT_SYSINFO_EHDR];
        test!(linux_gate_loc != 0, "linux_gate_loc == 0")?;
        let mut found_linux_gate = false;
        for mapping in &dumper.mappings {
            if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
                found_linux_gate = true;
                test!(
                    linux_gate_loc == mapping.start_address.try_into()?,
                    "linux_gate_loc != start_address"
                )?;

                // This doesn't work here, as we do not test via "fork()", so the addresses are different
                // let ll = mapping.start_address as *const u8;
                // for idx in 0..header::SELFMAG {
                //     let mag = unsafe { std::ptr::read(ll.offset(idx as isize)) == header::ELFMAG[idx] };
                //     test!(
                //         mag,
                //         format!("ll: {} != ELFMAG: {} at {}", mag, header::ELFMAG[idx], idx)
                //     )?;
                // }
                break;
            }
        }
        test!(found_linux_gate, "found no linux_gate")?;
        Ok(())
    }

    fn spawn_and_wait(num: usize) -> Result<()> {
        // One less than the requested amount, as the main thread counts as well
        for _ in 1..num {
            std::thread::spawn(|| {
                println!("1");
                loop {
                    std::thread::park();
                }
            });
        }
        println!("1");
        loop {
            std::thread::park();
        }
    }

    fn spawn_name_wait(num: usize) -> Result<()> {
        // One less than the requested amount, as the main thread counts as well
        for id in 1..num {
            std::thread::Builder::new()
                .name(format!("thread_{}", id))
                .spawn(|| {
                    println!("1");
                    loop {
                        std::thread::park();
                    }
                })?;
        }
        println!("1");
        loop {
            std::thread::park();
        }
    }

    fn spawn_mmap_wait() -> Result<()> {
        let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap();
        let memory_size = std::num::NonZeroUsize::new(page_size.unwrap() as usize).unwrap();
        // Get some memory to be mapped by the child-process
        let mapped_mem = unsafe {
            mmap::<BorrowedFd>(
                None,
                memory_size,
                ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
                MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON,
                None,
                0,
            )
            .unwrap()
        };

        println!("{} {}", mapped_mem as usize, memory_size);
        loop {
            std::thread::park();
        }
    }

    fn spawn_alloc_wait() -> Result<()> {
        let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).unwrap();
        let memory_size = page_size.unwrap() as usize;

        let mut values = Vec::<u8>::with_capacity(memory_size);
        for idx in 0..memory_size {
            values.push((idx % 255) as u8);
        }

        println!("{:p} {}", values.as_ptr(), memory_size);
        loop {
            std::thread::park();
        }
    }

    fn create_files_wait(num: usize) -> Result<()> {
        let mut file_array = Vec::<tempfile::NamedTempFile>::with_capacity(num);
        for id in 0..num {
            let file = tempfile::Builder::new()
                .prefix("test_file")
                .suffix::<str>(id.to_string().as_ref())
                .tempfile()
                .unwrap();
            file_array.push(file);
            println!("1");
        }
        println!("1");
        loop {
            std::thread::park();
            // This shouldn't be executed, but we put it here to ensure that
            // all the files within the array are kept open.
            println!("{}", file_array.len());
        }
    }

    pub(super) fn real_main(args: Vec<String>) -> Result<()> {
        match args.len() {
            1 => match args[0].as_ref() {
                "file_id" => test_file_id(),
                "setup" => test_setup(),
                "thread_list" => test_thread_list(),
                "mappings_include_linux_gate" => test_mappings_include_linux_gate(),
                "linux_gate_mapping_id" => test_linux_gate_mapping_id(),
                "spawn_mmap_wait" => spawn_mmap_wait(),
                "spawn_alloc_wait" => spawn_alloc_wait(),
                _ => Err("Len 1: Unknown test option".into()),
            },
            2 => match args[0].as_ref() {
                "spawn_and_wait" => {
                    let num_of_threads: usize = args[1].parse().unwrap();
                    spawn_and_wait(num_of_threads)
                }
                "spawn_name_wait" => {
                    let num_of_threads: usize = args[1].parse().unwrap();
                    spawn_name_wait(num_of_threads)
                }
                "create_files_wait" => {
                    let num_of_files: usize = args[1].parse().unwrap();
                    create_files_wait(num_of_files)
                }
                _ => Err(format!("Len 2: Unknown test option: {}", args[0]).into()),
            },
            3 => {
                if args[0] == "find_mappings" {
                    let addr1: usize = args[1].parse().unwrap();
                    let addr2: usize = args[2].parse().unwrap();
                    test_find_mappings(addr1, addr2)
                } else if args[0] == "copy_from_process" {
                    let stack_var: usize = args[1].parse().unwrap();
                    let heap_var: usize = args[2].parse().unwrap();
                    test_copy_from_process(stack_var, heap_var)
                } else {
                    Err(format!("Len 3: Unknown test option: {}", args[0]).into())
                }
            }
            4 => {
                if args[0] == "merged_mappings" {
                    let path = &args[1];
                    let mapped_mem: usize = args[2].parse().unwrap();
                    let mem_size: usize = args[3].parse().unwrap();
                    test_merged_mappings(path.to_string(), mapped_mem, mem_size)
                } else {
                    Err(format!("Len 4: Unknown test option: {}", args[0]).into())
                }
            }
            _ => Err("Unknown test option".into()),
        }
    }
}

#[cfg(target_os = "windows")]
mod windows {
    use super::*;
    use std::mem;

    #[link(name = "kernel32")]
    extern "system" {
        pub fn GetCurrentProcessId() -> u32;
        pub fn GetCurrentThreadId() -> u32;
        pub fn GetCurrentThread() -> isize;
        pub fn GetThreadContext(thread: isize, context: *mut crash_context::CONTEXT) -> i32;
    }

    #[inline(never)]
    pub(super) fn real_main(args: Vec<String>) -> Result<()> {
        let exception_code = u32::from_str_radix(&args[0], 16).unwrap();

        // Generate the exception and communicate back where the exception pointers
        // are
        unsafe {
            let mut exception_record: crash_context::EXCEPTION_RECORD = mem::zeroed();
            let mut exception_context = std::mem::MaybeUninit::uninit();

            let pid = GetCurrentProcessId();
            let tid = GetCurrentThreadId();

            GetThreadContext(GetCurrentThread(), exception_context.as_mut_ptr());

            let mut exception_context = exception_context.assume_init();

            let exception_ptrs = crash_context::EXCEPTION_POINTERS {
                ExceptionRecord: &mut exception_record,
                ContextRecord: &mut exception_context,
            };

            exception_record.ExceptionCode = exception_code as _;

            let exc_ptr_addr = &exception_ptrs as *const _ as usize;

            println!("{pid} {exc_ptr_addr} {tid} {exception_code:x}");

            // Wait until we're killed
            loop {
                std::thread::park();
            }
        }
    }
}

#[cfg(target_os = "macos")]
mod mac {
    use super::*;
    use std::time::Duration;

    #[inline(never)]
    pub(super) fn real_main(args: Vec<String>) -> Result<()> {
        let port_name = args.get(0).ok_or("mach port name not specified")?;
        let exception: u32 = args.get(1).ok_or("exception code not specified")?.parse()?;

        let client =
            crash_context::ipc::Client::create(&std::ffi::CString::new(port_name.clone())?)?;

        std::thread::Builder::new()
            .name("test-thread".to_owned())
            .spawn(move || {
                #[inline(never)]
                fn wait_until_killed(client: crash_context::ipc::Client, exception: u32) {
                    // SAFETY: syscalls
                    let cc = unsafe {
                        crash_context::CrashContext {
                            task: mach2::traps::mach_task_self(),
                            thread: mach2::mach_init::mach_thread_self(),
                            handler_thread: mach2::port::MACH_PORT_NULL,
                            exception: Some(crash_context::ExceptionInfo {
                                kind: exception,
                                code: 0,
                                subcode: None,
                            }),
                        }
                    };

                    // Send the crash context to the server and wait for it to
                    // finish dumping, we should be killed shortly afterwards
                    client
                        .send_crash_context(
                            &cc,
                            Some(Duration::from_secs(2)),
                            Some(Duration::from_secs(5)),
                        )
                        .expect("failed to send crash context/receive ack");

                    // Wait until we're killed
                    loop {
                        std::thread::park();
                    }
                }

                wait_until_killed(client, exception)
            })
            .unwrap()
            .join()
            .unwrap();

        Ok(())
    }
}

fn main() -> Result<()> {
    let args: Vec<_> = std::env::args().skip(1).collect();

    cfg_if::cfg_if! {
        if #[cfg(any(target_os = "linux", target_os = "android"))] {
            linux::real_main(args)
        } else if #[cfg(target_os = "windows")] {
            windows::real_main(args)
        } else if #[cfg(target_os = "macos")] {
            mac::real_main(args)
        } else {
            unimplemented!();
        }
    }
}