我正在使用
ptrace
箱的 nix
功能拦截子进程完成的系统调用。原则上,我的代码正在工作,因为它没有崩溃,系统调用被拦截,并且目标进程按预期运行。但对于相同的确定性目标进程,我的代码报告的系统调用与 strace
显示的系统调用不同。
这是我的代码的简化版本,与
strace
一样,将目标可执行文件作为 CLI 参数并在跟踪的子进程中生成它:
use std::{
env,
os::unix::process::CommandExt,
process::{Command, ExitCode},
};
use nix::{
libc,
sys::{
ptrace,
wait::{waitpid, WaitStatus},
},
unistd::{fork, ForkResult, Pid},
};
fn run_in_child() {
// Allow this process to be ptraced by the parent. When we replace the child
// process with the target process down below, the traced process will get
// paused by a SIGTRAP signal, for which we can wait in the parent process.
// This allows us to make sure the parent process has finished setting up
// its instrumentation before the target process really starts.
ptrace::traceme().unwrap();
// Skip the path of our own executable
let mut args = env::args().skip(1);
// Replace this child process with the target command
let err = Command::new(args.next().unwrap()).args(args).exec();
// The following is only executed if `exec` returned, which it only does in
// case of an error.
let cmd_str = env::args().skip(1).collect::<Vec<_>>().join(" ");
println!("Could not execute '{cmd_str}': {err}");
// Exit with the same error code
let exit_code = err.raw_os_error().unwrap();
unsafe { libc::_exit(exit_code) };
}
fn run_in_parent(child: Pid) {
// The child uses `PTRACE_TRACEME` to allow itself to be traced. When the child
// then calls `execve` it is stopped so that the parent (we) can finish our setup
// before the child continues. Hence we wait here until the child has called
// `execve`.
waitpid(child, None).expect("Could not wait for child to be ready");
println!("Child is ready");
// By default, if the child is stopped at a syscall, we get notified of that as a
// SIGTRAP. However, that can also happen for other reasons, so we tell ptrace to
// distinguish syscalls by setting a special bit in the status. This allows us to
// then match against WaitStatus::PtraceSyscall later.
ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD)
.expect("Could not set ptrace options");
loop {
ptrace::syscall(child, None).expect("Could not wait for syscall");
match waitpid(child, None) {
Ok(WaitStatus::Exited(_, exit_code)) => {
println!("Child has exited with code {exit_code}");
break;
}
Ok(WaitStatus::PtraceSyscall(..)) => {
// Child is doing a syscall. To figure out the details about the call,
// we need to inspect the registers
let regs = ptrace::getregs(child).expect("Could not get user registers");
// The number of the syscall is in the register `rax`. However, that
// register is also used for the return value. Therefore, the original
// value is stored separately.
let syscall_num = regs.orig_rax;
println!("Syscall #{syscall_num}");
// We get notified both before the syscall happens and when it
// returns. To avoid duplicates, we ignore the next event.
ptrace::syscall(child, None).expect("Could not wait for syscall");
match waitpid(child, None) {
Ok(WaitStatus::Exited(_, exit_code)) => {
println!("Child has exited with code {exit_code}");
break;
}
Ok(WaitStatus::PtraceSyscall(..)) => {
// Syscall returned as expected
}
x => {
println!("Something else: {x:?}")
}
}
}
x => {
println!("Something else: {x:?}")
}
}
}
}
fn main() -> ExitCode {
if env::args().len() < 2 {
println!("NOT ENOUGH ARGUMENTS");
ExitCode::FAILURE
} else {
match unsafe { fork() } {
Ok(ForkResult::Parent { child, .. }) => {
run_in_parent(child);
ExitCode::SUCCESS
}
Ok(ForkResult::Child) => {
run_in_child();
unsafe { libc::_exit(0) };
}
Err(_) => {
println!("Fork failed");
return ExitCode::FAILURE;
}
}
}
}
我正在针对以下演示程序对其进行测试,该程序是我使用
cargo build
在单独的 Cargo 项目中编译的(然后我将 path/to/that/project/target/debug/syscall-test
传递给我的代码和 strace
)。
fn main() {
println!("Hello, world!");
}
这是我的代码输出(左侧,用系统调用名称注释)和
strace
(右侧)的比较,每行一个系统调用,以及一个工具没有报告另一个工具报告的系统调用的间隙做了报告。
brk (#12) brk(NULL) = 0x55dc6a72c000
arch_prctl (#158) arch_prctl(0x3001 /* ARCH_??? */, 0x7fff972e04e0) = -1 EINVAL (Invalid argument)
mmap (#9)
access (#21) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat (#257) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
stat (#4) fstat(3, {st_mode=S_IFREG|0644, st_size=133281, ...}) = 0
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
stat (#4)
openat (#257)
fstat (#5)
mmap (#9) mmap(NULL, 133281, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f924da2f000
close (#3) close(3) = 0
openat (#257) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read (#0) read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3405\0\0\0\0\0\0"..., 832) = 832
fstat (#5) fstat(3, {st_mode=S_IFREG|0644, st_size=104984, ...}) = 0
mmap (#9) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f924da2d000
mmap (#9) mmap(NULL, 107592, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f924da12000
mmap (#9) mmap(0x7f924da15000, 73728, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f924da15000
mmap (#9) mmap(0x7f924da27000, 16384, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15000) = 0x7f924da27000
mmap(0x7f924da2b000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18000) = 0x7f924da2b000
close (#3) close(3) = 0
openat (#257) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
openat (#257)
openat (#257)
openat (#257)
openat (#257)
read (#0) read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220q\0\0\0\0\0\0"..., 832) = 832
pread64 (#17) pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\f\4K\246\21\256\356\256\273\203t\346`\6\0374"..., 68, 824) = 68
fstat (#5) fstat(3, {st_mode=S_IFREG|0755, st_size=157224, ...}) = 0
pread64 (#17) pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\f\4K\246\21\256\356\256\273\203t\346`\6\0374"..., 68, 824) = 68
mmap (#9) mmap(NULL, 140408, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f924d9ef000
mmap (#9) mmap(0x7f924d9f5000, 69632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7f924d9f5000
mmap (#9) mmap(0x7f924da06000, 24576, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x17000) = 0x7f924da06000
mmap (#9) mmap(0x7f924da0c000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c000) = 0x7f924da0c000
mmap (#9) mmap(0x7f924da0e000, 13432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f924da0e000
close (#3) close(3) = 0
openat (#257) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
openat (#257)
openat (#257)
openat (#257)
openat (#257)
read (#0) read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \22\0\0\0\0\0\0"..., 832) = 832
fstat (#5) fstat(3, {st_mode=S_IFREG|0644, st_size=18848, ...}) = 0
mmap (#9) mmap(NULL, 20752, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f924d9e9000
mmap (#9) mmap(0x7f924d9ea000, 8192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7f924d9ea000
mmap (#9) mmap(0x7f924d9ec000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f924d9ec000
mmap (#9) mmap(0x7f924d9ed000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x3000) = 0x7f924d9ed000
close (#3) close(3) = 0
openat (#257) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat (#257)
openat (#257)
openat (#257)
openat (#257)
read (#0) read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300A\2\0\0\0\0\0"..., 832) = 832
pread64 (#17) pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64 (#17) pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64 (#17) pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\356\276]_K`\213\212S\354Dkc\230\33\272"..., 68, 880) = 68
fstat (#5) fstat(3, {st_mode=S_IFREG|0755, st_size=2029592, ...}) = 0
pread64 (#17) pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64 (#17) pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64 (#17) pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\356\276]_K`\213\212S\354Dkc\230\33\272"..., 68, 880) = 68
mmap (#9) mmap(NULL, 2037344, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f924d7f7000
mmap (#9) mmap(0x7f924d819000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f924d819000
mmap (#9) mmap(0x7f924d991000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19a000) = 0x7f924d991000
mmap (#9) mmap(0x7f924d9df000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f924d9df000
mmap (#9) mmap(0x7f924d9e5000, 13920, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f924d9e5000
close (#3) close(3) = 0
mmap (#9) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f924d7f5000
arch_prctl (#158) arch_prctl(ARCH_SET_FS, 0x7f924d7f5bc0) = 0
mprotect (#10) mprotect(0x7f924d9df000, 16384, PROT_READ) = 0
mprotect (#10) mprotect(0x7f924d9ed000, 4096, PROT_READ) = 0
mprotect (#10) mprotect(0x7f924da0c000, 4096, PROT_READ) = 0
mprotect (#10) mprotect(0x7f924da2b000, 4096, PROT_READ) = 0
mprotect (#10) mprotect(0x55dc689a7000, 12288, PROT_READ) = 0
mprotect (#10) mprotect(0x7f924da7d000, 4096, PROT_READ) = 0
munmap (#11) munmap(0x7f924da2f000, 133281) = 0
set_tid_address (#218) set_tid_address(0x7f924d7f5e90) = 110877
set_robust_list (#273) set_robust_list(0x7f924d7f5ea0, 24) = 0
rt_sigaction (#13) rt_sigaction(SIGRTMIN, {sa_handler=0x7f924d9f5bf0, sa_mask=[], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f924da03420}, NULL, 8) = 0
rt_sigaction (#13) rt_sigaction(SIGRT_1, {sa_handler=0x7f924d9f5c90, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0x7f924da03420}, NULL, 8) = 0
rt_sigprocmask (#14) rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
prlimit64 (#302) prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
poll (#7) poll([{fd=0, events=0}, {fd=1, events=0}, {fd=2, events=0}], 3, 0) = 0 (Timeout)
rt_sigaction (#13) rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f924d83a090}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction (#13) rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction (#13) rt_sigaction(SIGSEGV, {sa_handler=0x55dc68974960, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7f924da03420}, NULL, 8) = 0
rt_sigaction (#13) rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction (#13) rt_sigaction(SIGBUS, {sa_handler=0x55dc68974960, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_SIGINFO, sa_restorer=0x7f924da03420}, NULL, 8) = 0
sigaltstack (#131) sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0
mmap (#9) mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f924da4d000
mprotect (#10) mprotect(0x7f924da4d000, 4096, PROT_NONE) = 0
sigaltstack (#131) sigaltstack({ss_sp=0x7f924da4e000, ss_flags=0, ss_size=8192}, NULL) = 0
brk (#12) brk(NULL) = 0x55dc6a72c000
brk (#12) brk(0x55dc6a74d000) = 0x55dc6a74d000
openat (#257) openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
prlimit64 (#302) prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
fstat (#5) fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read (#0) read(3, "55dc6894d000-55dc68953000 r--p 0"..., 1024) = 1024
read (#0) read(3, "0-7f924d9df000 r--p 0019a000 fd:"..., 1024) = 1024
read (#0) read(3, "_64-linux-gnu/libpthread-2.31.so"..., 1024) = 1024
read (#0) read(3, "da2d000 rw-p 00019000 fd:01 4613"..., 1024) = 1024
close (#3) close(3) = 0
sched_getaffinity (#204) sched_getaffinity(110877, 32, [0, 1, 2, 3]) = 8
write (#1) write(1, "Hello, world!\n", 14Hello, world!) = 14
sigaltstack (#131) sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=8192}, NULL) = 0
munmap (#11) munmap(0x7f924da4d000, 12288) = 0
exit_group (#231) exit_group(0) = ?
如您所见,我的代码报告的系统调用比
strace
报告的系统调用更多,但在某种情况下,情况也是相反的。这种差异从何而来?
我正在运行 Ubuntu 20.04 (
strace
5.5)、Rust 1.72.1 中的标准 strace
以及版本 0.28.0 中的 nix
板条箱。每个工具的输出在多次运行中都是一致的(除了 strace
中的内存地址)。