一、gdb调试
1、gdb调试的三种方式
- 目标板直接使用GDB进行调试。
- 目标板使用gdbserver,主机使用xxx-linux-gdb作为客户端。
- 目标板使用ulimit -c unlimited,生成core文件;然后主机使用xxx-linux-gdb ./test ./core。
例子:
(1)main.cpp
#include "sum.h"
using namespace std;
int main(int argc, char *argv[])
{
if(argc != 2){
cout << "please start with 2 values!" << endl;
return -1;
}
else{
Sum s;
int ret = s.add(atoi(argv[1]));
cout << "add result = " << s.getResult() << endl;
}
return 0;
}
(2)sum.h
#include <iostream>
using namespace std;
class Sum{
public:
Sum(){}
~Sum(){}
int add(int value);
int getResult();
private:
int _result = 0;
};
(3)sum.cpp
#include "sum.h"
int Sum::getResult()
{
return _result;
}
int Sum::add(int value)
{
int result = 0;
cout << "input value = " << value << endl;
for(int i = 0; i < value; i++){
result += i;
}
_result = result;
return result;
}
2、编译及运行
编译:
g++ main.cpp sum.h sum.cpp -o add -g
运行:
(法一):gdb + 编译后的文件名,然后输入run(简写r)即可运行。
(法二):命令行中先输入gdb,按回车,再输入file + 编译后的文件.
退出:q
注:cmake项目,在CMakeLists.txt文件中添加如下语句:
SET(CMAKE_BUILD_TYPE "Debug")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
3、断点
(1)设置断点
设置断点可以通过b或者break设置断点,断点的设置可以通过函数名、行号、文件名+函数名、文件名+行号以及偏移量、地址等进行设置。
格式:
- break 函数名
- break 行号
- break 文件名:函数名
- break 文件名:行号
- break +偏移量
- break -偏移量
- break *地址
(2)删除断点
格式:
- delete <断点id>:删除指定断点
- delete:删除所有断点
- clear
- clear 函数名
- clear 行号
- clear 文件名:行号
- clear 文件名:函数名
(3)设置条件断点
- break 断点 if 条件;比如break add if value==9,当输入的value为9的时候才会断住。
- condition 断点编号:给指定断点删除触发条件。
- condition 断点编号 条件:给指定断点添加触发条件。
(4)停用或启用断点
- disable
- disable 断点编号
- disable display 显示编号
- disable mem 内存区域
- enable
- enable 断点编号
- enable once 断点编号:该断点只启用一次,程序运行到该断点并暂停后,该断点即被禁用。
- enable delete 断点编号
- enable display 显示编号
- enable mem 内存区域
(5)断点commands高级功能
大多数时候需要在断点处执行一系列动作,gdb提供了在断点处执行命令的高级功能commands。
例:
#include <stdio.h>
int total = 0;
int square(int i)
{
int result=0;
result = i*i;
return result;
}
int main(int argc, char **argv)
{
int i;
for(i=0; i<10; i++) {
total += square(i);
}
return 0;
}
比如需要对如上程序square参数i为5的时候断点,并在此时打印栈、局部变量以及total的值,同时生成gdb的调试log,编写gdb.init如下:
set logging on gdb.log
b square if i == 5
commands
bt full
i locals
p total
print "Hit break when i == 5"
end
4、显示堆栈
如果遇到断点而暂停执行,或者coredump可以显示栈帧。通过bt可以显示栈帧,bt full可以显示局部变量。
格式:
- bt
- bu full:不仅显示backtrace,还显示局部变量
- bt N:显示开头N个堆栈
- bt full N
5、显示变量
“print 变量”(简写“p”)可以显示变量内容。
如果需要一行监控多个变量,可以通过p {var1, var2, var3}。
如果要跟踪自动显示,可以使用display {var1, var2, var3}。
6、显示寄存器
info reg可以显示寄存器内容。
在寄存器名之前加$可以显示寄存器内容:
- p $寄存器:显示寄存器内容
- p/x $寄存器:十六进制显示寄存器内容。
用x命令可以显示内容内容,“x/格式 地址”。
- x $pc:显示程序指针内容
- x/i $pc:显示程序指针汇编。
- x/10i $pc:显示程序指针之后10条指令。
- x/128wx 0xfc207000:从0xfc20700开始以16进制打印128个word。
还可以通过disassemble指令来反汇编。
- disassemble
- disassemble 程序计数器 :反汇编pc所在函数的整个函数。
- disassemble addr-0x40,addr+0x40:反汇编addr前后0x40大小。
7、单步执行
单步执行有两个命令next和step,两者的区别是next遇到函数不会进入函数内部(逐过程),step(逐语句)会执行到函数内部。
如果需要逐条汇编指令执行,可以分别使用nexti和stepi。
注:直接回车默认为上一次的命令。
- continue、step、stepi、next、nexti都可以指定重复执行的次数。
- ignore 断点编号 次数:可以忽略指定次数断点。
8、继续执行
调试时,使用continue命令继续执行程序。程序遇到断电后再次暂停执行;如果没有断点,就会一直执行到结束。
- continue:继续执行
- continue 次数:继续执行一定次数。
9、监视点
要想找到变量在何处被改变,可以使用watch命令设置监视点watchpoint。
格式:
- watch <表达式>:表达式发生变化时暂停运行
- awatch <表达式>:表达式被访问、改变时暂停执行
- rwatch <表达式>:表达式被访问时暂停执行
注:
其他变种还包括watch expr [thread thread-id] [mask maskvalue],其中mask需要架构支持。
GDB不能监控一个常量,比如watch 0x600850报错。但是可以watch *(int *)0x600850。
10、改变变量的值
“通过set variable <变量>=<表达式>”来修改变量的值。
- set $r0=xxx:设置r0寄存器的值为xxx。
11、生成内核转储文件
运行后,如果有段错误,会在当前目录下生成通过生成core.pid(pid为进程号)转储文件。或者通过“generate-core-file”也会生成core.pid(pid为进程号)转储文件。
通过gdb ./main ./core.pid查看恢复的现场。
(1)配置是否允许生成core文件
在生成内核储文件之前需要先查看配置是否允许生成core文件。
ulimit -a,如果core file size为0,则表示core文件大小为0,即不允许生成。
注:ulimit使用
(a)语法:
ulimit [-aHS][-c <core文件上限>][-d <数据节区大小>][-f <文件大小>][-m <内存大小>][-n <文件数目>][-p <缓冲区大小>][-s <堆叠大小>][-t <CPU时间>][-u <程序数目>][-v <虚拟内存大小>]
(b)参数:
- -a 显示目前资源限制的设定。
- -c <core文件上限> 设定core文件的最大值,单位为区块。
- -d <数据节区大小> 程序数据节区的最大值,单位为KB。
- -f <文件大小> shell所能建立的最大文件,单位为区块。
- -H 设定资源的硬性限制,也就是管理员所设下的限制。
- -m <内存大小> 指定可使用内存的上限,单位为KB。
- -n <文件数目> 指定同一时间最多可开启的文件数。
- -p <缓冲区大小> 指定管道缓冲区的大小,单位512字节。
- -s <堆叠大小> 指定堆叠的上限,单位为KB。
- -S 设定资源的弹性限制。
- -t <CPU时间> 指定CPU使用时间的上限,单位为秒。
- -u <程序数目> 用户最多可开启的程序数目。
- -v <虚拟内存大小> 指定可使用的虚拟内存上限,单位为KB。
(2)修改core文件大小
ulimit -c 0 # 禁止生成core文件
ulimit -c 100 # 限制core文件大小100块
ulimit -c unlimited # 不限制core文件大小
注:要永久生效则修改配置文件/etc/security/limits.conf,使用类似如下形式进行配置:
* soft core unlimited
(3)生成core文件
(a)通过kill触发生成
# 查看当前core文件大小限制
ulimit -c
# 设置成不限制大小
ulimit -c unlimited
# 再次查看core文件大小限制
ulimit -c
# 新启动一个bash进程
bash
# 查看当前bash的pid
echo $$
# 将该进程kill掉,触发core文件生成
kill -s SIGSEGV $$
# 确认core文件确实已经生成
ls
(b)使用gdb直接生成
# 查看当前bash的pid
echo $$
# 通过pid进行gdb调试。为了通用性这里直接使用$$
gdb -p $$
# 生成core文件。在gdb内部输入
generate-core-file
# 退出gdb。在gdb内部输入
quit
# 确认退出gdb。在gdb内部输入
y
# 确认当前目录下有core文件生成
ls -l | grep $$
使用gdb查看core文件即可定位到出错行,如下图。
(c)使用gcore生成
# 直接生成当前bash进程的core文件
gcore $$
# 确认core文件已在当前目录下生成
ls -l | grep $$
12、attach到进程
如果程序已经运行,或者是调试陷入死循环而无法返回控制台进程,可以使用attach命令。
attach pid
通过ps aux可以查看进程的pid,然后使用bt查看栈帧。
以top为例操作步骤为:
ps -aux查看进程pid,为16974.
sudo gdb attach 16974,使用gdb 附着到top命令。
使用bt full查看,当前栈帧。此时使用print等查看信息。
还可以通过info proc查看进程信息。
参考:https://www.cnblogs.com/arnoldlu/p/9633254.html