前言
在编写程序过程中可能遇到语法错误或者逻辑错误,语法错误可以通过编译器帮助我们检查出来,但是逻辑错误需要我们自己去发现。一般解决代码的逻辑问题除了加一些打印信息以外,还可以借助一些调试工具,下面为大家介绍一款常用的调试工具GDB。
1.1 关于gdb
GDB(GNU Debugger),从名称上可以看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等)。GDB是 Linux 下常用的程序调试器,并且是一款功能强大的开源调试工具,广泛用于C、C++、Fortran等编程语言的调试。它可以帮助开发者在程序运行过程中进行断点设置、变量查看、内存监测等操作,以及跟踪程序的执行流程。
1.2 gdb的作用
下面介绍一些GDB调试工具常用的功能:
- 断点设置:通过在代码中设置断点,可以让程序在指定位置停下来,方便我们观察变量值、执行路径等信息。GDB支持多种类型的断点设置,如函数断点、条件断点、临时断点等。
- 变量查看:GDB可以查看当前程序中各个变量的值,并且支持对变量进行修改。这对于排查逻辑错误非常有帮助,可以实时监测变量值是否符合预期。
- 回溯堆栈:当程序出现崩溃或异常情况时,GDB可以帮助我们获取堆栈信息。通过回溯堆栈,我们可以了解导致程序崩溃的原因所在,并进行相应修复。
- 内存监测:GDB提供了一系列内存监测功能,包括检测内存泄漏、检查数组越界访问、查看内存映射等。这些功能可以帮助开发者及时发现和解决与内存相关的问题。
- 多线程调试:对于多线程程序,GDB可以支持对多个线程的调试。开发者可以通过GDB查看各个线程的状态、堆栈信息,并进行线程间的切换和同步操作。
- 远程调试:GDB还支持远程调试,即在一台机器上运行GDB服务器,另一台机器上
运行GDB客户端。这样可以方便地在远程机器上进行调试操作,而不需要将代码复制到本地进行调试。
总之,GDB是一个强大且灵活的调试工具,它提供了丰富的功能来帮助开发者定位和解决程序中的问题。通过合理利用GDB,我们可以更高效地进行程序调试,并提高软件质量和开发效率。如果你是一个编程爱好者或者专业开发人员,不妨尝试使用GDB来提升自己的调试能力吧!
调试步骤
1. 第一步:加调试信息
启动gdb调试前,需要在编译时加上-g参数,保留调试信息(比如各行代码所在的行号、包含程序中所有变量名称的列表(又称为符号表)等),否则不能使用GDB进行调试。例如为demo.c加上调试信息然后进行编译执行以下命令:
生成默认的a.out
可以用readelf查看是否有debug调试信息:
如:
如果文件a.out没有调试信息,即输入该命令到终端没有输出内容,则无法被调试。
2. 第二步:启动gdb调试
- 若调试程序无参数,则直接gdb加程序名进入gdb调试模式。
然后用命令run运行程序即可。例如, 有以下程序:
gcc -g加调试并且编译生成a.out,然后用gdb a.out进入gdb调试:
- 若调试程序有参数,则编译和启动gdb步骤一样,只不过用run运行时后面加参数,如下图所示:
gdb的用法
常用gdb命令
gdb的使用方式是通过各种命令,下面是gdb常用的命令:
调试命令(缩写) | 作用 |
break(b) | 在源代码指定某行设置断点,可以加指定位置。 |
run(r) | 执行被调试程序,会停在第一个断点处。 |
continue(c) | 当程序停在某一断点处时,可以用该命令执行到下一个断点,若没有下一个断点则结束程序。 |
next(n) | 单步跳过,让程序一行一行执行。 |
step(s) | 单步运行,如果有调用函数,则进入调用函数内,否则和next命令一样一行一行执行程序。 |
print(p) | 打印变量的值,后面跟变量名,若打印指针内容则用*加指针名。 |
list(l) | 显示程序代码内容,包括行号。 |
unitl(u) | 若遇到循环内断点不想继续追踪,可以用次命令运行完循环体。 |
unitl(u) n | n为某一行代码行号,此命令会直接跳到改行停止。 |
finish(fi) | 结束当前正在执行函数,并在跳出函数后暂停程序的执行。 |
return(return) | 结束当前调用函数并返回指定值,到上一次函数调用处停止。 |
jump(j) | 使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续代码。 |
quit(q) | 退出gdb调试 |
Info | 后面可以跟随不同的参数,以获取不同类型的信息, 比如查看断点信息info b |
gdb添加断点
添加断点可以帮助我们更方便的锁定bug位置,下面介绍一下如何添加断点:
b filename.c:n | 在文件filename.c的第n行设置断点 |
b function_name | 在函数function_name开头设置断点 |
b *addr | 在内存地址addr处设置断点 |
案例,有以下程序:
进行gdb调试:
查看断点信息:info b
删除断点n: delete(d) n
禁用断点n: disable n
使能断点n: enable n
观察断点
GDB调试器不光可以打普通断点还可以打观察断点,观察断点可以监控变量或表达式的值的变化。观察断点用命令watch去设置。
使用 GDB 调试程序的过程中,可以借助观察断点来监控程序中某个变量或者表达式的值,只要发生改变,程序就会停止执行。相比普通断点,观察断点不需要我们预测变量(表达式)值发生改变的具体位置,而是直接加变量名或表达式。
watch expr | 为表达式(变量)expr设置一个观察点。一量表达式值有变化时,马上停住程序。 |
rwatch expr | 当表达式(变量)expr被读时,停住程序。 |
awatch expr | 当表达式(变量)的值被读或被写时,停住程序。 |
info watchpoints | 查看观察点、断点和捕捉点信息,同info break 一样. |
案例:
条件断点
当我们需要在特定条件下暂停程序的执行时,可以使用gdb设置条件断点。而条件断点可以在满足特定条件时触发,使程序停止在设定的断点处,方便我们进行调试工作。下面介绍如何设置条件断点:
在指定文件和的第n行上设置条件断点:b filename.c:n if <expr>
例如:
当在i等于3时,程序将会在第7行断住, 可以写成i==3也可以写成i=3。
若要修改断点条件可以用condtion命令,用法:
condition 断点号 修改条件
例如,之前设置b==3时产生该断点,那么使用condition可以修改断点产生的条件。
info命令
info可以用于获取程序执行状态的信息。info 指令后面可以跟随不同的参数,以获取不同类型的信息。使用 info 指令可以帮助开发者了解程序的执行状态,包括断点、变量、堆栈、寄存器、线程等信息。这些信息对于程序的调试和分析还是非常有用的。
以下是 info 指令常用的参数和其含义:
info breakpoints | 显示当前设置的断点信息,包括断点编号、断点位置、断点条件等。 |
info watchpoints | 显示当前设置的观察点信息,即监视变量的值的变化。 |
info frame | 显示当前函数的堆栈帧信息,包括函数名、参数、局部变量等。 |
info locals | 显示当前函数的局部变量的值。 |
info registers | 显示当前程序执行时的寄存器状态,包括通用寄存器、特殊寄存器等。 |
info threads | 显示当前线程的信息,包括线程编号、线程状态等。 |
info inferiors | 显示当前程序的进程信息,包括进程编号、进程状态等。 |
info program | 显示程序的加载地址和执行路径等信息。 |
info sharedlibrary | 显示已加载的共享库的信息。 |
info source | 显示当前源文件的信息,包括文件名、行数等。 |