158 lines
5 KiB
Rust
158 lines
5 KiB
Rust
//! 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);
|
|
}
|