使用 ptrace 进行系统调用拦截与 strace 输出不同

问题描述 投票:0回答:1

我正在使用

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
中的内存地址)。

rust system-calls strace ptrace
1个回答
0
投票

我发现了问题:我正在通过

cargo run
运行我的跟踪代码(不是目标代码)。

我的假设是,这相当于检查是否需要重建任何内容,然后像 shell 一样执行二进制文件,但当代码通过 Cargo 运行时,环境中的某些内容似乎有所不同,并且这种差异导致了额外的问题设置子进程期间的系统调用(@Useless此评论让我走上了正轨)。

如果我直接执行编译后的跟踪代码(即通过

target/
中的二进制文件),则输出与
strace
的输出匹配。

© www.soinside.com 2019 - 2024. All rights reserved.