【gdb调试】在ubuntu环境使用gdb调试一棵四层二叉树的数据结构详解

news2024/9/23 11:14:51

目录

🌞1. 整体思路

🌞2. 准备内容

🌼2.1 配置.c文件

🌼2.2 准备测试程序

🌼2.3 GDB调试基础

🌞3. GDB调试四层二叉树

🌼3.1 测试程序分析

🌼3.2 gdb分析

🌻1. 设置断点

🌻2. 启动程序并执行到断点处

🌻3. 打印变量的值

🌻4. 单步执行 s 进入buildTree函数内部

a. 第一层:根节点赋值

b. 第二层:节点赋值

c. 第三层:节点赋值

d. 第四层:节点赋值

e. 退出buildTree函数

🌻5. 单步执行 s 进入traverseTree函数内部:跟踪输出结果

🌻6. 跟踪错误

a. 查看指针 ptr 的值

b. 查看 ptr 所指向的地址

c. 回溯调用堆栈

d. 查看核心转储文件

🌞4. gdb技巧

🌼4.1 打印输出指定地址的值

🌼4.2 查看当前执行到哪行代码+代码内容


🌞1. 整体思路

在案例中我使用c语言编写了一个简单的四层二叉树进行 GDB 调试练习。这个程序故意在后面引发了一个段错误,导致程序崩溃。文章将使用 GDB 来诊断这个问题。


🌞2. 准备内容

🌼2.1 配置.c文件

建议先配置一下.c文件使其显示行数【方便后续快速定位bug】。默认情况下,GDB 不会在每次调试时自动显示行号。

编辑 Vim 的配置文件 ~/.vimrc(如果不存在则创建它),并添加以下行:set number

详细步骤如下:

打开配置文件 ~/.vimrc

nano ~/.vimrc

文件内容添加

set number

效果图如下:

然后运行以下命令使其生效:

source ~/.bashrc

这样使用vim 打开文件就会显示行数了


🌼2.2 准备测试程序

使用vim文本编辑器新建一个.c文件

vim tree3_01.c

输入测试程序:

#include <stdio.h>
#include <stdlib.h>
 
// 定义树节点
typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;
 
// 创建一个新的树节点
TreeNode* createNode(int data) {
    TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
    if (newNode == NULL) {
        fprintf(stderr, "Memory allocation failed.\n");
        exit(EXIT_FAILURE);
    }
    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}
 
// 构建四层树
TreeNode* buildTree() {
    TreeNode* root = createNode(1);
    root->left = createNode(2);
    root->right = createNode(3);
    root->left->left = createNode(4);
    root->left->right = createNode(5);
    root->right->left = createNode(6);
    root->right->right = createNode(7);
    root->left->left->left = createNode(8);
    root->left->left->right = createNode(9);
    return root;
}
 
// 递归遍历树并打印节点数据
void traverseTree(TreeNode* root) {
    if (root != NULL) {
        printf("%d ", root->data);
        traverseTree(root->left);
        traverseTree(root->right);
    }
}
 
int main() {
    // 构建树
    TreeNode* root = buildTree();
 
    // 打印树的结构
    printf("Tree Structure:\n");
    traverseTree(root);
    printf("\n");
 
    // 故意制造一个段错误,导致core dump
    int* ptr = NULL;
    *ptr = 10; // 这里将会产生段错误
 
    return 0;
}
 

gcc编译:

gcc -g -o tree3_01 tree3_01.c

此时ls查看会出现可执行文件tree3_01


🌼2.3 GDB调试基础

在使用GNU调试器(GDB)时,以下是一些常用的命令:

  • run (或 r): 启动程序并开始调试。
  • break (或 b): 在指定的位置设置断点。
  • continue (或 c): 继续执行程序直到下一个断点。
  • step (或 s): 单步执行程序,进入到函数中。
  • next (或 n): 单步执行程序,跳过函数内部的细节。
  • print (或 p): 打印变量的值。
  • backtrace (或 bt): 打印函数调用栈。
  • list (或 l): 显示源代码。
  • info (或 i): 显示调试信息,比如当前位置、变量类型等。
  • quit (或 q): 退出调试器。

🌞3. GDB调试四层二叉树

🌼3.1 测试程序分析

测试程序是一个简单的打印四层二叉树的c语言程序。

对于树TreeNode结构体和创建树节点createNode函数属于常规操作【不做分析】。

程序中的buildTree函数构建了一颗四层二叉树,并使用traverseTree函数先序遍历打印二叉树的数据结构:1 2 4 8 9 5 3 6 7


🌼3.2 gdb分析

现在,启动 GDB 并加载程序:

gdb ./tree3_01

进入 GDB,可以执行下列步骤来逐步调试:


🌻1. 设置断点

在程序出错的地方设置断点以停止程序执行,并检查变量。

break main

break mainb main等价。

这段输出是在 GDB 中设置断点的结果:

  • (gdb): 这是 GDB 的提示符,表示它正在等待用户输入命令。
  • break main: 这是用户输入的命令,表示在程序的 main 函数的起始处设置了一个断点。
  • Breakpoint 1 at 0x1398: 这一行显示了断点的信息。Breakpoint 1 表示这是第一个断点。0x1398 是断点的地址,表示断点被设置在程序代码的内存地址 0x1398 处。
  • file tree3_01.c, line 49: 这一行显示断点被设置位置在文件 tree3_01.c 的第 49 行处【还未执行】。

🌻2. 启动程序并执行到断点处

run

run和r等价

这个输出表明程序已经成功启动,并且停在了之前设置的断点处,也就是在 main 函数的第 49 行:

  • Starting program: /root/host/my_program/tree3_01: 这是 GDB 启动程序时的输出,指示程序已经开始执行。
  • [Thread debugging using libthread_db enabled]: 这个消息表明 GDB 正在使用 libthread_db 库进行线程调试,这是针对多线程程序的。
  • Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1": 这条消息表明 GDB 正在使用指定的线程库进行调试。

接着,输出显示了程序停在了 main 函数的第 49 行:

  • Breakpoint 1, main () at tree3_01.c:49: 这表示断点 1 已经触发,程序停在了 tree3_01.c 文件的第 49 行的 main 函数处。
  • 49            TreeNode* root = buildTree();:表示tree3_01.c 文件的第 49 行的代码【此时该行代码未执行】。

现在可以使用 GDB 的其他命令来查看程序状态,比如打印变量的值、单步执行等。


🌻3. 打印变量的值

可以使用 print 命令,后跟想要打印的变量名。

print root

print root和p root等价

这会打印 root 变量的值,即指向树根节点的指针。在这里,我们期望 root 指向一个已经创建好的二叉树的根节点。

打印 root 变量的结果显示为 (TreeNode *) 0x0,这意味着 root 指针当前指向了内存地址 0x0,即空指针【也证明了run之后到达断点的第49行代码未执行】。


🌻4. 单步执行 s 进入buildTree函数内部

step

step和s等价

step 命令进入 buildTree() 函数后,GDB 显示了当前所在的位置和执行的下一行代码。

  • buildTree () at tree3_01.c:26: 这行显示了当前所在的函数是buildTree以及函数参数为空。而 tree3_01.c:26 则表示这是在源文件 tree3_01.c 的第 26 行。
  • 当前程序执行到了 buildTree() 函数的开头,即第 26 行【未执行】

buildTree函数内部单步执行用到的还是n,除非需要进入buildTree函数里面的其他函数才用到s。


a. 第一层:根节点赋值

此时树结构如下:


b. 第二层:节点赋值

 此时树结构如下:


c. 第三层:节点赋值

 此时树结构如下:


d. 第四层:节点赋值

 此时树结构如下:


e. 退出buildTree函数

连续多次单步执行 n 即可


🌻5. 单步执行 s 进入traverseTree函数内部:跟踪输出结果

next

next和n等价。

跟踪输出的详细过程如下:

跟踪递归输出显示的输出结果为:1 2 4 8 9 5 3 6 7

这和预期输出的结果保持一致。


🌻6. 跟踪错误

单步执行 n 内容显示:

Program received signal SIGSEGV, Segmentation fault.
0x00005555555553d7 in main () at tree3_01.c:58
58        *ptr = 10; // 这里将会产生段错误

这个输出是 GDB 在程序运行时遇到段错误时所提供的信息:

  1. Program received signal SIGSEGV, Segmentation fault.

    这表示程序接收到了 SIGSEGV 信号,即段错误(Segmentation fault)信号。段错误通常发生在试图访问未分配给程序的内存或者访问已释放的内存时。

  2. 0x00005555555553d7 in main () at tree3_01.c:58

    这部分提供了造成段错误的代码位置信息。其中:

    • 0x00005555555553d7 是导致段错误的指令的地址。
    • main () 表示段错误发生在 main 函数内部。
    • tree3_01.c:58 指明了出错的源文件以及代码所在的行数,即在文件 tree3_01.c 的第 58 行。
  3. *58 ptr = 10; // 这里将会产生段错误

    这是在发生段错误的位置处的代码。具体地,这行代码尝试将值 10 写入指针 ptr 所指向的内存地址,但是 ptr 指向了一个空地址,因此导致了段错误。

现在我们需要进一步分析,为什么会发生段错误。可以使用以下几种方法:


a. 查看指针 ptr 的值

在发生段错误之前,可以查看指针 ptr 的值,看它是否为 NULL。

p ptr

这个输出表示指针 ptr 的值是 0x0,即空指针。

  • (int *) 表示这是一个指向整型数据的指针。
  • 0x0 是十六进制表示的地址,通常表示空指针。

因此,(int *) 0x0 表示指针 ptr 当前指向内存地址为 0x0,即空指针,那么后续执行的 *ptr = 10; 就会引发段错误。


b. 查看 ptr 所指向的地址

x ptr 查看指针 ptr 所指向的地址中的内容。

x ptr

输出表示 GDB 尝试查看指针 ptr 所指向的内存地址上的内容时出现了问题:

  • 0x0: 表示要查看的内存地址为 0x0
  • Cannot access memory at address 0x0 意味着 GDB 无法访问内存地址 0x0

说明:

  1. GDB 无法访问内存地址 0x0 是因为这个地址通常被操作系统保留为无效地址,用来表示空指针或者未分配的内存。因此,当 GDB 尝试访问地址 0x0 时,操作系统会阻止这种访问,因为这个地址不属于程序的有效内存范围。
  2. 通常情况下,访问空指针会导致程序出现段错误(Segmentation fault),这是因为试图在未分配的内存地址上读取或写入数据会导致操作系统干预并终止程序的执行,以保证系统的稳定性和安全性。

综合这些信息,由于 ptr 是空指针,即其指向的内存地址为 0x0,会导致错误。


c. 回溯调用堆栈

可以使用 backtrace (或bt)命令来查看调用堆栈,确定是从哪个函数调用了 main 函数并传递了一个空指针。

bt

输出表示了当前的函数调用堆栈情况,其中:

  • #0:表示当前所在的调用堆栈帧的索引,从 0 开始计数。
  • 0x00005555555553d7 in main () at tree3_01.c:58:说明当前位于 main 函数内,位于文件 tree3_01.c 的第 58 行。

输出表明程序在 main 函数的第 58 行出现了段错误(Segmentation fault),导致程序终止。


d. 查看核心转储文件

如果程序产生了核心转储文件,可以使用 GDB 打开它并查看导致段错误的堆栈跟踪信息。

gdb program core

  • program是可执行文件
  • core是coredump文件
gdb tree3_01 /tmp/dump/cores/core_tree3_01.50497_1712891407

其中gdb tree3_01 /tmp/dump/cores/core_tree3_01.50497_1712891407等价于
gdb ./tree3_01 /tmp/dump/cores/core_tree3_01.50497_1712891407

然后使用 backtrace(或bt) 命令来查看堆栈跟踪信息。

bt

这是 bt 命令的输出,表明当前程序执行时的函数调用栈:

  • #0: 表示当前栈帧的序号,这里是第一个栈帧。

  • 0x0000564e4be613d7: 这是当前正在执行的函数 main 的内存地址。

  • main (): 表示当前执行的函数是 main

  • at tree3_01.c:58: 表示 main 函数位于 tree3_01.c 文件中,并且是在第 58 行开始的。这里的 tree3_01.c 是源代码文件名,而 58 则是指示了具体的行号。


🌞4. gdb技巧

🌼4.1 打印输出指定地址的值

前面的打印每次都需要p root->xxxx...,如果树的深度太深则每次都需要从根节点root开始寻址太麻烦。

这里当我们已经知道了节点的地址后

打印指定地址0x555555559300的值和左右节点的值【这里是第三层】

p *((TreeNode*)0x555555559300)

打印其左右节点的值

p *((TreeNode*)0x555555559300)->left
p *((TreeNode*)0x555555559300)->right

 上面会显示当前的data值和左右指针的地址【即树TreeNode结构体的各个变量值】。


🌼4.2 查看当前执行到哪行代码+代码内容

思路:info line 结合 list 。

具体详情:

info line 获取当前执行代码的行号信息。

info line

  • 第53行代码的起始地址是 0x5555555553b5【 main 函数的偏移量为 41 的位置】。结束地址是 0x5555555553c1【 main 函数的偏移量为 53 的位置】。即 tree3_01.c 文件中第 53 行代码在程序运行时的地址范围,从 main 函数的偏移量为 41 的位置开始,到 main 函数的偏移量为 53 的位置结束。
  • 当前程序执行到tree3_01.c 文件中第 53 行代码【未执行】。

list 查看代码内容

list

如果没有指定参数,list 命令默认会显示当前执行位置的附近源代码。也可以指定行号或者函数名作为参数,以显示特定位置的源代码。

  • list: 显示当前执行位置周围的源代码。
  • list function_name: 显示名为 function_name 的函数的源代码。
  • list line_number: 显示指定行号的源代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1596427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenHarmony轻量系统开发【3】代码编译和烧录

3.1源码目录 下载完代码后&#xff0c;大家可以进入代码目录&#xff1a; 这里重点介绍几个比较重要的文件夹&#xff1a; 1 vendor文件夹 该文件夹存放的是厂商相关的配置&#xff0c;包括组件配置、HDF相关配置&#xff0c;代码目录如下&#xff1a; 可以看到有hisilicon文…

LLMs之ToolAlpaca:ToolAlpaca(通用工具学习框架/工具使用语料库)的简介、安装和使用方法、案例应用之详细攻略

LLMs之ToolAlpaca&#xff1a;ToolAlpaca(通用工具学习框架/工具使用语料库)的简介、安装和使用方法、案例应用之详细攻略 目录 ToolAlpaca的简介 0、《ToolAlpaca: Generalized Tool Learning for Language Models with 3000 Simulated Cases》翻译与解读 1、数据集列表 2…

连续上榜!Coremail连续十一次入选《中国网络安全行业全景图》

4月12日&#xff0c;国内专业权威咨询机构——安全牛&#xff0c;正式发布第十一版《中国网络安全行业全景图》&#xff08;以下简称“全景图”&#xff09;。该全景图包含了16项一级安全分类&#xff0c;108项二级安全分类&#xff0c;共收录454家网络安全厂商。 Coremail作为…

【保姆级】2024年OnlyFans订阅指南

OnlyFans是一个独特的社交媒体平台&#xff0c;它为创作者和粉丝提供了一个互动交流的空间。通过这个平台&#xff0c;创作者可以分享他们的独家内容&#xff0c;而粉丝则可以通过订阅来支持和享受这些内容。如果你对OnlyFans感兴趣&#xff0c;并希望成为其中的一员&#xff0…

D365开发-在视图按钮的js里,引用别的js里的公共方法

公共方法写法&#xff1a; "use strict"; var JJMC window.JJMC || {}; JJMC.SamMCommon JJMC.SamMCommon || {}; (function () { this.cloneRecord function (excludeAttrbuteNames){ / } }).call(JJMC.SamMCommon); 然后在需要调方法的command里面&#xff0c;之…

PNPM 8管理Node版本,卸载了旧版本Node找不到PNPM

前言 用 pnpm env 来管理 node 的版本&#xff0c;安装了新版本之后&#xff0c;卸载了之前的旧版本&#xff0c;调用 pnpm 报错 异常截图 解决方式 从终端获取报错文件到路径&#xff0c;进入编辑修改错误的 node bin 路径为正确的 node 启动路径即可也就是修改 "/Use…

vite+vue3+antDesignVue 记录-持续记录

记录学习过程 持续补充 每天的学习点滴 开始时间2024-04-12 1&#xff0c;报错记录 &#xff08;1&#xff09;env.d.ts文件 解决方法&#xff1a; 在env.d.ts文件中添加以下代码&#xff08;可以看一下B站尚硅谷的讲解视频&#xff09; declare module *.vue {import { Defi…

SpringBoot基于RabbitMQ实现消息延迟队列方案

知识小科普 在此之前&#xff0c;简单说明下基于RabbitMQ实现延时队列的相关知识及说明下延时队列的使用场景。 延时队列使用场景 在很多的业务场景中&#xff0c;延时队列可以实现很多功能&#xff0c;此类业务中&#xff0c;一般上是非实时的&#xff0c;需要延迟处理的&a…

宠物品牌出海 丨战略布局这样做让你爆单不停

宠物用品市场在电商领域增长迅速。面对国内市场竞争激烈&#xff0c;同质化严重&#xff0c;不少宠物用品公司开始寻求新的市场增长点&#xff0c;将目光转向国外市场。本文将探讨宠物品牌海外扩张的商机和策略&#xff0c;以便帮助其他公司应对挑战&#xff0c;抓住国际市场的…

利用国产库libhv动手写一个web_server界面(二)

目录 一、配置参数解析与响应 1.读取参数 2.设置参数 3.恢复默认参数 二、整体的界面实现以及交互效果 三、关于yaml文件乱码问题解决 四、参考文章 一、配置参数解析与响应 使用cJSON解析库&#xff0c;解析接收到的JSON数据字段&#xff0c;区别接收到的配置参数是请…

数据结构--选择排序

1、选择排序 1.1 基本认识 1.1.1 基本概念 选择排序是一种简单直观的排序算法&#xff0c;无论什么数据进去都是 O(n) 的时间复杂度。 1.1.2 算法步骤 &#xff08;1&#xff09;首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起…

【VTKExamples::Meshes】第十期 Decimation

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例Decimation,并解析接口vtkDecimatePro,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO…

linux项目部署 解决Nginx浏览器刷新出现404,但是不刷新是能够正常请求成功

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 提示&#xff1a;部署成功&#xff0c;访问登录页面登录也成功&#xff0c;强制刷新浏览器报404问题 进入到系统 刷新页面 解决流程 参考如图&#xff0c;再下面添加这条配置信息 location / {try_file…

macos 查看 远程服务器是否开放某个端口

想要使用mac查看远程服务器某个端口是否开发&#xff0c;可通过 nc 命令&#xff0c;如下&#xff1a; nc -zv <服务器IP> <端口号>如果该端口开发&#xff0c;结果为&#xff1a;succeeded! Connection to <服务器IP> port <端口号> [类型] succeed…

Jetson TX2刷机踩坑实录

刷机前准备 进行跳线 从右往左2号与3号跳线进行刷机 在主机&#xff08;非小车&#xff09;上安装ubuntu系统&#xff0c;用于刷机 安装虚拟机时&#xff0c;遇到无法自适应屏幕的问题&#xff1a; 点击屏幕下方安装vmtools的按钮&#xff1a; 生成了一个DVD文件&#xff1…

EasyImage2.0 简单图床开源 多功能 简单易用图床系统源码

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 支持API 支持仅登录后上传 支持设置图片质量 支持压缩图片大小 支持文字/图片水印 支持设置图片指定宽/高 支持上传图片转换为指定格式 支持限制最低宽度/高度上传 支持上传其他文件格…

Mac 软件清单

~自留备用~ Macbook用了几年之后, 512G的内置硬盘有些紧张了, 这几天总是提示空间不足, 就重装了下系统, 重装之后竟然不记得有些软件的名字和下载链接, 特此记录 Office 办公套件 直接从微软官网下载Office 安装包https://officecdnmac.microsoft.com/pr/C1297A47-86C4-4C1F…

在Docker里面修改mysql的密码(8.0以上版本)

介绍 我们在阿里或者华为的服务器上安装了mysql而且还公开了端口3306恰好你创建的容器的端口也是3306;那么我建议你修改mysql的密码,而且越复杂越好,因为我就被黑客给攻击过 修改密码 首先我们要启动好mysql容器 进入容器内部 **docker exec -it mysql bash ** 登入初始…

视频号正式推出电商项目,不需要自己直播,也能变现成功!

大家好&#xff0c;我是电商笨笨熊 视频号推出电商项目&#xff0c;这一举动又成了电商圈的热谈&#xff1b; 作为一个不需要自己直播也能卖货的变现方式&#xff0c;对于普通人确实是一个风口&#xff0c;解决了众多玩家想要利用直播变现但又没有相关经验&#xff0c;没有粉…