├── 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 | #include <unistd.h>
16 | pid_t fork(void);
17 | 返回值:子进程返回0,父进程返回子进程ID,出错返回-1 |
18 |
19 |
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 |
--------------------------------------------------------------------------------