【Linux】GDB调试演示过程
来源:互联网 发布:tcp ip协议 端口 编辑:程序博客网 时间:2024/05/01 14:48
作为内置和最常用的调试器,GDB 显然有着无可辩驳的地位。熟练使用 GDB,就好像所有 Linux 下的开发人员建议你用 VIM 一样,是个很 "奇怪" 的情节。
测试用源代码。
#include <stdio.h>int test(int a, int b){ int c = a + b; return c;}int main(int argc, char* argv[]){ int a = 0x1000; int b = 0x2000; int c = test(a, b); printf("%d/n", c); printf("Hello, World!/n"); return 0;}
编译命令 (注意使用 "-g" 参数生成调试符号):
$ gcc -g -o hello hello.c
开始调试:
$ gdb helloGNU gdb 6.8-debianCopyright (C) 2008 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <<a target=_blank title="http://gnu.org/licenses/gpl.html" href="http://gnu.org/licenses/gpl.html" target="_blank" style="color: rgb(51, 102, 153); text-decoration: none; ">http://gnu.org/licenses/gpl.html</a>>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "i486-linux-gnu"...(gdb)
1. 源码
在调试过程中查看源代码是必须的。list (缩写 l) 支持多种方式查看源码。
(gdb) l # 显示源代码23 int test(int a, int b)4 {5 int c = a + b;6 return c;7 }89 int main(int argc, char* argv[])10 {11 int a = 0x1000;
(gdb) l # 继续显示12 int b = 0x2000;13 int c = test(a, b);14 printf("%d/n", c);1516 printf("Hello, World!/n");17 return 0;18 }
(gdb) l 3, 10 # 显示特定范围的源代码3 int test(int a, int b)4 {5 int c = a + b;6 return c;7 }89 int main(int argc, char* argv[])10 {
(gdb) l main # 显示特定函数源代码5 int c = a + b;6 return c;7 }89 int main(int argc, char* argv[])10 {11 int a = 0x1000;12 int b = 0x2000;13 int c = test(a, b);14 printf("%d/n", c);
可以用如下命令修改源代码显示行数。
(gdb) set listsize 50
2. 断点
可以使用函数名或者源代码行号设置断点。
(gdb) b main # 设置函数断点Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) b 13 # 设置源代码行断点Breakpoint 2 at 0x8048429: file hello.c, line 13.
(gdb) b # 将下一行设置为断点 (循环、递归等调试很有用)Breakpoint 5 at 0x8048422: file hello.c, line 12.
(gdb) tbreak main # 设置临时断点 (中断后失效)Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) info breakpoints # 查看所有断点Num Type Disp Enb Address What2 breakpoint keep y 0x0804841b in main at hello.c:113 breakpoint keep y 0x080483fa in test at hello.c:5
(gdb) d 3 # delete: 删除断点 (还可以用范围 "d 1-3",无参数时删除全部断点)(gdb) disable 2 # 禁用断点 (还可以用范围 "disable 1-3")(gdb) enable 2 # 启用断点 (还可以用范围 "enable 1-3")(gdb) ignore 2 1 # 忽略 2 号中断 1 次
当然少不了条件式中断。
(gdb) b test if a == 10Breakpoint 4 at 0x80483fa: file hello.c, line 5.
(gdb) info breakpointsNum Type Disp Enb Address What4 breakpoint keep y 0x080483fa in test at hello.c:5 stop only if a == 10
可以用 condition 修改条件,注意表达式不包含 "if"。
(gdb) condition 4 a == 30
(gdb) info breakpointsNum Type Disp Enb Address What2 breakpoint keep y 0x0804841b in main at hello.c:11 ignore next 1 hits4 breakpoint keep y 0x080483fa in test at hello.c:5 stop only if a == 30
3. 执行
通常情况下,我们会先设置 main 入口断点。
(gdb) b mainBreakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) r # 开始执行 (Run)Starting program: /home/yuhen/Learn.c/helloBreakpoint 1, main () at hello.c:1111 int a = 0x1000;
(gdb) n # 单步执行 (不跟踪到函数内部, Step Over)12 int b = 0x2000;
(gdb) n13 int c = test(a, b);
(gdb) s # 单步执行 (跟踪到函数内部, Step In)test (a=4096, b=8192) at hello.c:55 int c = a + b;
(gdb) finish # 继续执行直到当前函数结束 (Step Out)Run till exit from #0 test (a=4096, b=8192) at hello.c:50x0804843b in main () at hello.c:1313 int c = test(a, b);Value returned is $1 = 12288
(gdb) c # Continue: 继续执行,直到下一个断点。Continuing.12288Hello, World!Program exited normally.
4. 堆栈
查看调用堆栈(call stack)无疑是调试过程中非常重要的事情。
(gdb) where # 查看调用堆栈 (相同作用的命令还有 info s 和 bt)#0 test (a=4096, b=8192) at hello.c:5#1 0x0804843b in main () at hello.c:13
(gdb) frame # 查看当前堆栈帧,还可显示当前代码#0 test (a=4096, b=8192) at hello.c:55 int c = a + b;
(gdb) info frame # 获取当前堆栈帧更详细的信息Stack level 0, frame at 0xbfad3290: eip = 0x80483fa in test (hello.c:5); saved eip 0x804843b called by frame at 0xbfad32c0 source language c. Arglist at 0xbfad3288, args: a=4096, b=8192 Locals at 0xbfad3288, Previous frame's sp is 0xbfad3290 Saved registers: ebp at 0xbfad3288, eip at 0xbfad328c
可以用 frame 修改当前堆栈帧,然后查看其详细信息。
(gdb) frame 1#1 0x0804843b in main () at hello.c:1313 int c = test(a, b);
(gdb) info frameStack level 1, frame at 0xbfad32c0: eip = 0x804843b in main (hello.c:13); saved eip 0xb7e59775 caller of frame at 0xbfad3290 source language c. Arglist at 0xbfad32b8, args: Locals at 0xbfad32b8, Previous frame's sp at 0xbfad32b4 Saved registers: ebp at 0xbfad32b8, eip at 0xbfad32bc
5. 变量和参数
(gdb) info locals # 显示局部变量c = 0
(gdb) info args # 显示函数参数(自变量)a = 4096b = 8192
我们同样可以切换 frame,然后查看不同堆栈帧的信息。
(gdb) p a # print 命令可显示局部变量和参数值$2 = 4096
(gdb) p/x a # 十六进制输出 (d: 十进制; u: 十进制无符号; x: 十六进制; o: 八进制; t: 二进制; c: 字符)$10 = 0x1000
(gdb) p a + b # 还可以进行表达式计算$5 = 12288
set variable 可用来修改变量值。
(gdb) set variable a=100(gdb) info argsa = 100b = 8192
6. 内存及寄存器
x 命令可以显示指定地址的内存数据。
格式: x/nfu [address]
- n: 显示内存单位(组或者行)。
- f: 格式 (除了 print 格式外,还有 字符串s 和 汇编 i)。
- u: 内存单位 (b: 1字节; h: 2字节; w: 4字节; g: 8字节)。
(gdb) x/8w 0x0804843b # 按四字节(w)显示 8 组内存数据0x804843b <main+49>: 0x8bf04589 0x4489f045 0x04c70424 0x048530240x804844b <main+65>: 0xfecbe808 0x04c7ffff 0x04853424 0xfecfe808
(gdb) x/8i 0x0804843b # 显示 8 行汇编指令0x804843b <main+49>: mov DWORD PTR [ebp-0x10],eax0x804843e <main+52>: mov eax,DWORD PTR [ebp-0x10]0x8048441 <main+55>: mov DWORD PTR [esp+0x4],eax0x8048445 <main+59>: mov DWORD PTR [esp],0x80485300x804844c <main+66>: call 0x804831c <printf@plt>0x8048451 <main+71>: mov DWORD PTR [esp],0x80485340x8048458 <main+78>: call 0x804832c <puts@plt>0x804845d <main+83>: mov eax,0x0
(gdb) x/s 0x08048530 # 显示字符串0x8048530: "%d/n"
除了通过 "info frame" 查看寄存器值外,还可以用如下指令。
(gdb) info registers # 显示所有寄存器数据eax 0x1000 4096ecx 0xbfad32d0 -1079168304edx 0x1 1ebx 0xb7fa1ff4 -1208344588esp 0xbfad3278 0xbfad3278ebp 0xbfad3288 0xbfad3288esi 0x8048480 134513792edi 0x8048340 134513472eip 0x80483fa 0x80483fa <test+6>eflags 0x286 [ PF SF IF ]cs 0x73 115ss 0x7b 123ds 0x7b 123es 0x7b 123fs 0x0 0gs 0x33 51
(gdb) p $eax # 显示单个寄存器数据$11 = 4096
7. 反汇编
我对 AT&T 汇编不是很熟悉,还是设置成 intel 格式的好。
(gdb) set disassembly-flavor intel # 设置反汇编格式
(gdb) disass main # 反汇编函数Dump of assembler code for function main:0x0804840a <main+0>: lea ecx,[esp+0x4]0x0804840e <main+4>: and esp,0xfffffff00x08048411 <main+7>: push DWORD PTR [ecx-0x4]0x08048414 <main+10>: push ebp0x08048415 <main+11>: mov ebp,esp0x08048417 <main+13>: push ecx0x08048418 <main+14>: sub esp,0x240x0804841b <main+17>: mov DWORD PTR [ebp-0x8],0x10000x08048422 <main+24>: mov DWORD PTR [ebp-0xc],0x20000x08048429 <main+31>: mov eax,DWORD PTR [ebp-0xc]0x0804842c <main+34>: mov DWORD PTR [esp+0x4],eax0x08048430 <main+38>: mov eax,DWORD PTR [ebp-0x8]0x08048433 <main+41>: mov DWORD PTR [esp],eax0x08048436 <main+44>: call 0x80483f4 <test>0x0804843b <main+49>: mov DWORD PTR [ebp-0x10],eax0x0804843e <main+52>: mov eax,DWORD PTR [ebp-0x10]0x08048441 <main+55>: mov DWORD PTR [esp+0x4],eax0x08048445 <main+59>: mov DWORD PTR [esp],0x80485300x0804844c <main+66>: call 0x804831c <printf@plt>0x08048451 <main+71>: mov DWORD PTR [esp],0x80485340x08048458 <main+78>: call 0x804832c <puts@plt>0x0804845d <main+83>: mov eax,0x00x08048462 <main+88>: add esp,0x240x08048465 <main+91>: pop ecx0x08048466 <main+92>: pop ebp0x08048467 <main+93>: lea esp,[ecx-0x4]0x0804846a <main+96>: retEnd of assembler dump.
可以用 "b *address" 设置汇编断点,然后用 "si" 和 "ni" 进行汇编级单步执行,这对于分析指针和寻址非常有用。
8. 进程
查看进程相关信息,尤其是 maps 内存数据是非常有用的。
(gdb) help info proc statShow /proc process information about any running process.Specify any process id, or use the program being debugged by default.Specify any of the following keywords for detailed info: mappings -- list of mapped memory regions. stat -- list a bunch of random process info. status -- list a different bunch of random process info. all -- list all available /proc info.
(gdb) info proc mappings # 相当于 cat /proc/{pid}/mapsprocess 22561cmdline = '/home/yuhen/Learn.c/hello'cwd = '/home/yuhen/Learn.c'exe = '/home/yuhen/Learn.c/hello'Mapped address spaces: Start Addr End Addr Size Offset objfile 0x8048000 0x8049000 0x1000 0 /home/yuhen/Learn.c/hello 0x8049000 0x804a000 0x1000 0 /home/yuhen/Learn.c/hello 0x804a000 0x804b000 0x1000 0x1000 /home/yuhen/Learn.c/hello 0x8a33000 0x8a54000 0x21000 0x8a33000 [heap] 0xb7565000 0xb7f67000 0xa02000 0xb7565000 0xb7f67000 0xb80c3000 0x15c000 0 /lib/tls/i686/cmov/libc-2.9.so 0xb80c3000 0xb80c4000 0x1000 0x15c000 /lib/tls/i686/cmov/libc-2.9.so 0xb80c4000 0xb80c6000 0x2000 0x15c000 /lib/tls/i686/cmov/libc-2.9.so 0xb80c6000 0xb80c7000 0x1000 0x15e000 /lib/tls/i686/cmov/libc-2.9.so 0xb80c7000 0xb80ca000 0x3000 0xb80c7000 0xb80d7000 0xb80d9000 0x2000 0xb80d7000 0xb80d9000 0xb80da000 0x1000 0xb80d9000 [vdso] 0xb80da000 0xb80f6000 0x1c000 0 /lib/ld-2.9.so 0xb80f6000 0xb80f7000 0x1000 0x1b000 /lib/ld-2.9.so 0xb80f7000 0xb80f8000 0x1000 0x1c000 /lib/ld-2.9.so 0xbfee2000 0xbfef7000 0x15000 0xbffeb000 [stack]
9. 线程
可以在 pthread_create 处设置断点,当线程创建时会生成提示信息。
(gdb) cContinuing.[New Thread 0xb7e78b70 (LWP 2933)]
(gdb) info threads # 查看所有线程列表* 2 Thread 0xb7e78b70 (LWP 2933) test (arg=0x804b008) at main.c:24 1 Thread 0xb7e796c0 (LWP 2932) 0xb7fe2430 in __kernel_vsyscall ()
(gdb) where # 显示当前线程调用堆栈#0 test (arg=0x804b008) at main.c:24#1 0xb7fc580e in start_thread (arg=0xb7e78b70) at pthread_create.c:300#2 0xb7f478de in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130
(gdb) thread 1 # 切换线程[Switching to thread 1 (Thread 0xb7e796c0 (LWP 2932))]#0 0xb7fe2430 in __kernel_vsyscall ()
(gdb) where # 查看切换后线程调用堆栈#0 0xb7fe2430 in __kernel_vsyscall ()#1 0xb7fc694d in pthread_join (threadid=3085405040, thread_return=0xbffff744) at pthread_join.c:89#2 0x08048828 in main (argc=1, argv=0xbffff804) at main.c:36
10. 其他
调试子进程。
(gdb) set follow-fork-mode child
临时进入 Shell 执行命令,Exit 返回。
(gdb) shell
调试时直接调用函数。
(gdb) call test("abc")
使用 "--tui" 参数,可以在终端窗口上部显示一个源代码查看窗。
$ gdb --tui hello
查看命令帮助。
(gdb) help b
最后就是退出命令。
(gdb) q
和 Linux Base Shell 习惯一样,对于记不住的命令,可以在输入前几个字母后按 Tab 补全。
----------- 分隔线 ---------------
GDB 还有很多指令,功能也异常强大。不过对于熟悉了 VS 那种豪华 IDE 的人来说,这种命令行调试是种巨大的痛苦。尽管我个人建议多用 GDB,但也不反对用 GUI 调试器来加快调试进程。Nemiver 就不错,推荐一下。
- 【Linux】GDB调试演示过程
- linux调试工具gdb的演示分析
- linux调试工具gdb的演示分析
- GDB 调试演示
- GDB调试演示
- GDB 调试演示
- GDB调试演示
- GDB 调试演示
- GDB 调试演示
- gdb调试多线程演示
- Gdb 调试过程
- Gdb 调试过程
- gdb远程调试过程
- gdb调试过程学习
- 通过gdb调试分析Linux内核的启动过程
- linux复习——gdb调试过程与core文件调试过程
- linux gdb调试
- LINUX下GDB调试
- Netlink Socket
- Oracle笔记 之 工具
- manacher 求最长回文子串
- HDU 4930 Fighting the Landlords(扯淡模拟题)
- Tyvj 1004 滑雪~
- 【Linux】GDB调试演示过程
- 函数模板
- Android系统编译时遇到的几个.mk的疑惑。
- LightOJ 1216 圆锥体积
- hdu 4925 Apple Tree--2014 Multi-University Training Contest 6
- Fermat’s Chirstmas Theorem
- java多线程的实现
- Eclipse启动Tomcat错误:Several ports (8080, 8009) required by Tomcat v6.0 Server at localhost are already
- Eclipse + CVS 之 如何将文件加入版本控制和脱离版本控制