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件事:- 如果当前进程被标记为 PTRACE 状态 , 那么就使自己进入停止运行的状态 。
- 发送 SIGCHLD 信号给父进程 。
- 让出 CPU 的执行权限 , 使 CPU 执行其他进程 。
- 执行以上过程后 , 追踪进程便进入了调试模式 , 过程如下图:
文章插图
?
- 当父进程(调试进程)接收到 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 变量中 。如下图所示:
文章插图
- access_process_vm() 函数的实现这里就不分析了 , 有兴趣的读者可以参考我之前对内存管理分析的文章自行进行分析 。
- 单步调试是一个比较有趣的功能 , 当把被调试进程设置为单步调试模式后 , 被调试进程没执行一条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;}...}
- 要把被调试的进程设置为单步调试模式 , 英特尔的 X86 CPU 提供了一个硬件的机制 , 就是通过把 eflags 寄存器的 Trap Flag 设置为1即可 。
- 当把 eflags 寄存器的 Trap Flag 设置为1后 , CPU 每执行一条指令便会产生一个异常 , 然后会触发 Linux 的异常处理 , Linux 便会发送一个 SIGTRAP 信号给被调试的进程 。
推荐阅读
- IDE 一篇文章带你明白:什么是编译器,什么是集成开发环境?
- 中国女性为什么更愿意掌握家庭财权?
- 一文掌握shell脚本中shift的用法及功能
- 掌握排列数组合数基本公式的推导 排列组合怎么算
- 反射?看一篇文章你就会了
- 秋季如何把头发养护好?掌握方法很重要!这几个方法,务必试试
- GC 一篇文章彻底了解Java垃圾收集机制
- |面对集团高层的动荡,职场人士需要掌握的生存法则与破局思维
- 一篇感恩母亲的演讲,感动哭了 感恩母亲演讲稿
- 一文掌握linux系统路由跟踪指令traceroute