本文是100个GDB小技巧阅读总结,100个GDB小技巧.
本文章将持续记录gdb使用技巧,不定时更新,欢迎留言讨论.

列出函数的名字

在这个例子中源代码如下:

#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
void *thread_func(void *p_arg)
{
        while (1)
        {
                sleep(10);
        }
}
int main(void)
{
        pthread_t t1, t2;

        pthread_create(&t1, NULL, thread_func, "Thread 1");
        pthread_create(&t2, NULL, thread_func, "Thread 2");

        sleep(1000);
        return 0;
}

然后保存文件为fucntion.c
gcc -g function.c -o function提示如下错误:

undefined reference to `pthread_create'
undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status

错误产生的原因是:
pthread库不是标准linux库,修改为如下方式即可:
gcc -pthread -g -o function function.c
然后gdb function即可.

另外这个例子和github上所给的例子有所不同,包含了头文件,在main函数中返回0,去掉相应的告警信息。

可以看到会列出函数原型以及不带调试信息的函数。

另外这个命令也支持正则表达式:“info functions regex”,这样只会列出符合正则表达式的函数名称,例如:

(gdb) info functions thre*
All functions matching regular expression "thre*":

File a.c:
void *thread_func(void *);

Non-debugging symbols:
0x0805082c pthread_create@plt
可以看到gdb只会列出名字里包含“thre”的函数。

退出正在调试的函数

例子

#include <stdio.h>
int func(void)
{
    int i = 0;

    i += 2;
    i *= 10;

    return i;
}

int main(void)
{
    int a = 0;
    a = func();
    printf("%d\n", a);
    return 0;
}

技巧

当单步调试一个函数时,如果不想继续跟踪下去了,可以有两种方式退出。

第一种用“finish”命令,这样函数会继续执行完,并且打印返回值,然后等待输入接下来的命令。以上面代码为例:

(gdb) n
17      a = func();
(gdb) s
func () at a.c:5
5         int i = 0;
(gdb) n
7         i += 2;
(gdb) fin
find    finish
(gdb) finish
Run till exit from #0  func () at a.c:7
0x08050978 in main () at a.c:17
17        a = func();
Value returned is $1 = 20

可以看到当不想再继续跟踪func函数时,执行完“finish”命令,gdb会打印结果:“20”,然后停在那里。
第二种用“return”命令,这样函数不会继续执行下面的语句,而是直接返回。也可以用“return expression”命令指定函数的返回值。仍以上面代码为例:

(gdb) n
17          a = func();
(gdb) s
func () at a.c:5
5            int i = 0;
(gdb) n
7            i += 2;
(gdb) n
8            i *= 10;
(gdb) re
record              remove-inferiors    return              reverse-next        reverse-step
refresh             remove-symbol-file  reverse-continue    reverse-nexti       reverse-stepi
remote              restore             reverse-finish      reverse-search
(gdb) return 40
Make func return now? (y or n) y
#0  0x08050978 in main () at a.c:17
17          a = func();
(gdb) n
18          printf("%d\n", a);
(gdb)     40
19          return 0;

可以看到“return”命令退出了函数并且修改了函数的返回值。

打印函数堆栈帧信息

例子

#include <stdio.h>
int func(int a, int b)
{
    int c = a * b;
    printf("c is %d\n", c);
}

int main(void) 
{
    func(1, 2);
    return 0;
}

技巧
使用gdb调试程序时,可以使用“i frame”命令(i是info命令缩写)显示函数堆栈帧信息。以上面程序为例:
(gdb) b func
Breakpoint 1 at 0x4004d2: file main.c, line 4.
(gdb) r
Starting program: /root/play/main
Breakpoint 1, func (a=1, b=2) at a.c:5
5 printf("c is %d\n", c);
(gdb) i frame
Stack level 0, frame at 0x7fffffffe590:
rip = 0x40054e in func (a.c:5); saved rip = 0x400577
called by frame at 0x7fffffffe5a0
source language c.
Arglist at 0x7fffffffe580, args: a=1, b=2
Locals at 0x7fffffffe580, Previous frame's sp is 0x7fffffffe590
Saved registers:
rbp at 0x7fffffffe580, rip at 0x7fffffffe588
(gdb) i registers
rax 0x2 2
rbx 0x0 0
rcx 0x0 0
rdx 0x7fffffffe688 140737488348808
rsi 0x2 2
rdi 0x1 1
rbp 0x7fffffffe580 0x7fffffffe580
rsp 0x7fffffffe560 0x7fffffffe560
r8 0x7ffff7dd4e80 140737351863936
r9 0x7ffff7dea560 140737351951712
r10 0x7fffffffe420 140737488348192
r11 0x7ffff7a35dd0 140737348066768
r12 0x400440 4195392
r13 0x7fffffffe670 140737488348784
r14 0x0 0
r15 0x0 0
rip 0x40054e 0x40054e
eflags 0x202
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) disassemble func
Dump of assembler code for function func:
0x0000000000400536 <+0>: push %rbp
0x0000000000400537 <+1>: mov %rsp,%rbp
0x000000000040053a <+4>: sub $0x20,%rsp
0x000000000040053e <+8>: mov %edi,-0x14(%rbp)
0x0000000000400541 <+11>: mov %esi,-0x18(%rbp)
0x0000000000400544 <+14>: mov -0x14(%rbp),%eax
0x0000000000400547 <+17>: imul -0x18(%rbp),%eax
0x000000000040054b <+21>: mov %eax,-0x4(%rbp)
=> 0x000000000040054e <+24>: mov -0x4(%rbp),%eax
0x0000000000400551 <+27>: mov %eax,%esi
0x0000000000400553 <+29>: mov $0x400604,%edi
0x0000000000400558 <+34>: mov $0x0,%eax
0x000000000040055d <+39>: callq 0x400410 printf@plt
0x0000000000400562 <+44>: leaveq
0x0000000000400563 <+45>: retq
End of assembler dump.
可以看到执行“i frame”命令后,输出了当前函数堆栈帧的地址,指令寄存器的值,局部变量地址及值等信息,可以对照当前寄存器的值和函数的汇编指令看一下。

设置临时断点

#include <stdio.h>
#include <pthread.h>
typedef struct
{
        int a;
        int b;
        int c;
        int d;
        pthread_mutex_t mutex;
}ex_st;

int main(void) {
        ex_st st = {1, 2, 3, 4, PTHREAD_MUTEX_INITIALIZER};
        printf("%d,%d,%d,%d\n", st.a, st.b, st.c, st.d);
        return 0;
}

技巧

在使用gdb时,如果想让断点只生效一次,可以使用“tbreak”命令(缩写为:tb)。以上面程序为例:
(gdb) tb a.c:15
Temporary breakpoint 1 at 0x400500: file a.c, line 15.
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint del y 0x0000000000400500 in main at a.c:15
(gdb) r
Starting program: /data2/home/nanxiao/a
Temporary breakpoint 1, main () at a.c:15
15 printf("%d,%d,%d,%d\n", st.a, st.b, st.c, st.d);
(gdb) i b
No breakpoints or watchpoints.
首先在文件的第15行设置临时断点,当程序断住后,用“i b”("info breakpoints"缩写)命令查看断点,发现断点没有了。也就是断点命中一次后,就被删掉了。

条件断点

#include <stdio.h>
int main(void)
{
        int i = 0;
        int sum = 0;

        for (i = 1; i <= 200; i++)
        {
            sum += i;
        }
    
        printf("%d\n", sum);
        return 0;
}

技巧
gdb可以设置条件断点,也就是只有在条件满足时,断点才会被触发,命令是“break … if cond”。以上面程序为例:
(gdb) start
Temporary breakpoint 1 at 0x4004cc: file a.c, line 5.
Starting program: /data2/home/nanxiao/a
Temporary breakpoint 1, main () at a.c:5
5 int i = 0;
(gdb) b 10 if i==101
Breakpoint 2 at 0x4004e3: file a.c, line 10.
(gdb) c
Continuing.

Breakpoint 2, main () at func.c:10
10 sum += i;
(gdb) p sum
$1 = 5050
可以看到设定断点只在i的值为101时触发,此时打印sum的值为5050。

忽略断点

#include <stdio.h>
int main(void)
{
    int i = 0;
    int sum = 0;
    for (i = 1; i <= 200; i++)
    {
        sum += i;
    }
    printf("%d\n", sum);
    return 0;
}

在设置断点以后,可以忽略断点,命令是“ignore bnum count”:意思是接下来count次编号为bnum的断点触发都不会让程序中断,只有第count + 1次断点触发才会让程序中断。以上面程序为例:
(gdb) b 9
Breakpoint 1 at 0x4004e3: file hello.c, line 9.
(gdb) ignore 1 5
Will ignore next 5 crossings of breakpoint 1.
(gdb) r
Starting program: /root/play/hello
Breakpoint 1, main () at hello.c:9
9 sum += i;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.x86_64
(gdb) p i
$1 = 6
可以看到设定忽略断点前5次触发后,第一次断点断住时,打印i的值是6。如果想让断点从i=1处开始生效,可以将count置为0:“ignore 1 0”.