前言:在程序出现bug的时候,最好的解决办法就是通过 GDB 调试程序,然后找到程序出现问题的地方。比如程序出现 段错误(内存地址不合法)时,就可以通过 GDB 找到程序哪里访问了不合法的内存地址而导致的。
本文不是介绍 GDB 的使用方式,而是大概介绍 GDB 的实现原理,当然 GDB 是一个庞大而复杂的项目,不可能只通过一篇文章就能解释清楚,所以本文主要是介绍 GDB 使用的核心的技术 - ptrace。
一、ptrace系统调用
ptrace() 系统调用是 Linux 提供的一个调试进程的工具,ptrace() 系统调用非常强大,它提供非常多的调试方式让我们去调试某一个进程,下面是 ptrace() 系统调用的定义:
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
下面解释一下 ptrace() 各个参数的作用:
- request:指定调试的指令,指令的类型很多,如:PTRACE_TRACEME、PTRACE_PEEKUSER、PTRACE_CONT、PTRACE_GETREGS等等,下面会介绍不同指令的作用。
- pid:进程的ID(这个不用解释了)。
- addr:进程的某个地址空间,可以通过这个参数对进程的某个地址进行读或写操作。
- data:根据不同的指令,有不同的用途,下面会介绍。
要自己动手写 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内核技术交流群:【 865977150】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值 699的内核资料包(含视频教程、电子书、实战项目及代码)
获取进程寄存器的值
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, ®s); // 获取被跟踪进程寄存器的值 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, ®s) 来获取进程寄存器的值,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 的系统调用是哪个系统调用呢?
通过查阅系统调用表,可以知道编号 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, ®s); // 获取被跟踪进程寄存器的值 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; }
从执行结果来看,只是打印系统调用号不太直观,那么我们怎么优化呢?
我们可以定义一个系统调用号与系统调用名的对应表来实现更清晰的输出结果,如下:
#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, ®s); // 获取被跟踪进程寄存器的值 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
来获取参数的值,所以有兴趣的就自己实现这个效果了。
#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, ®s); // 获取被跟踪进程寄存器的值 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; }