1 在C++函数、方法和操作符中设置断点
info funtions expr
list fun_name
break
ptype C
答应类的信息
2 调试静态构造/析构函数
在C++中使用一种称为静态初始化程序的函数或方法来初始化全局对象和静态函数。静态对象的初始化是在main()函数之前进行的。
在某些情况下,可能必须将调试器连接到正在运行的进程,然后对初始化例程进行调试。由于静态初始化例程是在程序启动阶段被调用的,所以在连接调试器时,他们已经执行。因此在初始化阶段应减慢程序的执行速度。添加另一个静态初始化函数,延迟程序的执行,这样就足够时间连接调试器。以下提供了一个示例:
//initial_delay.cpp
#include<unistd.h>
static int delay_done=0;
static int ask_mice()
{
while(!delay_done)
sleep(10);
return 42;
}
static int pol=ask_mice();
------------------------------------
//main.cpp
#include<stdio.h>
int main()
{
printf("Hello world!\n");
return 0;
}
$ g++ -g3 main.cpp initial_delay.cpp -o test
$ gdb test test_pid //将GDB连接到运行的进程test_pid
…
(gdb) set var delay_done=1
(gdb) continue
另一种将gdb连接到test_pid的方法:
$ gdb
(gdb) attach test_id
3 捕捉信号
在Unix上通过kull -l
来列出所有可用信号。大多数调试器允许用户指定如何处理每个接收的信号,在GDB中,是通过handle
命令实现的。可以选择将消息输出到屏幕上,或者停止程序的执行;也可以通知调试器忽略信息,这样就由注册的中断处理来处理它。还有一个命令可以通知调试器生成一个信号,并将它传送给程序,它就是signal
。
在调试信号问题时可采用如下策略:
* 提高可见性。令调试器收到信号时输出一条消息(例如,SIGUSR1),然后将此消息发送给程序。信号将在屏幕上留下明确的线索,这样更容易理解正在发生什么事情。如:
handle SIGUSR1 print nostop pass
* 禁止干扰信号。信号处理程序肯能对程序产生影响。可以令调试器忽略所有进入的信号,这样就不再条用信号处理程序。如:
handle SIGUSR1 noprint nostop nopass
* 引发bug。生成一个信号并将其发生给程序,以测试它是否出发bug。如:
signal SIGUSR1
4 捕获异常
当使用GDB的catch throw
命令时,调试器在抛出任何异常时停止;当使用 catch catch
命令时,调试器在捕获任何异常时停止。
5 操纵正在运行的程序
调试器除了可用于跟踪程序的控制流和分析数据外,还可以通过更改变量内容或从调试器内部调用函数来操纵程序的行为。
这些功能可帮助测试bug是否已修改,而免去了冗长的重新编译过程。这在重新编译程序需要很长时间,或者很难重新建立当前的程序状态时特别有用。
调试器一般可通过如下命令操纵程序的行为:
(1)操纵数据
* 修改变量值或函数的实际参数:
`set var < var_name>=< expr>`
* 修改函数的返回值,在GDB中跳到函数的最后一条
return <expr>
* 修改环境变量,在下次重启程序时生效
set environment <var><value>
(2)操纵流的控制
*调用函数。在GDB中使用call, print命令;在VS中,使用Immediate(即时)窗口;
* 从函数退出,跳过剩下的语句。在GDB中使用return <expr>
,在VS中没有等效的命令;
* 跳过或重新执行函数中的语句。在GDB中,使用jump命令,在VS中没有等效的命令。
使用GDB的ruturn语句可以返回选中帧之外的帧,从而跳过所有内部的帧。如:
从低下的帧,通过up命令直到选中main()所在帧,然后执行return 0
将提示从mian()函数返回。
return命令将清除帧栈,因此程序很可能会继续执行。当return命令从帧中退出时,程序将不再执行帧中的语句。这可能产生内存泄露或破坏类对象的现象,因为可能跳过了隐士的析构函数的调用。