文章目录

  • 一、介绍
  • 二、目标板直接使用GDB进行调试
  • 三、目标板使用gdbserver,主机使用xxx-linux-gdb调试
  • 3.1 Linux 环境 + VSCode 的方式在线调试
  • 3.1.1 步骤一:环境搭建
  • 3.1.2 步骤二:VSCode 调试配置导入
  • 3.1.3 步骤三:设备端启动调试程序
  • 3.1.4 步骤四:VSCode 连接gdbserver 并启动调试
  • 四、通过core+gdb离线分析
  • 4.1 什么是core文件?
  • 4.2 怎样配置生成 core 文件
  • 4.3 调试core文件
  • 五、GDB调试命令参考
  • 5.1 GDB调试 打印输出 (打印结构体指针指向的内容)
  • 5.2 切换当前栈、查看函数、具备变量等信息
  • 5.3 查看全局变量和函数的地址、
  • 六、参考资料


一、介绍

GDB调试的三种方式:

  1. 目标板直接使用GDB进行调试。
  2. 目标板使用gdbserver,主机使用xxx-linux-gdb作为客户端。
  3. 目标板使用ulimit -c unlimited,生成core文件;然后主机使用xxx-linux-gdb ./test ./core。

参考资料

二、目标板直接使用GDB进行调试

实践工程中,程序基本都是多进程,由于gdb命令来切换线程、打断点等操作较为麻烦,一般很少用这种方式直接调试。

三、目标板使用gdbserver,主机使用xxx-linux-gdb调试

目标板gdbserver+主机gdb远程调试的方式,比较适合目标板性能受限,只能提供gdbserver功能。

具体使用:参考章节二

3.1 Linux 环境 + VSCode 的方式在线调试

借助VSCode+gdbserver的方式可以直接在设备程序运行的情况下,通过VSCode工程代码来打断点调试。

核心思路:调试功能/接口,通过VSCode在对应的代码处打上断点即可。(省去加调试打印和重复编译)

3.1.1 步骤一:环境搭建

准备内容:(编译工具链、代码、带g程序)、(VSCode、WSL/SSH、GDB插件)、gdbserver。

  • 下面示例:电脑是win10系统
  • 方式一:win10的Linux子系统:win10下的VSCode的WSL+代码+可执行程序 (已验证)
  • 方式二:win10安装vmware并安装Ubuntu,使用Ubuntu下的VSCode+代码+可执行程序
  • 方式三:win10下的VSCode SSH远程到LInux服务器

3.1.2 步骤二:VSCode 调试配置导入

打开VSCode 配置Debug launch.json

{
    // 使用 IntelliSense 了解相关属性。
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
    {
        "name": "arm-gdb_V2", //配置名称,显示在配置下拉菜单中
        "type": "cppdbg", //配置类型
        "request": "launch", //请求配置类型,可以是启动或者是附加
        "program": "${workspaceFolder}/main/Bin/O1/Mystical_gdb", //程序可执行文件的完整路径,${workspaceFolder}表示VSCode 当前打开源码的根目录
        "args": [], //传递给程序的命令行参数
        "stopAtEntry": false,//可选参数,如果为true,调试程序应该在入口(main)处停止
        "cwd": "${workspaceFolder}", //目标的工作目录
        "miDebuggerPath": "arm-fslc-linux-gnueabi-gdb",  // 交叉工具的gdb
        "miDebuggerServerAddress": "10.1.30.142:3456", // 调试设备的IP端口
        "environment": [], //表示要预设的环境变量
        "externalConsole": false,//如果为true,则为调试对象启动控制台
        "MIMode": "gdb",//要连接到的控制台启动程序
        "setupCommands": [ //为了安装基础调试程序而执行的一个或多个GDB/LLDB命令
        {
            "description": "为 gdb 启用整齐打印",
            "text": "-enable-pretty-printing",
            "ignoreFailures": true
        }]
    },
    {
        "name": "arm-gdb_V3", //配置名称,显示在配置下拉菜单中
        "type": "cppdbg", //配置类型
        "request": "launch", //请求配置类型,可以是启动或者是附加
        "program": "${workspaceFolder}/Bin/O1/OBU/CloudService_gdb", //程序可执行文件的完整路径,${workspaceFolder}表示VSCode 当前打开源码的根目录
        "args": [], //传递给程序的命令行参数
        "stopAtEntry": false,//可选参数,如果为true,调试程序应该在入口(main)处停止
        "cwd": "${workspaceFolder}", //目标的工作目录
        "miDebuggerPath": "arm-fslc-linux-gnueabi-gdb",// 交叉工具的gdb
        "miDebuggerServerAddress": "10.1.30.142:3456", // 调试设备的IP端口
        "environment": [], //表示要预设的环境变量
        "externalConsole": false,//如果为true,则为调试对象启动控制台
        "MIMode": "gdb",//要连接到的控制台启动程序
        "setupCommands": [ //为了安装基础调试程序而执行的一个或多个GDB/LLDB命令
        {
          "description": "为 gdb 启用整齐打印",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        }]
    },
    {
        "name": "arm-gdb_O2_V2", //配置名称,显示在配置下拉菜单中
        "type": "cppdbg", //配置类型
        "request": "launch", //请求配置类型,可以是启动或者是附加
        "program": "${workspaceFolder}/main/Bin/O2/Mystical_gdb", //程序可执行文件的完整路径,${workspaceFolder}表示VSCode 当前打开源码的根目录
        "args": [], //传递给程序的命令行参数
        "stopAtEntry": false,//可选参数,如果为true,调试程序应该在入口(main)处停止
        "cwd": "${workspaceFolder}", //目标的工作目录
        "miDebuggerPath": "arm-linux-gnueabi-gdb",// 交叉工具的gdb
        "miDebuggerServerAddress": "10.1.36.46:3456", // 调试设备的IP端口  O2设备 miniOBU没有 网口 需要通过windows进行端口转发, 10.1.36.46为PC的IP
        "environment": [], //表示要预设的环境变量
        "externalConsole": false,//如果为true,则为调试对象启动控制台
        "MIMode": "gdb",//要连接到的控制台启动程序
        "setupCommands": [ //为了安装基础调试程序而执行的一个或多个GDB/LLDB命令
        {
          "description": "为 gdb 启用整齐打印",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        }]
    }]
}

配置完成后显示配置选项如下

ios 打印指针 对象 大小 打印指针内容_GDB

3.1.3 步骤三:设备端启动调试程序

  1. 将交叉编译好的gdbserver拷贝到设备中来
  2. 将编译好的程序也拷贝过来,假设程序名为app(VSCode关联的带g的程序、与这里的程序要对应)
  3. 运行gdbserver和app程序,命令:gdbserver : 3456 ./app

这里gdbserver监听端口3456和VSCode配置文件要对应起来。若程序为运行 状态可以 使用选项 --attach [pid] 采用附着的方式。

3.1.4 步骤四:VSCode 连接gdbserver 并启动调试

  1. 单击运行和调试,下拉选择对应类型
  2. 在代码上打断点,点击对应的调试配置项启动调试,既可开始调试
  3. 结束:先停止VSCode调试、再关闭目标板程序。

在代码上打断点,点击对应的调试配置项启动调试,既可开始调试,示例:

ios 打印指针 对象 大小 打印指针内容_字符串_02

四、通过core+gdb离线分析

直接参考示例:

ulimit -c unlimited 											#设置开启core文件生成
mount -t nfs -o nolock 192.168.3.65:/home/mayue/nfs /mnt;		#设备支持挂载时可用,可省略
echo "/mnt/core-%e-%p-%t" > /proc/sys/kernel/core_pattern		#设置core文件生成路径和命名规则

方式一:将上面内容加到启动脚本中,要放在启动程序之前
方式二:手动输入下面内容,然后重新启动程序

4.1 什么是core文件?

有问题的程序运行后,产生“段错误 (核心已转储)”时生成的具有堆栈信息和调试信息的文件。

编译时需要加 -g 选项使程序生成调试信息: gcc -g core_test.c -o core_test

注意:实践项目中,会同时编译出两个程序,一个带g,一个不带g。设备运行不带g程序,产生core文件后再用带g的程序来调试。

4.2 怎样配置生成 core 文件

(1) core文件开关

①使用 ulimit -c 查看core开关,如果为0表示关闭,不会生成core文件;

②使用 ulimit -c [filesize] 设置core文件大小,当最小设置为4之后才会生成core文件;

③使用 ulimit -c unlimited 设置core文件大小为不限制,这是常用的做法;

④如果需要开机就执行,则需要将这句命令写到 /etc/profile 等文件。

ios 打印指针 对象 大小 打印指针内容_字符串_03


(2) core文件命名和保存路径

①core文件有默认的名称和路径,但为了方便,我们通常会自己命名和指定保存路径;

②可以通过 /proc/sys/kernel/core_pattern 设置 core 文件名和保存路径,方法如下:

echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
命名的参数列表: 
%p - insert pid into filename 添加pid 
%u - insert current uid into filename 添加当前uid 
%g - insert current gid into filename 添加当前gid 
%s - insert signal that caused the coredump into the filename 添加导致产生core的信号 
%t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间 
%h - insert hostname where the coredump happened into filename 添加主机名 
%e - insert coredumping executable name into filename 添加命令名。

4.3 调试core文件

(1)方法1: gdb [exec file] [core file] 然后执行bt看堆栈信息:

ios 打印指针 对象 大小 打印指针内容_gdb_04


(2)方法②: gdb -c [core file],然后 file [exec file],最后再使用 bt 查看错误位置:

ios 打印指针 对象 大小 打印指针内容_局部变量_05

五、GDB调试命令参考

在进行gdb+core调试的过程中,经常遇到的问题或经常用到的命令:

  • 调试找不到动态库(系统动态库、自定义动态库)
  • gdb print变量显示不全 set print element 0
  • 查看栈信息:bt、(bt bull 打印栈帧信息的同时,打印出局部变量的值)
  • 查看某层堆栈信息 frame n
  • 查看当前栈层的信息:参数、局部变量(见下面章节)
  • 查看指针变量(结构体层级嵌套)
  • 控制参数/变量输出格式(16进制/字符串)(思路:字符串类参数可导出16进制,再重新导入测试)

命令

gdb ./myapp core-file		#启动调试

bt full		#打印所有堆栈信息

frame 0		#跳转到指定栈层

info frame # i frame 	#查看当前程序栈的信息
info args 				#查看当前程序栈的参数和参数内容
info locals 			#查看当前程序栈的局部变

p arg					#打印变量

示例

ios 打印指针 对象 大小 打印指针内容_局部变量_06

5.1 GDB调试 打印输出 (打印结构体指针指向的内容)

  1. GDB调试 打印输出
  2. GDB 调试:从入门实践到原理
  3. GDB格式化打印结构体
  4. GDB print命令高级用法
# 实践:打印出原始入参内容/局部变量,这里打印入参再套入程序中模拟是否可以复现问题
frame 1			#先指定frame
print *(struct lh_table *)t
print *(Lane_t *)cur_lane
print *(Lane_t *) 0x75a32268

通常情况下,在调试的过程中,我们需要查看某个变量的值,以分析其是否符合预期,这个时候就需要打印输出变量值。

命令

作用

whatis variable

查看变量的类型

ptype variable

查看变量详细的类型信息

info variables var

查看定义该变量的文件,不支持局部变量

打印字符串

使用x/s命令打印ASCII字符串,如果是宽字符字符串,需要先看宽字符的长度 print sizeof(str)

如果长度为2,则使用x/hs打印;如果长度为4,则使用x/ws打印。

命令

作用

x/s str

打印字符串

x/s 0x60000b32e2f6

根据某个地址查看字符串内容

set print elements 0

打印不限制字符串长度/或不限制数组长度

call printf(“%s\n”,xxx)

这时打印出的字符串不会含有多余的转义符

printf “%s\n”,xxx

同上

ios 打印指针 对象 大小 打印指针内容_gdb_07

打印数组

命令

作用

print *array@10

打印字符串

print array[60]@10

打印array数组下标从60开始的10个元素,即第60~69个元素

set print array-indexes on

打印数组元素时,同时打印数组的下标

打印指针

命令

作用

print ptr

查看该指针指向的类型及指针地址

print /x ptr

16进制打印该变量的值

print *(struct xxx *)ptr

查看指向的结构体的内容(ptr也可以是地址、可以一直往下套–给个示例)

ios 打印指针 对象 大小 打印指针内容_GDB_08

打印指定内存地址的值

使用x命令来打印内存的值,格式为x/nfu addr,以f格式打印从addr开始的n个长度单元为u的内存值。

  • n:输出单元的个数
  • f:输出格式,如x表示以16进制输出,o表示以8进制输出,默认为x
  • u:一个单元的长度,b表示1个byte,h表示2个byte(half word),w表示4个byte,g表示8个byte(giant word)

命令

作用

x/8xb array

以16进制打印数组array的前8个byte的值

x/8xw array

以16进制打印数组array的前16个word的值

打印局部变量

命令

作用

info locals

打印当前函数局部变量的值 (bt full也可以打印出局部变量)

backtrace full

打印当前栈帧各个函数的局部变量值,命令可缩写为bt

bt full n

从内到外显示n个栈帧及其局部变量

bt full -n

从外向内显示n个栈帧及其局部变量

打印结构体

命令

作用

set print pretty on

每行只显示结构体的一名成员

set print null-stop

不显示’\000’这种

5.2 切换当前栈、查看函数、具备变量等信息

参考:Linux中gdb 查看core堆栈信息1,其他内容:显示源代码、源代码内存

切换当前栈

frame n
或
f n

n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。

up 表示向栈的上面移动n层,可以不打n,表示向上移动一层。
down 表示向栈的下面移动n层,可以不打n,表示向下移动一层。

上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令: select-frame 对应于 frame命令。
up-silently 对应于 up 命令。
down-silently 对应于 down 命令。

查看当前栈层的信息

frame 或 f 会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。
info frame 或 info f 这个命令会打印出更为详细的当前栈层的信息,只不过,大多数都是运行时的内存地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。

  • x/10x $sp :查看当前程序栈的内容,打印stack的前10个元素
  • info frame : 查看当前程序栈的信息
  • info args :查看当前程序栈的参数
  • info locals : 查看当前程序栈的局部变量
  • info registers :查看当前寄存器的值(不包括浮点寄存器) ,info all-registers(包括浮点寄存器)
  • info catch(exception handlers) : 查看当前栈帧中的异常处理器

5.3 查看全局变量和函数的地址、

nm和gdb查看全局变量的地址是一致的

ios 打印指针 对象 大小 打印指针内容_局部变量_09


为什么gdb显示的函数地址和nm不一样?

因为 nm 显示函数开始的地址,而 gdb堆栈跟踪显示函数内部执行的确切位置。确切地说,它应该是堆栈帧中的返回地址,即当堆栈中位于其上方的函数返回时,指向该函数中要执行的下一条指令的指针。

请注意,如果您只是询问 gdb对于通过评估函数指针表达式的函数指针,它应该给出与 nm 相同的地址.

查看函数地址

根据函数地址 找 对应的函数名

ios 打印指针 对象 大小 打印指针内容_字符串_10

六、参考资料