文章插图
前言:在程序出现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:根据不同的指令 , 有不同的用途 , 下面会介绍 。
- 下面通过一个简单例子来说明 ptrace() 系统调用的使用 , 这个例子主要介绍怎么使用 ptrace() 系统调用获取当前被调试(追踪)进程的各个寄存器的值 , 代码如下(ptrace.c):
#include <sys/ptrace.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <sys/user.h>#include <stdio.h>int main(){pid_t child;struct user_regs_struct regs;child = fork();// 创建一个子进程if(child == 0) { // 子进程ptrace(PTRACE_TRACEME, 0, NULL, NULL); // 表示当前进程进入被追踪状态execl("/bin/ls", "ls", NULL);// 执行 `/bin/ls` 程序}else { // 父进程wait(NULL); // 等待子进程发送一个 SIGCHLD 信号ptrace(PTRACE_GETREGS, child, NULL, ®s); // 获取子进程的各个寄存器的值printf("Register: rdi[%ld], rsi[%ld], rdx[%ld], rax[%ld], orig_rax[%ld]n",regs.rdi, regs.rsi, regs.rdx,regs.rax, regs.orig_rax); // 打印寄存器的值ptrace(PTRACE_CONT, child, NULL, NULL); // 继续运行子进程sleep(1);}return 0;}
- 通过命令 gcc ptrace.c -o ptrace 编译并运行上面的程序会输出如下结果:
Register: rdi[0], rsi[0], rdx[0], rax[0], orig_rax[59]ptraceptrace.c
- 上面结果的第一行是由父进程输出的 , 主要是打印了子进程执行 /bin/ls 程序后各个寄存器的值 。而第二行是由子进程输出的 , 主要是打印了执行 /bin/ls 程序后面输出的结果 。
更多linux内核视频教程文档资料免费领取后台私信【内核】自行获取.
文章插图
Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
下面解释一下上面程序的执行流程:
- 主进程调用 fork() 系统调用创建一个子进程 。
- 的进程调用 ptrace(PTRACE_TRACEME,...) 把自己设置为被追踪状态 , 并且调用 execl() 执行 /bin/ls 程序 。
- 被设置为追踪(TRACE)状态的子进程执行 execl() 的程序后 , 会向父进程发送 SIGCHLD 信号 , 并且暂停自身的执行 。
- 父进程通过调用 wait() 接收子进程发送过来的信号 , 并且开始追踪子进程 。
- 父进程通过调用 ptrace(PTRACE_GETREGS, child, ...) 来获取到子进程各个寄存器的值 , 并且打印寄存器的值 。
- 父进程通过调用 ptrace(PTRACE_CONT, child, ...) 让子进程继续执行下去 。
- 从上面的例子可以知道 , 通过向 ptrace() 函数的 request 参数传入不同的值时 , 就会有不同的效果 。比如传入 PTRACE_TRACEME 就可以让进程进入被追踪状态 , 而转入 PTRACE_GETREGS 时 , 就可以获取被追踪的子进程各个寄存器的值等 。
推荐阅读
- IDE 一篇文章带你明白:什么是编译器,什么是集成开发环境?
- 中国女性为什么更愿意掌握家庭财权?
- 一文掌握shell脚本中shift的用法及功能
- 掌握排列数组合数基本公式的推导 排列组合怎么算
- 反射?看一篇文章你就会了
- 秋季如何把头发养护好?掌握方法很重要!这几个方法,务必试试
- GC 一篇文章彻底了解Java垃圾收集机制
- |面对集团高层的动荡,职场人士需要掌握的生存法则与破局思维
- 一篇感恩母亲的演讲,感动哭了 感恩母亲演讲稿
- 一文掌握linux系统路由跟踪指令traceroute