├── README.md ├── base_trace ├── hello.c ├── tracer1.c ├── tracer2.c ├── tracer3.c └── tracer4.c └── gdb ├── bp_manual.c ├── debuglib.c ├── debuglib.h ├── hello.c ├── mini.s ├── step.c └── target.s /README.md: -------------------------------------------------------------------------------- 1 | #
ptarce
2 | 3 | 4 | 5 | ##概述 6 | ptrace是process和trace的简写,直译为进程跟踪。它提供了一种使父进程得以监视和控制其子进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪。 7 | 8 | 9 | ##基础知识 10 | ###进程 11 | 程序是一个静态的概念,它只是一些预先编译好的指令和数据集合的一个文件;而进程是一个动态的概念,它是程序运行时的一个过程。 12 | #####创建进程 13 | 14 | 15 | 18 | 19 |
#include <unistd.h>
16 | pid_t fork(void);
17 |                         返回值:子进程返回0,父进程返回子进程ID,出错返回-1
20 | fork函数被调用一次,却能够返回两次,它可能有三种不同的返回值:
21 |     1)在父进程中,fork返回新创建子进程的进程ID;
22 |     2)在子进程中,fork返回0;
23 |     3)如果出现错误,fork返回-1; 24 | 25 | 在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。可以通过fork返回的值来判断当前进程是子进程还是父进程。 26 | ###系统调用 27 |     在现代的操作系统里,程序运行的时候,本身是没有权利访问多少系统资源的。由于系统有限的资源有可能被多个不同的应用程序同时访问,因此,如果不加以保护,那么各个应用程序难免产生冲突。所以现代操作系统都将可能产生冲突的系统资源给保护起来,阻止应用程序直接访问。这些资源包括文件、网络、IO、各种设备等。 28 | 每个操作系统都会提供一套接口,来封装对系统资源的调用,这套接口就是系统调用。
29 | 30 |     操作系统把进程空间分为了用户空间和内核空间,系统调用是运行在内核空间的,而应用程序基本都是运行在用户空间的。应用程序想要访问系统资源,就必须通过系统调用。用户空间的应用程序要想调用内核空间的系统调用,就需要从用户空间切换到内核空间,这一般是通过中断来实现的。什么是中断呢?中断是一个硬件或者软件发出的请求,要求CPU暂停当前的工作转手去处理更加重要的事情。中断一般具有两个属性,中断号和中断处理程序。在内核中,有一个叫做中断向量表的数组来存放中断号和中断处理程序。当中断到来的时候,CPU会根据中断号找到对应的中断处理程序,并调用它。中断处理程序执行完成后,CPU会继续执行之前的代码。 31 |
32 | 33 |     通常意义上,中断有两种类型,一种称为硬件中断,这种中断来自于硬件的异常或其他事件的发生;另一种称为软件中断,软件中断通常是一条指令(i386下是int),带有一个参数记录中断号。linux系统使用int 0x80来触发所有的系统调用,和中断一样,系统调用带有一个系统调用号,这个系统调用就像身份标识一样来表明是哪一个系统调用,这个系统调用号会放在eax寄存器中。如果系统调用有一个参数,那么参数通过ebx寄存器传入,x86下linux支持的系统调用参数至多有6个,分别使用6个寄存器来传递,它们分别是ebx、ecx、edx、esi、edi和ebp。 34 |
35 | 36 |     触发系统调用后,CPU首先需要切换堆栈,当程序的当前栈从用户态切换到内核态后,会找到系统调用号对应的调用函数,它们都是以"sys_"开头的,当执行完调用函数后,返回值会存放在eax寄存器返回到用户态。 37 | 38 | 39 | 40 | ###信号 41 |     信号是在软件层次上对中断机制的一种模拟,它是一种进程间异步通信的机制。
42 | 43 |     一个进程要发信号给另一个进程,可以使用这些函数:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。它其实是通过系统调用把信号先发给内核。当另一个进程从内核态回用户态的时候,它会先去找一下有没有发给自己的信号,如果有,就处理掉。
44 | 45 | 进程可以通过三种方式来响应一个信号:
46 | (1)忽略信号,即对信号不做任何处理;
(2)捕捉信号。通过signal()定义信号处理函数,当信号发生时,执行相应的处理函数;
47 | (3)执行缺省操作,Linux对每种信号都规定了默认操作。 48 | 49 | 50 | ##ptrace函数详解 51 | 函数原型如下: 52 | 53 | #include 54 | long ptrace(enum __ptrace_request request, pid_t pid, 55 | void *addr, void *data); 56 | 57 | ptrace有四个参数:
58 |   1. enum \_\_ptrace\_request request:指示了ptrace要执行的命令。
59 |   2. pid\_t pid: 指示ptrace要跟踪的进程。
60 |   3. void \*addr: 指示要监控的内存地址。
61 |   4. void \*data: 存放读取出的或者要写入的数据。
62 | 63 | request参数决定了ptrace的具体功能: 64 | 65 | 1.PTRACE_TRACEME 66 | 67 | 68 | 69 | ptrace(PTRACE_TRACEME,0 ,0 ,0) 70 | 71 | 描述:本进程被其父进程所跟踪。 72 | 73 | 74 |
75 | 2.PTRACE\_PEEKTEXT, PTRACE\_PEEKDATA 76 | 77 | 78 | 79 | ptrace(PTRACE_PEEKDATA, pid, addr, data) 80 | 81 | 描述:从内存地址中读取一个字,数据地址由函数返回,pid表示被跟踪的子进程,内存地址由addr给出,data参数被忽略。 82 | 83 |
84 | 3.PTRACE\_POKETEXT, PTRACE\_POKEDATA 85 | 86 | 87 | 88 | ptrace(PTRACE_POKEDATA, pid, addr, data) 89 | 90 | 描述:往内存地址中写入一个字。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据地址。 91 | 92 |
93 | 4.PTRACE_PEEKUSR 94 | 95 | 96 | 97 | ptrace(PTRACE_PEEKUSR, pid, addr, data) 98 | 99 | 描述:从 USER区域中读取一个字节,pid表示被跟踪的子进程,addr表示读取数据在USER区域的偏移量,返回值为函数返回值,data参数被忽略。 100 | 101 |
102 | 5.PTRACE_POKEUSR 103 | 104 | 105 | 106 | ptrace(PTRACE_POKEUSR, pid, addr, data) 107 | 108 | 描述:往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。 109 |
110 | 6.PTRACE_CONT 111 | 112 | ptrace(PTRACE_CONT, pid, 0, signal) 113 | 114 | 描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。 115 | 116 |
117 | 7.PTRACE_SYSCALL 118 | 119 | ptrace(PTRACE_SYS, pid, 0, signal) 120 | 121 | 描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。 122 | 123 |
124 | 8.PTRACE_KILL 125 | 126 | ptrace(PTRACE_KILL,pid) 127 | 128 | 描述:杀掉子进程,使它退出。pid表示被跟踪的子进程。 129 | 130 |
131 | 9.PTRACE_SINGLESTEP 132 | 133 | ptrace(PTRACE_KILL, pid, 0, signle) 134 | 135 | 描述:设置单步执行标志,单步执行一条指令。pid表示被跟踪的子进程。signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。当被跟踪进程单步执行完一个指令后,被跟踪进程被中止,并通知父进程。 136 | 137 |
138 | 139 | 10.PTRACE_ATTACH 140 | 141 | ptrace(PTRACE_ATTACH,pid) 142 | 143 | 描述:跟踪指定pid 进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。 144 | 145 |
146 | 11.PTRACE_DETACH 147 | 148 | ptrace(PTRACE_DETACH,pid) 149 | 150 | 描述:结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行。 151 | 152 |
153 | 12.PTRACE_GETREGS 154 | 155 | ptrace(PTRACE_GETREGS, pid, 0, data) 156 | 157 | 描述:读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。 158 | 159 |
160 | 13.PTRACE_SETREGS 161 | 162 | ptrace(PTRACE_SETREGS, pid, 0, data) 163 | 164 | 描述:设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。 165 | 166 | 167 | 以上列出的都是一些较常用的功能介绍,更多详细的信息可以参考linux下man手册: 168 | man ptrace。 169 | ##ptrace函数示例 170 |     以上部分我们对ptrace函数做了概念上的介绍,现在我们通过几个小示例来应用ptarce,加深对概念的理解。 171 | ###读取系统调用号 172 | 173 | hello.c 174 | 175 | 176 | 177 | #include 178 | int main(){ 179 | printf("Hello, world!\n"); 180 | return 0; 181 | } 182 | 183 | 编译,然后测试运行: 184 | 185 | robin@ubuntu:~/work/ptrace$ gcc hello.c -o hello 186 | robin@ubuntu:~/work/ptrace$ ./hello 187 | Hello, world! 188 | 189 | tracer1.c 190 | 191 | #include 192 | #include 193 | #include 194 | #include 195 | #include 196 | #include 197 | 198 | 199 | /** 200 | * 获取子进程系统调用号 201 | * */ 202 | int main() { 203 | 204 | pid_t child; 205 | child = fork(); 206 | if (child < 0) { 207 | perror("fork error"); 208 | } else if (child == 0) { 209 | //子进程执行 210 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 211 | execl("hello", "hello", NULL); 212 | } else { 213 | //父进程执行 214 | long orig_eax; 215 | int status; 216 | while (1) { 217 | //等待子进程信号 218 | wait(&status); 219 | if (WIFEXITED(status))//子进程发送退出信号,退出循环 220 | break; 221 | //调用ptrace从子进程取数据 222 | orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL); 223 | printf("orig_eax = %ld \n", orig_eax); 224 | //让子进程继续执行 225 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 226 | } 227 | } 228 | return 0; 229 | } 230 | 231 | 232 | 233 | 编译并运行,输出如下: 234 | 235 | robin@ubuntu:~/work/ptrace$ gcc tracer1.c -o tracer1 236 | robin@ubuntu:~/work/ptrace$ ./tracer1 237 | orig_eax = 11 238 | ... 239 | ... 240 | orig_eax = 4 241 | Hello, world! 242 | orig_eax = 4 243 | orig_eax = 252 244 | 245 | 246 | 247 | 篇幅所限,输出内容只截取了第一个和最后几个,中间部分输出省略了。
248 | ####示例说明 249 | 250 |     hello.c不用多说,就是一个最简单的C程序,编译成一个名为hello的可执行文件,给tracer.c调用执行。 251 | 252 |     在tracer.c的main方法里,通过fork()函数来创建一个子进程,在子进程中,先调用 PTRACE_TRACEME让父进程跟踪自己,然后调用execl函数执行hello。
253 | 254 |     在父进程里,循环调用wait函数,等待子进程信号直到收到子进程退出信号。当收到子进程非退出信号后开始通过ptrace函数来操作子进程。这里通过PTRACE\_PEEKUSER来读取子进程里的数据,这里传入 4 * ORIG\_EAX作为数据地址,ORIG\_EAX是一个宏值定义,查看源码,可以知道它的值是0x24,它表示在系统堆栈中相对栈顶指针的一个偏移量,当发生系统调用时,它其实存放的系统调用号。在32位的机器上,系统调用表中的表项是以32位(4字节)类型存放的,所以这里需要将给定的系统调用号乘以4。第一个返回的结果是11,它是子进程执行的第一个系统调用,如果想查看一下各个系统调用编号对应的名字,可以参考头文件:/usr/include/asm/unistd.h。每次获取系统调用号之后,调用PTRACE\_SYSCALL让子线程继续执行,直到发生下一次系统调用。
255 | 256 |     仅仅只是一个简单的printf调用,为什么发生了这么多次系统调用?默认情况下,Linux中的gcc编译器会动态链接到C运行时库。这意味着任何程序在运行时首先要做的事情是加载动态库,这需要很多代码实现。我们这个tracer程序追踪的是整个进程,而不仅仅是main函数,所以会发生很多次系统调用。
257 |     如果我们把hello.c编译成一个静态库,执行如下命令编译: 258 | 259 | robin@ubuntu:~/work/ptrace$ gcc -static hello.c -o hello 260 | 这时候再执行tarcer就会少一些系统调用,因为编译时已经把C运行时库链接到hello目标文件里面,运行的时候不需要再动态去链接C运行时库了。
261 | 262 |     我们注意观察最后输出的几行,在输出Hello, world!前后输出了2次系统调用号4: 263 | 264 | orig_eax = 4 265 | Hello, world! 266 | orig_eax = 4 267 | 查看源码,4这个系统调用号的作用是写文件。而printf函数的实现正是调用了wirte函数: 268 | 269 | #include 270 | ssize_t write(int fd, const void *buf, size_t count); 271 | 这里输出了2次系统调用号4的原因是,当程序执行到系统调用的时候,进程会从用户态进入内核态,并且把系统调用号放到eax寄存器,这时候产生一条信号,会通知到父进程; 在内核态执行完成后,进程会从内核态回到用户态,这时候又会产出信号。
272 | 273 | ###读取系统调用参数 274 |     在tracer1这个简单程序中,我们读取了子进程执行过程中的系统调用号,并知道当系统调用号是4的时候会发生write系统调用,输出Hello, world!。这里我们更进一步,当发生wirite系统调用的时候看看怎么去获取对应的参数。
275 | ####tracer2.c部分代码: 276 | 277 | if (orig_eax == SYS_write) { 278 | if (insyscall == 0) { 279 | /* Syscall entry */ 280 | insyscall = 1; 281 | params[0] = ptrace(PTRACE_PEEKUSER,child, 4 * EBX,NULL); 282 | params[1] = ptrace(PTRACE_PEEKUSER,child, 4 * ECX,NULL); 283 | params[2] = ptrace(PTRACE_PEEKUSER,child, 4 * EDX,NULL); 284 | printf("write called with %ld, %ld, %ld\n",params[0], params[1],params[2]); 285 | } 286 | } 287 | 288 | 编译执行,输出如下: 289 | 290 | robin@ubuntu:~/work/ptrace$ gcc -o tracer2 tracer2.c 291 | robin@ubuntu:~/work/ptrace$ ./tracer2 292 | write called with 1, -1217363968, 14 293 | Hello, world! 294 | 295 | x86 linux中,ebx, ecx, edx这3个寄存器是用来存放系统调用参数的,这里我们对照wirte函数申明,ebx存放的是fd,这里输出1正是标准输出的fd号;ecx存放的是字符串数据的地址;edx存放的是字符串长度,"Hello, world!"的长度正好是14。 296 | 297 | ###读取寄存器值 298 | 在tracer2这个例子中,我们用PTRACE\_PEEKUSER来获取子进程系统调用的参数,但是这是比较笨拙的方法。我们可以使用PRACE_GETREGS作为ptrace的第一个参数来调用,可以只需一次函数调用就取得所有的相关寄存器值。 299 | 300 | ####tracer3.c部分代码: 301 | 302 | struct user_regs_struct regs; 303 | 304 | ptrace(PTRACE_GETREGS, child,NULL, ®s); 305 | printf("Write called with %ld, %ld, %ld\n",regs.ebx, regs.ecx,regs.edx); 306 | 307 | 编译执行,可以看到输出内容和tracer2相同。 308 | 309 | ###改变子进程执行结果 310 |     通过tracer2或tracer3我们可以读取子进程发生系统调用时的参数,在本示例中,我们去改变这个参数,从而影响子进程的执行结果。 311 | ####tracer4.c完整代码: 312 | 313 | 314 | #include 315 | #include 316 | #include 317 | #include 318 | #include 319 | #include 320 | #include 321 | #include 322 | #include 323 | #include 324 | 325 | const int word_size = sizeof(int); //字长,本机系统是32位,所以字长应该为4字节 326 | 327 | /** 328 | * 反转str 329 | * */ 330 | void reverse(char *str) { 331 | 332 | int i, j; 333 | char temp; 334 | for (i = 0, j = strlen(str) - 2; i <= j; ++i, --j) { 335 | temp = str[i]; 336 | str[i] = str[j]; 337 | str[j] = temp; 338 | } 339 | } 340 | 341 | /** 342 | * 从子进程读数据 343 | * */ 344 | void getdata(pid_t child, long addr, char *str, int len) { 345 | 346 | char *laddr; 347 | int i, j; 348 | 349 | union u { 350 | long val; 351 | char chars[word_size]; 352 | } data; //联合体,val和chars指向同一块内存 353 | 354 | i = 0; 355 | j = len / word_size; 356 | 357 | laddr = str; 358 | 359 | while (i < j) { 360 | //每次读一个字,也就是读4byte 361 | data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); 362 | //拷贝到事先分配好的字符串地址 363 | memcpy(laddr, data.chars, word_size); 364 | ++i; 365 | laddr += word_size; 366 | } 367 | 368 | //读最后小于4字节的几个字节内容 369 | j = len % word_size; 370 | if (j != 0) { 371 | data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); 372 | memcpy(laddr, data.chars, j); 373 | } 374 | //结束符 375 | str[len] = '\0'; 376 | } 377 | 378 | /** 379 | * 往子进程写数据 380 | * */ 381 | void putdata(pid_t child, long addr, char *str, int len) { 382 | 383 | char *laddr; 384 | int i, j; 385 | union u { 386 | long val; 387 | char chars[word_size]; 388 | } data; 389 | 390 | i = 0; 391 | j = len / word_size; 392 | laddr = str; 393 | 394 | while (i < j) { 395 | memcpy(data.chars, laddr, word_size); 396 | //每次写入一个字 397 | ptrace(PTRACE_POKEDATA, child, addr + i * 4, data.val); 398 | ++i; 399 | laddr += word_size; 400 | } 401 | j = len % word_size; 402 | if (j != 0) { 403 | memcpy(data.chars, laddr, j); 404 | ptrace(PTRACE_POKEDATA, child, addr + i * 4, data.val); 405 | } 406 | } 407 | 408 | int main() { 409 | 410 | pid_t child; 411 | child = fork(); 412 | if (child < 0) { 413 | perror("fork error"); 414 | } else if (child == 0) { 415 | //子进程执行 416 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 417 | execl("hello", "hello", NULL); 418 | } else { 419 | //父进程执行 420 | long orig_eax; 421 | long params[3]; 422 | int status; 423 | char *str, *laddr; 424 | int toggle = 0; 425 | while (1) { 426 | //等待子进程信号 427 | wait(&status); 428 | if (WIFEXITED(status)) //遇到子进程退出信号,退出循环 429 | break; 430 | orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL); 431 | if (orig_eax == SYS_write) { 432 | if (toggle == 0) { 433 | toggle = 1; 434 | params[0] = ptrace(PTRACE_PEEKUSER, child, 4 * EBX, NULL); 435 | params[1] = ptrace(PTRACE_PEEKUSER, child, 4 * ECX, NULL); 436 | params[2] = ptrace(PTRACE_PEEKUSER, child, 4 * EDX, NULL); 437 | 438 | str = (char *) calloc((params[2] + 1), sizeof(char)); 439 | getdata(child, params[1], str, params[2]); 440 | reverse(str); 441 | putdata(child, params[1], str, params[2]); 442 | } else { 443 | toggle = 0; 444 | } 445 | } 446 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 447 | } 448 | } 449 | return 0; 450 | } 451 | 452 | 编译并运行,输出结果如下: 453 | 454 | robin@ubuntu:~/idework/eclipsecdt/ptraceDemo$ ./tracer4 455 | !dlrow ,olleH 456 | 457 | 可以看到,"Hello, world!"字符串被反转输出了。 458 | 459 | 460 | 461 | 462 | ###GDB调试 463 | ####GDB概述 464 |     GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。一般来说,GDB主要帮忙你完成下面四个方面的功能: 465 |
466 |         1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
467 |         2、可让被调试的程序在你所指定的调置的断点处停住。
468 |         3、当程序被停住时,可以检查此时你的程序中所发生的事。
469 |         4、动态的改变你程序的执行环境。
470 | 471 | ####单步执行 472 |     调试器可以跟踪程序执行,监控程序每一条指令的运行,原理其实通过ptrace调用PTRACE\_SINGLESTEP来实现的。下面通过一个小示例来看看PTRACE\_SINGLESTEP单步功能。 473 | #####step.c代码如下: 474 | #include 475 | #include 476 | #include 477 | #include 478 | #include 479 | #include 480 | #include 481 | #include 482 | #include 483 | #include 484 | #include 485 | #include 486 | 487 | /** 488 | * 演示PTRACE_SINGLESTEP 489 | * */ 490 | int main(int argc, char** argv) { 491 | if (argc < 2) { 492 | perror("set a progarm to exec"); 493 | return -1; 494 | } 495 | pid_t child_pid = fork(); 496 | if (child_pid == 0) { 497 | //子进程执行 498 | if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) { 499 | perror("ptrace_traceme error"); 500 | return -1; 501 | } 502 | execl(argv[1], argv[1], 0); 503 | } else if (child_pid > 0) { 504 | //父进程执行 505 | int counter = 0; 506 | int status; 507 | while (1) { 508 | //等待子进程信号 509 | wait(&status); 510 | if (WIFEXITED(status)) //子进程发送退出信号,退出循环 511 | break; 512 | counter++; 513 | //调用ptrace从子进程取数据 514 | struct user_regs_struct regs; 515 | //取eip寄存器,这里存放的是cpu将要执行的指令地址 516 | ptrace(PTRACE_GETREGS, child_pid, NULL, ®s); 517 | //取指令内容 518 | unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0); 519 | printf("counter = %u,EIP = 0x%08x,instr = 0x%08x\n", counter, 520 | regs.eip, instr); 521 | //重新启动子进程,当子进程执行了下一条指令后再将其停止 522 | if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) { 523 | perror("ptrace singlestep error"); 524 | return -1; 525 | } 526 | } 527 | } else { 528 | perror("fork error"); 529 | return -1; 530 | } 531 | 532 | return 0; 533 | } 534 | 该程序fork一个子进程来运行给定程序,然后在父进程里面通过ptrace来控制子进程,我们可以调试一下helloworld,可以看到输出了近10万条记录,在上面的tracer1也有提到,因为要引用C基础库的东西,所以会执行很多指令。为了方便我们观察调试,这里使用汇编语言来编写一个调试目标程序: 535 | 536 | .section .data #数据段 537 | output:.string "hello,mini\n" #定义字符串变量 538 | .section .text #代码段 539 | .globl _start #指定入口 540 | _start: 541 | # ssize_t write(int fd, const void *buf, size_t count); 542 | movl $4, %eax #系统调用号 543 | movl $1, %ebx #fd 544 | movl $output, %ecx #buf 545 | movl $11, %edx #count 546 | int $0x80 547 | movl $1, %eax #返回值 548 | int $0x80 549 | 550 | 把上面这段代码保存为mini.s,通过下面2条命令来编译:
551 | 552 | as mini.s -o mini.o 553 | ld -o mini mini.o 554 | 编译完成后,我们再使用step来调试一下mini:
555 | 556 | 557 | robin@ubuntu:~/idework/eclipsecdt/ptraceDemo/gdb$ ./step mini 558 | counter = 1,EIP = 0x08048074,instr = 0x000004b8 559 | counter = 2,EIP = 0x08048079,instr = 0x000001bb 560 | counter = 3,EIP = 0x0804807e,instr = 0x049091b9 561 | counter = 4,EIP = 0x08048083,instr = 0x00000bba 562 | counter = 5,EIP = 0x08048088,instr = 0x01b880cd 563 | hello,mini 564 | counter = 6,EIP = 0x0804808a,instr = 0x000001b8 565 | counter = 7,EIP = 0x0804808f,instr = 0x656880cd 566 | 567 | OK,所以现在我们可以看到指令指针以及每一步的指令。如何验证这是否正确呢?可以通过在可执行文件上执行objdump –d来实现: 568 | 569 | robin@ubuntu:~/idework/eclipsecdt/ptraceDemo/gdb$ objdump -d mini 570 | 571 | mini: file format elf32-i386 572 | 573 | 574 | Disassembly of section .text: 575 | 576 | 08048074 <_start>: 577 | 8048074: b8 04 00 00 00 mov $0x4,%eax 578 | 8048079: bb 01 00 00 00 mov $0x1,%ebx 579 | 804807e: b9 91 90 04 08 mov $0x8049091,%ecx 580 | 8048083: ba 0b 00 00 00 mov $0xb,%edx 581 | 8048088: cd 80 int $0x80 582 | 804808a: b8 01 00 00 00 mov $0x1,%eax 583 | 804808f: cd 80 int $0x80 584 | 用这份输出对比我们的跟踪程序输出,应该很容易观察到相同的地方。 585 | 586 | ###断点 587 | ####int 3指令 588 | 调试器实现断点功能是通过int 3指令来实现的。
589 | int 3指令产生一个特殊的单字节操作码(CC),这是用来调用调试异常处理例程的。(这个单字节形式非常有价值,因为这样可以通过一个断点来替换掉任何指令的第一个字节,包括其它的单字节指令也是一样,而不会覆盖到其它的操作码)。 590 | ####手动设置断点 591 | 现在展示如何在程序中设定断点。用于这个示例的目标程序如下: 592 | #####target.s: 593 | 594 | .section .data 595 | 596 | hello:.string "hello\n" 597 | world:.string "world\n" 598 | 599 | .section .text 600 | .globl _start 601 | 602 | _start: 603 | 604 | #print hello 605 | movl $4, %eax 606 | movl $1, %ebx 607 | movl $hello, %ecx 608 | movl $6, %edx 609 | int $0x80 610 | 611 | #print world 612 | movl $4, %eax 613 | movl $1, %ebx 614 | movl $world, %ecx 615 | movl $6, %edx 616 | int $0x80 617 | 618 | movl $1, %eax 619 | int $0x80 620 | 621 | 这里还是使用汇编语言,这样能够避免使用C语言时涉及到的编译和符号的问题。上面列出的程序功能就是在一行中打印“Hello,”,然后在下一行中打印“world”。这个例子与上一个例子很相似。 622 | 623 | 我们希望设定的断点位置应该在第一条打印之后,但恰好在第二条打印之前。我们就让断点打在第一个int 0×80指令之后吧,也就是movl $4, %eax。首先,需要知道这条指令对应的地址是什么。运行objdump –d: 624 | 625 | robin@ubuntu:~/idework/eclipsecdt/ptraceDemo/gdb$ objdump -d target 626 | 627 | target: file format elf32-i386 628 | 629 | 630 | Disassembly of section .text: 631 | 632 | 08048074 <_start>: 633 | 8048074: b8 04 00 00 00 mov $0x4,%eax 634 | 8048079: bb 01 00 00 00 mov $0x1,%ebx 635 | 804807e: b9 a7 90 04 08 mov $0x80490a7,%ecx 636 | 8048083: ba 06 00 00 00 mov $0x6,%edx 637 | 8048088: cd 80 int $0x80 638 | 804808a: b8 04 00 00 00 mov $0x4,%eax 639 | 804808f: bb 01 00 00 00 mov $0x1,%ebx 640 | 8048094: b9 ae 90 04 08 mov $0x80490ae,%ecx 641 | 8048099: ba 06 00 00 00 mov $0x6,%edx 642 | 804809e: cd 80 int $0x80 643 | 80480a0: b8 01 00 00 00 mov $0x1,%eax 644 | 80480a5: cd 80 int $0x80 645 | 通过上面的输出,我们知道要设定的断点地址是0×804808a。 646 | 647 | ####通过int 3指令在调试器中设定断点 648 | 649 | 要在被调试进程中的某个目标地址上设定一个断点,调试器需要做下面两件事情: 650 | 651 | 1. 保存目标地址上的数据 652 | 653 | 2. 将目标地址上的第一个字节替换为int 3指令 654 | 655 | 然后,当调试器向操作系统请求开始运行进程时,进程最终一定会碰到int 3指令。此时进程停止,操作系统将发送一个信号。这时就是调试器再次出马的时候了,接收到一个其子进程(或被跟踪进程)停止的信号,然后调试器要做下面几件事: 656 | 657 | 1. 在目标地址上用原来的指令替换掉int 3 658 | 659 | 2. 将被跟踪进程中的指令指针向后递减1。这么做是必须的,因为现在指令指针指向的是已经执行过的int 3之后的下一条指令。 660 | 661 | 3. 由于进程此时仍然是停止的,用户可以同被调试进程进行某种形式的交互。这里调试器可以让你查看变量的值,检查调用栈等等。 662 | 663 | 4. 当用户希望进程继续运行时,调试器负责将断点再次加到目标地址上(由于在第一步中断点已经被移除了),除非用户希望取消断点。 664 | 665 | 让我们看看这些步骤如何转化为实际的代码。 666 | 667 | /* 等待子进程信号 */ 668 | wait(&wait_status); 669 | 670 | /* 取子进程寄存器 */ 671 | ptrace(PTRACE_GETREGS, child_pid, 0, ®s); 672 | /* eip是子进程执行的指令地址 */ 673 | printf("Child started. EIP = 0x%08x\n", regs.eip); 674 | 675 | /* 读取地址0x804808a 对应的指令*/ 676 | unsigned addr = 0x804808a; 677 | unsigned data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0); 678 | printf("Original data at 0x%08x: 0x%08x\n", addr, data); 679 | 这里调试器从被跟踪进程中获取到指令指针,然后检查当前位于地址0×804808a处的字长内容。运行程序,将打印出: 680 | 681 | Child started. EIP = 0x08048074 682 | Original data at 0x0804808a: 0x000004b8 683 | 684 | 685 | 目前为止一切顺利,下一步: 686 | 687 | /* 把'int 3'替换进 0x804808a处 对应的指令*/ 688 | unsigned data_with_trap = (data & 0xFFFFFF00) | 0xCC; 689 | ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data_with_trap); 690 | 691 | /* 再读 地址0x804808a处 对应的指令*/ 692 | unsigned readback_data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0); 693 | printf("After trap, data at 0x%08x: 0x%08x\n", addr, readback_data); 694 | 注意看我们是如何将int 3指令插入到目标地址上的。这部分代码将打印出: 695 | 696 | After trap, data at 0x0804808a: 0x000004cc 697 | 698 | 再一次如同预计的那样——0xb8被0xcc取代了。调试器现在运行子进程然后等待子进程在断点处停止住。 699 | 700 | /* 让子进程运行直到遇到断点 int 3*/ 701 | ptrace(PTRACE_CONT, child_pid, 0, 0); 702 | 703 | wait(&wait_status); 704 | if (WIFSTOPPED(wait_status)) { 705 | printf("Child got a signal: %s\n", strsignal(WSTOPSIG(wait_status))); 706 | } else { 707 | perror("wait"); 708 | return; 709 | } 710 | 711 | /* 看看子进程目前执行到哪了 */ 712 | ptrace(PTRACE_GETREGS, child_pid, 0, ®s); 713 | printf("Child stopped at EIP = 0x%08x\n", regs.eip); 714 | /*等 用户响应*/ 715 | getchar(); 716 | 717 | 这段代码打印出: 718 | 719 | hello 720 | Child got a signal: Trace/breakpoint trap 721 | Child stopped at EIP = 0x0804808b 722 | 723 | 724 | 注意,“Hello,”在断点之前打印出来了——同我们计划的一样。同时我们发现子进程已经停止运行了——就在这个单字节的陷阱指令执行之后。 725 | 726 | 按下enter键,继续执行: 727 | 728 | /* 移除int 3,把原先的指令写回去*/ 729 | ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data); 730 | /* 指令地址 -1*/ 731 | regs.eip -= 1; 732 | ptrace(PTRACE_SETREGS, child_pid, 0, ®s); 733 | ptrace(PTRACE_CONT, child_pid, 0, 0); 734 | 735 | wait(&wait_status); 736 | if (WIFEXITED(wait_status)) { 737 | printf("Child exited\n"); 738 | } else { 739 | printf("Unexpected signal\n"); 740 | } 741 | 742 | 输出: 743 | 744 | world 745 | Child exited 746 | 747 | 748 | 749 | 这会使子进程打印出“world”然后退出,同之前计划的一样。 750 | 751 | ##参考资料 752 | Playing with ptrace, Part I 753 |
754 | Playing with ptrace, Part II 755 |
756 | How debuggers work: Part 1 - Basics 757 |
758 | How debuggers work: Part 2 - Breakpoints 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | -------------------------------------------------------------------------------- /base_trace/hello.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | int main() 4 | { 5 | printf("Hello, world!\n"); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /base_trace/tracer1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * 获取子进程系统调用号 10 | * */ 11 | int main() { 12 | 13 | pid_t child; 14 | child = fork(); 15 | 16 | if (child < 0) { 17 | perror("fork error"); 18 | } else if (child == 0) { 19 | //子进程执行 20 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 21 | execl("hello", "hello", NULL); 22 | } else { 23 | //父进程执行 24 | long orig_eax; 25 | int status; 26 | while (1) { 27 | //等待子进程信号 28 | wait(&status); 29 | if (WIFEXITED(status)) //子进程发送退出信号,退出循环 30 | break; 31 | //调用ptrace从子进程取数据 32 | orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL); 33 | printf("orig_eax = %ld \n", orig_eax); 34 | //让子进程继续执行 35 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 36 | } 37 | } 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /base_trace/tracer2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * 读取子进程write系统调用参数 13 | * */ 14 | int main() { 15 | 16 | pid_t child; 17 | child = fork(); 18 | if (child < 0) { 19 | perror("fork error"); 20 | } else if (child == 0) { 21 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 22 | execl("hello", "hello", NULL); 23 | } else { 24 | long orig_eax, eax; 25 | long params[3]; 26 | int status; 27 | int insyscall = 0; 28 | while (1) { 29 | wait(&status); 30 | if (WIFEXITED(status)) 31 | break; 32 | orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL); 33 | if (orig_eax == SYS_write) { 34 | if (insyscall == 0) { 35 | /* Syscall entry */ 36 | insyscall = 1; 37 | params[0] = ptrace(PTRACE_PEEKUSER, child, 4 * EBX, NULL); 38 | params[1] = ptrace(PTRACE_PEEKUSER, child, 4 * ECX, NULL); 39 | params[2] = ptrace(PTRACE_PEEKUSER, child, 4 * EDX, NULL); 40 | printf("Write called with %ld, %ld, %ld \n", params[0], 41 | params[1], params[2]); 42 | } else { 43 | /* Syscall exit */ 44 | eax = ptrace(PTRACE_PEEKUSER, child, 4 * EAX, NULL); 45 | printf("Write returned with %ld \n", eax); 46 | insyscall = 0; 47 | } 48 | } 49 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 50 | } 51 | } 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /base_trace/tracer3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * 读取子进程寄存器值 13 | * */ 14 | int main() 15 | { pid_t child; 16 | long orig_eax, eax; 17 | long params[3]; 18 | int status; 19 | int insyscall = 0; 20 | struct user_regs_struct regs; 21 | child = fork(); 22 | if(child == 0) { 23 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 24 | execl("hello", "hello", NULL); 25 | } 26 | else { 27 | while(1) { 28 | wait(&status); 29 | if(WIFEXITED(status)) 30 | break; 31 | orig_eax = ptrace(PTRACE_PEEKUSER,child, 4 * ORIG_EAX,NULL); 32 | if(orig_eax == SYS_write) { 33 | if(insyscall == 0) { 34 | /* Syscall entry */ 35 | insyscall = 1; 36 | ptrace(PTRACE_GETREGS, child,NULL, ®s); 37 | printf("Write called with %ld, %ld, %ld \n",regs.ebx, regs.ecx,regs.edx); 38 | } 39 | else { /* Syscall exit */ 40 | eax = ptrace(PTRACE_PEEKUSER,child, 4 * EAX,NULL); 41 | printf("Write returned with %ld\n", eax); 42 | insyscall = 0; 43 | } 44 | } 45 | ptrace(PTRACE_SYSCALL, child,NULL, NULL); 46 | } 47 | } 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /base_trace/tracer4.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | const int word_size = sizeof(int); //字长,本机系统是32位,所以字长应该为4字节 13 | 14 | /** 15 | * 反转str 16 | * */ 17 | void reverse(char *str) { 18 | 19 | int i, j; 20 | char temp; 21 | for (i = 0, j = strlen(str) - 2; i <= j; ++i, --j) { 22 | temp = str[i]; 23 | str[i] = str[j]; 24 | str[j] = temp; 25 | } 26 | } 27 | 28 | /** 29 | * 从子进程读数据 30 | * */ 31 | void getdata(pid_t child, long addr, char *str, int len) { 32 | 33 | char *laddr; 34 | int i, j; 35 | 36 | union u { 37 | long val; 38 | char chars[word_size]; 39 | } data; //联合体,val和chars指向同一块内存 40 | 41 | i = 0; 42 | j = len / word_size; 43 | 44 | laddr = str; 45 | 46 | while (i < j) { 47 | //每次读一个字,也就是读4byte 48 | data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); 49 | //拷贝到事先分配好的字符串地址 50 | memcpy(laddr, data.chars, word_size); 51 | ++i; 52 | laddr += word_size; 53 | } 54 | 55 | //读最后小于4字节的几个字节内容 56 | j = len % word_size; 57 | if (j != 0) { 58 | data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 4, NULL); 59 | memcpy(laddr, data.chars, j); 60 | } 61 | //结束符 62 | str[len] = '\0'; 63 | } 64 | 65 | /** 66 | * 往子进程写数据 67 | * */ 68 | void putdata(pid_t child, long addr, char *str, int len) { 69 | 70 | char *laddr; 71 | int i, j; 72 | union u { 73 | long val; 74 | char chars[word_size]; 75 | } data; 76 | 77 | i = 0; 78 | j = len / word_size; 79 | laddr = str; 80 | 81 | while (i < j) { 82 | memcpy(data.chars, laddr, word_size); 83 | //每次写入一个字 84 | ptrace(PTRACE_POKEDATA, child, addr + i * 4, data.val); 85 | ++i; 86 | laddr += word_size; 87 | } 88 | j = len % word_size; 89 | if (j != 0) { 90 | memcpy(data.chars, laddr, j); 91 | ptrace(PTRACE_POKEDATA, child, addr + i * 4, data.val); 92 | } 93 | } 94 | 95 | int main() { 96 | 97 | pid_t child; 98 | child = fork(); 99 | if (child < 0) { 100 | perror("fork error"); 101 | } else if (child == 0) { 102 | //子进程执行 103 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 104 | execl("hello", "hello", NULL); 105 | } else { 106 | //父进程执行 107 | long orig_eax; 108 | long params[3]; 109 | int status; 110 | char *str, *laddr; 111 | int toggle = 0; 112 | while (1) { 113 | //等待子进程信号 114 | wait(&status); 115 | if (WIFEXITED(status)) //遇到子进程退出信号,退出循环 116 | break; 117 | orig_eax = ptrace(PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL); 118 | if (orig_eax == SYS_write) { 119 | if (toggle == 0) { 120 | toggle = 1; 121 | params[0] = ptrace(PTRACE_PEEKUSER, child, 4 * EBX, NULL); //fd 122 | params[1] = ptrace(PTRACE_PEEKUSER, child, 4 * ECX, NULL); //str地址 123 | params[2] = ptrace(PTRACE_PEEKUSER, child, 4 * EDX, NULL); //str长度 124 | str = (char *) calloc((params[2] + 1), sizeof(char)); 125 | getdata(child, params[1], str, params[2]); 126 | reverse(str); 127 | putdata(child, params[1], str, params[2]); 128 | } else { 129 | toggle = 0; 130 | } 131 | } 132 | ptrace(PTRACE_SYSCALL, child, NULL, NULL); 133 | } 134 | } 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /gdb/bp_manual.c: -------------------------------------------------------------------------------- 1 | /* Code sample: manual setting of a breakpoint, using ptrace 2 | ** 3 | ** Eli Bendersky (http://eli.thegreenplace.net) 4 | ** This code is in the public domain. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | extern char* strsignal(int); 22 | 23 | /* Run a target process in tracing mode by exec()-ing the given program name. 24 | */ 25 | void run_target(const char* programname) 26 | { 27 | printf("target started. will run '%s'\n", programname); 28 | 29 | /* Allow tracing of this process */ 30 | if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) { 31 | perror("ptrace"); 32 | return; 33 | } 34 | 35 | /* Replace this process's image with the given program */ 36 | execl(programname, programname, 0); 37 | } 38 | void run_debugger(pid_t child_pid) 39 | { 40 | int wait_status; 41 | struct user_regs_struct regs; 42 | 43 | /* 等待子进程信号 */ 44 | wait(&wait_status); 45 | 46 | /* 取子进程寄存器 */ 47 | ptrace(PTRACE_GETREGS, child_pid, 0, ®s); 48 | /* eip是子进程执行的指令地址 */ 49 | printf("Child started. EIP = 0x%08x\n", regs.eip); 50 | 51 | /* 读取地址0x804808a处 对应的指令*/ 52 | unsigned addr = 0x804808a; 53 | unsigned data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0); 54 | printf("Original data at 0x%08x: 0x%08x\n", addr, data); 55 | 56 | /* 把'int 3'替换进 0x804808a处 对应的指令*/ 57 | unsigned data_with_trap = (data & 0xFFFFFF00) | 0xCC; 58 | ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data_with_trap); 59 | 60 | /* 再读 地址0x804808a处 对应的指令*/ 61 | unsigned readback_data = ptrace(PTRACE_PEEKTEXT, child_pid, (void*)addr, 0); 62 | printf("After trap, data at 0x%08x: 0x%08x\n", addr, readback_data); 63 | 64 | /* 让子进程运行直到遇到断点 int 3*/ 65 | ptrace(PTRACE_CONT, child_pid, 0, 0); 66 | 67 | wait(&wait_status); 68 | if (WIFSTOPPED(wait_status)) { 69 | printf("Child got a signal: %s\n", strsignal(WSTOPSIG(wait_status))); 70 | } else { 71 | perror("wait"); 72 | return; 73 | } 74 | 75 | /* 看看子进程目前执行到哪了 */ 76 | ptrace(PTRACE_GETREGS, child_pid, 0, ®s); 77 | printf("Child stopped at EIP = 0x%08x\n", regs.eip); 78 | /*等 用户响应*/ 79 | getchar(); 80 | 81 | /* 移除int 3,把原先的指令写回去*/ 82 | ptrace(PTRACE_POKETEXT, child_pid, (void*)addr, (void*)data); 83 | /* 指令地址 -1*/ 84 | regs.eip -= 1; 85 | ptrace(PTRACE_SETREGS, child_pid, 0, ®s); 86 | 87 | ptrace(PTRACE_CONT, child_pid, 0, 0); 88 | 89 | wait(&wait_status); 90 | 91 | if (WIFEXITED(wait_status)) { 92 | printf("Child exited\n"); 93 | } 94 | else { 95 | printf("Unexpected signal\n"); 96 | } 97 | } 98 | 99 | 100 | int main(int argc, char** argv) 101 | { 102 | pid_t child_pid; 103 | 104 | if (argc < 2) { 105 | fprintf(stderr, "Expected a program name as argument\n"); 106 | return -1; 107 | } 108 | 109 | child_pid = fork(); 110 | if (child_pid == 0) 111 | run_target(argv[1]); 112 | else if (child_pid > 0) 113 | run_debugger(child_pid); 114 | else { 115 | perror("fork"); 116 | return -1; 117 | } 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /gdb/debuglib.c: -------------------------------------------------------------------------------- 1 | /* Code sample: simplistic "library" of debugging tools. 2 | ** 3 | ** Eli Bendersky (http://eli.thegreenplace.net) 4 | ** This code is in the public domain. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "debuglib.h" 21 | 22 | 23 | /* Print a message to stdout, prefixed by the process ID 24 | */ 25 | void procmsg(const char* format, ...) 26 | { 27 | va_list ap; 28 | fprintf(stdout, "[%d] ", getpid()); 29 | va_start(ap, format); 30 | vfprintf(stdout, format, ap); 31 | va_end(ap); 32 | } 33 | 34 | 35 | /* Run a target process in tracing mode by exec()-ing the given program name. 36 | */ 37 | void run_target(const char* programname) 38 | { 39 | procmsg("target started. will run '%s'\n", programname); 40 | 41 | /* Allow tracing of this process */ 42 | if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) { 43 | perror("ptrace"); 44 | return; 45 | } 46 | 47 | /* Replace this process's image with the given program */ 48 | execl(programname, programname, 0); 49 | } 50 | 51 | 52 | long get_child_eip(pid_t pid) 53 | { 54 | struct user_regs_struct regs; 55 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 56 | return regs.eip; 57 | } 58 | 59 | 60 | void dump_process_memory(pid_t pid, unsigned from_addr, unsigned to_addr) 61 | { 62 | procmsg("Dump of %d's memory [0x%08X : 0x%08X]\n", pid, from_addr, to_addr); 63 | for (unsigned addr = from_addr; addr <= to_addr; ++addr) { 64 | unsigned word = ptrace(PTRACE_PEEKTEXT, pid, addr, 0); 65 | printf(" 0x%08X: %02x\n", addr, word & 0xFF); 66 | } 67 | } 68 | 69 | 70 | /* Encapsulates a breakpoint. Holds the address at which the BP was placed 71 | ** and the original data word at that address (prior to int3) insertion. 72 | */ 73 | struct debug_breakpoint_t { 74 | void* addr; 75 | unsigned orig_data; 76 | }; 77 | 78 | 79 | /* Enable the given breakpoint by inserting the trap instruction at its 80 | ** address, and saving the original data at that location. 81 | */ 82 | static void enable_breakpoint(pid_t pid, debug_breakpoint* bp) 83 | { 84 | assert(bp); 85 | bp->orig_data = ptrace(PTRACE_PEEKTEXT, pid, bp->addr, 0); 86 | ptrace(PTRACE_POKETEXT, pid, bp->addr, (bp->orig_data & 0xFFFFFF00) | 0xCC); 87 | } 88 | 89 | 90 | /* Disable the given breakpoint by replacing the byte it points to with 91 | ** the original byte that was there before trap insertion. 92 | */ 93 | static void disable_breakpoint(pid_t pid, debug_breakpoint* bp) 94 | { 95 | assert(bp); 96 | unsigned data = ptrace(PTRACE_PEEKTEXT, pid, bp->addr, 0); 97 | assert((data & 0xFF) == 0xCC); 98 | ptrace(PTRACE_POKETEXT, pid, bp->addr, (data & 0xFFFFFF00) | (bp->orig_data & 0xFF)); 99 | } 100 | 101 | 102 | debug_breakpoint* create_breakpoint(pid_t pid, void* addr) 103 | { 104 | debug_breakpoint* bp = malloc(sizeof(*bp)); 105 | bp->addr = addr; 106 | enable_breakpoint(pid, bp); 107 | return bp; 108 | } 109 | 110 | 111 | void cleanup_breakpoint(debug_breakpoint* bp) 112 | { 113 | free(bp); 114 | } 115 | 116 | 117 | int resume_from_breakpoint(pid_t pid, debug_breakpoint* bp) 118 | { 119 | struct user_regs_struct regs; 120 | int wait_status; 121 | 122 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 123 | /* Make sure we indeed are stopped at bp */ 124 | assert(regs.eip == (long) bp->addr + 1); 125 | 126 | /* Now disable the breakpoint, rewind EIP back to the original instruction 127 | ** and single-step the process. This executes the original instruction that 128 | ** was replaced by the breakpoint. 129 | */ 130 | regs.eip = (long) bp->addr; 131 | ptrace(PTRACE_SETREGS, pid, 0, ®s); 132 | disable_breakpoint(pid, bp); 133 | if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) { 134 | perror("ptrace"); 135 | return -1; 136 | } 137 | wait(&wait_status); 138 | 139 | if (WIFEXITED(wait_status)) 140 | return 0; 141 | 142 | /* Re-enable the breakpoint and let the process run. 143 | */ 144 | enable_breakpoint(pid, bp); 145 | 146 | if (ptrace(PTRACE_CONT, pid, 0, 0) < 0) { 147 | perror("ptrace"); 148 | return -1; 149 | } 150 | wait(&wait_status); 151 | 152 | if (WIFEXITED(wait_status)) 153 | return 0; 154 | else if (WIFSTOPPED(wait_status)) { 155 | return 1; 156 | } 157 | else 158 | return -1; 159 | } 160 | 161 | 162 | -------------------------------------------------------------------------------- /gdb/debuglib.h: -------------------------------------------------------------------------------- 1 | /* Code sample: simplistic "library" of debugging tools. 2 | ** 3 | ** Eli Bendersky (http://eli.thegreenplace.net) 4 | ** This code is in the public domain. 5 | */ 6 | #include 7 | 8 | 9 | /* Print out a message, prefixed by the process ID. 10 | */ 11 | void procmsg(const char* format, ...); 12 | 13 | 14 | /* Run the given program in a child process with exec() and tracing 15 | ** enabled. 16 | */ 17 | void run_target(const char* programname); 18 | 19 | 20 | /* Retrieve the child process's current instruction pointer value. 21 | */ 22 | long get_child_eip(pid_t pid); 23 | 24 | 25 | /* Display memory contents in the inclusive range [from_addr:to_addr] from the 26 | ** given process's address space. 27 | */ 28 | void dump_process_memory(pid_t pid, unsigned from_addr, unsigned to_addr); 29 | 30 | 31 | struct debug_breakpoint_t; 32 | typedef struct debug_breakpoint_t debug_breakpoint; 33 | 34 | 35 | /* Create a breakpoint for the child process pid, at the given address. 36 | */ 37 | debug_breakpoint* create_breakpoint(pid_t pid, void* addr); 38 | 39 | 40 | /* Clean up the memory allocated for the given breakpoint. 41 | ** Note: this doesn't disable the breakpoint, just deallocates it. 42 | */ 43 | void cleanup_breakpoint(debug_breakpoint* bp); 44 | 45 | 46 | /* Given a process that's currently stopped at breakpoint bp, resume 47 | ** its execution and re-establish the breakpoint. 48 | ** Return 0 if the process exited while running, 1 if it has stopped 49 | ** again, -1 in case of an error. 50 | */ 51 | int resume_from_breakpoint(pid_t pid, debug_breakpoint* bp); 52 | 53 | 54 | -------------------------------------------------------------------------------- /gdb/hello.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | int main() 4 | { 5 | printf("Hello, world!\n"); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /gdb/mini.s: -------------------------------------------------------------------------------- 1 | .section .data 2 | output:.string "hello,mini\n" 3 | 4 | .section .text 5 | .globl _start 6 | 7 | _start: 8 | 9 | # ssize_t write(int fd, const void *buf, size_t count); 10 | 11 | movl $4, %eax 12 | movl $1, %ebx 13 | movl $output, %ecx 14 | movl $11, %edx 15 | int $0x80 16 | 17 | movl $1, %eax 18 | int $0x80 19 | -------------------------------------------------------------------------------- /gdb/step.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /** 15 | * 演示PTRACE_SINGLESTEP 16 | * */ 17 | int main(int argc, char** argv) { 18 | if (argc < 2) { 19 | perror("set a progarm to exec"); 20 | return -1; 21 | } 22 | pid_t child_pid = fork(); 23 | if (child_pid == 0) { 24 | //子进程执行 25 | if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) { 26 | perror("ptrace_traceme error"); 27 | return -1; 28 | } 29 | execl(argv[1], argv[1], 0); 30 | } else if (child_pid > 0) { 31 | //父进程执行 32 | int counter = 0; 33 | int status; 34 | while (1) { 35 | //等待子进程信号 36 | wait(&status); 37 | if (WIFEXITED(status)) //子进程发送退出信号,退出循环 38 | break; 39 | counter++; 40 | //调用ptrace从子进程取数据 41 | struct user_regs_struct regs; 42 | //取eip寄存器,这里存放的是cpu将要执行的指令地址 43 | ptrace(PTRACE_GETREGS, child_pid, NULL, ®s); 44 | //取指令内容 45 | unsigned instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0); 46 | printf("counter = %u,EIP = 0x%08x,instr = 0x%08x\n", counter, 47 | regs.eip, instr); 48 | //重新启动子进程,当子进程执行了下一条指令后再将其停止 49 | if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) { 50 | perror("ptrace singlestep error"); 51 | return -1; 52 | } 53 | } 54 | } else { 55 | perror("fork error"); 56 | return -1; 57 | } 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /gdb/target.s: -------------------------------------------------------------------------------- 1 | .section .data 2 | 3 | hello:.string "hello\n" 4 | world:.string "world\n" 5 | 6 | .section .text 7 | .globl _start 8 | 9 | _start: 10 | 11 | #print hello 12 | movl $4, %eax 13 | movl $1, %ebx 14 | movl $hello, %ecx 15 | movl $6, %edx 16 | int $0x80 17 | 18 | #print world 19 | movl $4, %eax 20 | movl $1, %ebx 21 | movl $world, %ecx 22 | movl $6, %edx 23 | int $0x80 24 | 25 | movl $1, %eax 26 | int $0x80 27 | 28 | --------------------------------------------------------------------------------