《GDB调试之ptrace实现原理》

《C语言程序调用栈:backtrace+backtrace_symbols+backtrace_symbols_fd》

目录

strace是什么?

ptrace系统调用

运行被跟踪程序

编写跟踪进程代码

获取进程寄存器的值


strace是什么?


按照strace官网的描述, strace是一个可用于诊断、调试和教学的Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。

strace底层使用内核的ptrace特性来实现其功能。

在运维的日常工作中,故障处理和问题诊断是个主要的内容,也是必备的技能。strace作为一种动态跟踪工具,能够帮助运维高效地定位进程和服务故障。它像是一个侦探,通过系统调用的蛛丝马迹,告诉你异常的真相。

这次主要分享一下一个动手的东西,就是自己动手写一个 strace 工具。

用过 strace 的同学都知道,strace 是用来跟踪进程调用的 系统调用,还可以统计进程对 系统调用 的统计等。strace 的使用方式有两种,如下:

  • strace 执行的程序
  • strace -p 进程pid

第一种用于跟踪将要执行的程序,而第二种用于跟踪一个运行中的进程。

下图就是使用 strace 对 ls 命令跟踪的结果:

$ strace ls
--- Process 15332 created
--- Process 15332 loaded C:\Windows\System32\ntdll.dll at 00007ffdd6fd0000
--- Process 15332 loaded C:\Windows\System32\kernel32.dll at 00007ffdd4660000
--- Process 15332 loaded C:\Windows\System32\KernelBase.dll at 00007ffdd40b0000
--- Process 15332 thread 2208 created
--- Process 15332 thread 9072 created
--- Process 15332 loaded D:\Program Files (x86)\cygwin64\bin\cygwin1.dll at 0000000180040000
--- Process 15332 loaded D:\Program Files (x86)\cygwin64\bin\cygintl-8.dll at 00000003c0ba0000
--- Process 15332 thread 13508 created
--- Process 15332 loaded D:\Program Files (x86)\cygwin64\bin\cygiconv-2.dll at 00000003cf8b00000       0 [main] ls (15332) **********************************************42      42 [main] ls (15332) Program name: D:\Program Files (x86)\cygwin64\bin\ls.exe (windows pid 15332)27      69 [main] ls (15332) OS version:   Windows NT-10.024      93 [main] ls (15332) **********************************************
--- Process 15332 loaded C:\Windows\System32\advapi32.dll at 00007ffdd4500000
--- Process 15332 loaded C:\Windows\System32\msvcrt.dll at 00007ffdd6830000
--- Process 15332 loaded C:\Windows\System32\sechost.dll at 00007ffdd4a50000
--- Process 15332 loaded C:\Windows\System32\rpcrt4.dll at 00007ffdd48d0000此处省略一大堆19   24022 [main] ls 588 proc_terminate: nprocs 019   24041 [main] ls 588 proc_terminate: leaving25   24066 [main] ls 588 pinfo::exit: Calling dlls.cleanup_forkables n 0x0, exitcode 0x021   24087 [main] ls 588 pinfo::exit: Calling ExitProcess n 0x0, exitcode 0x0
--- Process 15332 (pid: 588) thread 9072 exited with status 0x0
--- Process 15332 (pid: 588) thread 2208 exited with status 0x0
--- Process 15332 (pid: 588) thread 6340 exited with status 0x0
--- Process 15332 (pid: 588) thread 13508 exited with status 0x0
--- Process 15332 (pid: 588) exited with status 0x0

ptrace系统调用


要自己动手写 strace 的第一步就是了解 ptrace() 系统调用的使用,我们来看看 ptrace() 系统调用的定义:

int ptrace(long request, long pid, long addr, long data);

ptrace() 系统调用用于跟踪进程的运行情况,下面介绍一下其各个参数的含义:

  • request:指定跟踪的动作。也就是说,通过传入不同的 request 参数可以对进程进行不同的跟踪操作。其可选值有:

    • PTRACE_TRACEME
    • PTRACE_PEEKTEXT
    • PTRACE_POKETEXT
    • PTRACE_CONT
    • PTRACE_SINGLESTEP
    • ...
  • pid:指定要跟踪的进程PID。
  • addr:指定要读取或者修改的内存地址。
  • data:对于不同的 request 操作,data 有不同的作用,下面会介绍。

前面介绍过,使用 strace 跟踪进程有两种方式,一种是通过 strace 命令启动进程,另外一种是通过 -p 指定要跟踪的进程。

ptrace() 系统调用也提供了两种 request 来实现上面两种方式:

  • 第一种通过 PTRACE_TRACEME 来实现

  • 第二种通过 PTRACE_ATTACH 来实现

本文我们主要介绍使用第一种方式。由于第一种方式使用跟踪程序来启动被跟踪的程序,所以需要启动两个进程。通常要创建新进程可以使用 fork() 系统调用,所以自然而然地我们也使用 fork() 系统调用。

我们新建一个文件 strace.c,输入代码如下:

int main(int argc, char *argv[])
{pid_t child;child = fork();if (child == 0) {// 子进程...} else {// 父进程...}return 0;
}

上面的代码通过调用 fork() 来创建一个子进程,但是没有做任何事情。之后,我们就会在 子进程 中运行被跟踪的程序,而在 父进程 中运行跟踪进程的代码。

运行被跟踪程序


前面说过,被跟踪的程序需要在子进程中运行,而要运行一个程序,可以通过调用 execl() 系统调用。所以可以通过下面的代码,在子进程中运行 ls 命令:

#include <unistd.h>
#include <stdlib.h>int main(int argc, char *argv[])
{pid_t child;child = fork();if (child == 0) {execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {// 父进程...}return 0;
}

execl() 用于执行指定的程序,如果执行成功就不会返回,所以 execl(...) 的下一行代码 exit(0) 不会被执行到。

由于我们需要跟踪 ls 命令,所以在执行 ls 命令前,必须调用 ptrace(PTRACE_TRACEME, 0, NULL, NULL) 来告诉系统需要跟踪这个进程,代码如下:


#include <sys/ptrace.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc, char *argv[])
{pid_t child;child = fork();if (child == 0) {ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {// 父进程...}return 0;
}

这样,被跟踪进程部分的代码就完成了,接下来开始编写跟踪进程部分代码。

编写跟踪进程代码


如果编译运行上面的代码,会发现什么效果也没有。这是因为当在子进程调用 ptrace(PTRACE_TRACEME, 0, NULL, NULL) 后,并且调用 execl() 系统调用,那么子进程会发送一个 SIGCHLD 信号给父进程(跟踪进程)并且自己停止运行,直到父进程发送调试命令,才会继续运行。

由于上面的代码中,父进程(跟踪进程)并没有发送任何调试命令就退出运行,所以子进程(被跟踪进程)在没有运行的情况下就跟着父进程一起退出了,那么就不会看到任何效果。

现在我们开始编写跟踪进程的代码。

由于被跟踪进程会发送一个 SIGCHLD 信息给跟踪进程,所以我们先要在跟踪进程的代码中接收 SIGCHLD 信号,接收信号通过使用 wait() 系统调用完成,代码如下:

#include <sys/ptrace.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int main(int argc, char *argv[])
{pid_t child;int status;child = fork();if (child == 0) {ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号}return 0;
}

上面的代码通过调用 wait() 系统调用来接收被跟踪进程发送过来的 SIGCHLD 信号,接下来需要开始向被跟踪进程发送调试命令,来对被跟踪进程进行调试。

由于本文介绍怎么跟踪进程调用了哪些 系统调用,所以我们需要使用 ptrace() 的 PTRACE_SYSCALL 命令,代码如下:

#include <sys/ptrace.h>
#include <sys/user.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int main(int argc, char *argv[])
{pid_t child;int status;struct user_regs_struct regs;int orig_rax;child = fork();if (child == 0) {ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号// 1. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用前,可以获取系统调用的参数)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号// 2. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用后,可以获取系统调用的返回值)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号}return 0;
}

从上面的代码可以发现,我们调用了两次 ptrace(PTRACE_SYSCALL, child, NULL, NULL),这是因为跟踪系统调用时,需要跟踪系统调用前的环境(比如获取系统调用的参数)和系统调用后的环境(比如获取系统调用的返回值),所以就需要调用两次 ptrace(PTRACE_SYSCALL, child, NULL, NULL)

获取进程寄存器的值


Linux系统调用是通过 CPU寄存器 来传递参数的,所以要想获取调用了哪个系统调用,必须获取进程寄存器的值。获取进程寄存器的值,可以通过 ptrace() 系统调用的 PTRACE_GETREGS 命令来实现,代码如下:

#include <sys/ptrace.h>
#include <sys/user.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int main(int argc, char *argv[])
{pid_t child;int status;struct user_regs_struct regs;int orig_rax;child = fork();if (child == 0) {ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号// 1. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用前,可以获取系统调用的参数)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号ptrace(PTRACE_GETREGS, child, 0, &regs); // 获取被跟踪进程寄存器的值orig_rax = regs.orig_rax; // 获取rax寄存器的值printf("orig_rax: %d\n", orig_rax); // 打印rax寄存器的值// 2. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用后,可以获取系统调用的返回值)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号}return 0;
}

上面的代码通过调用 ptrace(PTRACE_GETREGS, child, 0, &regs) 来获取进程寄存器的值,PTRACE_GETREGS 命令需要在 data 参数传入类型为 user_regs_struct 结构的指针,user_regs_struct 结构定义如下(在文件 sys/user.h 中):

struct user_regs_struct {unsigned long r15,r14,r13,r12,rbp,rbx,r11,r10;unsigned long r9,r8,rax,rcx,rdx,rsi,rdi,orig_rax;unsigned long rip,cs,eflags;unsigned long rsp,ss;unsigned long fs_base, gs_base;unsigned long ds,es,fs,gs;
};

其中 user_regs_struct 结构的 orig_rax 保存了系统调用号,所以我们可以通过 orig_rax 的值来知道调用了哪个系统调用。

编译运行上面的代码,会输出结果:orig_rax: 12,就是说当前调用的是编号为 12 的系统调用。那么编号为 12 的系统调用是哪个系统调用呢?可以通过下面链接来查看:

https://www.cnblogs.com/gavanwanggw/p/6920826.html

通过查阅系统调用表,可以知道编号 12 的系统调用为 brk(),如下:

系统调用号     函数名     入口点     源码
...
12            brk       sys_brk    mm/mmap.c
...

上面的程序只跟踪了一个系统调用,那么怎么跟踪所有的系统调用呢?很简单,只需要把跟踪的代码放到一个无限循环中即可。代码如下:

#include <sys/ptrace.h>
#include <sys/user.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>int main(int argc, char *argv[])
{pid_t child;int status;struct user_regs_struct regs;int orig_rax;child = fork();if (child == 0) {ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号while (1) {// 1. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用前,可以获取系统调用的参数)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号if (WIFEXITED(status)) { // 如果子进程退出了, 那么终止跟踪break;}ptrace(PTRACE_GETREGS, child, 0, &regs); // 获取被跟踪进程寄存器的值orig_rax = regs.orig_rax; // 获取rax寄存器的值printf("orig_rax: %d\n", orig_rax); // 打印rax寄存器的值// 2. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用后,可以获取系统调用的返回值)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号if (WIFEXITED(status)) { // 如果子进程退出了, 那么终止跟踪break;}}}return 0;
}

if (WIFEXITED(status)) ... 这行代码用于判断子进程(被跟踪进程)是否已经退出,如果退出了就停止跟踪。现在可以编译并运行这个程序,输出结果如下:


[root@localhost liexusong]$ ./strace
orig_rax: 12
orig_rax: 9
orig_rax: 21
orig_rax: 2
orig_rax: 5
orig_rax: 9
orig_rax: 3
orig_rax: 2
orig_rax: 0
orig_rax: 5
orig_rax: 9
orig_rax: 10
orig_rax: 9
orig_rax: 9
orig_rax: 3
orig_rax: 2
orig_rax: 0
orig_rax: 5
orig_rax: 9
orig_rax: 10
...

从执行结果来看,只是打印系统调用号不太直观,那么我们怎么优化呢?

我们可以定义一个系统调用号与系统调用名的对应表来实现更清晰的输出结果,如下:

#include <sys/ptrace.h>
#include <sys/user.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>struct syscall {int  code;char *name;
} syscall_table[] = {{0, "read"},{1, "write"},{2, "open"},{3, "close"},{4, "stat"},{5, "fstat"},{6, "lstat"},{7, "poll"},{8, "lseek"},...{-1, NULL},
}char *find_syscall_symbol(int code) {struct syscall *sc;for (sc = syscall_table; sc->code >= 0; sc++) {if (sc->code == code) {return sc->name;}}return NULL;
}int main(int argc, char *argv[])
{pid_t child;int status;struct user_regs_struct regs;int orig_rax;child = fork();if (child == 0) {ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号while (1) {// 1. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用前,可以获取系统调用的参数)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号if(WIFEXITED(status)) { // 如果子进程退出了, 那么终止跟踪break;}ptrace(PTRACE_GETREGS, child, 0, &regs); // 获取被跟踪进程寄存器的值orig_rax = regs.orig_rax; // 获取rax寄存器的值printf("syscall: %s()\n", find_syscall_symbol(orig_rax)); // 打印系统调用// 2. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用后,可以获取系统调用的返回值)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号if(WIFEXITED(status)) { // 如果子进程退出了, 那么终止跟踪break;}}}return 0;
}

上面例子添加了一个函数 find_syscall_symbol() 来获取系统调用号对应的系统调用名,实现也比较简单。编译运行后输出结果如下:

[root@localhost liexusong]$ ./strace
syscall: brk()
syscall: mmap()
syscall: access()
syscall: open()
syscall: fstat()
syscall: mmap()
syscall: close()
syscall: open()
syscall: read()
syscall: fstat()
syscall: mmap()
syscall: mprotect()
syscall: mmap()
syscall: mmap()
syscall: close()
...

从执行结果来看,现在可以打印系统调用的名字了,但我们知道 strace 命令还会打印系统调用参数的值,我们可以通过 ptrace() 系统调用的 PTRACE_PEEKTEXT 和 PTRACE_PEEKDATA 来获取参数的值,所以有兴趣的就自己实现这个效果了。

本文完整代码在:https://github.com/liexusong/build-strace-by-myself/blob/main/strace.c

#include <sys/ptrace.h>
#include <sys/user.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>struct syscall {int  code;char *name;
} syscall_table[] = {{0, "read"},{1, "write"},{2, "open"},{3, "close"},{4, "stat"},{5, "fstat"},{6, "lstat"},{7, "poll"},{8, "lseek"},{9, "mmap"},{10, "mprotect"},{11, "munmap"},{12, "brk"},{13, "rt_sigaction"},{14, "rt_sigprocmask"},{15, "rt_sigreturn"},{16, "ioctl"},{17, "pread64"},{18, "pwrite64"},{19, "readv"},{20, "writev"},{21, "access"},{22, "pipe"},{23, "select"},{24, "sched_yield"},{25, "mremap"},{26, "msync"},{27, "mincore"},{28, "madvise"},{29, "shmget"},{30, "shmat"},{31, "shmctl"},{32, "dup"},{33, "dup2"},{34, "pause"},{35, "nanosleep"},{36, "getitimer"},{37, "alarm"},{38, "setitimer"},{39, "getpid"},{40, "sendfile"},{41, "socket"},{42, "connect"},{43, "accept"},{44, "sendto"},{45, "recvfrom"},{46, "sendmsg"},{47, "recvmsg"},{48, "shutdown"},{49, "bind"},{50, "listen"},{51, "getsockname"},{52, "getpeername"},{53, "socketpair"},{54, "setsockopt"},{55, "getsockopt"},{56, "clone"},{57, "fork"},{58, "vfork"},{59, "execve"},{60, "exit"},{61, "wait4"},{62, "kill"},{63, "uname"},{64, "semget"},{65, "semop"},{66, "semctl"},{67, "shmdt"},{68, "msgget"},{69, "msgsnd"},{70, "msgrcv"},{71, "msgctl"},{72, "fcntl"},{73, "flock"},{74, "fsync"},{75, "fdatasync"},{76, "truncate"},{77, "ftruncate"},{78, "getdents"},{79, "getcwd"},{80, "chdir"},{81, "fchdir"},{82, "rename"},{83, "mkdir"},{84, "rmdir"},{85, "creat"},{86, "link"},{87, "unlink"},{88, "symlink"},{89, "readlink"},{90, "chmod"},{91, "fchmod"},{92, "chown"},{93, "fchown"},{94, "lchown"},{95, "umask"},{96, "gettimeofday"},{97, "getrlimit"},{98, "getrusage"},{99, "sysinfo"},{100, "times"},{101, "ptrace"},{102, "getuid"},{103, "syslog"},{104, "getgid"},{105, "setuid"},{106, "setgid"},{107, "geteuid"},{108, "getegid"},{109, "setpgid"},{110, "getppid"},{111, "getpgrp"},{112, "setsid"},{113, "setreuid"},{114, "setregid"},{115, "getgroups"},{116, "setgroups"},{117, "setresuid"},{118, "getresuid"},{119, "setresgid"},{120, "getresgid"},{121, "getpgid"},{122, "setfsuid"},{123, "setfsgid"},{124, "getsid"},{125, "capget"},{126, "capset"},{127, "rt_sigpending"},{128, "rt_sigtimedwait"},{129, "rt_sigqueueinfo"},{130, "rt_sigsuspend"},{131, "sigaltstack"},{132, "utime"},{133, "mknod"},{134, "uselib"},{135, "personality"},{136, "ustat"},{137, "statfs"},{138, "fstatfs"},{139, "sysfs"},{140, "getpriority"},{141, "setpriority"},{142, "sched_setparam"},{143, "sched_getparam"},{144, "sched_setscheduler"},{145, "sched_getscheduler"},{146, "sched_get_priority_max"},{147, "sched_get_priority_min"},{148, "sched_rr_get_interval"},{149, "mlock"},{150, "munlock"},{151, "mlockall"},{152, "munlockall"},{153, "vhangup"},{154, "modify_ldt"},{155, "pivot_root"},{156, "_sysctl"},{157, "prctl"},{158, "arch_prctl"},{159, "adjtimex"},{160, "setrlimit"},{161, "chroot"},{162, "sync"},{163, "acct"},{164, "settimeofday"},{165, "mount"},{166, "umount2"},{167, "swapon"},{168, "swapoff"},{169, "reboot"},{170, "sethostname"},{171, "setdomainname"},{172, "iopl"},{173, "ioperm"},{174, "create_module"},{175, "init_module"},{176, "delete_module"},{177, "get_kernel_syms"},{178, "query_module"},{179, "quotactl"},{180, "nfsservctl"},{181, "getpmsg"},{182, "putpmsg"},{183, "afs_syscall"},{184, "tuxcall"},{185, "security"},{186, "gettid"},{187, "readahead"},{188, "setxattr"},{189, "lsetxattr"},{190, "fsetxattr"},{191, "getxattr"},{192, "lgetxattr"},{193, "fgetxattr"},{194, "listxattr"},{195, "llistxattr"},{196, "flistxattr"},{197, "removexattr"},{198, "lremovexattr"},{199, "fremovexattr"},{200, "tkill"},{201, "time"},{202, "futex"},{203, "sched_setaffinity"},{204, "sched_getaffinity"},{205, "set_thread_area"},{206, "io_setup"},{207, "io_destroy"},{208, "io_getevents"},{209, "io_submit"},{210, "io_cancel"},{211, "get_thread_area"},{212, "lookup_dcookie"},{213, "epoll_create"},{214, "epoll_ctl_old"},{215, "epoll_wait_old"},{216, "remap_file_pages"},{217, "getdents64"},{218, "set_tid_address"},{219, "restart_syscall"},{220, "semtimedop"},{221, "fadvise64"},{222, "timer_create"},{223, "timer_settime"},{224, "timer_gettime"},{225, "timer_getoverrun"},{226, "timer_delete"},{227, "clock_settime"},{228, "clock_gettime"},{229, "clock_getres"},{230, "clock_nanosleep"},{231, "exit_group"},{232, "epoll_wait"},{233, "epoll_ctl"},{234, "tgkill"},{235, "utimes"},{236, "vserver"},{237, "mbind"},{238, "set_mempolicy"},{239, "get_mempolicy"},{240, "mq_open"},{241, "mq_unlink"},{242, "mq_timedsend"},{243, "mq_timedreceive"},{244, "mq_notify"},{245, "mq_getsetattr"},{246, "kexec_load"},{247, "waitid"},{248, "add_key"},{249, "request_key"},{250, "keyctl"},{251, "ioprio_set"},{252, "ioprio_get"},{253, "inotify_init"},{254, "inotify_add_watch"},{255, "inotify_rm_watch"},{256, "migrate_pages"},{257, "openat"},{258, "mkdirat"},{259, "mknodat"},{260, "fchownat"},{261, "futimesat"},{262, "newfstatat"},{263, "unlinkat"},{264, "renameat"},{265, "linkat"},{266, "symlinkat"},{267, "readlinkat"},{268, "fchmodat"},{269, "faccessat"},{270, "pselect6"},{271, "ppoll"},{272, "unshare"},{273, "set_robust_list"},{274, "get_robust_list"},{275, "splice"},{276, "tee"},{277, "sync_file_range"},{278, "vmsplice"},{279, "move_pages"},{280, "utimensat"},{281, "epoll_pwait"},{282, "signalfd"},{283, "timerfd_create"},{284, "eventfd"},{285, "fallocate"},{286, "timerfd_settime"},{287, "timerfd_gettime"},{288, "accept4"},{289, "signalfd4"},{290, "eventfd2"},{291, "epoll_create1"},{292, "dup3"},{293, "pipe2"},{294, "inotify_init1"},{295, "preadv"},{296, "pwritev"},{297, "rt_tgsigqueueinfo"},{298, "perf_event_open"},{299, "recvmmsg"},{300, "fanotify_init"},{301, "fanotify_mark"},{302, "prlimit64"},{303, "name_to_handle_at"},{304, "open_by_handle_at"},{305, "clock_adjtime"},{306, "syncfs"},{307, "sendmmsg"},{308, "setns"},{309, "getcpu"},{310, "process_vm_readv"},{311, "process_vm_writev"},{312, "kcmp"},{313, "finit_module"},{314, "sched_setattr"},{315, "sched_getattr"},{316, "renameat2"},{317, "seccomp"},{318, "getrandom"},{319, "memfd_create"},{320, "kexec_file_load"},{321, "bpf"},{322, "execveat"},{323, "userfaultfd"},{324, "membarrier"},{325, "mlock2"},{326, "copy_file_range"},{327, "preadv2"},{328, "pwritev2"},{329, "pkey_mprotect"},{330, "pkey_alloc"},{331, "pkey_free"},{332, "statx"},{333, "io_pgetevents"},{334, "rseq"},{424, "pidfd_send_signal"},{425, "io_uring_setup"},{426, "io_uring_enter"},{427, "io_uring_register"},{428, "open_tree"},{429, "move_mount"},{430, "fsopen"},{431, "fsconfig"},{432, "fsmount"},{433, "fspick"},{434, "pidfd_open"},{435, "clone3"},{436, "close_range"},{437, "openat2"},{438, "pidfd_getfd"},{439, "faccessat2"},{440, "process_madvise"},{512, "rt_sigaction"},{513, "rt_sigreturn"},{514, "ioctl"},{515, "readv"},{516, "writev"},{517, "recvfrom"},{518, "sendmsg"},{519, "recvmsg"},{520, "execve"},{521, "ptrace"},{522, "rt_sigpending"},{523, "rt_sigtimedwait"},{524, "rt_sigqueueinfo"},{525, "sigaltstack"},{526, "timer_create"},{527, "mq_notify"},{528, "kexec_load"},{529, "waitid"},{530, "set_robust_list"},{531, "get_robust_list"},{532, "vmsplice"},{533, "move_pages"},{534, "preadv"},{535, "pwritev"},{536, "rt_tgsigqueueinfo"},{537, "recvmmsg"},{538, "sendmmsg"},{539, "process_vm_readv"},{540, "process_vm_writev"},{541, "setsockopt"},{542, "getsockopt"},{543, "io_setup"},{544, "io_submit"},{545, "execveat"},{546, "preadv2"},{547, "pwritev2"},{-1, NULL},
};char *find_syscall_symbol(int code) {struct syscall *sc;for (sc = syscall_table; sc->code >= 0; sc++) {if (sc->code == code) {return sc->name;}}return NULL;
}int main(int argc, char *argv[])
{pid_t child;int status;struct user_regs_struct regs;int orig_rax;child = fork();if (child == 0) {ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl("/bin/ls", "/bin/ls", NULL);exit(0);} else {wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号while (1) {// 1. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用前,可以获取系统调用的参数)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号if(WIFEXITED(status)) { // 如果子进程退出了, 那么终止跟踪break;}ptrace(PTRACE_GETREGS, child, 0, &regs); // 获取被跟踪进程寄存器的值orig_rax = regs.orig_rax; // 获取rax寄存器的值printf("syscall: %s()\n", find_syscall_symbol(orig_rax)); // 打印rax寄存器的值// 2. 发送 PTRACE_SYSCALL 命令给被跟踪进程 (调用系统调用后,可以获取系统调用的返回值)ptrace(PTRACE_SYSCALL, child, NULL, NULL);wait(&status); // 接收被子进程发送过来的 SIGCHLD 信号if(WIFEXITED(status)) { // 如果子进程退出了, 那么终止跟踪break;}}}return 0;
}

strace实现原理:ptrace系统调用相关推荐

  1. 一文带你看透 GDB 的 实现原理 -- ptrace真香

    文章目录 Ptrace 的使用 GDB 的基本实现原理 Example1 通过ptrace 修改 被追踪进程的内存数据 Example2 通过ptrace 对被追踪进程进行单步调试 Ptrace的实现 ...

  2. Linux ptrace系统调用详解:利用 ptrace 设置硬件断点

    <GDB调试之ptrace实现原理> <C语言程序调用栈:backtrace+backtrace_symbols+backtrace_symbols_fd> <strac ...

  3. strace 哇,好多系统调用

    我不敢在没有认真考虑后果的情况下在生产环境中运行 strace(1),而是首先尝试它的替代品.尽管广为人知的是(并且不断被重新发现)strace 是一个神奇的工具,但少得多的人知道的是,它目前是 - ...

  4. ptrace 系统调用

    ptrace 是 Linux 环境下,许许多多分析调试工具,如 strace,ltrace 等,所使用的最核心的系统调用.ptrace,即 process trace,指进程追踪.它能让一个进程控制及 ...

  5. ptrace系统调用的实现

    最近遇到这样一个问题,机器跑着跑着画面冻结了,打开top看到Xorg的cpu占用率100%.想用gdb挂上去看一下,结果gdb一直卡着挂不上去.后来又换用perf分析,结果发现进程99%的时间花在了一 ...

  6. linux(4)-Ptrace 系统调用的使用

    文章目录 问题 运行环境 程序组成 实现思路 模块划分 完整代码 程序运行及结果 问题 利用ptrace系统调用实现一个简单的软件调试器.基本功能包括能够截获被调试进程的信号,被调试进程进入断点后能够 ...

  7. [strace]跟踪进程的系统调用

    转自:https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316692.html 简介 strace常用来跟踪进程执行时的系统调用和所接收的信号 ...

  8. Linux进程原理及系统调用

    Linux进程原理及系统调用 进程 进程的生命周期 task_struct数据结构分析 进程优先级/系统调用 进程优先级 进程系统调用 内核线程 退出进程 进程 ​ 操作系统作为硬件的使用层,提供使用 ...

  9. 操作系统原理,系统调用,系统调用与库函数API等函数之间的调用关系,功能与机制设计,系统调用的执行过程与Linux系统调用执行示例,不同操作系统下的PCB

    操作系统原理,系统调用,功能与机制设计,系统调用的执行过程与Linux系统调用执行示例,不同操作系统下的PCB 一.系统调用:操作系统功能调用,用户在编程时可以调用的操作系统功能. 1.系统调用是操作 ...

最新文章

  1. 推荐7款实用强大的神器工具,建议你先收藏,总有一天你会用到!
  2. d3.js 旋转图形_【IOS游戏推荐】百万畅销游戏刚从STEAM移植至IOS平台,在极端地形中冒险前进!——旋转轮胎:泥泞奔驰...
  3. 基于英飞凌AURIX的平衡单车组逐飞BLDC项目开源
  4. java公寓管理系统设计与实现_学生公寓(宿舍)管理系统的设计与实现(论文范文, jspjava).docx_蚂蚁文库...
  5. 一款Windows管理Linux的软件
  6. 技术干货 | 基于 Qt Quick Plugin 快速构建桌面端跨平台组件
  7. Android之ActivityManager与Proxy模式的运用
  8. 19.删除链表的倒数第N个节点 golang
  9. django——url(路由)配置
  10. 术中导航_密码术中的计数器(CTR)模式
  11. pandas添加、修改dataframe中index的列名
  12. linux下mkdir头文件_Linux中mkdir函数与Windows中_mkdir函数的区别
  13. java 生成一个空文件系统_如何使用java创建一个空白的PPT文档?
  14. 计算机识别人脸原理,人脸识别:原理、方法与技术
  15. 唐诗欣赏静夜思用html设计,古诗鉴赏:静夜思
  16. CPU缓存体系对Go程序的影响
  17. 数据库基础---选择,投影,连接,除法运算
  18. mc服务器物品id,我的世界物品id1period;12period;2 | 手游网游页游攻略大全
  19. Axure交互-鼠标移入移除显示与隐藏
  20. “GANs”与“ODEs”:数学建模的终结?

热门文章

  1. WPF TextBox控件中文字实现垂直居中
  2. 数学差学计算机和编程难吗,数学很差能学计算机吗
  3. leetcode190-颠倒二进制位
  4. 【原】PSD图标素材的全自动切图方法,适用于IOS、安卓、web前端等领域
  5. c# Linq Where 抛出异常 导致 程序崩溃
  6. 是否应该扔掉就代码,重写整个软件?
  7. javascript实现下拉条联动_JavaScript gt;gt;gt; 003
  8. 如何通过ssh登录linux,如何用SSH登录linux?
  9. oracle数字加 39,Oracle数据库之SQL单行函数—数字函数-Oracle
  10. mac r 导出csv文件_R在Max OS进行导入和导出xlsx文件