summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/tests/task_dumper.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/minidump-writer/tests/task_dumper.rs')
-rw-r--r--third_party/rust/minidump-writer/tests/task_dumper.rs158
1 files changed, 158 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/tests/task_dumper.rs b/third_party/rust/minidump-writer/tests/task_dumper.rs
new file mode 100644
index 0000000000..1411acc34a
--- /dev/null
+++ b/third_party/rust/minidump-writer/tests/task_dumper.rs
@@ -0,0 +1,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);
+}