GNU GDB调试手册

  人家都说Windows平台下的程序员是幸福的,因为Visual Studio实在是太好用了。
  我想一听到上面这句话,绝大多数Linux平台C/C++程序员都将会沉默,至少网上搜Linux平台下C/C++开发调试,很大一部分文章都是介绍搭建IDE的,这也侧面说明了Linux平台下没有一个占绝对主流地位好用的IDE。自己平时写程序是用的SlickEdit,这个IDE很贵,支持代码补全和跳转,调试过程也支持断点、Watch等特性,但是使用过程中还是有这样那样的小问题(比如非英文字串显式、速度比较慢),但是总体比较而言还是比较优秀的跨平台C/C++ IDE。虽然线下编码调试使用这货当然可以,然则线上环境就无能为力了,再加上自己之前对gdb也只是了解个表明,这次就顺着gdb的文档深挖一下!
  当然,对于那种持续添加跟踪代码,重新编译程序,然后运行程序并分析跟踪代码输出,修正程序错误之后再删除跟踪代码,并且针对发现的每个新的程序问题都重复上面步骤的同学,即使某些时候确实能达到调试跟踪的效果,但迟早也会被自己累死(蠢死)。
  真是一看吓一跳,gdb的命令行调试要远比IDE的功能高级的多,如果只是通常的设置断点,监测变量什么的可能IDE比较方便,但是一旦上升到使用那些高级点的调试技巧,反而gdb在命令行的模式下更为的方便和高效,起码启动速度和响应速度会高很多。
  前方预警:这将会是一篇很长很长的文档摘读,而且个人翻译的可能比较生硬,期待改进。

一、开始使用gdb

1.1 启动gdb

1
2
3
4
➜  ~ gdb program [core|pid]
➜ ~ gdb --args g++ -O2 -o main main.cpp
(gdb) shell echo $SHELL
(gdb) set logging on

  gdb接需要调试的程序可执行文件,后面可以附加core文件或者pid进程号(调试正在运行的进程),如果有第三个参数gdb总是先尝试解释他为core文件,然后才是进程号。第二种格式采用–args,待调试的程序执行的时候需要额外的参数,此时可执行程序后面的参数不再被gdb解释。
  组合键Ctrl-D或者输入quit可以退出调试状态,如果之前attach到一个进程上面去了,可以用detach命令释放进程,如果quit有表达式那么表达式就作为gdb的退出状态,否则是被调试的子进程的退出状态。gdb中使用Ctrl-C不会终止gdb,而是会终止gdb正在运行的调试指令返回到gdb命令状态,这条特性有时候也十分重要,比如程序陷入了死循环,此时通过Ctrl-C暂停程序的执行,然后通过backtrace就可以查看整个死循环的调用栈,从而帮助定位问题。
  在gdb中可以使用shell或者!做前导来执行shell中的命令而不用退出gdb(gdb的退出和重启动的代价是很大的),make命令可以用来重新编译程序而不用先导shell|!符号,而gdb自动重新加载符号。
  通过set logging on可以打开日志功能,默认会将日志输出追加到当前目录的gdb.txt文件中。

1.2 gdb中的命令

  gdb是很智能的,所有的命令都可以使用TAB、TAB-TAB进行补齐或者提示,不输入任何命令的空换行(在可能的情况下)表示重新执行前一条指令,这节省了重复输入step|list等指令的劳动。gdb允许指令和参数的缩减输入,没有歧义的时候会运行,而在遇到歧义的时候起会提示出各个歧义的命令列表供参考。
  有些命令参数中可能含有特殊的符号(比如括号),尤其在C++允许函数重载的时候需要使用参数类型来进行区分,此时参数需要使用 ‘ 单引号来包围,而且在需要的时候gdb也会自动帮你包围并给出候选列表的。

1.3 gdb帮助

  gdb本身附带了很多帮助命令,比如:
  help command:显示名的帮助信息
  apropos args:会在命令名、文档中搜索指定的表达式
  info args|registers|breakpoints|…:可以显示很多状态信息,具体可以查看help info
  set var val:设置环境变量
  show var:显示环境变量的值

1.4 调试执行程序

  对于待调试程序编译时候,总应该使用-g选项生成调试信息。

1.4.1 启动调试程序

  run/r
  必须在gdb启动的时候指定调试文件,或者启动后再使用exec|exec-file指定调试文件。启动的时候gdb会创建子进程(inferior process)并在子进程中立即运行该程序。run命令后面可以接参数,所有的参数都会被传递给可执行程序,而下次调试如果直接run而不添加参数,上一轮的参数会被继承自动添加(下面的start也是一样)。同时,GDB会自动保存之前设置的断点等信息,这也是推荐启动GDB不要轻易退出,即便是重新编译程序的情况下,通过run可以让目标程序重新运行,而之前设置的断点等信息不会丢失。
  除了在调用run/start的时候指定参数,还可以单独调用set args方法设置参数。参数的展开和shell规则一样,而且支持输入输出重定向(比如>aa.txt),展开后传递给run/start使用。
  start
  该命令会在程序入口函数的地方(C/C++默认是main)添加一个临时断点,然后程序立即运行并在入口函数的地方被暂停。
  此处还需要注意,C++程序在执行main之前会创建static和全局变量,他们的构造函数会被调用,所以程序入口函数不一定会被最早执行的程序代码。

1.4.2 调试运行的进程

  attach process-id
  可以用来跟踪一个已经在运行的进程,然后被跟踪的进程会暂停执行,然后可以做任何的调试操作,以及continue让程序继续执行。同时,gdb还会在当前工作路径、源代码搜索路径等地方搜索该进程的可执行文件,当然也可以使用file命令手动加载可执行文件。
  detach
  一旦detach之后程序会继续执行,而且和gdb再无瓜葛了。如果当前有attach的进程而使用Ctrl-D退出gdb,gdb会提示是否要detach调试的进程。
  kill
  该命令会kill掉当前调试的进程,无论是run/start创建的还是attach调试的。常用情况是修改代码后需要重新调试,此时可以先kill掉当前调试,然后编译出新的可执行程序,再使用run/start开启调试,此时gdb会发现可执行程序已经修改了,会重新读取符号表信息(但是之前的断点等信息还是得以保留的)。

1.4.4 调试多线程程序

  线程除了共享地址空间之外,拥有自己独立的寄存器状态、执行栈和线程存储。gdb可以查看跟踪进程的所有线程的状态信息,但是只能有一个线程处于当前被跟踪的状态。
  info threads [thread-id-list]
  显示所有(或者指定)线程的信息,带星号标识的是当前跟踪的线程。
  thread thread-id
  切换thread-id线程为当前跟踪线程。
  thread apply [thread-id-list | all] command
  在所有(或者指定)线程线程上执行command命令。
  thread name [name]
  设置线程的名字(info threads中会显示),如果不带参数,会恢复名字为操作系统默认名字。

1.4.5 调试fork创建的多进程程序

  GDB对fork产生新进程的调试没有特殊的支持,当使用fork产生子进程的时候,GDB会继续调试父进程,子进程不会被阻碍而是继续执行,而此时如果子进程遇到断点后,子进程会收到SIGTRAP信号,当在非调试模式下默认会导致子进程中止。如果要调试子进程,可以让子进程启动后睡眠一段时间(或者监测某个文件是否创建),在这个空隙中使用ps查看子进程的进程号,然后再启动gdb去attach到这个进程上面去调试。
  通过catch命令可以让gdb在fork/vfork/exec调用的时候暂停执行。
  影响fork多进程调试还有以下几个变量:
  follow-fork-mode
  可选值是parent或child,指明当正在被调试的进程fork出子进程的时候,如果该值是parent,那么父进程被调试子进程正常执行,这也是默认行为;而当其为child的时候,则新创建的子进程被调试,父进程正常执行。
  detach-on-fork
  可选值是on或off,知名当被调试的进程fork出子进程后,是否detach某个进程还是调试他们俩,如果是on,那么子进程(或父进程,取决于上面follow-fork-mode的值)将会被detach正常执行,这是默认行为;否则两个进程都会被gdb控制,其中的一个进程被正常调试,另外一个进程被suspend住。
  follow-exec-mode
  gdb对于exec调用的反应,其值可以为new或same,当值为new的时候,之前fork的子进程和exec后的进程都会被保留;如果是same,则exec的进程使用之前fork的进程,exec会用新的执行镜像替代原先的可执行程序,这是默认行为。

1.4.6 设置书签保存历史状态

  gdb可以在程序执行的过程中保留快照(状态)信息,称之为checkpoint,可以在进来返回到该处再次查看当时的信息,比如内存、寄存器以及部分系统状态。通过设置checkpoint,万一调试的过程中错误发生了但是已经跳过了错误发生的地方,就可以快速返回checkpoint再开始调试,而不用重启程序重新来过。
  checkpoint
  对当前执行状态保存一个快照,gdb会自动产生一个对应的编号供后续标识使用。从提示信息看来,其实每建立一个checkpoint,都会fork出一个子进程,所以每个checkpoint都有一个唯一的进程ID所对应着。
  info checkpoints
  查看所有的checkpoint,包括进程ID、代码地址和当时的行号或者符号。
  restart checkpoint-id
  恢复程序状态到checkpoint-id指明的checkpoint,所有的程序变量、寄存器、栈都会被还原到那个状态,同时gdb还会将时钟信息回退到那个点。恢复过程中程序的状态和系统的部分状态是支持的,比如文件指针等信息,但是写入文件的数据、传输到外部设备的数据都无法被回退。
  delete checkpoint checkpoint-id
  删除指定的checkpoint。
  此外,checkpoint的作用还在于断点、观测点不是什么情况下都可用的情况下,因为Linux系统有时候为了安全考虑,会随机化新进程的地址空间,这样重启调试程序会导致之前使用绝对地址设置的断点、观测点不可用。

二、中断和继续运行

2.1 断点、监视点和捕获点

  gdb除了普通断点,还支持检测点(watchpoint)、捕获点(catchpoint)两类特殊的断点类型,他们都能使用enable、disable、delete进行管理。检测点会在检测的变量以及使用运算符组合成的表达式的值发生改变的时候暂停程序的执行;catchpoint是在某个事件发生的时候暂停程序的执行,比如C++抛出异常、加载库的时候。
  还需注意的是,GDB显示的是将要执行的代码行,而非执行后的代码行!

2.1.1 普通断点

  break [location]
  location可以是函数名、行号、指令地址,在程序执行到这些指定位置前会暂停下来(暂停时候这些位置的任何代码都不会被执行)。如果没有location参数,则断点会在下一条执行的指令前设置,在最内帧中gdb会在下次执行到该点的时候暂停,这种情况最常见的是在循环中使用。
  使用break +/-offset,可以在当前选中帧中正在执行的源代码行的前面或者后面设置偏移位置的断点。
  break … if cond
  每次指定到该点的时候会先计算cond的状态,当cond!=0的时候断点生效,程序执行暂停下来。而且在同一个位置,可以使用不同的条件设置多个条件断点。
  tbreak args
  只生效一次的临时断点,一旦断点生效程序暂定在这个断点处的时候,该断点会被自动删除掉。
  rbreak regex
  在所有匹配regex正则表达式的函数名处都设置断点,并打印出这些新设置的断点。正则表达式的元字符含义同grep工具的含义(而同shell有所差别)。这在C++设置重载函数断点的时候十分有效。
  rbreak file:regex
  通过一个文件名的前缀,可以缩小rbreak设置断点的范围(否则库的头文件也会被搜索并设置断点)。
  info break|breakpoints
  查看断点(同时还包括watchpoint和catchpoint),同时如果断点被触发了,还会显示触发的次数信息。

  在一个位置上设置断点,其本质上可以对应于多个位置,比如:同名重载函数、C++合成的多个构造函数、模板函数和模板类、inline函数。对于这种单个断点多个地址的情况,gdb会自动在需要的位置插入断点,当使用info break显示的时候,会以breakpoint-number.location-number的方式显示,断点管理可以引用breakpoint-number,或者针对某个子地址的断点操作。
  动态库也支持断点,不过其地址需要在加载库的时候才能解析,因此动态库中的断点在未能解析地址前都是pend状态,同时动态库在程序执行的过程中也可能进行多次加载和卸载的操作,gdb在只要有动态库加载和卸载操作的时候,都会重新计算断点的位置信息。

2.1.2 监视点

  监视点是监视特定内存位置、特定表达式的值是否改变而触发程序暂停执行,而不用去关心该值到底在代码的哪个位置被修改的。监视的表达式可以是:某个变量的引用、强制地址解析(比如(int )0x12345678,你无法watch一个地址,因为地址是永远也不会改变的)、合理的表达式(比如a-b+c/d,gdb会检测其中引用的各个变量)。
  watch [-l|-location] expr [thread thread-id] [mask maskvalue]
  thread-id可以设置特定线程改变expr的时候触发中断,默认情况下针对硬件模式的检测点所有的线程都会检测该表达式;-location会让gdb计算expr的表达式,并将计算的结果作为地址,并探测该地址上的值(数据类型由expr计算结果决定)。
  很多平台都有实现监视点的专有硬件,如果没有GDB也会采用虚拟内存技术来实现监视点,而如果前面两种技术都不可用,则GDB会采用软件实现监事点本身。软件模式速度会非常慢,因为如果不支持硬件模式gdb会每次step并计算检测的表达式,x86构架支持硬件模式,而且监视点的作用也有限,只能当前单个线程能侦测其变化,虽然该值也可能会被其他线程修改。
  监视点在跟踪那种变量不知道哪里被修改的情形特别的有效。不过监视点需要变量在当前上下文可访问的情况下才可以使用,所以在使用中会通常配合断点先到达事发的附近位置,然后再创建对应的监测点。
  watch命令还存在两个变体:rwatch当expr被程序读的时候触发中断;awatch会在程序读取或者写入expr的时候被中断。rwatch和awatch只支持硬件模式的检测点。
  info watchpoints
  查看检测点信息,和短线信息输出格式类似。
  当检测点旧监测的变量或者表达式超过了其作用域的时候,gdb会自动删除失效的观测点,所以当跳出变量的作用域后又进入的话,需要重新创建检测点。尤其当调试程序重启的时候,所有局部变量的观测点都会消失,只有全局变量的检测点会保留。

2.1.3 捕获点

  可以让gdb在某些事件发生的时候暂停执行,比如C++抛出异常、捕获异常、调用fork()、加载动态链接库以及某些系统调用的时候,其格式为catch event,还有一个变体tcatch event设置临时捕获点,其中event的参数可以为:
  throw|rethrow|catch [regex]
  在C++异常抛出、重新抛出、捕获的时候触发,可选使用regex参数限定特定的异常类型(在gcc-4.8开始支持),内置变量$_exception会记录在catchpoint激活时候的异常。
  当异常发生时候,程序通常会停留在libstdc++的异常支持点,此时可以通过使用up命令切换帧跳转到用于异常代码处。
  syscall [name | number | group:groupname | g:groupname] …
  在进入和/或返回系统调用的时候触发。name可以指明catch的系统调用名(定义在/usr/include/asm/unistd.h,且gdb会帮助智能补全),group|g:groupname可以用来指定一组类别的系统调用,比如g:network,通过智能补全可以查看支持的group信息。
  exec|fork|vfork
  load|unload [regex]
  加载和卸载共享库时候触发,可选regex进行过滤。
  signal [signal… | ‘all’]
  可以在软件收到信号的时候触发。gdb本身会占用SIGTRAP和SIGINT两个信号,如果不添加额外参数,会catch除了这两个信号之外的所有信号。
  使用info break命令,watchpoint的信息会被展示出来,可以像普通断点一样管理之。

2.1.4 断点管理(删除、禁用)

  clear
  clear用于清除GDB将执行的下一个指令处的断点,另外一种表述方法就是在最内栈中,可以用来删除正在被暂停的这个断点。
  clear location
  删除指定位置处的断点,可以是func、file:func、linenum、file:linenum。
  delete [breakpoints] [range…]
  参数breakpoints可用可不用,可以指明breackpoints、watchpoints、catchpoints的值或者范围来删除它们,他们的编号在同一编号空间中。
  disable|enable [breakpoints] [range…]
  禁用|启用某些断点,如果没有指明范围,则是针对所有的断点。
  enable [breakpoints] once range…
  临时启用某些断点,一旦这些断点激活,就会被自动disable。
  enable [breakpoints] count cnt range…
  这种情况下临时使能的断点,会在每次被激活的时候递减cnt计数,当其值为0的时候,会禁用该断点。这个count还有一个好用的方式是和ignore相结合,可以在循环中跳过cnt个循环。
  enable [breakpoints] delete range…
  临时启用某些断点,一旦这些断点激活,就会被自动delete,跟通过tbreak设置的断点起始状态一样。

2.1.5 条件断点

  当指定的cond为true的时候,断点就会被触发,其可以用于普通断点,也可以用于watchpoint(虽然值一旦改变就会触发,但是可以用cond过滤得到感兴趣的值)。条件断点可以具有side-effect,比如其可以调用函数、写入日志、执行命令等操作,副作用是可控的,除非该条件断点上又设置了别的断点,而别的断点使程序暂停此时该cond可能就没有被检查调用。
  普通断点和检测点可以在命令break/watch的时候使用 if关键字指明条件,但是catch不能识别if关键字;其上所有的断点都可以在创建之后使用condition关键字指明条件来创建和删除条件:
  condition bnum expr
  创建和删除断点的条件,在expr为true的时候断点才会被触发。
  条件断点的表达式几乎可以是任何类型,可以是库函数、甚至是自己的函数(该函数需要被链接到程序中)。不过需要注意的是,如果调用了库函数,但是库函数没有调试符号(大多数发型库的行为),那么将其使用到条件中时该库函数的返回类型只假定为int,而且强制转换也不行,这一限制不仅在条件表达式中,在GDB任何表达式中调用库函数都是这样的。
  condition bnum
  删除条件后断点,退化成非条件断点。
  ignore bnum cnt
  让断点可以忽略cnt次后生效,如果是使用continue命令恢复程序的执行,也可以使用continue cnt的方式代替ignore指令。如果一个断点有ignore和condition两种属性,那么在ignore到达0之前是不会检查condition条件的。

2.1.6 断点命令列表

  commands [range…]
  … command-list …
  end
  通过command-list的方式,可以让断点(breakpoint、watchpoint、catchpoint)在暂停的时候执行一些命令串(比如用来答应表达式或者寄存器的当前值,使能其他断点等),如果要删除断点的命令串,可以使用commands紧邻着end就可以了。如果没有range参数,该commands默认作用于最后一个创建的断点(或者命令列表,使用rbreak等条件创建的多个断点)。
  在上面说的命令列表中,在执行指定操作后,可以使用标准的命令来恢复程序的执行,比如continue、step等。可以在命令列表的开头使用silent,就不会打印当前断点的额外信息,对于变量等显示,可以使用echo、output、printf等命令来查看,同时还可以使用set命令,让给部分变量赋上正确的值,然后让程序继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) break 31
Breakpoint 4 at 0x434c62: file ./source/main.cpp, line 31.
(gdb) commands
Type commands for breakpoint(s) 4, one per line.
End with a line saying just "end".
>silent
>printf "origin port is %d\n", srv_port
>set srv_port = srv_port + 100
>cont
>end
(gdb) start
port number is: 8911

  如果此时使用info b,可以查看当前断点的命令列表的详情:

1
2
3
4
5
6
7
8
9
(gdb) info b
Num Type Disp Enb Address What
4 breakpoint keep y 0x0000000000434c62 in main(int, char**) at ./source/main.cpp:31
breakpoint already hit 1 time
silent
printf "origin port is %d\n", srv_port
set srv_port = srv_port + 100
cont
(gdb)

2.1.7 动态打印

  上面的printf可以方便的在gdb的终端界面进行打印操作,此外gdb还允许进行打印的灵活配置,该具就是dprintf
  其通过function配置可以随意调用打印函数,而channel配置可以重定向到任何输出流,dprintf的行号可以on-the-fly的任意位置插入打印语句,其格式化输出也是和上面的printf如出一辙。GDB不会检查设置function和channel的合法性,如果非法值会在调试的过程中报错。

1
2
3
4
5
6
7
8
9
10
11
(gdb) set dprintf-style call
(gdb) set dprintf-function fprintf
(gdb) set dprintf-channel stderr
(gdb) dprintf 35,"at line 35, the srv_port is%d\n", srv_port
Dprintf 1 at 0x434cd4: file ./source/main.cpp, line 35.
(gdb) info b
Num Type Disp Enb Address What
1 dprintf keep y 0x0000000000434cd4 in main(int, char**) at ./source/main.cpp:35
breakpoint already hit 1 time
call (void) fprintf (stdout,"at line 35, the srv_port is%d\n", srv_port)
(gdb)

2.1.8 断点保存

  save breakpoints [filename]
  通过上面的命令,可以将所有当前的断点定义和他们的type、commands、ignore counts等信息保存到文件中去,而source命令可以读取这些保存的结果。需要注意的是,对于引用了局部变量的watchpoints很有可能无法重建,因为无法获取当时创建时候的有效上下文信息。
  保存的结果是纯文本文件,其内部实际是一系列的gdb命令列表,可以方便的编译该文件,比如移除不关心的断定定义。

2.2 继续和单步执行

  continue|c|fg [ignore-count]
  从程序最后停止的位置继续执行,且该位置上的所有断点将会被bypassed。可选的ignore-count和ignore作用相同,该参数只有在当前停止是由于断点触发的时候才有效,否则该参数将会被忽略。
  如果想从别的地方继续程序的执行(而不是当前暂停的位置),可以选用return到函数的调用位置、jump到任意位置开始恢复执行。
  step|s
  继续执行当前的程序直到达到一个新的代码行号后,停止程序并将控制转给GDB。step只会停留在代码行的第一条指令处,如果在当前行中有调用带有调试符号的函数,那么step会继续停留,即step会进入到带符号的函数中去,否则其行为类似于next命令而不会进入到函数中去。
  说到这里,需要注意的是step命令只会停留到带有调试符号的代码中,如果在没有调试符号的函数环境中使用step,那么程序会一直执行直到到达一个含有调试信息的函数;否则,需要stepi命令。
  step count
  持续执行count次的step,如果在这之前遇到一个断点或者和step无关的信号,step将会停止。
  next|n [count]
  在当前(最内层)的stack frame中执行到下一个代码行,和step不同的是代码行中的函数调用将会被直接执行而不会停止。类似的,next也只会在代码行的首个指令处停止执行。
  finish|fin
  继续执行直到当前选定的stack frame的函数返回,如果可以的话就打印函数的返回值,简言之就是将当前函数执行完。finish的一个常见用途就是不小心跟踪到一个函数中间去(比如原本应该用next的时候使用了step),此时就可以使用finish带你到原本next应该停留的位置上去。
  和这个命令对应的是return [expr]命令,该命令是取消当前函数的后续执行直接返回,并且可选返回一个表达式作为调用结果。
  until|u
  继续执行直到在当前stack frame中到达超过当前行号的位置(当然也可以手动使用临时断点达到类似效果),这个命令在循环中使用的比较多,而在非循代码下跟next命令比较相似,而且until命令在退出当前stack frame的时候总是会停止程序的执行。手册也表述为执行程序直到达到当前循环体外的下一行代码,实际是达到内存地址比当前地址更高的机器指令,同时注意的是,编译器为了效率可能会重排循环代码,比如GCC使用循环主体底部条件编译程序,这时候until的行为可能会有些诡异,多执行一次until就可以达到目的了。
  until|u location
  相比不带参数的until效率会更高(采用了临时断点方式实现),会执行程序直到特定位置或者退出当前stack frame,所以只有location是在当前stack frame内部的时候才会到达,location可以是行号、地址等任意类型的位置信息。
  该until命令的另外一个好处,是可以跳过(skip over)递归函数调用。

1
2
3
4
5
4 int factorial(int value){
5 if( value > 1)
6 value *= factorial(value - 1);
7 return value;
8 }

  如果当前执行点在第5行,until 7命令会持续整个递归调用,直到value=120的时候在第7行暂停。
  advanced location
  和until比较类似,但是不会跳过递归调用,同时location的位置也不需要限定在当前的stack frame。
  stepi/si count
  执行一条(count条)机器指令,在使用这种方式调试的时候,建议使用display/i $pc显示机器指令的汇编代码。
  nexti/ni count
  和上面类似,只是在遇到函数调用指令的时候不会跟踪函数调用,会直接等待函数返回。

2.3 跳过函数和代码文件

  有些时候调试中可能对某个函数、某个文件中的所有函数或者特定文件中的特定函数不感兴趣。
  比如:foo(boring());如果不想调试boring()函数,方法是先step进入boring(),然后使用finish跳出该函数,再继续调试foo()函数。使用skip命令可以设置忽略某些函数的调试达到上述的效果。
  skip function [linespec]
  执行该命令后,linespec指定的函数名或者linespec行号所在的函数将会被跳过。如果没有指定linespec,那么当前正在被调试的函数名将会被作为参数使用。
  skip file [filename]
  执行该命令后,所有源代码在filename中实现的函数,都会在stepping的过程中被跳过。如果没有指定filename,那么当前调试焦点所在的源代码文件将作为其参数。
  skip [options]
  是一个基本更灵活的命令(不过测试发现极大发行版的gdb不支持该模式),通过-file|-function可以得到上面另个子命令相同的效果,而且可以任意组合使用。更强大的是使用-gfile|-rfunction的正则表达式模式,来指定符合正则表达式的文件后者函数名。

1
2
(gdb) skip -gfile utils/*.cpp
(gdb) skip -rfu ^std::(allocator|basic_string)<.*>::~?\1 *\(

  如果没有任何参数,其效果类似于命令skip function
  info skip
  skip enable/disable/delete [range]
  常规的skip管理命令。

2.4 信号

  信号是操作系统定义在程序中可以异步发生的事件,有些信号是程序正常执行的一部分(比如SIGALRM),而有些信号预示着错误甚至是致命的(比如SIGSEGV),通常会终止程序的执行。很多信号如果没有事先设置好处理handler,默认的处理方式是终止该程序的执行。额外提一下,Linux下进程的内存布局可以通过访问/proc/pid/maps查看。
  GDB可以感知程序中任何信号的发生,可以设置对特定信号的特定处理方式。正常情况下GDB是让非错误的信号(比如SIGALRM)传递到调试的程序,而让错误信号发生的时候停止程序的执行(具体可以使用info signals查看),可以通过handle命令修改特定信号的行为。
  catch signal [sig…|’all’]
  给对应信号设置catchpoint。
  handle sig [keywords…]
  修改GDB对信号的默认处理方式,sig参数可以是SIG打头的信号名、low-high的信号值范围、’all’所有信号;keywords可以是下面的关键字
  nostop、stop:当该信号发生的时候gdb是否要停止程序的执行;stop->print
  print、noprint:当该信号发生的时候是否打印相关信息;noprint->nostop
  pass|noignore、nopass|ignore:GDB决定是否让你的应用程序看到该信号,即应用程序消费该信号。对于非错误信号的默认模式是nostop|noprint|pass,对于错误信号的模式是stop|print|pass。
  当一个信号停止了程序的执行,被调试的应用程序在continue之前是觉察不到该信号的,在GDB报告一个信号的发生的时候,可以通过handle命令的pass|nopass来设置是否传送该信号,然后继续程序的执行。
  GDB对于调试中的信号处理做了一定的优化
  如果信号是nostop的,此时使用stepi/step/next进行跟踪调试的时候,将会优先执行信号处理函数,在信号处理函数返回后再执行主线调试代码,即会step over信号处理。不过信号处理函数本身也可能有断点等因素导致执行流过早的停止。
  当使用了stop的时候,并且程序设置了对应信号的信号处理函数,此时程序如果因为信号暂停的话,则使用step/stepi/next将会跳入到信号处理函数中去跟踪。

2.5 多线程程序

  在多线程程序调试中,具有两种工作模式:all-stop和non-stop。

2.5.1 all-stop mode

  当进程中任意线程因为某种原因停止执行的时候,所有其他线程也同时被GDB停止执行。好处是程序停止的时候所有线程的状态都被保留下来了,可以切换线程查看整个程序当时的所有状态(缺点是整个业务全部阻塞暂停了)。
  在默认设置下,当恢复程序执行的时候,所有的线程也都会立即执行,即使是使用step/stepi/next这种单步跟踪的模式也是如此。线程的调度是操作系统控制的,即使单步跟踪的时候很有可能其他的线程执行了更多指令后,当前线程才执行完单步指令并停止;还有可能单步跟踪后发现自己停止在了其他的线程上面,因为任何时候任何线程因为断点、信号、异常的时候都会停止程序的执行,并且GDB会自动选定切换到该线程上面去(同时会打印Switching的切换信息)。
  有些操作系统支持OS调度锁定的支持,就可以修改上述GDB的默认行为。
  schedule-locking
  off:不会锁定,所有线程可以自由运行;
  on:当程序resume的时候,只有当前线程可以运行;
  step:该模式是为单步模式优化的模式,当跟踪的时候阻止其他线程抢占当前线程。当step的时候其他线程无法运行,而在使用continue、until、finish类似指令的时候其他线程可以自由运行。除非其他线程运行时候触发了断点,否则GDB不会切换当前调试的线程。
  schedule-multiple
  该设置是针对多进程情况下的调试。
  off:只有当前线程所在的进程的所有线程允许恢复执行,该off为默认值。
  on:所有进程的所有线程都可以执行。

2.5.2 non-stop mode

  当进程中任意线程停止时,在跟踪检查停止线程的时候其他的线程任然继续执行,其好处就是对线上系统进行最小化的干扰入侵,整个程序还是可以继续响应外部请求的。
  通过下面的设置可以开启non-stop模式,一般都是在gdb开始的时候或者连接调试目标的时候设置才会生效,且在调试过程中不能切换该模式,不是所有平台都支持non-stop模式,所以即使设置了GDB还是有可能fallback到all-stop模式。

1
2
(gdb) set pagination off
(gdb) set non-stop on

  non-stop模式的验证也十分的简单,在一个多线程的网络程序中先设置断点,此时发送一个请求那么处理线程会被停止,接下来删除该断点,再次发送一个请求,看该请求是否得到正常响应。如果non-stop正常开启生效的话,此时的info threads会显示其他线程都是running的状态。
  non-stop模式下的命令默认都是针对当前线程的,如果要针对所有线程,通常需要使用-a参数,比如continue -a。

2.5.3 线程相关的断点

  break location thread thread-id [if …]
  在多线程模式下,可以设置线程相关的断点(而不是默认针对所有线程),其语法类似,只不过需要使用thread关键字指明GDB分配的线程标识符。当线程退出或者gdb在detach的时候,该线程相关联的断点会被自动删除。

2.5.4 中断的系统调用

  使用GDB调试多线程程序的时候有一个副作用,就是当线程因为断点或其他因素暂停的时候,其他线程此时如果阻塞在系统调用上,此时的系统调用可能会过早的退出!因为多线程程序调试的时候线程之间需要可能会用信号等手段来通信。
  对此,应当培养良好的系统调用使用习惯,多做系统调用返回值的判断。

三、程序的执行的回放

  GDB提供process record and replay target(后面简称R&RT)的功能,可以记录程序的执行过程(execution log),并在后续时间上进行正向、逆向的程序运行。在调试目标运行的时候,如果接下来的执行指令在log中存在,就会进入replay mode,该模式下不会真正的执行对应的指令,而是将事件、寄存器值、内存值直接从日志中提取出来;如果接下来的执行指令不在log中,GDB将会在record mode执行,所有的指令将会按照正常方式执行,GDB还会记录log方便将来reply。
  进程的R&RT支持正向和逆向执行,即使对应的平台底层调试不支持reverse exec,但是通过reply mode可能模拟这个功能,而record mode可能由于平台的限制不支持reverse exec。
  record *method
  开启进程的R&RT模式。默认的方法是使用full recording,其实际是GDB采用软件模拟的方式实现的,该模式支持replay reverse exec,不过不支持non-stop mode和异步执行模式的多线程调试。R&RT只能调试正在执行的进程,所以事先需要先使用run|start命令开启调试进程,然后使用record method进行记录。
  record stop
  停止进程的R&RT,此时所有的log将会被删除,而调试进程将会被终止挥着保持在其最终状态。
  如果是在record模式下(在log的最末尾)使用该命令,被调试的进程将会在下一个要被记录的指令处停止,即如果record了一段时间后停止record,那么调试进程将会保持在类似record从未发生过的状态;如果在replay模式(就是在log的某个中间位置而非末尾)下执行该命令,那么被调试的进程将会在那个点开始进行live正常调试;如果被调试进程退出,或者被detach,那么R&RT将会自动停止。
  record goto begin|start|end|n
  跳转到指定的log位置,除了特殊的begin|start|end,其中的n是log中的instruction的标号。
  record save|restore filename
  log的保存和恢复。如果不指明文件名将会是默认的’gdb_record.process_id’。
  set record full insn-number-max limit|unlimited
  设置log记录的指令的数目,默认值是200,000。如果设定的limit值是个正数,记录的指令数目超过这个限制后GDB会依次从最先的指令中删除这些旧指令以在不超过这个限制的条件下保存新指令记录,如果其参数值是0或者unlimited,那么久不会有保存数的限制,最终保存的多少将受内存大小的限制。
  set record full stop-at-limit on|off
  设置在full方式下当保存指令数目超过限制的生活,如果ON(默认),则GDB会在首次超过限制的生活停止调试进程,并向用户询问是否继续执行下去;如果是OFF,则GDB会自动进行上面删除最旧记录添加最新记录的行为。
  info record
  显示record记录相关信息,包括当前是Record mode还是Replay mode、最小最大指令记录、当前保存的指令数目、可记录最大条目数限制值等信息。
  record delete
  当在reply模式下执行该命令的时候,会删除当前所在所在指令之后的所有log记录,即丢失当前指令之后的所有记录,并开始新的record。

四、栈信息检查和回溯

  由函数调用的原理,一个运行的程序会有多层的stack frame,当被调试程序停止的时候GDB会默认选中当前执行的stack frame(当然后面可以手动选择在各个stack frame之间选择切换),后续的操作都隐含针对当前选中的stack frame。

4.1 栈结构

  栈帧(stack frame)包含的是传递给函数的参数、函数中的局部变量、已经函数执行的地址(返回地址)信息。当程序最开始执行的时候,只有main函数的一个frame,称其为initial frame或者outermost frame,frame会在函数调用的时候创建并在函数返回的时候删除,当前正在执行的栈称为innermost frame,其地址由frame pointer register寄存器保存,从而可以方便的访问调用参数和局部变量。
  GDB会从innermost开始upward,由0开始对frame进行编号,以方便对特定的stack frame进行引用。
  某些编译器提供某种方法使得编译出来的函数没有stack frame,比如gcc的-fomit-frame-pointer选项,为了就是让那些大量调用的函数节省frame的建立时间,不过其缺点是栈回溯变得困难了。

4.2 Backtrace

  从当前执行的frame(innermost, 0)开始,沿着其调用者upward的过程。
  backtrace|bt [n|-n]
  如果没有参数,会打印整个stack,每一行代表一条frame;如果n表示打印innermost n的frame;如果-n表示只打印最outermost n的frame。
  命令info stack是backtrace的别名。
  backtrace|bt full [n|-n]
  参数和上面的类似,但是还会同时打印每frame的局部变量(调用参数会直接在frame的那一行显示出来)。
  
  在多线程程序的调试中,GDB默认只对当前选中的线程显示backtrace,如果要对其他的线程同样执行类似的跟踪,需要使用前缀的方式thread apply all backtrace才能达到效果,尤其对于调试多线程程序的coredump十分有效。
  backtrace中的函数参数、变量的值可能显示不完美,对于非标量类型的变量会用…显示,而有限变量可能会被优化掉(比如调用的过程中使用寄存器传参),此时其值会被用’‘代替。
  默认栈完全回溯会跟踪到main这个outermost层次,如果需要跟踪启动代码,可以设置set backtrace past-main on变量,不过我想一般都用不着这么个层次吧。
  默认情况下backtrace是没有层次限制的(一直顶到main),通过set backtrace limite n|0|unlimited可以进行设置。

4.3 选择栈的frame和查看frame

  上面说过,很多命令都是隐含针对当前选定的frame操作的。因为GDB对frame都进行了编号,所以既可以用编号进行引用选择,也可以采用相对的up/down操作进行frame的切换。
  frame|f n
  使用frame编号进行选择,0代表最innermost(当前正在执行)的栈,最大值就是main frame的编号值。
  frame|f stack-addr
  在stack-addr处选择frame。这通常在frame因为bug被破坏了,导致GDB无法为frame进行编号的生活,以及当程序具有多个stacks并且在他们之间切换的时候。
  up|down|up-silently|down-silently n
  相对形式的切换frame,参数n默认为1,其值也可以为正数或者负数,遍历调用栈可以方便查看各个栈的信息,而不会影响程序的执行路径。

  frame|f
  该命令不会切换当前选择的帧,而是简洁的打印当前帧的一些信息。
  info frame [addr]
  更加详细的打印当前选中frame的信息。
  info args|locals
  前者打印当前frame的调用参数,后者显示所有可访问的局部变量(包括static或者automatic声明的)。

五、调试与源代码文件

5.1 打印源代码

  通过list命令可以显示源代码信息,默认情况下回显示10行源代码,这个设置可以通过变量listsize进行调整。list可以接受0、1、2个参数,使用起来较为的灵活。
  list location
  该location参数可以是行号,也可以是函数名,表示以该行号为中心或者以该函数开始位置为中心,进行listsize代码行的打印。
  list +|-
  向前|向后打印刚刚打印代码的位置。
  list
  这个list会接着打印而不用理会之前的参数。但是+|-的参数会被保留,维持向前、向后的打印顺序。
  **list [first],[last]
  打印指定范围内的代码。

5.2 指定位置信息

5.2.1 行表示方法

  -|+offset
  指定位置是相对于当前行的偏移。不过针对不同的命令,current line的含义也有差异,对于list,表示的是最后一次打印的行,对于breakpoint表示的是在当前stack frame执行停止的位置。
  filename:linenum
  在文件filename的linenum行,如果filename是相对路径名,那么它将会匹配所有的源代码文件。
  function
  指代的是函数体的开始位置。
  function:label
  在function中出现的label。
  filename:function
  在文件filename中出现的函数体的开始位置,指明文件名主要是用于消除函数的二义性。
  label
  在当前stack frame的label标签指定的位置,如果没有当前stack frame的环境(比如被调试的进程还没有运行),则GDB不会去搜索label。

5.2.2 显式的位置表示

  允许用户采用key=value的方式指明参数名和参数值,这种使用方法在函数、label、文件等具有相同名字的时候具有很大的区分性,同时准确的位置信息也会让GDB更快的定位目标。
  -source filename
  通常单独的source没有意义,它是和下面的function、line等结合使用的。为了消除可能的文件名歧义,建议对于常用的文件名增加一些额外的路径前缀。
  -function function
  -label label
  -line number
  这里的line既可以是绝对的行号,也可以是相对的(+|-3)行号位置。比如break -s main.c -li 3

3.2.3 地址信息位置表示

  通常的地址信息会使用addr的形式来表示。
  expression
  funcaddr
  *’filename’:funcaddr

5.3 修改源代码

  edit命令可以修改源代码文件,其编辑的位置是在调用edit命令时候,调试程序中当前活动的那一行。可以通过给edit命令额外的参数确定位置:number 当前活动文件的行号,function 函数定义的开始位置。
  EDITOR环境变量可以指定修改源代码所使用的编辑器名字,因此通过设置export EDITOR=vim就可以完成默认编辑器的设定。
  打印完是不是可以直接shell make编译啦!

5.4 源代码和机器码的对应及反汇编

  通过info line可以建立源代码行号和程序地址之前的对应关系,而disassemble可以显示一段地址中机器指令。GDB还有一个变量’disassemble-next-line’,当打开他的时候会在程序停止的时候自动显示要执行的下一行源代码的第一条汇编代码,而如果下一条执行的指令没有调试信息,就会显示下一条机器指令的反汇编结果。
  info line location
  打印locatin代表的源代码行编译后的其实地址和结束地址。当然也可以进行反方向的隐射查询,通过地址查询其对应源代码的那一行

1
2
3
4
(gdb) info line 14
Line 14 of "aa.cpp" starts at address 0x4008d9 <main(int, char**)+47> and ends at 0x4008e2 <main(int, char**)+56>.
(gdb) info line *0x4008e0
Line 14 of "aa.cpp" starts at address 0x4008d9 <main(int, char**)+47> and ends at 0x4008e2 <main(int, char**)+56>.

  disassemble [/m|/s|/r] [start, end]|[start, +len]
  disassemble [/m|/s|/r] location
  可以显示一段地址范围的反汇编信息,或者函数名等其他代表的地址信息的反汇编结果,如果是跨文件的记住使用disassemble ‘foo.c’::bar这样的形式。/m或者/s参数可以显示源代码和反汇编代码同时交叉显示的效果,/r还会以hex的方式显示机器码,当然这对一般人来说使用较少。
  set disassembly-flavor intel|att
  反汇编是显示intel还是att的风格,个人比较偏向att的格式,这也是GDB的默认行为。

六、查看数据  

  最常用的检查数据的命令就是print|p|inspect,其接受一个表达式作为参数,会根据调试的语言对表达式进行求值,并将其结果打印出来。

1
print expr

  一种更加底层检查数据的方式是使用x命令,该命令会检查内存中特定地址的数据并通过特定的格式显示出来。
  如果想知道表达式的类型而不是关系其值的话,可以使用ptype expr命令,就可以显示出struct、class等复杂变量的类型信息。

6.1 表达式

  GDB的表达式可以支持普通表达式、条件表达式、函数调用、类型转换、字符串常量、宏等类型,同时使用’{elem1, elem2, …}’这种表达式还可以创数组常量。
  @ 该操作符用于将一段内存当做数组使用
  :: 该曹组福用于指明在文件或者函数中的变量
  {type} addr addr可以是一个整形或者指针,该表达式用于将指定位置的内存引用为type类型的变量。

6.2 二义性的表达式

  由于重载和模板机制,在C++这类语言中二义性的表达式非常常见,在这种情况下通常需要给出函数的参数签名’function(types)’等信息才能和其他同名对象进行区分。GDB会自动探测二义性的存在,给出所有可能性的选择列表,并最终给出’>’提示符信息让用户选择,固定的选项是’[0] cancel’和’[1] all’,后面的选项是各个明细,比如break String::after这样的命令,用空格分割的选项才会打上断点。
  set multiple-symbols all|ask|cancel
  默认参数值是all,当某个命令的表达式有多个选项的时候,GDB会自动选择所有可能的选项,但是某些环境下必须有一个选项的时候,GDB会显示出上面所述的菜单供选择;ask表示只要二义性检测到了,就会给出菜单让做出选择;cancel表示当debuger探测到二义性的时候会当做错误,相应的命令被放弃执行。

6.3 程序变量

  程序的变量算是最常被在表达式中使用的,变量是在当前选择的stack frame中可见的,他们可以是:全局变量、file-static变量、根据变成语言语法规则可见的变量。这里有一个例外,就是你可以引用file-scope的变量或者函数,即使当前执行的上下文不在该文件当中。
  各个函数中的变量,以及各个文件中的函数或者变量可能会出现重名的情况,这个时候就需要’::’操作符进行指定

1
2
3
file::variable  file::function
function::variable
(gdb) p 'aa.cpp'::main::b

  在调试的时候,有时候会出现在函数进入作用域和返回的生活,某些局部变量的值是错误的,尤其是当进行机器指令单步的时候,因为很多机器上stack frame的建立(包括局部变量的定义)和函数返回的时候stack frame的销毁,都不是单个机器指令可以解决的,所以在stack frame完全建立和销毁的时候,检查到的变量值很可能是异常的。
  一些没有被使用的变量也可能被编译器优化掉,这时候价差这些变量的值可以提示他们不存在。

6.4 人工数组

  通常用于需要打印内存中连续对象的时候以及变长数组的时候特别的有用,其语法结构是*addr@len,在@的左边是目标数组的第一个元素,右边的操作数是数组的期望长度,所得到的结果是一个元素跟左操作数类型一样的数组。

1
2
int *arry = (int *)malloc(len * sizeof (int));
p *arry@len

  另外一种创建人工数组的方式是使用cast类型转换,将一个值重新使用数组去解释,比如:

1
2
3
4
(gdb) p/x (short[4])0x123456789AL
$4 = {0x789a, 0x3456, 0x12, 0x0}
(gdb) p/x (short[])0x123456789AL
$5 = {0x789a, 0x3456, 0x12, 0x0}

  当然如果你不设置数组的长度,而直接使用(type[])value,那么GDB会自动计算数组的长度sizeof(value)/sizeof(type)
  有时候人工数组也不见得方便,比如存储的元素是有规律的但是在物理上不是连续的,那么上面的方法就没法使用了,这个时候最常用的是用convenience变量,然后直接用RET依次遍历元素:

1
2
3
4
set $i = 0
p dtab[$i++]->fv
RET
RET...

6.5 输出格式

  print默认是根据表达式的类型自动显示的,在大多数情况下都可以工作的很好,但是在print中可以指定输出格式得到个性化得显示结果。下面的格式参数通过/和print命令相连,比如print/x $pc
  x 把表达式当做一个int,并使用hex方式显示该整数
  d|u 作为有符号|无符号十进制整数打印
  o|t 作为八进制|二进制数打印
  a 在打印地址的时候,通过最近符号加偏移的方式解析这个地址,和info symbol得到的结果类似
  c 以十进制和字符的方式显示
  f 以浮点数的形式打印
  s 如果可以以字符串的方式显示。如果只想的目标是单字节的数据,则表示为null结尾的字符串;如果是单字节的数组,则表示为定长度的字符串;其他情况正常方式显示
  z 类似x以十六进制显示数据,但是头部的字符会使用0补齐
  同时,如果字符串是中文,而且非默认自动(UTF-8)编码的,可以使用命令show host-charset切换正常显示中文。而且对于字符串,print达到200个就使用…不完全显示了,所以如果想看超长完整字符串内容,可以使用set print element 0来设置一下。

6.6 内存检查

  通过命令’x’可以进行内存数据检查。其命名格式为(nfu是控制内存显示的大小、格式等参数信息的)
  x /nfu addr
  n(显示数目) 默认值是1,指明需要单位内存(u指明)显示的数目。如果n的值是负数,那么将会从addr开始进行反方向的内存显示。
  f(显示格式) 显示的格式参数和上面print命令的输出格式相类似(包括:x、d、u、o、t、a、c、f、s),初次还包括i用于机器指令的显示。默认情况下是使用x进行hex显示,该值会随着x|print命令的操作而更新。
  u 显示单位尺寸 b(bytes,1字节)、h(半字,2字节)、w(字,4字节)、g(gaint字,8字节),同样该参数会随着每次使用x被自动记住并在下次命令中自动应用。对于i显示机器指令该参数会被忽略,而对于s该参数默认是b。
  由于fu两个格式参数的空间是不重叠的,所以可以以任意顺序显示,但是n必须在他俩之前。
  addr 指定需要检查的起始地址位置,比如函数名等,其最终会被解析成一个整形的地址值。

1
2
(gdb) x/20i main
(gdb) x/4xw $sp

6.7 自动显示

  对于需要经常查看的表达式,可以使用display命令将其添加到自动显示列表中,这样每次程序停止执行的时候,就会自动显示该表达式的值,加入的表达式会自动产生一个递增的整形编号。display也可以使用print|x命令的参数进行格式化输出控制,而且其内部如果发现格式化参数包含i|s或者数字,就会调用x;否则会调用print进行输出。
  display[/fmt] expr
  display/fmt addr 使用’i’、’s’格式,或者指定了数字
  将表达式或者要检查的内存地址添加到自动显示列表中去。
  比如想要自动显示下一条要执行的指令,可以做如下添加:display/i $pc
  undisplay dnums…
  将对应编号的表达式或者内存从自动显示列表中删除。列表可以使用逗号’,’进行分割,以及使用2-4这种范围表示。
  info display
  enable|disable display dnums…
  查看、禁用或者使能自动显示。
  display
  进行自动显示列表中表达式值的显示,就像程序刚停止时候自动显示的效果相同。
  如果添加自动显示的表达式引用到了局部变量,GDB会自动考虑其上下文信息,如果执行的上下文中该局部变量没有定义则不会显示其值,而如果下次再次进入定义该变量的上下文,则该表达式变得有意义会再次显示其值。无论如何,info display总会显示该表达式的相关信息。

6.8 数值历史(Value History)

  通过使用print命令显示的值会被自动保存在GDB的数值历史当中,该值会一直被保留直到符号表被重新读取或者放弃的时候(比如使用file或symbol-file),此时所有的值历史将会被丢弃。在使用print打印值的时候,会将值编以整形的历史编号,然后可以使用$num的方式方便的引用,单个的$表示最近的数值历史,而$$表示第二新的数值历史,$$n表示倒数第n新的数值历史(所以可以推断$$0==$; $$1==$$;)。
  比如刚刚打印了一个指向结构体的指针,那么print $就可以显示结构体的信息,而命令print $.nex甚至可以显示结构体中的某些字段。

6.9 Convenience变量

  GDB允许自由创建便捷变量用于保存值,后续可以方便的引用该值,该类型的变量由GDB管理而与正在调试的程序没有直接的关联。便捷变量也是使用符号$打头的,可以使用任意名字(除了GDB使用的寄存器名)。
  在任意时候使用一个便捷变量都会创建他,如果没有提供初始化值那么该变量就是void,直到给其进行赋值操作为止。便捷变量没有固定的类型,可以为普通变量、数组、结构体等,而且其类型可以在赋值过程中改变(为当前值的类型)。
  show convenience|conv
  显示道目前所有的便捷变量和便捷函数,以及其值。
  init-if-undefined $variable = expr
  如果该变量还没有被创建或初始化,则创建这个变量。如果该变量已经被创建了,则不会创建和初始化该变量,并且expr表达式也不会被求值,所以这种情况下也不会有expr的副作用发生。
  便捷变量的一种经典用法,就是之前提到的连续查看变量时候用于计数作用:

1
2
set $i = 0
print bar[$i++]->contents

  下面的一些便捷变量是GDB自动创建的:
  $_exitcode
  当调试程序终止的时候,GDB将会根据程序的退出值自动设置该变量,并且将$_exitsignal变量设置为void。
  $_exitsignal
  当调试中的程序因为一个未捕获信号而终止,此时GDB会自动将变量$_exitsignal设置为该信号,同时重置变量$_exitcode为void。

6.10 Convenience函数

  便捷函数和便捷变量一样使用$打头引用,其可以像一个普通函数一样在表达式中使用,便捷函数只被GDB内部使用。

1
(gdb) print $_isvoid ($_exitsignal)

  $_isvoid (expr)
  如果expr是void则返回1,否则返回0。
  GDB还有很多的便捷函数支持,但是需要在编译GDB的时候提供Python的支持才可以使用。下面的这些函数意义显而易见,就不在啰嗦了。
  $_memeq(buf1, buf2, length); $_regex(str, regex); $_streq(str1, str2); $_strlen(str)

6.11 寄存器

GDB可以像普通变量一样引用机器的寄存器值,通过$打头。
  info registers
  info all-registers
显示机器所有寄存器的名字和其当前值信息。后者比前者会多出浮点寄存器和向量寄存器。

6.12 操作系统信息

通过直接的info os可以显示出GDB支持的操作系统信息类别。
  info os infotype
  files 列出目标上面打开的文件信息。
  modules 列出目标上加载的内核模块信息。
  msg 列出目标上所有的Sys V消息队列信息。
  processes 列出目标上所有的进程信息。
  semaphores 列出目标上所有Sys V信号量信息。
  shm 列出目标上所有Sys V共享内存信息。
  sockets 列出目标上所有Internet-domain socket信息。
  threads 列出目标上所有线程信息。

6.13 内存和文件拷贝

  通过dump、append、restore命令可以在目标机之间传递数据和内存信息。dump命令和append命令将数据写入(追加)到文件中,而restore会读取文件内容并将其加载到调试进程的指定内存位置中。GDB默认使用binary的文件格式,推荐使用该格式。
  dump|append memory filename start_addr end_addr
  dump|append value filename expr
  restore filename binary bias start end
  其中一个比较重要的参数是bias,如果其值为非0,那么这个值将会作为偏移添加到文件中所有地址值上面。因为二进制文件总是从地址0开始,所以在恢复的时候需要添加这个偏移值。

6.14 产生core文件

  core文件是运行进程的内存镜像和运行状态信息(比如寄存器等)的集合。通过配置,操作系统可以在程序挂掉的时候自动产生coredump文件,而在GDB调试的时候,也支持使用命令方式手动创建coredump文件,这样就可以快速创建一个程序在当时的快照。
  generate-core-file|gcore [file]
  如果没有提供名字,那么默认将产生core.pid文件名格式的dump文件。

6.15 内存查找

  使用命令find可以对内存进行序列字节的查找操作。
  find [/sn] start_addr, +len, val1 [, val2, …]
  find [/sn] start_addr, end_addr, val1 [, val2, …]
  该命令会在内存中搜索var1、var2…字符序,其范围从startaddr开始,以结束地址或者长度结束。
  参数s表示搜索的大小,可以是b、h、w、g(跟之前的x命令一样),如果该参数没有指定,GDB会考虑当前调试程序的语言环境确定,比如0x43这种整数就默认为w(4字节)类型;n表示打印最大搜索命中数量,默认行为是全部打印。该命令还可以搜索字符串,字符串使用双引号括住。
  对于查找结果,命中数目保存在变量$numfound中,而最后一个值命令的地址保存在**$
**中。

七、跟踪点(Tracepoint)

  有些情况不适合把线上的业务停下来,进行长时间的跟踪和调试;还有些行为只有在线上实时运行的环境才能重现,延时或者线下环境无法复现的问题,针对这种类型的情况,需要不打断程序的运行去观察程序的行为。
  通过GDB的tracecollect命令,可以在程序中设置跟踪点,然后在跟踪点到达(命中hit)的时候计算事先构造的表达式的值;接下来,通过tfind命令,可以检查这些跟踪点中表达式在当时所记录下来的值。当前跟踪点的功能值在remote target的情况下支持,该功能是在remote stub中实现的,但是当前情况下的GDB还没有实现,目前主要使用的方式是使用文件的方式操作。

7.1 设置跟踪点

  跟踪点实际上是一种特殊的断点,所以可以使用任何标准断点的命令来操作跟踪点,和断点不同的是不支持ignore count、不支持针对特定于线程的跟踪点。对于每一个跟踪点,可以事先指明到达的时候所需要搜集的数据,包括寄存器、局部变量和全局变量,之后可以使用GDB的命令来检查这些在当时收集到的数据值。
  跟踪点是动态跟踪工具,意味着这些跟踪点可以被插入在目标的任何位置。
  静态跟踪点暂且不表。

7.1.1 创建和删除跟踪点

  trace location
  **其中的location可以是和break命令参数一样的任意有效位置,通过trace命令可以创建一个跟踪点,当debuger遇到的时候会暂停,收集完相应地数据后继续执行。GDB还支持pending tracepoint,类似于断点的原理,表示在一些共享库还没有加载的时候地址无法解析。

1
2
3
trace foo.c:121 trace *0x12322 trace +2
trace my_function //函数的第一行
trace *my_function //函数的准确起始地址

  trace location if *cond
  带有条件的,只有当cond取值为true(非零)的时候,才会进行数据的收集,避免每次到达跟踪点都收集数据导致数据量暴增,而往往很多情况下的数据是没有价值的。
  条件跟踪点既可以在创建的时候使用if进行限定,也可以后续使用类似breakpoint情况下的condition命令进行设定修改。和断点不同的是,GDB不会自己去对条件表达式进行取值,而是将该表达式编码给agent(独立于GDB)。
  delete tracepoint [num]
  永久性地删除一个或者所有(不带参数)的跟踪点。
  disable|enable tracepoint [num]
  禁用|使能某个或者所有的跟踪点。
  info tracepoints [num…]
  显示某个或者全部跟踪点的信息,使用方法和info breakpoints相同。

7.1.2 跟踪点计数

  passcount [n [num]]
  passcount是一种自动停止trace experiment的方式,当tracepoint被命中n次之后trace experiment会自动停止。
  当使用passcount带有n但是不带num参数的时候,n将会作用于最近添加的跟踪点。如果不使用passcount进行相关设定,默认行为的跟踪点将会一直运行直到被用户显式地停止。

7.1.3 跟踪状态变量

  trace state variable是一种特殊类型的变量,它是在target-side创建和管理的(但是对GDB可见),和GDB的convenience变量语法相同,必须采用tvariable命令显式创建,并且总是64位的整形。
  虽然trace state variable是被target端管理的,但是可以在GDB中像使用convenience variable一样使用它们,GDB会在trace experiment运行的时候从target端获取该变量的当前值。该变量也是使用符号$打头的,和GDB其他变量在同一个名字空间中,所以需要注意不能重名。
  tvariable $name [ = expr]
  创建名为$name的trace state variable,并且可选地给其一个初始值。初始值得表达式expr是在该变量创建的时候就进行取值的,得到的结果将会转换成64位整形类型。后续再使用tvariable接上已经存在的变量名的时候,实际不会进行创建操作,而是将之前的变量进行重新赋值。如果没有提供初始值,默认初始值是0。
  info tvariable
  delete tvariable [ $name … ]
  如果没有提供额外参数,将会删除所有的变量。

7.1.4 跟踪点行为列表

  actions [num]
  用于指明在跟踪点被命中的时候所需要执行的操作,如果没有提供num参数,那么该设置默认针对于最新创建的跟踪点。
  这些行为列表中的命令一行一个,通过单个end标示着结束,当前所支持的行为包括collect、teval和while-stepping,其实使用起来跟command命令十分的类似,只不过其命令的内容只能是上面允许的,而不能是其他的GDB调试命令。要想删除跟踪点的actions,则提供空的end就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) trace sum
Tracepoint 1 at 0x4008a4: file aa.cpp, line 5.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect a, b
>collect $regs
>while-stepping 4 #single step 4 times, collect data
>collect c
>end
>end
(gdb)

  collect [/mods] expr1, expr2, …
  收集用逗号分隔的各个表达式的值。收集的对象除了全局变量、静态变量、局部变量,还可以包含:
  $regs 所有的寄存器
  $args 函数的所有调用参数
  $locals 所有局部变量
  $_ret 函数的返回地址
  teval expr1, expr2, …
  当跟踪点被命中的时候会计算表达式的值,该命令接收使用逗号风格的多个表达式。取值表达式的结果将会被丢弃,所以该命令的主要用处是在不添加这些值在trace buffer的同时,将表达式计算并赋值给trace state variable。
  while-stepping n
  在跟踪点之后进行n步的单步调试,并且在每次单步执行后进行数据的搜集,需要收集的数据在while-stepping后使用collect表示,并使用end结尾。
  set default-collect expr1, expr2
  该变量可以是使用逗号分隔的一大串表达式表明默认需要收集的值,表明在每个跟踪点默认都要搜集的数据。注意的是这个表达式是在每个跟踪点独立解析的,所以相同的表达式、名字在各个跟踪点可能被解析为不同对象。

7.1.5 开始和停止跟踪执行

  tstart
  该命令启动trace experiment并且开始搜集数据,该命令的副作用是丢弃所有在trace buffer中的已经存在的历史数据。如果给该命令提供了参数,他们将会被视为note并被存储起来。
  tstop
  该命令会停止trace experiment,如果某个跟踪点已经达到其passcount,或者trace buffer的缓冲区已经满了,那么trace experiment会自动停止。如果给该命令提供了参数,他们将会被视为note并被存储起来。
  tstatus
  用于显示跟踪和收集到的数据信息。

7.1.6 Tracepoints的限制

  因为收集数据的行为是在target自动进行的,所以GDB无法做出细致的控制,而且后续检查数据的时候也仅基于已经收集到的历史数据。
  (1) 因为跟踪点的首要目标是收集作值,所以GDB的复杂表达式往往是不可用的。在收集的过程中不能调用函数、进行类型转换、访问convenience变量、修改变量值等操作。

7.2 使用跟踪记录的数据调试

  当trace experiment结束后,可以使用GDB命令来查看已经收集到的数据,这些收集的数据都是每次命中跟踪点收集到数据的快照。在检查数据的时候都会选中一个快照,然后所有GDB的命令(比如print、info registors、backtrace等)的请求,都会像当时跟踪点命中时候的状态值进行对应返回,而此时如果请求那些没有收集到的数据将会失败。

7.2.1 tfind

  通过使用命令tfind n可以查看缓冲区中第n个快照,缓冲区中的快照都是从0开始连续递增形式编号的,gdb每命中一个tracepoint就会产生一个快照,如果tfind你没有接参数,将会依次访问下一个快照。
  tfind start
  选择快照列表中的第一个快照,跟tfind 0命令效果等价。
  tfind none|stop
  停止基于trace snapshots的跟踪,恢复在线实施跟踪。
  tfind |-
  如果tfind不带参数,则继续选择相对于当前选择快照的下一个快照;如果使用-,则表示向前的顺序选择前一个快照。
  tfind tracepoint num
  因为snapshot是连续编号的,但是每个snapshot可能对应任意的tracepoint,通过这个命令可以选择对应于编号num的tracepoint的下一个快照,可以将其想象成连续跟踪特定tracepoint的变化状态,如果参数num被省略,则num和当前选中的快照的tracepoint编号相同。
  tfind pc addr
  选择和特定地址addr相关的下一个快照。当然关于地址含有outside、range等各种变体。
  tfind line [file:]n
  选择和特定行号关联的下一个快照。

7.2.2 tdump

  该命令不接受任何参数,其用于打印当前trace snapshot所收集到的所有数据。tdump通过扫描跟踪点的当前收集行为,然后按照其行为打印对应每个表达式的值。

7.3 使用Trace Files

  可以将线上收集到的数据保存到文件当中,后续可以通过target tfile进行加载,就像使用原始的trace数据一样访问。
  tsave [-r] filename
  默认的情况下该命令保存在host的文件系统中,所以GDB将会从target上面拷贝对应的数据到本地保存。如果target支持的话,可以通过’-r’参数,直接将数据保存在target的文件系统上面,这样在收集到的数据量很大的时候会更加的高效。
  target tfile filename
  加载tfile数据,使后面可以感觉和target在线访问的效果,只不过只能访问历史数据而不能进行任何新的trace experiment。

八、GDB对编程语言的支持

8.1 GDB对C/C++语言的支持

8.1.1 GDB对C/C++语言的支持

  因为总所周知的原因,GCC和GDB对C/C++的支持一直很不错,包括在gdb命令行中的表达式、操作符的使用就可见一斑了。C++要比C复杂的多,所以下面罗列了一些GDB和C++调试的一些东西。
  C++成员函数的调用是支持的,形如:count = aml->GetOriginal(x, y);
  当成员函数正在被调试(作为选中的stack frame)的时候,调试的表达式具有和成员函数一样的名字空间;
  在某种程度上支持C++重载函数的调用,GDB会进行解析并指向正确的函数调用。但是限制是:GDB不支持用户定义类型转换所支撑的函数重载,以及调用不纯在的构造函数,没有对应类型实例化的模板函数,也不能处理省略的和默认的函数参数。对于数值类型转换、类型提升都是正常支持的。函数重载默认是使能的,除非使用set overload-resolution off关闭之,然后就可以显式调用重载版本的重载函数了:p’foo(char,int)’(‘x’,13),自然GDB的补全功能不会让你输的很累的。
  GDB支持C++的::名字解析操作符,正如同函数代码中使用方式一样。

8.1.2 针对C++的其他GDB特性

  针对C++的重载特性,GDB可以自动补全所有的重载版本列表,方便识别和选择;rbreak regex这种正则形式的断点,可以在所有重载版本的函数上实现添加。
  catch throw|rethrow|catch 可以提供C++异常处理的监测支持。
  ptype typename 可以显示该类型的继承关系,同时其成员变量和成员函数也显示的较为详细。
  info vtbl expr 对多态机制虚函数表的支持。

8.2 C语言预处理宏

  宏本身是个比较麻烦的东西,因为可以某些点定义、某些点取消定义、某些点重新定义,GDB支持对含有宏的表达式进行展开并显示展开后结果的功能。如果要让编译后的程序具有宏调试功能,需要额外的编译参数-gdwarf-2和-g3(-g是不够的)

1
➜  gdb gcc -gdwarf-2 -g3 sample.c -o sample

  上面的gdwarf-2具有gdwarf-3、gdwarf-4等版本,推荐支持的情况下使用最新版本的调试格式。
  macro exp|expand expression
  显示expression中所有宏被展开后的结果。因为GDB只会进行宏展开而不会对表达式取值,所以这里的expression不需要是合法的表达式。
  info macro [-a|-all] [–] macro
  显示当前的或者全部的macro宏定义,同时显示该宏定义所在的源代码文件及其所在行号位置信息。其中的–表示参数列表结束,因为C中有些宏是可以使用-开头的,这里用于消除歧义作用。
  info macros location
  显示所有在location位置有效的宏定义及他们的位置信息。(显示结果有点多哦)
  macro define macro replacement-list
  macro define macro(arglist) replacement-list
  自定义宏及其展开式,通过该命令创建的宏会在GDB求值的每个表达式中都会生效(只对GDB本身作用,与代码中使用的宏无关),直到其显式使用macro undef进行删除,同时该宏还会覆盖程序中同名宏的展开(有点诡异)。
  macro undef macro
  只会删除上面使用macro define定义的宏,而不会删除程序代码中定义的宏。
  macro list
  显示所有通过macro define定义的宏。
  除了通常在源代码中执行宏定义,还可以在编译的时候在命令行中通过‘-Dname=value’的方式定义,这样的宏也支持使用info macro命令查看,不过其行号信息显示为0。

九 修改调试程序的执行

  例如在程序调试的过程中发现了明显的错误,想要验证修改该简单错误后程序执行会不会得到正确结果的时候,GDB的alter execution功能就会比较的好用,支持比如:修改某个寄存器、变量、内存的值,向程序发送某个信号,从不同的地方重新执行,直接跳出函数执行等操作。

9.1 给变量重新赋值

  下面都是将变量x修改为新值,区别是使用print会同时打印这个变量的值,同时还会降其放入历史记录中去。在使用set的时候还需注意,因为gdb中有很多变量也是使用set设置更新的(比如width),此时就应该使用set variable|var而不能使用set简写了,并且使用后者总是一个好习惯,可以防止莫名其妙的修改了环境变量值而不知。

1
2
(gdb) print x=8
(gdb) set x=8

  GDB中对于变量类型的转换要比C/C++语言宽松的多,比如可以把整数当做指针使用等,也可以直接将值放到指定内存位置上:

1
2
3
4
5
(gdb) print &a
$2 = (int *) 0x7fffffffe1ec
(gdb) set {int}0x7fffffffe1ec = 9789
(gdb) print a
$3 = 9789

9.2 从不同位置恢复执行

  默认使用continue恢复程序执行的时候,会从之前停止的位置继续执行,而使用jump命令可以指定程序恢复执行的位置。该命令很多时候是调回已经执行的代码,重新设置某些变量,然后再次执行下来。
  jump|j location
  在location的位置恢复执行,如果该处有断点则会立即停止,而且有时候为了达到这个效果会同tbreak组合使用。jump除了修改程序计数器之外,不会修改当前stack frame、stack pointer、内存信息和其他的寄存器信息,所以如果使用jump调到别的函数位置开始执行,那么结果将会变得十分离奇,因此如果location的地址不在当前执行函数范围内,GDB会给出信息要求确认。
  将$pc直接设置新值,然后运行continue也会得到相同的效果。

9.3 向程序发送信号

  signal sig
  恢复程序的执行并立即给予其sig信号。如果sig为0,程序将不会收到信号并恢复执行,这常常用于程序因为收到某个信号而停下来,该信号原本会传递给应用程序的时候,通过’signal 0’可以让程序不收到该信号而继续执行。注意在多线程的环境下,当恢复执行程序的时候信号会被传送到当前选定的线程(而可能不是最后停止下来的线程),因此如果要使用’signal 0’屏蔽信号,必须先选中对应正确的线程,否则GDB也会探测并给出确认提示。
  此处的signal发送信号和命令kill发送信号是不同的,后者仍然会通过GDB根据handle觉得过滤处理,而signal会直接将信号传递给调试的应用程序。
  queue-signal sig
  将sig信号排队给当前线程,并在程序恢复执行的时候立即发送,要求sig的handle必须是pass的,否则GDB会报错。
  sig参数还可以是0,起效果是当前线程所有排队的信号将会被清空,程序恢复执行的时候将没有信号发给该线程。该命令和上面signal不同的是signal会导致程序的恢复执行,再则queue-signal发送的信号必须是pass处理的信号。

9.4 函数中返回 

  return [expr]
  该命令可以取消一个函数的执行,如果提供了expr表达式,则该表达式的值将会作为函数的返回值。
  当使用了该命令,GDB会放弃当前选定的stack frame及其包含在其内部的所有stack frame,使得该函数的调用者作为最内层stack frame。return命令不会恢复程序的执行,而是使得程序的状态处于函数刚刚返回的状态,与其不同的是finish命令,会恢复函数的执行直到函数函数的正常范围时停止。

9.5 调用程序的函数

  print expr
  对表达式expr求值,并打印显示结果,其表达式可以包含对被调试程序中的函数的调用。
  call expr
  同样对表达式expr求值,但是不显示void返回函数的结果,如果调用的函数本身会返回值,那么该返回的值还是会被打印,并添加到值历史记录中去。

十、调试目标和远程调试

  GDB的调试目标可以是process(进程)、exec file、core file、recording sessions,以及突破单机限制采用串口线、网络相连的remote调试目标。调试目标可以使用target命令进行设置,target+TAB可以查看当前gdb所支持的调试目标的种类。
  target type parameters
  参数type常见的有exec|core,这种方式也可以使用exec-file、core-file方式指定,甚至在gdb启动的时候就作为启动参数附加上去。target工具最常用的,还是在于对远程调试的支持上。远程调试,是主要在于一些运行程序的远程操作系统的限制(比如内核限制、内存限制)导致无法全功能运行gdb调试器的情况下尤为的有用,此外就是gdbsever和gdb规定了一套通信协议,而前者尺寸比后者要小的多,运行起来更轻便的同时,也容易在一个新平台上面进行快速的移植和支持。

10.1 连接到远程调试目标

10.1.1 两种远程连接模式

  GDB支持两种模式的远程调试连接:remote和extend-remote,两者基本功能相同,但是在处理出错、退出等细节性的方面差异很大。
  target remote
  当调试的程序退出或者detach后(还比如在gdb中使用kill主动杀死正在调试的进程),target将会断开连接,当使用了gdbserver的时候,gdbserver也会自动退出。
  gdbserver的启动模式有三种:PROG、–attach、–multi,前两者是在gdbserver启动的时候指定调试的二进制程序或者要attach的调试进程,在remote模式下只能连接这两种模式的gdbserver。
  不支持run命令,连接到远程后被调试程序已经运行了,此时可以使用step、continue等各项调试指令。
  不支持attach命令,必须在启动的时候通过–attach参数指定。
  target extend-remote
  当调试的程序退出或者detach后,target任然保持着和gdbserver连接(此时只有gdb退出后,gdbserver才会reopen侦听),即使当前没有调试程序在运行。使用–once参数可以让gdbserver在第一个连接断开后退出,否则如果需要使远程的gdbserver退出,可以使用monitor exit命令。
  extend-remote除了上面PROG、–attach外,还可以连接使用–multi模式启动的gdbserver,连接后在使用命令设置远程调试文件或者远程attach进程。
  支持使用run命令,启动的程序是通过set remote exec-file的方式指定的。如果gdbserver启动的时候指明了调试文件,那么也不需要使用run命令了,可以类似使用step、continue等命令恢复程序执行。
  支持attach命令。

10.1.2 指明Host和Target文件

  具有调试符号的执行文件才可以用于调试。在远程调试模式下,允许GDB通过建立的调试连接访问远程的程序文件(remote program file access)。
  我们将运行gdb的成为Host,运行gdbserver的称为Target,如果远程程序文件访问是支持的,那么允许Target调试的程序是stripped之后的(确实调试符号),只需要Host加载的程序是带有调试符号的就可以了,除此之外还需要使用set sysroot的方式指明其他组件和库的调试信息。即使此时Host和Target的程序不一致,但是也必须保证后者是前者strip得到的,否则调试将会有异样的结果。

10.1.3 一些远程调试相关命令

  target remote|extended-remote serial-device
  target remote|extended-remote [tcp:]host:port
  host主机可以是主机名或者IP地址,如果host和target运行在同一台主机上,则host字段可以被省略(:占位还是需要保留)。
  target remote|extended-remote udp:host:port
  target remote|extended-remote | command
  在后台运行command,并通过管道与之通信,command必须是shell的命令并使用/bin/sh进行解析。

  detach
  当完成调试的时候,可以使用该命令进行对GDB控制的释放,当使用remote模式连接的时候,此时GDB可以自由连接其他的target了;而如果使用extend-remote连接的时候,此时GDB仍然处于和target连接状态。
  disconnect
  断开和target的连接,此时GDB可以自由连接其他的target了。
  monitor cmd
  允许想远程发送任意命令,被远程monitor所解析。motinor exit可以让gdbserver立即退出,通常在disconnect命令之后使用,而monitor set debug|remote-debug 0|1还可以设置后续描述到的这两个调试参数。

  gdb允许想远程目标上传、接收、删除文件,这对于嵌入式调试(已占用串口线)十分方便,命令如下:
  remote put hostfile targetfile
  remote get targetfile hostfile
  remote delete targetfile

10.2 gdbserver程序

  如前面所说,gdbserver运行的Target端可以是strip后没有调试符号的运行程序,而在Host端负责符号相关的支持。OPTIONS参数选项比较重要的有–debug,可以显示一些额外的调试信息,而–remote-debug会显示一些远程调试协议相关的信息,主要是gdbserver本身开始调试使用的。
  gdbserver [OPTIONS] COMM PROG [ARGS …]
  命令中的COMM是串口设备字段或者TCP/IP的网络字段,ARGS是作为给PROG的运行参数使用。
  gdbserver [OPTIONS] –attach COMM PID
  调试一个运行着的进程的时候,不需要指明运行的二进制程序的位置。在extended-remote模式下还允许使用attach命令进行进程指定。pid可以使用pidof工具辅助查找(有多个同名运行的进程的话会一并全部返回,使用-s可以返回单个PID)。
  gdbserver [OPTIONS] –multi COMM
  允许gdbserver在不指定调试程序、调试进程的情况下启动,后续通过extended-remote连接后再行设定。

十一、GDB TUI(Text User Interface)调试界面

  就像之前说到的,Windows下面的程序员是幸福的,因为他们有着号称最好用的IDE——Visual Studio。其实内行看门道,到这里发现GNU GDB也是当之无愧的程序调试利器,只不过像通常Linux平台下的软件一样擅长功能而不善于表达,导致给外人看来一种很难用的“假象”。
  GDB内部集成了一个TUI的用户界面,在启动GDB的时候可以使用gdb -tui参数的方式使能,该模式是使用的curses库实现的一个建议UI界面。这个界面基本只是显示的基本功能,远远达不到IDE效果,不过它的好处是比较的易用(因为他没啥功能……),只需要在command窗口进行常规的gdb调试,分割出来的其他窗口可以自动显示源代码、反汇编、寄存器等信息,操作要有好许多。该模式下箭头可以滚动源代码子窗口,而Ctrl-P、Ctrl-N组合键可以浏览以前的GDB历史命令。
  TUI在list代码、当前执行位置等信息的显示上比命令模式的GDB确实要方便很多,但是困扰我的是很多终端更新不及时显示错乱,需要不短的刷新界面才能正常显示。此外,在Linux下面还有一个十分常用的gdb调试外壳,就是DDD,感兴趣的也可以去了解一下。
  PS:CGDB貌似更好用啊,有时间尝试一下!

参考