memo: C++ | Debug with GDB

入门:C/C++中的段错误(Segmentation fault)【转】

GDB调试

1
2
gcc -g test.c -o test   #把调试信息加入二进制代码中
gdb test                #启动gdb调试

一、暂停/恢复程序运行

1. 启动gdb

1
2
3
gdb <program>		#当前目录下编译好的二进制代码
gdb <program> core	#同时调试程序和core文件(core dump后产生的文件)
gdb <program> <PID>	#如果是服务程序,可以指定进程id,gdb会自动attach上去调试它,program应在PATH环境变量中搜得到

2. 设置断点

用break命令设置断点:

  1. break <function> :在进入指定函数时停住

    1
    2
    
    break class::function
    break function(type, type)
    
  2. break <linenum> :在指定行号停住

  3. break +offset / -offset : 在当前行的前 / 后的offset行停住

  4. break filename:linenum :在源文件filename的linenum行停住

  5. break filename::function :在源文件filename的function函数入口停住

  6. break *address :在程序运行的内存地址处停住

  7. break :没有加参数时,表示在下一条指令处停住

  8. break … if <condition> :…可以是上述参数,condition表示条件,在条件成立时停住 (条件断点)

    1
    
    break if i=100	#当i=100时,停住程序
    

查看断点,可使用 info 命令:

  1. info breakpoints [n] ( n 表示断点号)
  2. info break [n]

3. 设置观察点

watchpoint 一般用来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点:

  1. watch <expr> :为表达式(变量)expr 设置一个观察点。
  2. rwatch <expr> :当表达式(变量)expr 被读时,停住程序。
  3. awatch <expr> :当表达式(变量)的值被读或写时,停住程序
  4. info watchpoints :列出当前设置的所有观察点

4. 设置捕捉点

catchpoint 用来捕捉程序运行时的一些事件。如:载入共享库(动态链接库)或是 C++ 的异常。设置捕捉点的格式为:

  1. catch <event> :当event发生时,停住程序

    1
    2
    
    catch throw		#一个C++抛出的异常
    catch catch		#一个C++捕捉到的异常
    
  2. tcatch <event> :只设置一次捕捉点,当程序停住后,点被自动删除

5. 维护断点

GDB 中的断点也就是上述的三类。在 GDB 中,如果你觉得已定义好的停止点没有用了,你可以使用 delete 、 clear 、 disable 、 enable 这几个命令来进行维护。

  1. clear :清除所有的已定义的停止点
  2. clear <function>clear <filename:function> :清除所有设置在函数中的停止点
  3. clear <linenum>clear <filename:linenum> :清除所有设置在指定行上的停止点
  4. delete [breakpoints] [range…] :删除指定的断点(breakpoints是断点号,若不指定断点号,表示删除所有的断点。range表示断点号的范围(如:3-7)。其简写命令为 d
  5. disable [breakpoints] [range…] :断点不会被删除,当再次需要时,enable即可。如果不指定断点,会disable所有断点
  6. enable [breakpoints] [range…]
  7. enable [breakpoints] once range… :使能指定的断点一次,当程序停住,该断点立刻被GDB禁用disable
  8. enable [breakpoints] delete range… :使能指定的断点一次,当程序停止后,该断点立刻被删除

6. 维护停止条件

一般来说,为断点设置一个条件,我们使用 if 关键词,后面跟其断点条件。并且,条件设置好后,我们可以用 condition 命令来修改断点的条件。 (只有 break 和 watch 命令支持 if, catch 目前暂不支持 if )

  1. conditon <bnum> <expression> :修改断点号为 bnum 的停止条件为 expression
  2. condition <bnum> :清除断点号 bnum 的停止条件
  3. ignore <bnum> <count> :指定程序运行时,忽略断点号为 bnum 的停止条件 count 次

7. 为断点设定运行命令

可以使用 GDB 提供的 command 命令来设置停止点的运行命令。 也就是说,当运行的程序在被停止住时,我们可以让其自动运行一些别的命令,这很有利行自动化调试。对基于 GDB 的自动化调试是一个强大的支持。

commands [bnum] … command-list … end

为断点号 bnum 写一个命令列表,当程序被该断点停住时,gdb 会依次运行命令列表中的命令。例如:

1
2
3
4
5
break foo if x>0	#断点设置在函数foo中,断点条件是x>0
commands
printf "x is %d/n",x	#打印 x 的值
continue			#然后继续运行程序
end

如果要清除断点上的命令序列,那么只要简单的执行一下 commands 命令,并直接在输个 end 就行了。

8. 断点菜单

在 C++ 中,可能会重复出现同一个名字的函数若干次(函数重载),在这种情况下, break <function> 不能告诉 GDB 要停在哪个函数的入口。 当然,你可以使用 break <function(type)> 也就是把函数的参数类型告诉 GDB,以指定一个函数。 否则的话,GDB 会给你列出一个断点菜单供你选择你所需要的断点。你只要输入你菜单列表中的编号就可以了。如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(gdb) b String::after
[0] cancel
[1] all
[2] file:String.cc; line number:867
[3] file:String.cc; line number:860
[4] file:String.cc; line number:875
[5] file:String.cc; line number:853
[6] file:String.cc; line number:846
[7] file:String.cc; line number:735
> 2 4 6
Breakpoint 1 at 0xb26c: file String.cc, line 867.
Breakpoint 2 at 0xb344: file String.cc, line 875.
Breakpoint 3 at 0xafcc: file String.cc, line 846.
Multiple breakpoints were set.
Use the "delete" command to delete unwanted
breakpoints.
(gdb)

可见,GDB 列出了所有 after 的重载函数,你可以选一下列表编号就行了。 0 表示放弃设置断点, 1 表示所有函数都设置断点。

9. 恢复程序运行和单步调试

当程序被停住了,可以用continue命令恢复程序的运行直到程序结束,或到下一个断点处。也可以使用step 或者 next 单步执行

  1. continue [ignore-count] 或者 c [ignore-count] 或者 fg [ignore-count] :恢复程序运行直到

Basic:

1
2
3
gcc -ggdb3 hello.c  #gdb3会生成更丰富的调试信息,可以和gdb更好的完成内联功能
gdb a.out
(gdb) start     #主要的临时断点
  • Output
    1
    2
    3
    4
    5
    
    Temporary breakpoint 1 at 0x1169: file hello.c, line 3.
    Starting program: /home/jack/backup/opencvTest/a.out
    
    Temporary breakpoint 1, main () at hello.c:3
    3       {
    

显示代码片段:

1
(gdb) list			#显示源代码
  • Output
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    1       #include<stdio.h>
    2       int main(void)
    3       {
    4           int i = 0;
    5           printf("Hello, world\n");
    6           printf("i is %d\n", i);
    7           i++;
    8           printf("i is now %d\n", i);
    9           return 0;
    10      }
    

无法显示中文


GDB

Ref:

1. More than you knew

按下 Ctrl+x+a,从1979进入八十年代!类似图形界面的TUI,再按一遍回到命令行

  • ctrl + l : 刷新屏幕

  • ctrl + p / ctrl+n : prev / next command

  • ctrl + x + 2 : 第2个窗口,cycle though

  • 或者输入:tui enable,或者 layout src,或者 layout asm(显示汇编代码),进入 TUI

2. GDB has Python

  • Full Pyton interpreter with access to standard modules (unless your gdb installaion is messed up!)

  • The gdb python module gives most access to gdb

    1
    2
    3
    
    (gdb) python gdb.execute()			#执行gdb命令
    (gdb) python gdb.parse_and_eval()	#to get data from inferior
    (gdb) python help('gdb')			#to see online help
    
  • Python Pretty Printers

    1
    2
    3
    4
    5
    
    class MyPrinter(object)
    	def __init__(self,val):
        	self.val = val;
        def to_string(self):
            return (self.val['member'])
    
    1
    2
    3
    4
    
    import gdb.printing
    pp = gdb.printing.RegexpCollectionPrettyPrinter('mystruct')
    pp.add_printer('mystruct', '^mystruct$', MyPrinter)
    gdb.printing.register_pretty_printer( gdb.current_objfile(),pp)
    

3. In-built pretty printers for STL

GDB will ( try to ) pretty-print most STL container classes ( std : : vector , std : string , etc ) , e.g.

1
2
3
4
5
6
10			vec.push_back(5);
(gdb) next
12			return 0;
(gdb) print vec
$6 = std::vector of length 3, capacity 4 = {3, 4, 5}
(gdb)

Note that this relies on Python pretty printers installed on the target system

Compiling / linking with a different version of libstdc + + ( e . g . executable built on a different host than the one beingused to debug ) , then pretty printing might give strange results.

There are many ( list with info pretty-printers ) , includingstd : string , std : bitset , std : list , std : multimap , std : queue , std : set , std : shared _ ptr ,std : stack , std : tuple , std : unique _ ptr , std : vector , std : weak _ ptr , and iterators.

4. .gdbinit

5. GDB is built on ptrace and signals

GDB是建立在ptrace之上的。当一个正在被跟踪的程序收到一个信号,它会暂停并且tracer会通过waitpid 注意到,所以当 the inferior收到信号,它就会停止然后gbd获得控制。通常gdb会回到prompt,但是具体会做什么取决于信号和设置。

1
(gdb) info signals

有两个信号很特别:

  • SIGINT 当在按下 Crtl+c 会产生
  • SIGTRAP 当 the inferior 遇到断点或者单步调试时会产生。(改变代码,0xCC操作码生成陷阱)
1
2
3
4
5
6
(gdb) handle SIGINT stop pirnt pass
Signal		 Stop		Print	Pass to program	Description
SIGINT		 Yes		Yes		Yes				Interrupt
(gdb) handle SIGINT stop print nopass
Signal		 Stop		Print	Pass to program	Description
SIGINT		 Yes		Yes		No				Interrupt

6. Breakpoints & watchpionts

1
2
3
4
5
watch foo 				#stop when foo is modified
watch -l foo			#watch location
rwatch foo				#stop when foo is read
watch foo thread 3		#stop when thread 3 modifies foo
watch foo if foo > 10	#stop when foo is>10

foo 是一个局部变量

7. thread apply

1
2
3
thread apply 1-4 print $sp
thread apply all backtrace
thread apply all backtrace full

8. Dynamic Printf

不需改变代码,用printf的话需要考虑加在哪里,然后重新编译运行,查看输出,没查到结果,又要重复一遍

9. Calling inferior functions

call foo will call foo in your inferior

10. Catchpoints

像断点

11. Remote debugging

通过serial/sockets 调试远程服务器。

gdbserver localhost:2000 ./a.out

12. Multiprocess Debugging


Modern

Source video: 【GDB调试教程】如何设置条件断点?如何动态修改变量?Python和C++混合怎么调试?如何附加到进程?- 双笙子佯谬 - bilibili

(2024-01-19)

  1. 调试 Python 程序

    Docs: DebuggingWithGdb

    用 gdb 调试 python,进入之后把要调试的 test.py 文件作为参数传入. vid

    1
    2
    3
    4
    5
    6
    7
    
    gdb test.py  # This trivial way won't work.
    gdb python   # debug python
    r test.py    # i.e., shell cmd: `python test.py`
    # the appended arguments will be put into argv 
    
    # Or, with `-ex`
    gdb python -ex 'r test.py'
    

    这样,GDB 就可以捕获 python 的异常。

    TODO: Debugging Python C extensions with GDB - Redhat Developers

  2. Temporary breakpoint only break once.

    1
    
    tb func