gdb使用

GDB: The GNU Project Debugger

gdb能够在程序运行时看到程序里面发生了什么,还能看到程序挂掉的时候在干嘛。。。所以说当你在找bug时是很有用的。。。
官方说gdb可以主要做下面四种事情来帮助你找bug:

  1. 启动你的程序,指定任何可能导致bug的内容
  2. 使你的程序在特定的条件停下来
  3. 当你的程序停下后,检查发生了什么
  4. 在你的程序里改变一些东西,用于去尝试修正一些错误的影响并继续获取其他信息

渣翻译,将就看。。

依照我翻译的意思,大致将主要命令分为四类:

1.下断点
2.给断点设置条件
3.程序断住后查看相关的信息(变量,堆栈,环境变量)
4.改变程序的一些参数,如变量值,执行语句顺序等等

下面依照这四条来理一下相关命令,一些其他的命令在后面单独说。

1.下断点

1). break

最常用的下断点命令,可以指定在某文件某行号设断点,也可以在某函数设断点,指定所在的线程等,当执行到相应地方就能停住

2). watch

当指定变量的值被改变时停住。其实这里的对象应当是表达式,即当指定表达式的值被改变时停住,但我一般都用来看变量了。另外还有被读时改变,被读被写时改变等。

  1. watch 命令可以设置硬件观察点方式或软件观察点的方式。而 rwatch 命令与 awatch 命令只能设置硬件观察点的方式。
  2. 每个命令的功能不同。
    watch 命令,即变量(或表达式)的值改变,程序都会停下来。
    rwatch 命令,即当发生读取变量行为时,程序就会暂停住。
    awatch 命令,即当发生读取变量或改变变量值的行为时,程序就会暂停住。

3). catch

这个我没怎么用过,感觉是更高级一点的断点,它可以让程序在发生某些事件时停下,如加载动态库时(然而我用的版本并没有这个选项),系统调用等等。
特殊的停止点

(gdb) help catch 
Set catchpoints to catch events.

List of catch subcommands:

catch assert -- Catch failed Ada assertions, when raised.
catch catch -- Catch an exception, when caught.
catch exception -- Catch Ada exceptions, when raised.
catch exec -- Catch calls to exec.
catch fork -- Catch calls to fork.
catch handlers -- Catch Ada exceptions, when handled.
catch load -- Catch loads of shared libraries.
catch rethrow -- Catch an exception, when rethrown.
catch signal -- Catch signals by their names and/or numbers.
catch syscall -- Catch system calls by their names, groups and/or numbers.
catch throw -- Catch an exception, when thrown.
catch unload -- Catch unloads of shared libraries.
catch vfork -- Catch calls to vfork.

4). bookmark

bookmark

保存设定书签时的各种状态,以便回到你设置书签时的状态。当你调试程序不停next时,突然发现已经跑过了,特别是情况复现费时间时,是不是感觉很难受,书签就可以解决这种问题~~~ 可惜的是这个多线程好像不支持?
需要开启record才能使用

使用goto-bookmark 命令回到特定书签。相关指令有这些:

reverse-continue -- Continue program being debugged but run it in reverse.
reverse-finish -- Execute backward until just before selected stack frame is called.
reverse-next -- Step program backward, proceeding through subroutine calls.
reverse-nexti -- Step backward one instruction, but proceed through called subroutines.
reverse-step -- Step program backward until it reaches the beginning of another source line.
reverse-stepi -- Step backward exactly one instruction.

基本就是平常使用的c,f

5). checkpoint

当一个bug复现较难,这个命令就比较有用了,它可以为当前调试的程序创建一个快照,就在你敲checkpoint那一行,创建检查点后你就可以随便调试了,当你想再次回到那个点,只需要恢复这个快照,restart即可。

info checkpoint
checkpoint
restart

以上三条命令分别是查看检查点,设置检查点,恢复检查点,这个跟书签很像,但书签可以多次返回,而checkpoint 无法在同一点连续设置。结果就是用户只能返回一次,而无法随时任意多次的返回检查点。

(gdb) start
Temporary breakpoint 1 at 0x1180: file test.c, line 11.
Starting program: /root/local/test
warning: Error disabling address space randomization: Operation not permitted

Temporary breakpoint 1, main () at test.c:11
11	{
(gdb) checkpoint
checkpoint 1: fork returned pid 14123.
(gdb) n
12		fun();
(gdb) n
the val is 0
13		int j = 0;
(gdb) restart 1
Switching to process 14123
#0  main () at test.c:13
13		int j = 0;
(gdb)

使用ps aux查看

root     14117  0.1  0.6  57760 49904 pts/1    S+   16:52   0:00 gdb test
root     14119  0.0  0.0   2364   648 pts/1    t    16:52   0:00 /root/local/test
root     14123  0.0  0.0   2496   840 pts/1    t    16:52   0:00 /root/local/test

6). tracepoint

有些时候,程序的运行结果对时间敏感,当你断住程序时,程序的运行结果可能和原先不同。原先我都是用断点的命令列表来获取一些信息,然后continue,以避免断住程序带来的影响。而追踪点就可以很好的完成这项任务。gdb对追踪点的解释是不停止程序运行追踪程序执行。这个是真的需要远程才能使用了,但是在一台机器上同样可以,我们在本地使用gdbserver然后本地再使用gdb。

(gdb) help tracepoints
Tracing of program execution without stopping the program.

List of commands:

actions -- Specify the actions to be taken at a tracepoint.
collect -- Specify one or more data items to be collected at a tracepoint.
end -- Ends a list of commands or actions.
passcount -- Set the passcount for a tracepoint.
tdump -- Print everything collected at the current tracepoint.
teval -- Specify one or more expressions to be evaluated at a tracepoint.
tfind -- Select a trace frame.
tfind end -- De-select any trace frame and resume 'live' debugging.
tfind line -- Select a trace frame by source line.
tfind none -- De-select any trace frame and resume 'live' debugging.
tfind outside -- Select a trace frame whose PC is outside the given range (exclusive).
tfind pc -- Select a trace frame by PC.
tfind range -- Select a trace frame whose PC is in the given range (inclusive).
tfind start -- Select the first trace frame in the trace buffer.
tfind tracepoint -- Select a trace frame by tracepoint number.
tsave -- Save the trace data to a file.
tstart -- Start trace data collection.
tstatus -- Display the status of the current trace data collection.
tstop -- Stop trace data collection.
tvariable -- Define a trace state variable.
while-stepping -- Specify single-stepping behavior at a tracepoint.

help一下吧
下面是使用screen命令,在设备同时开启了gdb和gdbserver。
trace

2.给断点设置条件

1).设定条件

当下断点时可以指定一个条件,当条件满足才断住,可以指定多个逻辑表达式。
例如:

break fun_name if foo==0&&foo1==1

这样一来,只有foo为0且foo1为1时才会在函数fun_name处停住了
另一种方法是condition

condition breaknum expr

例如先b fun_name
然后假如这个断点断点号为1

condition 1 foo==0&&foo1==1

可以达到相同效果

2).为断点设置运行命令

command < breakpointnum >
commandline1
commandline2

end

可以给断点设置命令,也就是当断住时可以执行你设好的指令。。我有时会在命令里干一些事情然后continue,,,,总之gdb里的命令都可以用

3)只命中一次的断点

tbreak twatch 命中即删除

4)忽略前n次breackpoint

ignore 命令
可以忽略前n次断点命中

5)使用正则下断点

rbreak 给所有匹配正则的函数下断点

3.程序断住后查看相关的信息(变量,堆栈,环境变量)

看变量,数组,内存,堆栈,环境变量,机器码,寄存器等等,反正很强大。。

1).查看数组

p *array@array_len

查看数组名为array长度为array_len的数组内容

2).查看内存

x /2bx 0x400def

/后跟的分别是个数,单位,格式,例如上面就代表16进制显示两个byte,起始地址是0x400def

3).查看汇编代码

disassemble fun_name

还可以指定其他的,help查看
stepi,nexti单步执行汇编指令

4).查看加载的库

info sharedlibrary
这个命令也可以确定动态库的符号表是否已被加载

5)查看调用栈

除了bt之外,还有其他独立的工具也可以比较方便的查看,比如gstack和pstack

6)查看当前栈的局部变量

info locals [-q] [-t TYPEREGEXP] [NAMEREGEXP]

7)查看程序入参

info args [-q] [-t TYPEREGEXP] [NAMEREGEXP]

8)查看定义的类型

info locals [-q] [-t TYPEREGEXP] [NAMEREGEXP]
查看定义的类型,比如结构体,函数等等。

9)查看源代码

可以使用list或l命令查看源代码,在不想使用tui时可以简单查看代码,使用list或l,不带参数一次只能显示十行,若想查看后面的源码可一直按回车键,直到所有源码显示完。
带一个参数代表查看指定行的上下各5行
list -代表查看上一次使用list查看的代码的前10行
带两个以逗号分隔的参数代表查看这两个行之前的代码

4.改变程序的一些参数,如变量值,执行语句顺序等等

1).改变变量的值

p val = xxx

将val的值改为xxx

2).更改程序执行路径

jump < linenum >
跳到指定行执行

这个厉害了,随便你跑,当然还是要小心不要把程序跑死了
还有以偏移量为参数的

jump +1
跳过下一行继续执行

当然有正还有负,但是正比较常用

3).强制函数返回

return
还能指定返回值

4).强制调用函数

call < funname >

此处需注意,若欲调用的函数在当前上下文中未查询到原型,则返回值会被认为是int类型,而返回值被认为是可变参的,若在这种情况下调用malloc函数就会出问题,返回的值是一个整数,而不是void*,此时若强转为void *或别的指针类型,在64位系统上会出问题,因为64位cpu,int类型为4字节,而指针类型为8字节,因此返回出来的值被截断了,再怎么转也是错误的。此时正确的调用方式是把函数指针强转成正确的类型以保证返回值正确,比如想用malloc分配100字节空间,然而使用ptype查看函数原型发现是type = int(),则可断定原型未找到,则如下方式可正确调用:

p  ((void *(*)())malloc)(100)

其中void *(*) ()是一个函数指针类型
或者还有一种更简单的办法,直接使用强转

(gdb) call  (void*)malloc(10)
$5 = (void *) 0x22927d90

5.其他调试性功能

还有一个命令display,可以每次停住时自动显示,也可以单步跟踪时自动显示指定的内容~
step 可以一步一步的执行,next同step,但它不会跟进函数
另外还有reverse-next,reverse-step,可以反向走回去。可惜它不支持多线程,而且好像也要远程调用。。。

1)dprintf

在特定的地方设置动态打印
使用实例:

dprintf  perf_msgr_client.cc:133,"Test send i = %d,ops = %d,inflight = %d\n",i,ops,inflight

2)主动生成core文件

generate-core-file [filename],直接在gdb里执行
另外编译gdb时生成的gcore也可以完成这个任务,后面直接跟pid

6.杂项

1).禁止分页

有时候gdb会输出很多信息导致分页,需要你输入return或quit后才能接着显示后面的信息,禁止分页就可以避免这种烦恼了

set pagination off

2).环境变量

set $foo = 0

环境变量有时也很有用,比如有个函数,原型为void fun(int iFoo,FooStruct *stFoo);这个函数iFoo是入参,stFoo是出参,FooStruct是一个结构体类型,你想利用call手动调用这个函数以获取一些信息用来调试,那么此时可以这样:

p $Foo = (FooStruct *)malloc(sizeof(FooStruct))
call fun(num, $Foo)

$Foo就是一个环境变量

3).多线程线程切换

info thread
thread thread-id

上面两条命令分别是查看线程信息,以及切换线程。注意thread-id是info thread时分配的线程号,而不是tid(thread id)。

4).设置执行时调度锁定模式

set scheduler-locking [off|on|step]

多线程情况下,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。
on:完全锁定,只有当前线程可以执行
off:不锁定,线程可能在任何时候被抢占(默认,也就是说所有线程都可能运行)
step:step时只有当前线程执行,next时其他线程可能会被执行

5).忽略信号处理

用gdb调试时,有时会经常出现SIGUSR1,然后gdb就停在一些不是你设定断点的地方
查看gdb对SIGUSR1信号的处理情况:

(gdb) info signal SIGUSR1

停用

handle SIGUSR1 noprint nostop

6).切换栈帧

我们都知道,在发生函数调用的时候,调用者的栈里可能存放这着它的局部变量和它调用函数的入参,返回地址等等信息,等将来被调用函数返回时可以继续处理,而gdb可以切换栈帧(或是保存的寄存器之类的?),你可以查看之前的调用者的一些环境。

up down frame

上面这三个命令分别是向上向下移动栈帧及移动到指定栈帧,0是栈顶的那个栈帧,可以通过bt查看
简单整理,仅供参考

需要注意切换后寄存器的值是不准的,详见gdb通过frame切换栈帧之后寄存器是否准确

7).汇编风格显示与设置

at&t风格汇编: 指令 源操作数 目的操作数
intel 风格汇编:指令 目的操作数 源操作数

show disassembly-flavor

显示风格

set disassembly-flavor [intel | att]

设置风格

8)执行shell命令

shell

9)直接杀掉调试的进程

kill

10)可视化gdb

ctrl+x+a进入tui(Text User Interface),显示正在执行的文件源码,所以开始运行后才有显示。 可以显示源码,汇编,寄存器等,如下
显示有问题时可使用Ctrl + L刷新窗口

(gdb) help layout
Change the layout of windows.
Usage: layout prev | next | LAYOUT-NAME
Layout names are:
   src   : Displays source and command windows.
   asm   : Displays disassembly and command windows.
   split : Displays source, disassembly and command windows.
   regs  : Displays register window. If existing layout
           is source/command or assembly/command, the
           register window is displayed. If the
           source/assembly/command (split) is displayed,
           the register window is displayed with
           the window that has current logical focus.

tui
需要注意的是,进入tui后,是有聚焦窗口这个概念的,当上下翻页时,实际是操作的focus的窗口,可以使用focus来切换窗口

(gdb)info win
(gdb)focus asm
(gdb)focus next
(gdb)focus prev

除了gdb自带的tui之外,还可以使用cgdb。
cgdb
刚进去是位于gdb输入框,按下Esc键的时候,就会进入cgdb模式,再按i回到gdb窗口,使用vim的小伙伴肯定感觉很熟悉了,而且help命令也是在按ECS之后:help来显示的。
其他命令就待以后再摸索吧

10)调试带参数的进程

如果想要从头开始调试带参数的进程,需要一起附加,比如执行main,有两个参数arg1和arg2:
方法1:

$gdb --args ./main arg1 arg2
(gdb)

方法2:

$gdb ./main
(gdb)r arg1 arg2

方法3:

$gdb ./main
(gdb)set args arg1 arg2

11)解决多线程异常退出

使用gdb调试程序时,加了断点,进行触发后,却异常退出,在刚加载时打印了一些告警,如下:

Starting program: /usr/sbin/ccs_app 
warning: File "/lib/libthread_db-0.9.33.so" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
        add-auto-load-safe-path /lib/libthread_db-0.9.33.so
line to your configuration file "//.gdbinit".
To completely disable this security protection add
        set auto-load safe-path /
line to your configuration file "//.gdbinit".
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
        info "(gdb)Auto-loading safe path"
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
[CDB]Update the state of the cdb!(ret=0)

异常停止时打印如下:

Program terminated with signal SIGTRAP, Trace/breakpoint trap. The program no longer exists.

按照提示创建文件并添加内容:

~ # vi .gdbinit
add-auto-load-safe-path /lib/libthread_db-0.9.33.so

重新调试进程后可以正常断住
如果不是上面的打印,还有一种可能是libpthread.so没有符号表,所以导致的异常。

12)设置动态库搜索路径

需要加载动态库的符号表时,可以使用如下命令设置

(gdb)set sysroot xxx 
(gdb)set solib-search-path yyy

sysroot是设置一个替代的系统根目录,相应绝对路径的动态库会去路径下找。
solib-search-path也是用来查找动态库的路径,但是是相对路径,并且优先于PATH和LD_LIBRARY_PATH进行查找。

(gdb) show sysroot
The current system root is "target:".
(gdb) show solib-search-path
The search path for loading non-absolute shared library symbol files is .

sysroot的值是target的意思应当是gdb加载的是正在调试的文件系统上的so文件,如果是远程调试场景,即gdb连接gdbserver,那指的就是嵌入式设备上的so文件。
而对于solib-search-path,默认值为空。
可以使用info sharedlibrary查看动态库的符号是否加载。

13)设置源代码搜索路径

如果编译时加了-g,并且有对应的源文件,则可以使用list或tui。
找到可执行文件原来的路径

(gdb)readelf a.out -p .debug_str

增加一个目录到源代码搜索路径

(gdb)directory <dirname> 

设置源代码路径替换规则

(gdb)set substitute-path <from> <to>

14)自定义命令

define commandName 
    statement1
    statement2
end 

其中statement可以是任意gdb命令。自定义命令还支持最输入参数: a r g 0 , arg0, arg0arg1…… a r g N , argN, argNargc标明参数个数。
自定义命令的帮助信息:

document myassign
    其中的文字会被直接显示
end

例子:
定义命令

(gdb) define test
Type commands for definition of "test".
End with a line saying just "end".
>echo "the argc is "
>echo $argc
>echo "\n"
>p $arg0
>p $arg1
>end

定义帮助信息:

(gdb) document test
Type documentation for "test".
End with a line saying just "end".
>hello world!
>end

输出:

(gdb) test 1 2
"the argc is "2"
"$10 = 1
$11 = 2
(gdb) help test
hello world!

15)gdb脚本

可以将任何gdb命令包括自定义命令保存到脚本,然后加载。

$ cat gdb.cmd
define test
echo the argc is $argc\n
p $arg0
p $arg1
end

document test
hello world!
end

echo test gdb script!!\n

加载方式:

gdb 程序名  -x 脚本
gdb 程序名 -command=脚本
(gdb)source 脚本

16)多进程调试

info inferiors 查看所有进程
inferiors 2 切换到编号为2的进程

detach inferiors 2 detach掉编号为2的进程
kill inferiors 2 kill掉编号为2的进程

set follow-fork-mode parent 只调试父进程(GDB默认)
set follow-fork-mode child 只调试子进程
show follow-fork-mode 查看follow-fork-mode当前值

set detach-on-fork on 只调试一个进程,父进程或子进程(GDB默认)
set detach-on-fork off 同时调试父子进程,另一个进程阻塞在fork位置
show detach-on-fork 查看detach-on-fork当前值

set schedule-multiple off 只有当前进程会执行,其他进程挂起(GDB默认)
set schedule-multiple on 所有的进程都会正常执行
show schedule-multiple 查看schedule-multiple当前值

之前调试存在fork的进程时,只会设置set follow-fork-mode child,然后每次跟完代码,子进程退出了,就退出gdb重进。
然而gdb还有其他多进程命令,可以配合使用。

17)代码行号无法对应

使用gdb远程跟踪程序时如果出现行号对的上但逻辑错乱,可能是符号与实际代码不一致,此时查看汇编代码,如果确定与代码逻辑不一致,需要重编进程,确保使用的进程(远程调试)和运行的进程一致。如果是本地gdb,则可能是代码开启了优化

18)使用python脚本

通过source命令加载脚本

19)查看gdb编译时的配置

~ gdb --configuration
This GDB was configured as follows:
   configure --host=x86_64-linux-gnu --target=x86_64-linux-gnu
             --with-auto-load-dir=$debugdir:$datadir/auto-load
             --with-auto-load-safe-path=$debugdir:$datadir/auto-load
             --with-expat
             --with-gdb-datadir=/usr/share/gdb (relocatable)
             --with-jit-reader-dir=/usr/lib/gdb (relocatable)
             --without-libunwind-ia64
             --with-lzma
             --with-babeltrace
             --with-intel-pt
             --with-mpfr
             --with-xxhash
             --with-python=/usr (relocatable)
             --with-python-libdir=/usr/lib (relocatable)
             --with-debuginfod
             --without-guile
             --enable-source-highlight
             --with-separate-debug-dir=/usr/lib/debug (relocatable)
             --with-system-gdbinit=/etc/gdb/gdbinit
             --with-system-gdbinit-dir=/etc/gdb/gdbinit.d

20)记录日志

有时可能想把gdb日志记录下来

set trace-commands on
set logging on

如果文件里出现了33m等,可能是颜色问题,参考博客修改

21)开机设置core文件生成

#开启core 
mkdir -p /core
echo "/core/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
ulimit -S -c unlimited

另一个:

  # for core file
  mkdir -p /core 2>/dev/null
  echo "/core/core-%e-$(${NOS_SCRIPT_DIR}/brandkv BUILD_NOS_VERSION)-%t" > /proc/sys/kernel/core_pattern
  ulimit -c unlimited # 50000 core file size 50M
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值