summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/tests/task_dumper.rs
blob: 1411acc34a5b975e69f69209018a281ded5bdc77 (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
//! All of these tests are specific to the MacOS task dumper
#![cfg(target_os = "macos")]

use minidump_writer::{mach::LoadCommand, task_dumper::TaskDumper};
use std::fmt::Write;

fn call_otool(args: &[&str]) -> String {
    let mut cmd = std::process::Command::new("otool");
    cmd.args(args);

    let exe_path = std::env::current_exe().expect("unable to retrieve test executable path");
    cmd.arg(exe_path);

    let output = cmd.output().expect("failed to spawn otool");

    assert!(output.status.success());

    String::from_utf8(output.stdout).expect("stdout was invalid utf-8")
}

/// Validates we can iterate the load commands for all of the images in the task
#[test]
fn iterates_load_commands() {
    let lc_str = call_otool(&["-l"]);

    let mut expected = String::new();
    let mut lc_index = 0;

    expected.push('\n');

    while let Some(nlc) = lc_str[lc_index..].find("Load command ") {
        lc_index += nlc;

        let block = match lc_str[lc_index + 13..].find("Load command ") {
            Some(ind) => &lc_str[lc_index + 13..lc_index + 13 + ind],
            None => &lc_str[lc_index..],
        };

        // otool prints the load command index for each command, but we only
        // handle the small subset of the available load commands we care about
        // so just ignore that
        let block = &block[block.find('\n').unwrap() + 1..];

        // otool also prints all the sections for LC_SEGMENT_* commands, but
        // we don't care about those, so ignore them
        let block = match block.find("Section") {
            Some(ind) => &block[..ind],
            None => block,
        };

        lc_index += 13;

        let cmd = block
            .find("cmd ")
            .expect("load commnd didn't specify cmd kind");
        let cmd_end = block[cmd..]
            .find('\n')
            .expect("load cmd didn't end with newline");
        if matches!(
            &block[cmd + 4..cmd + cmd_end],
            "LC_SEGMENT_64" | "LC_UUID" | "LC_ID_DYLIB" | "LC_LOAD_DYLINKER"
        ) {
            expected.push_str(block);
        }
    }

    let task_dumper = TaskDumper::new(
        // SAFETY: syscall
        unsafe { mach2::traps::mach_task_self() },
    );

    let mut actual = String::new();

    // Unfortunately, Apple decided to move dynamic libs into a shared cache,
    // removing them from the file system completely, and unless I'm missing it
    // there is no way to get the load commands for the dylibs since otool
    // only understands file paths? So we just get the load commands for the main
    // executable instead, this means that we miss the `LC_ID_DYLIB` commands
    // since they only apply to dylibs, but this test is more that we can
    // correctly iterate through the load commands themselves, so this _should_
    // be fine...
    let exe_img = task_dumper
        .read_executable_image()
        .expect("failed to read executable image");

    {
        let lcmds = task_dumper
            .read_load_commands(&exe_img)
            .expect("failed to read load commands");

        for lc in lcmds.iter() {
            match lc {
                LoadCommand::Segment(seg) => {
                    let segname = std::str::from_utf8(&seg.segment_name).unwrap();
                    let segname = &segname[..segname.find('\0').unwrap()];
                    write!(
                        &mut actual,
                        "
      cmd LC_SEGMENT_64
  cmdsize {}
  segname {}
   vmaddr 0x{:016x}
   vmsize 0x{:016x}
  fileoff {}
 filesize {}
  maxprot 0x{:08x}
 initprot 0x{:08x}
   nsects {}
    flags 0x{:x}",
                        seg.cmd_size,
                        segname,
                        seg.vm_addr,
                        seg.vm_size,
                        seg.file_off,
                        seg.file_size,
                        seg.max_prot,
                        seg.init_prot,
                        seg.num_sections,
                        seg.flags,
                    )
                    .unwrap();
                }
                LoadCommand::Dylib(_dylib) => {
                    unreachable!();
                }
                LoadCommand::Uuid(uuid) => {
                    let id = uuid::Uuid::from_bytes(uuid.uuid);
                    let mut uuid_buf = [0u8; uuid::fmt::Hyphenated::LENGTH];
                    let uuid_str = id.hyphenated().encode_upper(&mut uuid_buf);

                    write!(
                        &mut actual,
                        "
     cmd LC_UUID
 cmdsize {}
    uuid {uuid_str}
",
                        uuid.cmd_size,
                    )
                    .unwrap();
                }
                LoadCommand::DylinkerCommand(dy_cmd) => {
                    write!(
                        &mut actual,
                        "
          cmd LC_LOAD_DYLINKER
      cmdsize {}
         name {} (offset {})",
                        dy_cmd.cmd_size, dy_cmd.name, dy_cmd.name_offset,
                    )
                    .unwrap();
                }
            }
        }
    }

    similar_asserts::assert_eq!(expected, actual);
}