gdb 简介
GDB 全称“GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序。
一般来说,GDB主要帮助我们完成以下四个方面的功能:
1. 启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
2. 在某个指定的地方或条件下暂停程序。
3. 当程序被停住时,可以检查此时你的程序中所发生的事。
4. 在程序执行过程中修改程序中的变量或条件,将一个bug产生的影响修正从而测试其他bug。要使用GDB调试某个程序,该程序编译时必须加上编译选项 -g,否则该程序是不包含调试信息的; GCC编译器支持 -O 和 -g一起参与编译。GCC编译过程对进行优化的程度可分为5个等级.
重要快捷键:
ctrl+A+X:进入(TextUser Interface),它为GDB调试的文本用户界面,可以方便地显示源代码、汇编和寄存器文本窗口.
GDB调试步骤
- 启动GDB
- 直接调试目标程序:
gdb ./hello_server
- 附加进程id:
gdb attach pid
- 调试core文件:
gdb filename corename
- 退出GDB
使用命令:q(quit的缩写)或者 Ctr + d 退出GDB。
如果GDB attach某个进程,退出GDB之前要用命令 detach 解除附加进程。
GDB常用命令
命令名称 | 命令缩写 | 命令说明 |
run | r | 运行一个待调试的程序 |
continue | c | 让暂停的程序继续运行 |
next | n | 运行到下一行 |
step | s | 单步执行,遇到函数会进入 |
until | u | 运行到指定行停下来 |
finish | fi | 结束当前调用函数,回到上一层调用函数处 |
return | return | 结束当前调用函数并返回指定值,到上一层函数调用处 |
jump | j | 将当前程序执行流跳转到指定行或地址 |
p | 打印变量或寄存器值 | |
backtrace | bt | 查看当前线程的调用堆栈 |
frame | f | 切换到当前调用线程的指定堆栈 |
thread | thread | 切换到指定线程 |
break | b | 添加断点 |
tbreak | tb | 添加临时断点 |
delete | d | 删除断点 |
enable | enable | 启用某个断点 |
disable | disable | 禁用某个断点 |
watch | watch | 监视某一个变量或内存地址的值是否发生变化 |
list | l | 显示源码 |
info | i | 查看断点 / 线程等信息 |
ptype | ptype | 查看变量类型 |
disassemble | dis | 查看汇编代码 |
set args | set args | 设置程序启动命令行参数 |
show args | show args | 查看设置的命令行参数 |
- run命令
默认情况下,以 gdb ./filename
方式启用GDB调试只是附加了一个调试文件,并没有启动这个程序,需要输入run命令(简写为r)启动这个程序
- continue命令
当GDB触发断点或者使用 Ctrl + C 命令中断下来后,想让程序继续运行,只要输入 continue(简写为c)命令即可。
- break命令
break命令(简写为b)用于添加断点,可以使用以下几种方式添加断点:
break FunctionName,在函数的入口处添加一个断点;
break LineNo,在当前文件行号为LineNo处添加断点;
break FileName:LineNo,在FileName文件行号为LineNo处添加一个断点;
break FileName:FunctionName,在FileName文件的FunctionName函数的入口处添加断点;
break -/+offset,在当前程序暂停位置的前/后 offset 行处下断点;
break … if cond,下条件断点;
- info break、enable、disable和delete命令
命令格式及作用:
info break,也可简写为 i b,作用是显示当前所有断点信息;
disable 断点编号,禁用某个断点,使得断点不会被触发;
enable 断点编号,启用某个被禁用的断点;
delete 断点编号,删除某个断点。
- backtrace和frame命令
命令格式及作用:
backtrace,也可简写为 bt,用于查看当前调用堆栈。
frame 堆栈编号,也可简写为 f 堆栈编号,用于切换到其他堆栈处。
- list命令
命令格式及作用:
list,输出上一次list命令显示的代码后面的代码,如果是第一次执行list命令,则会显示当前正在执行代码位置附近的代码;
list -,带一个减号,显示上一次list命令显示的代码前面的代码;
list LineNo,显示当前代码文件第 LineNo 行附近的代码;
list FileName:LineNo,显示 FileName 文件第 LineNo 行附近的代码;
list FunctionName,显示当前文件的 FunctionName 函数附近的代码;
list FileName:FunctionName,显示 FileName 文件的 FunctionName 函数附件的代码;
list from,to,其中from和to是具体的代码位置,显示这之间的代码;
list命令默认只会输出 10 行源代码,也可以使用如下命令修改:
show listsize#查看 list 命令显示的代码行数; set listsize count#设置 list 命令显示的代码行数为 count;
- print和ptype命令
命令格式及作用:
print param,用于在调试过程中查看变量的值;
print param=value,用于在调试过程中修改变量的值;
print a+b+c,可以进行一定的表达式计算,这里是计算a、b、c三个变量之和;
print func(),输出func函数执行的结果,常见的用途是打印系统函数执行失败原因:print > strerror(errno);
print *this,在c++对象中,可以输出当前对象的各成员变量的值;
- whatis和ptype命令
命令格式及功能:
whatis val,用于查看变量类型;
ptype val,作用和 whatis 类似,但功能更强大,可以查看复合数据类型,会打印出该类型的成员变量。
- thread命令
命令格式及作用:
info thread,查看当前进程的所有线程运行情况;
thread 线程编号,切换到具体编号的线程上去;
- next、step命令
next 和 step 都是单步执行,但也有差别:
next 是 单步步过(step over),即遇到函数直接跳过,不进入函数内部。
step 是 单步步入(step into),即遇到函数会进入函数内部。
- return、finish命令
return 和 finish 都是退出函数,但也有差别:
return 命令是立即退出当前函数,剩下的代码不会执行了,return 还可以指定函数的返回值。
finish 命令是会继续执行完该函数剩余代码再正常退出。
- until命令
该命令使得程序执行到指定位置停下来,命令参数和 break 命令一样。
- jump命令
命令格式及作用:
jump LineNo,跳转到代码的 LineNo 行的位置;
jump +10,跳转到距离当前代码下10行的位置;
jump *0x12345678,跳转到 0x12345678 地址的代码处,地址前要加星号;
jump 命令有两点需要注意的:
中间跳过的代码是不会执行的;
跳到的位置后如果没有断点,那么GDB会自动继续往后执行;
- set args 和 show args命令
很多程序启动需要我们传递参数,set args 就是用来设置程序启动参数的,show args 命令用来查询通过 set args 设置的参数,命令格式:
set args args1,设置单个启动参数 args1;
set args “-p” “password”,如果单个参数之间有空格,可以使用引号将参数包裹起来;
set args args1 args2 args3,设置多个启动参数,参数之间用空格隔开;
set args,不带参数,则清除之前设置的参数;
- tbreak命令
该命令时添加一个临时断点,断点一旦被触发就自动删除,使用方法同 break。
- watch命令
watch 命令用来监视一个变量或者一段内存,当这个变量或者内存的值发生变化时,GDB就会中断下来。被监视的某个变量或内存地址会产生一个 watch point(观察点)。
命令格式:
watch 整型变量;
watch 指针变量,监视的是指针变量本身;
watch *指针变量,监视的是指针所指的内容;
watch 数组变量或内存区间;
- call命令
命令格式及作用:
call func(),执行 func() 函数,同 print func()。
- help命令
通过 help 命令可以查看目标命令的具体用法。
GDB多线程调试
概述
多线程程序的编写更容易产生异常或 Bug(例如线程之间因竞争同一资源发生了死锁、多个线程同时对同一资源进行读和写等等)。GDB调试器不仅仅支持调试单线程程序,还支持调试多线程程序。本质上讲,使用GDB调试多线程程序的过程和调试单线程程序类似,不同之处在于,调试多线程程序需要监控多个线程的执行过程。
用GDB调试多线程程序时,该程序的编译需要添加 -lpthread 参数。
一些命令
1. info thread,查看当前调试程序启动了多少个线程,并打印出各个线程信息;
2. thread 线程编号,将该编号的线程切换为当前线程;
3. thread apply 线程编号1 线程编号2 … command,将GDB命令作用指定对应编号的线程,可以指定多个线程,若要指定所有线程,用 all 替换线程编号;
4. break location thread 线程编号,在 location 位置设置普通断点,该断点只作用在特定编号的线程上;
设置线程锁
使用GDB调试多线程程序时,默认的调试模式是:一个线程暂停运行,其他线程也随即暂停;一个线程启动运行,其他线程也随即启动。但在一些场景中,我们希望只让特定线程运行,其他线程都维持在暂停状态,即要防止线程切换,要达到这种效果,需要借助 set scheduler-locking 命令。
命令格式及作用:
set scheduler-locking on
,锁定线程,只有当前或指定线程可以运行;set scheduler-locking off
,不锁定线程,会有线程切换;set scheduler-locking step
,当单步执行某一线程时,其他线程不会执行,同时保证在调试过程中当前线程不会发生改变。但如果在该模式下执行 continue、until、finish 命令,则其他线程也会执行;show scheduler-locking
,查看线程锁定状态;
结语
在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。
这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。
我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。