一篇掌握GDB调试程序的核心技术-ptrace系统调用与使用示例( 三 )

  • 我们再来看看 , 进程是怎么处理的 SIGTRAP 信号的 。信号是通过 do_signal() 函数进行处理的 , 而对 SIGTRAP 信号的处理逻辑如下:
  • int do_signal(struct pt_regs *regs, sigset_t *oldset) {for (;;) {unsigned long signr;spin_lock_irq(¤t->sigmask_lock);signr = dequeue_signal(¤t->blocked, &info);spin_unlock_irq(¤t->sigmask_lock);// 如果进程被标记为 PTRACE 状态if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {/* 让调试器运行*/current->exit_code = signr;current->state = TASK_STOPPED;// 让自己进入停止运行状态notify_parent(current, SIGCHLD); // 发送 SIGCHLD 信号给父进程schedule();// 让出CPU的执行权限...}}}上面的代码主要做了3件事:
    1. 如果当前进程被标记为 PTRACE 状态 , 那么就使自己进入停止运行的状态 。
    2. 发送 SIGCHLD 信号给父进程 。
    3. 让出 CPU 的执行权限 , 使 CPU 执行其他进程 。
    • 执行以上过程后 , 追踪进程便进入了调试模式 , 过程如下图:

    一篇掌握GDB调试程序的核心技术-ptrace系统调用与使用示例

    文章插图
     
    ?
     
    • 当父进程(调试进程)接收到 SIGCHLD 信号后 , 表示被调试进程已经标记为被追踪状态并且停止运行 , 那么调试进程就可以开始进行调试了 。
    • 获取被调试进程的内存数据(PTRACE_PEEKTEXT / PTRACE_PEEKDATA)
    • 调试进程(如GDB)可以通过调用 ptrace(PTRACE_PEEKDATA, pid, addr, data) 立即获取被调试进程 addr 处虚拟内存地址的数据 , 但每次只能读取一个大小为 4字节的数据 。
    • 我们来看看 ptrace() 对 PTRACE_PEEKDATA 操作的处理过程 , 代码如下:
    asmlinkage int sys_ptrace(long request, long pid, long addr, long data){...switch (request) {case PTRACE_PEEKTEXT:case PTRACE_PEEKDATA: {unsigned long tmp;int copied;copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);ret = -EIO;if (copied != sizeof(tmp))break;ret = put_user(tmp, (unsigned long *)data);break;}...}
    • 从上面代码可以看出 , 对 PTRACE_PEEKTEXT 和 PTRACE_PEEKDATA 的处理是相同的 , 主要是通过调用 access_process_vm() 函数来读取被调试进程 addr 处的虚拟内存地址的数据 。
    • access_process_vm() 函数的实现主要涉及到 内存管理 相关的知识 , 可以参考我以前对内存管理分析的文章 , 这里主要大概说明一下 access_process_vm() 的原理 。
    • 我们知道每个进程都有个 mm_struct 的内存管理对象 , 而 mm_struct 对象有个表示虚拟内存与物理内存映射关系的页目录的指针 pgd 。如下:
    struct mm_struct {...pgd_t *pgd; /* 页目录指针 */...}
    • 而 access_process_vm() 函数就是通过进程的页目录来找到 addr 虚拟内存地址映射的物理内存地址 , 然后把此物理内存地址处的数据复制到 data 变量中 。如下图所示:

    一篇掌握GDB调试程序的核心技术-ptrace系统调用与使用示例

    文章插图
     
    • access_process_vm() 函数的实现这里就不分析了 , 有兴趣的读者可以参考我之前对内存管理分析的文章自行进行分析 。
    单步调试模式(PTRACE_SINGLESTEP)
    • 单步调试是一个比较有趣的功能 , 当把被调试进程设置为单步调试模式后 , 被调试进程没执行一条CPU指令都会停止执行 , 并且向父进程(调试进程)发送一个 SIGCHLD 信号 。
    • 我们来看看 ptrace() 函数对 PTRACE_SINGLESTEP 操作的处理过程 , 代码如下:
    asmlinkage int sys_ptrace(long request, long pid, long addr, long data){...switch (request) {case PTRACE_SINGLESTEP: {/* set the trap flag. */long tmp;...tmp = get_stack_long(child, EFL_OFFSET) | TRAP_FLAG;put_stack_long(child, EFL_OFFSET, tmp);child->exit_code = data;/* give it a chance to run. */wake_up_process(child);ret = 0;break;}...}