看懂GDB调试核心:剖析ptrace原理及其应用场景!(上)

简介: 看懂GDB调试核心:剖析ptrace原理及其应用场景!

前言:在程序出现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, &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 的系统调用是哪个系统调用呢?

通过查阅系统调用表,可以知道编号 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;
}

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

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

#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_PEEKTEXTPTRACE_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, &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;
}
相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
3月前
|
NoSQL Linux C语言
Linux GDB 调试
Linux GDB 调试
62 10
|
3月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
|
3月前
|
NoSQL
技术分享:如何使用GDB调试不带调试信息的可执行程序
【8月更文挑战第27天】在软件开发和调试过程中,我们有时会遇到需要调试没有调试信息的可执行程序的情况。这可能是由于程序在编译时没有加入调试信息,或者调试信息被剥离了。然而,即使面对这样的挑战,GDB(GNU Debugger)仍然提供了一些方法和技术来帮助我们进行调试。以下将详细介绍如何使用GDB调试不带调试信息的可执行程序。
92 0
|
5月前
|
NoSQL Linux C语言
Linux gdb调试的时候没有对应的c调试信息库怎么办?
Linux gdb调试的时候没有对应的c调试信息库怎么办?
41 1
|
5月前
|
NoSQL Linux C语言
Linux gdb调试的时候没有对应的c调试信息库怎么办?
Linux gdb调试的时候没有对应的c调试信息库怎么办?
31 0
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试正在运行的程序
Linux C/C++ gdb调试正在运行的程序
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试core文件
Linux C/C++ gdb调试core文件
|
5月前
|
NoSQL Linux C++
Linux C/C++ gdb调试
Linux C/C++ gdb调试
|
6月前
|
NoSQL Ubuntu 测试技术
【GDB自定义指令】core analyzer结合gdb的调试及自定义gdb指令详情
【GDB自定义指令】core analyzer结合gdb的调试及自定义gdb指令详情
94 1
|
6月前
|
NoSQL 编译器 C语言
【GDB调试技巧】提高gdb的调试效率
【GDB调试技巧】提高gdb的调试效率
80 1