forked from mstange/samply
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdsymutil_sigkill.rs
More file actions
122 lines (110 loc) · 4.64 KB
/
Copy pathdsymutil_sigkill.rs
File metadata and controls
122 lines (110 loc) · 4.64 KB
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
//! Regression test for: `samply record` SIGKILLs `dsymutil`.
//!
//! samply injects `DYLD_INSERT_LIBRARIES` (+ `SAMPLY_BOOTSTRAP_SERVER_NAME`) into
//! the *entire* descendant process tree it launches. Any descendant that loads
//! the preload hands its mach task port to samply and lets it "control us
//! completely" (see `samply-mac-preload`). For `dsymutil` this takeover ends in a
//! deterministic `SIGKILL`, which breaks builds run under `samply record` on
//! macOS (the linker invokes `dsymutil` and reports `running dsymutil failed:
//! signal: killed`).
//!
//! This test launches, under the built `samply` binary, a small locally-built
//! "spawner" that execs `dsymutil` on a Mach-O with DWARF, and asserts that
//! `dsymutil` is NOT killed (the desired behaviour).
//!
//! macOS-only (`cfg(target_os = "macos")`), so it compiles out elsewhere. It
//! needs Xcode's `dsymutil` and a working `cc`, both present on `macos-latest`
//! CI runners. Launch-mode profiling does not need `task_for_pid` entitlements
//! (the child volunteers its task port), so no `samply setup` is required.
//!
//! Run with:
//! cargo test -p samply --test dsymutil_sigkill -- --nocapture
#![cfg(target_os = "macos")]
use std::path::Path;
use std::process::Command;
fn cc(args: &[&str]) {
let status = Command::new("cc").args(args).status().expect("failed to run cc");
assert!(status.success(), "cc {args:?} failed");
}
fn xcrun_dsymutil() -> String {
let out = Command::new("xcrun")
.args(["-f", "dsymutil"])
.output()
.expect("failed to run xcrun");
assert!(out.status.success(), "xcrun -f dsymutil failed");
String::from_utf8(out.stdout).unwrap().trim().to_string()
}
#[test]
fn dsymutil_is_not_killed_under_samply() {
let samply = env!("CARGO_BIN_EXE_samply");
let dsymutil = xcrun_dsymutil();
let tmp = std::env::temp_dir().join(format!("samply_dsym_repro_{}", std::process::id()));
std::fs::create_dir_all(&tmp).unwrap();
// A Mach-O with enough DWARF that dsymutil does real work.
let src = tmp.join("big.cpp");
{
use std::fmt::Write as _;
let mut s = String::from("#include <cstdio>\n");
for i in 0..1200 {
writeln!(s, "template<int N> struct S{i} {{ int v[N%7+1]; int f(int x){{return x*{i}+N;}} }};").unwrap();
writeln!(s, "int g{i}(int x){{ S{i}<{}> s; return s.f(x)+{i}; }}", i % 9 + 1).unwrap();
}
s.push_str("int main(){int t=0;");
for i in 0..1200 {
write!(s, "t+=g{i}(t);").unwrap();
}
s.push_str("printf(\"%d\\n\",t);return 0;}\n");
std::fs::write(&src, s).unwrap();
}
let macho = tmp.join("bigcpp");
cc(&["-g", "-O0", "-o", macho.to_str().unwrap(), src.to_str().unwrap()]);
// A locally-built (non-restricted) parent that execs dsymutil and reports
// how the child died via its own exit code: 0 = clean, 1 = killed by signal.
let spawner_src = tmp.join("spawner.c");
std::fs::write(
&spawner_src,
r#"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char** argv){
pid_t pid = fork();
if(pid==0){ execl(argv[1],"dsymutil","-f",argv[2],"-o",argv[3],(char*)0); _exit(127); }
int st=0; waitpid(pid,&st,0);
if(WIFSIGNALED(st)){ fprintf(stderr,"dsymutil killed by signal %d\n", WTERMSIG(st)); return 1; }
fprintf(stderr,"dsymutil exited code %d\n", WEXITSTATUS(st)); return 0;
}
"#,
)
.unwrap();
let spawner = tmp.join("spawner");
cc(&["-O0", "-o", spawner.to_str().unwrap(), spawner_src.to_str().unwrap()]);
let out_dwarf = tmp.join("out.dwarf");
let profile = tmp.join("profile.json.gz");
// samply record --save-only -o <profile> -- <spawner> <dsymutil> <macho> <out.dwarf>
let status = Command::new(samply)
.args(["record", "--save-only", "-o"])
.arg(&profile)
.arg("--")
.arg(&spawner)
.arg(&dsymutil)
.arg(&macho)
.arg(&out_dwarf)
.status()
.expect("failed to run samply");
// The spawner exits 0 iff dsymutil completed normally. Under the bug it exits
// 1 because dsymutil was SIGKILLed by samply.
let killed = !status.success();
let produced_output = Path::new(&out_dwarf).exists();
// Clean up only after we've inspected the results (out.dwarf lives in `tmp`).
let _ = std::fs::remove_dir_all(&tmp);
assert!(
!killed,
"dsymutil was killed when run under `samply record` \
(spawner exit = {:?}). samply must not SIGKILL build subprocesses it \
injects into.",
status.code()
);
assert!(produced_output, "dsymutil did not produce its output");
}