GDB 调试演示[ 2009-08-30 00:02:01 | 作者: yuhen ]字号: 大 | 中 | 小作为内置跟最常用的调试器,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 hello GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
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 # 显示源代码 2
3 int test(int a, int b)
4 {
5 int c = a + b;
6 return c;
7 }
8
9 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);
15
16 printf("Hello,
jordan shoes for sale, 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 }
8
9 int main(int argc, char* argv[])
10 {
(gdb) l main # 显示特定函数源代码 5 int c = a + b;
6 return c;
7 }
8
9 int main(int argc, char* argv[])
10 {
11 int a = 0x1000;
12 int b = 0x2000;
13 int c = test(a,
jordan shoes pics, 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 What
2 breakpoint keep y 0x0804841b in main at hello.c:11
3 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 == 10 Breakpoint 4 at 0x80483fa: file hello.c, line 5.
(gdb) info breakpoints Num Type Disp Enb Address What
4 breakpoint keep y 0x080483fa in test at hello.c:5
stop only if a == 10
可以用 condition 修改条件,注意表白式不包括 "if"。
(gdb) condition 4 a == 30
(gdb) info breakpoints Num Type Disp Enb Address What
2 breakpoint keep y 0x0804841b in main at hello.c:11
ignore next 1 hits
4 breakpoint keep y 0x080483fa in test at hello.c:5
stop only if a == 30
3. 履行
通常情形下,咱们会先设置 main 进口断点。
(gdb) b main Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) r # 开始执行 (Run) Starting program: /home/yuhen/Learn.c/hello
Breakpoint 1, main () at hello.c:11
11 int a = 0x1000;
(gdb) n # 单步执行 (不跟踪到函数内部, Step Over) 12 int b = 0x2000;
(gdb) n 13 int c = test(a, b);
(gdb) s # 单步执行 (跟踪到函数内部, Step In) test (a=4096, b=8192) at hello.c:5
5 int c = a + b;
(gdb) finish # 继承执行直到当前函数停止 (Step Out) Run till exit from #0 test (a=4096, b=8192) at hello.c:5
0x0804843b in main () at hello.c:13
13 int c = test(a, b);
Value returned is $1 = 12288
(gdb) c # Continue: 持续执行,直到下一个断点。 Continuing.
12288
Hello, 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:5
5 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:13
13 int c = test(a, b);
(gdb) info frame Stack 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 = 4096
b = 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 args
a = 100
b = 8192
6. 内存及寄存器
x 命令可以显示指定地址的内存数据。
格式: x/nfu [address]n: 显示内存单位(组或者行)。f: 格式 (除了 print 格式外,
jordan shoes,还有 字符串s 和 汇编 i)。u: 内存单位 (b: 1字节; h: 2字节; w: 4字节; g: 8字节)。
(gdb) x/8w 0x0804843b # 按四字节(w)显示 8 组内存数据 0x804843b <main+49>: 0x8bf04589 0x4489f045 0x04c70424 0x04853024
0x804844b <main+65>: 0xfecbe808 0x04c7ffff 0x04853424 0xfecfe808
(gdb) x/8i 0x0804843b # 显示 8 行汇编指令 0x804843b <main+49>: mov DWORD PTR [ebp-0x10],eax
0x804843e <main+52>: mov eax,DWORD PTR [ebp-0x10]
0x8048441 <main+55>: mov DWORD PTR [esp+0x4],eax
0x8048445 <main+59>: mov DWORD PTR [esp],0x8048530
0x804844c <main+66>: call 0x804831c <printf@plt>
0x8048451 <main+71>: mov DWORD PTR [esp],0x8048534
0x8048458 <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 4096
ecx 0xbfad32d0 -1079168304
edx 0x1 1
ebx 0xb7fa1ff4 -1208344588
esp 0xbfad3278 0xbfad3278
ebp 0xbfad3288 0xbfad3288
esi 0x8048480 134513792
edi 0x8048340 134513472
eip 0x80483fa 0x80483fa <test+6>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 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,
jimmy choo bridal shoes,[esp+0x4]
0x0804840e <main+4>: and esp,0xfffffff0
0x08048411 <main+7>: push DWORD PTR [ecx-0x4]
0x08048414 <main+10>: push ebp
0x08048415 <main+11>: mov ebp,esp
0x08048417 <main+13>: push ecx
0x08048418 <main+14>: sub esp,0x24
0x0804841b <main+17>: mov DWORD PTR [ebp-0x8],0x1000
0x08048422 <main+24>: mov DWORD PTR [ebp-0xc],0x2000
0x08048429 <main+31>: mov eax,DWORD PTR [ebp-0xc]
0x0804842c <main+34>: mov DWORD PTR [esp+0x4],eax
0x08048430 <main+38>: mov eax,DWORD PTR [ebp-0x8]
0x08048433 <main+41>: mov DWORD PTR [esp],eax
0x08048436 <main+44>: call 0x80483f4 <test>
0x0804843b <main+49>: mov DWORD PTR [ebp-0x10],eax
0x0804843e <main+52>: mov eax,DWORD PTR [ebp-0x10]
0x08048441 <main+55>: mov DWORD PTR [esp+0x4],eax
0x08048445 <main+59>: mov DWORD PTR [esp],0x8048530
0x0804844c <main+66>: call 0x804831c <printf@plt>
0x08048451 <main+71>: mov DWORD PTR [esp],0x8048534
0x08048458 <main+78>: call 0x804832c <puts@plt>
0x0804845d <main+83>: mov eax,0x0
0x08048462 <main+88>: add esp,0x24
0x08048465 <main+91>: pop ecx
0x08048466 <main+92>: pop ebp
0x08048467 <main+93>: lea esp,[ecx-0x4]
0x0804846a <main+96>: ret
End of assembler dump.
可以用 "b *address" 设置汇编断点,而后用 "si" 和 "ni" 进行汇编级单步执行,这对于剖析指针和寻址无比有用。
8. 进程
查看进程相干信息,尤其是 maps 内存数据是十分有用的。
(gdb) help info proc stat Show /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}/maps process 22561
cmdline = '/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) c Continuing.
[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 的人来说,这种命令行调试是种宏大的苦楚,
christian louboutin peep toe。只管我个人提议多用 GDB,但也不反对用 GUI 调试器来加快调试进程。Nemiver 就不错,推举一下。