GDB 调试演示

转自:http://www.rainsts.net/article.asp?id=863

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, 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, 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 格式外,还有 字符串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,[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 的人来说,这种命令行调试是种巨大的痛苦。尽管我个人建议多用 GDB,但也不反对用 GUI 调试器来加快调试进程。Nemiver 就不错,推荐一下。

发布了30 篇原创文章 · 获赞 2 · 访问量 16万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览