Linux调试(GDB)

1,408 阅读4分钟

1 GDB的启动和退出

1.1 启动调试

gdb program core

gdb program PID

启动参数:

1. gdb参数

gdb --args program --arg1 --arg2

2. set args 命令

gdb program

set args --arg1 --arg2

3. run 命令

gdb program

run --arg1 --arg2

1.2 退出调试

quit

q

中断信号(Ctrl+c)不会退出gdb,只是中断当前调试命令

1.3 日志

gdb默认不开启日志,通过如下命令可以开启或关闭日志,并设置日志文件路径,写文件的模式

set logging on

set logging off

set logging file   默认日志文件是gdb.txt

set logging overwrite [on/off]  默认是off,也就是日志是append到之前的日志文件

set logging redirect [on/off]  默认是off,也就是日志同时输出到终端和日志文件

show logging

2 命令

2.1 命令补全

TAB 自动补全,在存在多个候选的情况下需要再次按下TAB才会显示候选

ALT+?直接显示候选

2.2 多线程

info thread 列出所有线程

thread ThreadNo  切换到指定线程(这里的ThreadNo并不是ThreadId,而是info thread列出的线程序号

thread apply [ThreadNo/all] cmd  对每个线程执行cmd命令

2.3 多进程

set follow-fork-mode [parent/child/ask]

  • parent 跟踪父进程
  • child 跟踪子进程
  • ask 每次fork时询问跟踪父进程还是子进程

show follow-fork-mode

3暂停和继续

3.1 断点

break 在当前位置设置断点(对于最内部的函数调用,即在当前暂停的位置设置断点,对于上层调用栈,则是在下一语句设置断点)

break NS::Class::Function

break filename.cpp:linenum

break location thread thread-id

break location [-force-condition] if …

tbreak 一次性断点

rbreak .   所有函数设置断点

rbreak filename:. 指定文件中的所有函数设置断点

info break

Num     Type           Disp Enb  Address    What

1       breakpoint     keep y   

        stop only if i==1

        breakpoint already hit 1 time

1.1                         y    0x080486a2 in void foo() at t.cc:8

1.2                         y    0x080486ca in void foo() at t.cc:8

 

 3.2 观察点

watch 监视数据修改

watch var [thread thread-id]

watch (type)address

rwatch  监视数据读取

awatch  监视数据读写

 3.3 捕获点

catch throw/rethrow/catch [regexp] 捕获C++异常

catch exec/fork/vfork  捕获对exec/fork/vfork的调用

catch syscall [name | number | group:groupname | g:groupname] 捕获系统调用或返回

catch signal [signal…|all]

        不指定信号则捕获除SIGTRAP和SIGINT以外的其他信号

        All表示捕获所有信号

tcatch   只捕获一次

 3.4 删除断点

clear [location]  删除指定位置的断点

delete [breakpoints] 删除指定的断点,不指定断点则删除所有断点

 3.5 禁用/启用/跳过断点

disable [breakpoints]  默认禁用所有断点

enable [breakpoints]  默认启用所有断点

enable [breakpoints] once 启用一次

enable [breakpoints] count [count] 启用指定次数

enable [breakpoints] delete  启用一次,触发后删除断点,类似tbreak

ignore [breakpoints] count  后续count次进入断点不暂停

3.6 断点配置保存和加载

save breakpoints [filename]  保存所有断点

source filename            加载所有断点

 3.7 跳转指令

step [count]

next [count]

finish

until [location]  执行到指定位置停下,默认下一行,如果在循环最后一行,则是跳出循环

stepi [count]   执行一个机器指令

nexti [count]   执行一个机器指令,如果是函数调用则跳过

4 调用栈

4.1 打印调用栈列表

backtrace 显示全部调用栈

backtrace n 显示最顶部的n个调用栈

backtrace -n 显示最底部的n个调用栈

4.2 跳转调用栈

frame n  跳转到第n个调用栈,当前正在执行的调用栈为第0个

up [n]       向栈顶方向移动n个调用栈,默认移动一个

down [n]  向栈底方向移动n个调用栈,默认移动一个

4.3 打印调用栈详细信息

info frame      打印当前所在的调用栈信息

info frame n   打印第n个调用栈信息

4.4 在调用栈上执行命令

frame apply [all|n|-n] command

如下命令可以打印出每个调用栈的sp寄存器的值

frame apply all p $sp

5 源码

5.1 list

list linenum 打印当前文件第linenum行周围的代码

list function 打印函数function开始附近的代码

list -  打印上次输出代码之前的代码

list +  打印上次输出代码之后的代码

list     打印上次输出代码之后的代码

list first,last   打印first到last之间的代码

5.2 设置源码路径

set substitute-path <old_path>  <new_path>

5.3 查看汇编代码

disassemble 

6 数据

6.1 表达式

@ 打印数组

a@2   打印数组a的前两个元素  // int a
*a@2  打印数组a的前两个元素  // int *a = new int[2]

:: 限定范围的变量

file::var       文件内静态变量
function::var   函数内的变量

{type} addr 按照指定类型输出某地址上的数据

6.2 变量可见范围

gdb可见的变量(即print可打印的变量)包括:

  • 全局变量和文件内的静态变量
  • 当前栈帧中的局部变量(包括静态变量)

6.3 输出格式

  • x 十六进制输出
  • d 有符号十进制
  • u 无符号十进制
  • o 八进制
  • t 二进制(不是b,因为b在x命令中有别的含义)
  • a 地址格式(类似info symbol,可以快速判断栈上变量位于哪个函数中
  • c 字符格式,超出ASCII范围用\nnn表示
  • f 浮点数格式
  • s 字符串格式
  • z 十六进制输出,前面用0补全,输出类似0x00000001
  • r 使用raw格式,默认基于Python的格式化方式
p/x a
p/s str

6.4 打印内存

x/nfu addr f是输出格式,u每个数据的大小,n是数据数量,三者都是可选参数

f 输出格式

x命令的默认输出格式是x,除了上面提到的输出格式外,x命令还支持两种格式

  • i 输出机器指令
  • m 显示memory tag (需要CPU架构支持)

u 数据大小

  • b 一个字节(byte)
  • h 两个字节(halfword)
  • w 四个字节(word)
  • g 八个字节(giant word)

6.5 自动打印

display/f <expr> 定义一个自动打印的表达式

undisplay <dnums> 删除一个自动打印表达式

delete display <dnums> 删除一个自动打印表达式

disable display <dnums> 禁用一个自动打印表达式

enable display <dnums> 启用一个自动打印表达式

info display 显示所有自动打印的表达式列表

如果表达式中有局部变量,当离开作用域后将自动失效(不是删除),再次进入作用域后会自动生效。

6.6 寄存器

gdb可以打印寄存器的值,寄存器需要在名字前加$,例如指令寄存器就是$pc

info registers 显示除浮点和向量寄存器以外的所有寄存器的名字和值

info all-registers 显示所有寄存器的名字和值

6.7 协处理器信息(arm和x86支持)

info float 显示浮点数计算单元(协处理器)的信息

6.8 生成core文件

gcore [file] 生成core文件,如果没有指定file,默认生成core.<pid>

6.9 搜索内存块

find [/sn] start_addr, +len, val1[, val2, ...] 搜索start_addr到start_addr+len内存范围内是否存在连续的val1,val2,...序列

find [/sn] start_addr, end_addr, val1[, val2, ...] 搜索start_addr到end_addr内存范围内是否存在连续的val1,val2,...序列

s, 搜索的基本单元

  • b 字节
  • h 两个字节
  • w 四个字节
  • g 八个字节

这个仅仅是表示后面的val的数据大小,搜索的边界不会和这个字节数对齐

n, 最大搜索数量,默认搜索全部匹配的结果

7 符号

gdb可以查看到可执行文件的符号表信息

什么是符号:

  • 具名函数(不含lambda函数)
  • 全局变量或文件静态变量
  • 类型名
  • typedef定义的别名

7.1 查看符号

info address <symbol> 显示符号地址和符号类型(函数、静态变量等)

(gdb) info address main
Symbol "main()" is a function at address 0x4005c0.
(gdb) info address a
Symbol "a" is static storage at address 0x601044.

info symbol <address> 显示地址所在的符号及其位于哪个段

(gdb) info symbol &a
a in section .bss
(gdb) info symbol 0x601044
a in section .bss
(gdb) info symbol &main
main in section .text

7.2 查看类型

whatis <expression>或<address> 显示表达式或地址所在符号的类型

ptype[/flag] arg 显示表达式或类型的类型(如果是类包含成员等信息)

(gdb) ptype/o GetObject()
type = class Base {
                         private:
/*    8      |     4 */    int a;
/*   12      |     4 */    float b;
/*   16      |     1 */    bool c;
/* XXX  7-byte hole  */
/*   24      |     8 */    std::string d;

                           /* total size (bytes):   32 */
                         } *

info types [-q] [<regex>] 显示正则匹配到的符号,如果没有指定参数则显示所有符号

info types 显示所有符号

7.3 查看源码文件

info sources [--dirname |--basename] [--] <regex> 列出正则匹配到的源码文件,如果没有指定参数则显示所有源码文件

7.4 查看函数

info functions [-q] [-n] 显示所有的函数

(gdb) info functions
All defined functions:

File /home/hekai/workspace/gdbtest/app/demo.cpp:
18:     void func();
23:     int main();

Non-debugging symbols:
0x0000000000400440  _init
0x0000000000400470  printf@plt
0x0000000000400480  __gmon_start__@plt
0x0000000000400490  __libc_start_main@plt
0x00000000004004a0  sleep@plt
0x00000000004004b0  _start
0x00000000004004e0  deregister_tm_clones
0x0000000000400510  register_tm_clones
0x0000000000400550  __do_global_dtors_aux
0x0000000000400570  frame_dummy
0x00000000004005f0  __libc_csu_init
0x0000000000400660  __libc_csu_fini
0x0000000000400664  _fini

info functions [-q] [-n] [-t `type_regexp`] [`regexp`] 显示函数签名匹配且函数名匹配的函数

info fun step  显示函数名包含step的函数
info fun -t '(.*int.*'  显示有一个int参数的函数
info fun -t '^int ('    显示返回值类型为int的函数

7.5 查看变量

info variables [-q] [-n] 显示所有的函数外变量(全局变量,静态变量,包含函数静态变量),但是函数内静态变量属于Non-debugging symbols

(gdb) info variables 
All defined variables:

Fil>e /home/hekai/workspace/gdbtest/app/demo.cpp:
16:     static int a;

Non-debugging symbols:
0x0000000000400670  _IO_stdin_used
0x0000000000400678  __dso_handle
0x00000000004006a0  __GNU_EH_FRAME_HDR
0x00000000004007f0  __FRAME_END__
0x0000000000600de0  __frame_dummy_init_array_entry
0x0000000000600de0  __init_array_start
0x0000000000600de8  __do_global_dtors_aux_fini_array_entry
0x0000000000600de8  __init_array_end
0x0000000000600df0  __JCR_END__
0x0000000000600df0  __JCR_LIST__
0x0000000000600df8  _DYNAMIC
0x0000000000601000  _GLOBAL_OFFSET_TABLE_
0x0000000000601038  __data_start
0x0000000000601038  data_start
0x000000000060103c  func()::a
0x0000000000601040  __TMC_END__
0x0000000000601040  __bss_start
0x0000000000601040  _edata
0x0000000000601040  completed
0x0000000000601048  _end

info variables [-q] [-n] [-t `type_regexp`] [`regexp`] 显示类型匹配且名称匹配的变量

info scope <location> 列出指定位置作用域内的本地变量

(gdb) info scope func
Scope for func:
Symbol a is in static storage at address 0x60103c, length 4.

8 注入

gdb调试时可以修改被调试程序的数据,调用被调试程序的方法,甚至直接跳转执行流程。当然仅限于执行中的进程,对于core文件调试是无效的

8.1 修改变量

set var <symbol>=<value> 通过变量名修改变量

set var a=1 set {type}<address> = <value> 通过地址修改数据 set {int}0x83040 = 4

8.2 跳转

jump <location>

jump main.cpp:18 set $pc <address>

set $pc main          跳转到main函数入口
set $pc main+15       跳转到main函数入口地址+15  (如果地址有误将直接引发段错误)
set $pc 0x400565      跳转到0x400565            (如果地址有误将直接引发段错误)

8.3 发送信号

signal <signum> 发送信号给被调试程序并继续执行

signal 2
signal SIGINT

通过signal命令发送的信号将直接给到被调试的程序,而通过shell的kill命令发送的信号会被gdb过滤,并根据其信号处理表决定是否交由被调试程序处理。

queue-signal <signum> 在下次继续执行程序前向被调试程序发送信号,可以设置多个信号,SIGINT不能被设置

queue-signal 2
queue-signal SIGINT

8.4 函数返回

return [expression] 立刻退出函数,并可以通过表达式指定函数返回值

8.5 函数调用

print <expression> 如果expression是一个函数,则会执行改函数并打印返回值

print func() call <expression> call func()

参考资料:Top (Debugging with GDB) (sourceware.org)