三 , ptrace实现原理
本文使用的 Linux 2.4.16 版本的内核
- 看懂本文需要的基础:进程调度 , 内存管理和信号处理等相关知识 。
- 调用 ptrace() 系统函数时会触发调用内核的 sys_ptrace() 函数 , 由于不同的原因 CPU 架构有着不同的调试方式 , 所以 Linux 为每种不同的 CPU 架构实现了不同的 sys_ptrace() 函数 , 而本文主要介绍的是 X86 CPU 的调试方式 , 所以 sys_ptrace() 函数所在文件是 linux-2.4.16/arch/i386/kernel/ptrace.c 。
asmlinkage int sys_ptrace(long request, long pid, long addr, long data){struct task_struct *child;struct user *dummy = NULL;int i, ret;...read_lock(&tasklist_lock);child = find_task_by_pid(pid); // 获取 pid 对应的进程 task_struct 对象if (child)get_task_struct(child);read_unlock(&tasklist_lock);if (!child)goto out;if (request == PTRACE_ATTACH) {ret = ptrace_attach(child);goto out_tsk;}...switch (request) {case PTRACE_PEEKTEXT:case PTRACE_PEEKDATA:...case PTRACE_PEEKUSR:...case PTRACE_POKETEXT:case PTRACE_POKEDATA:...case PTRACE_POKEUSR:...case PTRACE_SYSCALL:case PTRACE_CONT:...case PTRACE_KILL:...case PTRACE_SINGLESTEP:...case PTRACE_DETACH:...}out_tsk:free_task_struct(child);out:unlock_kernel();return ret;}
- 从上面的代码可以看出 , sys_ptrace() 函数首先根据进程的 pid 获取到进程的 task_struct 对象 。然后根据传入不同的 request 参数在 switch 语句中进行不同的操作 。
#define PTRACE_TRACEME0#define PTRACE_PEEKTEXT1#define PTRACE_PEEKDATA2#define PTRACE_PEEKUSR3#define PTRACE_POKETEXT4#define PTRACE_POKEDATA5#define PTRACE_POKEUSR6#define PTRACE_CONT7#define PTRACE_KILL8#define PTRACE_SINGLESTEP9#define PTRACE_ATTACH0x10#define PTRACE_DETACH0x11#define PTRACE_SYSCALL24#define PTRACE_GETREGS12#define PTRACE_SETREGS13#define PTRACE_GETFPREGS14#define PTRACE_SETFPREGS15#define PTRACE_GETFPXREGS18#define PTRACE_SETFPXREGS19#define PTRACE_SETOPTIONS21
- 由于 ptrace() 提供的操作比较多 , 所以本文只会挑选一些比较有代表性的操作进行解说 , 比如 PTRACE_TRACEME、PTRACE_SINGLESTEP、PTRACE_PEEKTEXT、PTRACE_PEEKDATA 和 PTRACE_CONT 等 , 而其他的操作 , 有兴趣的朋友可以自己去分析其实现原理 。
- 当要调试一个进程时 , 需要使进程进入被追踪模式 , 怎么使进程进入被追踪模式呢?有两个方法:
- 被调试的进程调用 ptrace(PTRACE_TRACEME, ...) 来使自己进入被追踪模式 。
- 调试进程(如GDB)调用 ptrace(PTRACE_ATTACH, pid, ...) 来使指定的进程进入追踪模式 。
- 第一种方式是进程自己主动进入被追踪模式 , 而第二种是进程被动进入被追踪模式 。
- 被调试的进程必须进入追踪模式才能进行调试 , 因为 Linux 会对被追踪的进程进行一些特殊的处理 。下面我们主要介绍第一种进入追踪模式的实现 , 就是 PTRACE_TRACEME 操作过程 , 代码如下:
asmlinkage int sys_ptrace(long request, long pid, long addr, long data){...if (request == PTRACE_TRACEME) {if (current->ptrace & PT_PTRACED)goto out;current->ptrace |= PT_PTRACED; // 标志 PTRACE 状态ret = 0;goto out;}...}
- 从上面的代码可以发现 , ptrace() 对 PTRACE_TRACEME 的处理就是把当前进程标志为 PTRACE 状态 。
- 当然事情不会这么简单 , 因为当一个进程被标记为 PTRACE 状态后 , 当调用 exec() 函数去执行一个外部程序时 , 将会暂停当前进程的运行 , 并且发送一个 SIGCHLD 给父进程 。父进程接收到 SIGCHLD 信号后就可以对被调试的进程进行调试 。
- 我们来看看 exec() 函数是怎样实现上述功能的 , exec() 函数的执行过程为 sys_execve() -> do_execve() -> load_elf_binary():
static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs){...if (current->ptrace & PT_PTRACED)send_sig(SIGTRAP, current, 0);...}
- 从上面代码可以看出 , 当进程被标记为 PTRACE 状态时 , 执行 exec() 函数后便会发送一个 SIGTRAP 的信号给当前进程 。
推荐阅读
- IDE 一篇文章带你明白:什么是编译器,什么是集成开发环境?
- 中国女性为什么更愿意掌握家庭财权?
- 一文掌握shell脚本中shift的用法及功能
- 掌握排列数组合数基本公式的推导 排列组合怎么算
- 反射?看一篇文章你就会了
- 秋季如何把头发养护好?掌握方法很重要!这几个方法,务必试试
- GC 一篇文章彻底了解Java垃圾收集机制
- |面对集团高层的动荡,职场人士需要掌握的生存法则与破局思维
- 一篇感恩母亲的演讲,感动哭了 感恩母亲演讲稿
- 一文掌握linux系统路由跟踪指令traceroute