1. Introduction to GDB
GDB (GNU Debugger) is a debugging tool for GCC. Its powerful functions are described as follows:
GDB mainly helps you complete the following four functions:
- 1. Start your program, you can run the program as you want according to your custom requirements.
- 2. Allows the debugged program to stop at the breakpoint you specify. (Breakpoints can be conditional expressions)
- 3. When the program is stopped, you can check what happened in your program at that time.
- 4. Dynamically change the execution environment of your program.
2. GDB basic debugging commands
2.1 Preparation
Debug information is added to the executable via the gcc -g
option .
$ gcc -g hello.c -o hello
If building with a Makefile, -g
options .
CFLAGS := -Wall -O2 -g
Note that after adding the optimization option to the GCC compiler, the actual execution order may be different from the source code order due to optimization, so using the debugger to trace the runtime sometimes executes to inexplicable places, causing confusion.
2.2 Start
gdb 程序名
Start with the command .
2.3 Set breakpoints
Breakpoints can be set on function names and line numbers, etc. When the program reaches the breakpoint, it will automatically stop running. At this point, you can view the variable value at that moment, display the stack frame, reset breakpoints, or re-run. The breakpoint command break
can be abbreviated as b
, the command is break <断点>
.
Breakpoints can be set by the function name, the line number in the current file, or by specifying the file name and then the line number, or by specifying the offset from the pause position, or by using an address. Format:
Format | illustrate |
---|---|
break <function name> | Set a breakpoint on the specified function in the currently executing file |
break <line number> | Set a breakpoint on a specific line in the currently executing file |
break <filename:linenumber> | Set a breakpoint on a specified line of a specified file, the most common way to set a breakpoint |
break <filename:functionname> | Set a breakpoint on the specified function of the specified file |
break <+/- offset> | Set breakpoint at current command line +/- offset out |
break <*address> | Set a breakpoint at the specified address |
The set breakpoint can be info break
confirmed .
2.4 Running
Start the run with the run 参数
command , where the parameters are the parameters of the executable program. If a breakpoint is set, it will execute to the position where the breakpoint is set and pause the operation. Can be abbreviated as r
.
2.5 Display stack frame
backtrace
The command can display the stack frame when a breakpoint or exception is encountered and execution is suspended, the command is abbreviated as bt
. In addition, the aliases for backtrace are where
and info stack
.
Format | illustrate |
---|---|
bt |
show all stack frames |
bt <N> |
Display only the first N stack frames |
bt <-N> |
Show only the last N stack frames |
bt full |
Show not only backtrace, but also local variables |
bt full <N> |
After displaying the stack frame, you can see where the program stopped and the call path of the program.
2.6 Display variables
print
Commands can display variables, which can be abbreviated as p
.
Format: print 变量
.
2.7 Display registers
info registers
Registers can be displayed, abbreviated as info reg
.
Add $ before the register name to display the contents of the register, eg p $eax
.
p/格式 $寄存器
The display format of the registers can be specified, eg p/c $eax
. The available formats are as follows:
Format | illustrate |
---|---|
x | Display as hexadecimal number |
d | Display as a decimal number |
u | Display as unsigned decimal |
O | Display as an octal number |
t | Display as binary number |
a | address |
c | Display as characters (ASCII) |
f | floating point decimal |
s | display as string |
i | Displayed in machine language, only available in the x command that displays memory |
The program pointer can be written as $pc or as $eip to p $pc
display contents of the program pointer. The program pointer points to the address of the running point of the current program.
x
The command can display the contents of the memory in the format: x/<格式> <地址>
. For example, x/i $ps displays assembly instructions.
Generally, when using the x command, the format is x/<NFU> <ADDR>. Here ADDR is the address you want to display, N is the number of repetitions, F is the previous display format, and the unit represented by U is as follows:
unit | illustrate |
---|---|
b | byte |
h | Halfword (2 bytes) |
w | word (4 bytes) (default) |
g | Double word (8 bytes) |
For example the command x\10i $pc prints 10 instructions starting at the address pointed to by pc.
Disassembly command disassemble
, abbreviated as disas
. Format:
- disassemble. Disassemble the current entire function.
- disassemble program counter. Disassemble the entire function of the function where the program counter is located.
- disassemble start address end address. Disassemble the part from the start address to the end address.
2.8 Single Step
Single-stepping means executing line by line according to the source code. The command to execute a line in the source code is next
, abbreviated as n
. If you encounter a function call during execution and want to execute it inside the function, use the step
command , abbreviated as p
.
If you want to execute assembly instructions one by one, you can use the nexti and stepi commands, respectively.
2.9 Continue to run
Continue running the program with the continue
command , abbreviated as c
. The program will pause again after hitting the breakpoint. Use the continue <次数>
command to specify the number of times to ignore breakpoints.
2.10 Watchpoints
In large software or programs that use a lot of pointers, it is difficult to figure out where variables are changed. To find out where variables are changed, you can use the watch
command (watchpoint). The format is as follows:
- watch <expression>. Pause when the expression changes.
- awatch <expression>. Pauses execution when an expression is accessed or changed.
- rwatch <expression>. Pauses execution when the expression is accessed.
Note that setting watchpoints may slow down operation.
2.11 Deleting breakpoints and watchpoints
delete
The command deletes breakpoints and watchpoints, abbreviated as d
. The format is delete <断点编号>
, it means to delete the breakpoint or watchpoint indicated by the number, and the number can be viewed with the command info b.
clear
command to delete a defined breakpoint. Available commands include:
clear <function name>
clear <line number>
clear <file name: line number> The
clear <file name: function name>
disable
command disables breakpoints. The command format is as follows:
disable: Disable all breakpoints.
disable <breakpoint number>: Disable the specified breakpoint.
disable display <display number>: Disable the automatic display defined by the display command.
disable mem <memory display>: Disables the memory area defined by the mem command.
enable
command to enable breakpoints. The command format is as follows:
enable
enable <breakpoint number>
enable once <breakpoint number>: enable the specified breakpoint only once.
enable delete <breakpoint number>
enable display <display number>
enable mem <memory display>
2.12 Other breakpoints
Hardware breakpoints (hbreak), suitable for programs in unmodifiable memory areas such as ROM space, are not available on some architectures.
Temporary breakpoints (tbreak) and temporary hardware breakpoints (thbreak), pause when running there, and the breakpoint will be deleted at this time, which is convenient to use when you only need to stop once.
2.13 Changing the value of a variable
Format: set variable <变量=表达式>
. For example, the command set variable options = 0 changes the value of the variable options to 0.
2.14 Generating a core dump file
Use generate-core-file
to generate a core dump file of the process being debugged. View the run history when the dump file was generated through the core dump file and debug objects.
gcore
command to generate a core dump file directly from the command line. Used on the command line gcore pid
, where pid is the process ID. This command does not need to stop the running program to obtain the core dump file, which is very useful when the cause of the problem needs to be analyzed separately on other machines, or when the problem occurs at the customer site.
3. Kernel dump
The biggest benefit of a core dump is that it preserves the state at the time of the problem. As long as there is an executable and a core dump of the program at the time of the problem, you can know the current state of the process.
3.1 Enable core dump
Most Linux distributions turn off the kernel dump function by default. You can use the ulimit
command to check whether the current kernel dump function is valid.
$ ulimit -c
0
The -c option indicates the size limit for the core dump file, and 0 indicates that the core dump is invalid. To enable core dump execute the command:
$ ulimit -c unlimited
or
$ ulimit -c 上限
Unlimited means that there is no limit to the size of the kernel dump file. When a problem occurs, all the memory of the process can be dumped into the kernel dump file. Or set the upper limit of the core dump file, and specify the upper limit size in the parameter in Kb.
When an exception occurs in the program, a core dump file will be generated in the current directory. For example, the program a.out generates the dump file core and starts GDB with:
$ gdb -c core ./a.out
Use GDB's list command to view nearby source code. How to use the command
Format | illustrate |
---|---|
list <linenum> | 显示程序第 linenum 行周围的源代码 |
list <function> | 显示函数名为 function 的函数的源代码 |
list | 显示当前行后面的源代码 |
list - | 显示当前行前面的源代码 |
set listsize <count> | 设置一次显示源代码的行数 |
show listsize | 查看当前 listsize 的设置 |
list <first>,<last> | 显示从 first 行到 last 行之间的源代码 |
list ,<last> | 显示从当前行到 last 行之间的源代码 |
list + | 往后显示源代码 |
3.2 在专用目录中生成内核转储
转储保存位置的完整路径可以通过 sysctl 变量 kernel.core_pattern 设置。在文件 /etc/sysctl.conf 中设置如下:
$ cat /etc/sysctl.conf
kernel.core_pattern = /var/core/%t-%e-%p-%c.core
kernel.core_uses_pid = 0
$ sysctl -p
此外,还可以在 /proc/sys/kernel 下修改设置。
/proc/sys/kernel/core_uses_pid 可以控制产生的 core 文件的文件名中是否添加 pid 作为扩展 ,如果添加则文件内容为 1 ,否则为 0。
proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名 ,可以这样修改 :
$ echo "/corefile/core-%e-%p-%t" > core_pattern
kernel.core_pattern 中可以设置的格式符如下:
格式符 | 说明 |
---|---|
%% | % 字符本身 |
%p | 被转储进程的进程 ID(PID) |
%u | 被转储进程的真实用户 ID(real UID) |
%g | 被转储进程的真实组 ID(real GID) |
%s | 引发转储的信号编号 |
%t | 转储时刻(从 1970/1/1 0:00 开始的秒数) |
%h | 主机名(同 uname(2) 返回的 nodename) |
%e | 可执行文件名 |
%c | 转储文件的大小上限(内核版本 2.6.24 后可用) |
3.3 使用用户模式辅助程序自动压缩内核转储文件
修改 /etc/sysctl.conf中 的 kernel.core_pattern 变量来设置。
$ cat /etc/sysctl.conf
kernel.core_pattern= | usr/local/sbin/core_helper %t %e %p %c
kernel.core_uses_pid= 0
$ sysctl -p
core_helper 的内容:
$cat usr/local/sbin/core_helper
#!/bin/sh
execgzip ->/var/core/$1-$2-$3-$4.core.gz
这样,发生内核转储时就会在 /var/core 下生成压缩的内核转储文件。
3.4 启用整个系统的内核转储功能
/etc/profile 文件中可以设置开启所有用户的内核转储功能,默认情况下禁止内核转储:
ulimit -S -c 0 > /dev/null 2>&1
将其修改为
ulimit -S -c unlimited > /dev/null 2>&1
接下来要让通过 init 脚本启动的守护进程的内核转储功能有效。在 /etc/sysconfig/init 文件中添加一行命令。
DAEMON_COREFILE_LIMIT='unlimited'
最后在 /etc/sysctl.conf 中加入以下设置。
fs.suid_dumpable=1
这个设置使得被 SUID 的程序也能内核转储。重新启动启动,就可以启用整个系统的内核转储。
在我使用的Ubuntu、Debian和移植的嵌入式Linux系统中,没有找到 /etc/sysconfig 文件夹。修改了文件 /etc/security/limits.conf,按照文件的内容提示修改,使 unlimited 永久生效。
3.4 利用内核转储掩码排除共享内存
多进程程序如果使用庞大的共享内存,内核转储时所有进程的共享内存全部转储,会对磁盘造成巨大的压力,转储过程也会加重系统的负载。由于共享内存的内容是相同的,只需要在某个进程中转储共享内存。
通过 /proc/<PID>/coredump_filter 进行设置。coredump_filter 使用比特掩码表示内存类型。如下所示:
比特掩码 | 内存类型 |
---|---|
比特 0 | 匿名专用内存 |
比特 1 | 匿名共享内存 |
比特 2 | file-backed 专用内从 |
比特 3 | file-backed 共享内存 |
比特 4 | ELF 文件映射(内核版本 2.6.24 后可用) |
要跳过所有的共享内存区段,应将值改位 1。
4、GDB调试技巧
4.1 attach 到进程
要调试已经启动的进程,或是调试陷入死循环而无法返回控制台的进程时,可以使用 attach
命令。格式:attach <pid>
,执行这一命令可以 attach 到进程 ID 为 pid 的进程上。
attach 之后就能使用普通的 gdb 命令。
gdb 和进程分离时使用 detach
命令,调试的进程就从 gdb 的控制下释放出来。进程被 detach 后继续运行。
进程信息可以用 info proc
命令显示。
守护者进程在启动好子进程后,会自动关闭主进程,如果没有设定监控模式的话,gdb 会提示断开与进程的链接。所以必须设定监控对象,设置命令为 set follow-fork-mode child/parent
。
4.2 条件断点
break <断点> if <条件>
,这条命令将测试给定的条件,如果为帧则暂停运行。
如果断点已经存在,condition <断点编号> <条件>
命令给断点添加触发条件,condition <断点编号>
命令删除指定编号断点的触发条件。
4.3 反复执行
ignore <断点编号> <次数>
:在编号指定的断点、监视点或捕获点忽略指定的次数。
continue <次数>
: 达到指定次数前,执行到断电时不暂停。
s/stepi/n/nexti <次数>:执行指定次数的相应命令。
finish:执行完当前函数后暂停。
until:执行完当前函数等代码块后暂停,如果是循环,则在执行完循环后暂停,常用于跳出循环。
until <地址>:执行到指定地址停止。
4.4 断点命令
commands
命令可以定义在断点终端后自动执行的命令。格式如下:
(gdb) commands <断点编号>
<命令>
...
end
4.5 值的历史
通过 print 命令显示过的值会记录在内部的值历史中。这些值通过 $ 进行引用,使用 show value 命令可以显示历史中的最后 10 个值。
变量 | 说明 |
---|---|
$ | 值历史的最后一个值 |
$n | 值历史的第 n 个值 |
$$ | 值历史的倒数第 2 个值 |
$$n | 值历史的倒数第 n 个值 |
$_ | x 命令显示过的最后的地址 |
$__ | x 命令显示过的最后的地址的值 |
$_eexitcode | 调试过程中的程序的返回代码 |
$bpnum | 最后设置的断点编号 |
还可以随意定义变量,变量以 $ 开头,由英文字母和数字组成。例如:
(gdb) set $i=0
(gdb) p $i
$i = 0
4.6 命令历史
show history
将命令历史保存到文件中,默认命令历史文件位于 ./.gdb_history。
set history expansion
show history expansion
可以使用 csh 风格的 ! 字符。
set history filename <文件名>
show history filename
将命令历史保存到文件中。可以通过环境变量 GDBHISTFILE 改变默认文件名。
set history save
show history save
启用命令历史保存到文件和恢复的功能。
set history size <数字>
show history size
可设置保存到命令历史中的命令数量。默认值为 256。
4.7 初始化文件(.gdbinit)
Linux 环境下的初始化文件为 .gdbinit。如果存在 .gdbinit 文件,GDB 会在启动之前将其作为命令文件运行。初始化文件和命令文件的运行顺序如下。
- 1、$HOME/.gdbinit。
- 2、运行命令行选项。
- 3、./.gdbinit。
- 4、通过 -x 选项给出的命令文件。
初始化文件和命令文件的语法相同。利用 define 命令可以自定义命令,document 命令给自定义命令添加说明,GDB 运行时使用 help <命令名> 可以查看定义的命令。示例如下:
define li
x/10i $pc
end
document li
list machine instruction
end
除了初始化文件,还可以把设置写在文件中,在运行 GDB 时读取这些文件。命令为 source <文件名>
,例如:
(gdb) source gdbcalc
(gdb) p $log10(10000.0)
$1 = 4
其中 gdbcalc 文件内容如下:
#!/usr/bin/gdb -x
file /usr/bin/gdb
start
set $e = 2.7182818284590452354
set $pi = 3.14159265358979323846
set $fabs = (double (*)(double)) fabs
set $sqrt = (double (*)(double)) sqrt
set $cbrt = (double (*)(double)) cbrt
set $exp = (double (*)(double)) exp
set $exp2 = (double (*)(double)) exp2
set $exp10 = (double (*)(double)) exp10
set $log = (double (*)(double)) log
set $log2 = (double (*)(double)) log2
set $log10 = (double (*)(double)) log10
set $pow = (double (*)(double, double)) pow
set $sin = (double (*)(double)) sin
set $cos = (double (*)(double)) cos
set $tan = (double (*)(double)) tan
set $asin = (double (*)(double)) asin
set $acos = (double (*)(double)) acos
set $atan = (double (*)(double)) atan
set $atan2 = (double (*)(double, double)) atan
set $sinh = (double (*)(double)) sinh
set $cosh = (double (*)(double)) cosh
set $tanh = (double (*)(double)) tanh
set $asinh = (double (*)(double)) asinh
set $acosh = (double (*)(double)) acosh
set $atanh = (double (*)(double)) atanh
5、总结
GDB 常用命令及缩略形式如下表:
命令 | 简写形式 | 说明 |
---|---|---|
backtrace | bt、where | 显示 backtrace |
break | 设置断点 | |
continue | c、cont | 继续运行 |
delete | d | 删除断点 |
finish | 运行到函数结束 | |
info breakpoints | 显示断点信息 | |
next | n | 执行下一行 |
p | 显示表达式 | |
run | r | 运行程序 |
step | s | 一次执行一行,包括函数内部 |
x | 显示内存内容 | |
until | u | 执行到指定行 |
directory | dir | 插入目录 |
disable | dis | 禁用断点 |
down | do | 在当前调用的栈帧中选择要显示的栈帧 |
edit | e | 编辑文件或函数 |
frame | f | 选择要显示的栈帧 |
forward-search | fo | 向前搜索 |
generate-core-file | gcore | 生成内核转储 |
help | h | 显示帮助一览 |
info | i | 显示信息 |
list | l | 显示函数或行 |
nexti | ni | 执行下一行(以汇编代码为单位) |
print-object | po | 显示目标信息 |
sharedlibrary | share | 加载共享库的符号 |
setpi | si | 执行下一行 |