最近工作当中遇到一个问题,在函数调用过程中返回错误了,但那个函数里if分支相当多,而且还调用了其他很多的函数,就是函数调用层级太深了,在没有日志输出的情况下根本无法判断到底是哪个函数返回错误了,于是 gdb 的 watch 派上了用场。
watch 必须要和断点一起使用,没有断点的话 watch 什么呢,而且很多情况下你要 watch 的是一个局部范围内的变量。
watch_break_pointer.cpp
#include <stdio.h>
#include <stdlib.h>
extern int initConfig();
extern int setConfig();
extern int getConfig();
int getLocalConfig();
int main()
{
getLocalConfig();
return 0;
}
int getLocalConfig()
{
int ret = 0;
ret |= initConfig();
ret |= setConfig();
ret |= getConfig();
return ret;
}
configManager.cpp
#include <stdio.h>
#include <stdlib.h>
int initConfig()
{
return 0;
}
int setConfig()
{
return -1;
}
int getConfig()
{
return 0;
}
可以看到,如果设置了断点,然后就设置 watch 是没用的,而当断点被触发时再设置 watch 点则可以看出,它是基于调用栈帧的,如:
这个 断点是在函数的入口处,只有当断点触发时才能设置 watch 点。而当 watch 变量值发生变化时,程序则停止执行,所以我们需要输入 c 继续往下执行。
当继续往下执行后,ret 值发生了变化,程序停在了 watch_break_pointer.cpp 21 行处,这表明在这行之前 ret 发生了变化,因为函数在返回给 ret 后 gdb 才检测到 ret 发生变化,所以停在了变化的下一行准备继续执行。上面我们讲到 watch 是基于调用栈帧的,那从哪里可以得出这样的结论呢?刚才我们 watch 了一个函数入口处的变量,那如果我们 watch 一个函数里的变量呢?是否会成功呢?
#include <stdio.h>
#include <stdlib.h>
extern int initConfig();
extern int setConfig();
extern int getConfig();
int getLocalConfig();
int main()
{
getLocalConfig();
return 0;
}
int getLocalConfig()
{
int ret = 0;
ret |= initConfig();
ret |= setConfig();
ret |= getConfig();
int rt = 0;
rt = setConfig();
return ret;
}
发现了程序被停止了 2 次,因为我们在函数入口 watch rt,此时 rt 应该是一个随机值,当程序执行到 rt = 0 时程序被停止,再往下执行时 rt = -1 再被停止。当我把断点断在函数内部时也是可以设置watch 的,如:
但是变量 ret 在21行后是没有发生变化的,所以不会有 ret 值变化的断点触发。所以 watch 是基于是调用栈帧的,而调用栈帧其实也就是函数调用。所以当你要设置 watch 时,就要看你把断点断在哪里了。