gdb调试之快速入门

一、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

 

猜你喜欢

转载自blog.csdn.net/King_weng/article/details/109078702