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

目录

🌞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 dumpint* 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.mzph.cn/news/817912.shtml

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

相关文章

这些矛盾点不搞清楚,私域怎么做得起来!

最近不少人都在问&#xff0c;私域做不起来怎么办&#xff1f;有很多企业砸了不少钱&#xff0c;有些还做了好几年&#xff0c;依旧没有起色。 有些企业觉得私域做不起来的阻碍有运营方面的原因&#xff0c;比如文案写不好&#xff0c;社群不知道怎么管理&#xff1b;有团队方…

Apache Zeppelin 命令执行漏洞复现(CVE-2024-31861)

0x01 产品简介 Apache Zeppelin 是一个让交互式数据分析变得可行的基于网页的开源框架&#xff0c;Zeppelin提供了数据分析、数据可视化等功能&#xff0c; 0x02 漏洞概述 Apache Zeppelin 中代码生成控制不当&#xff08;“代码注入”&#xff09;漏洞。攻击者可以使用 She…

Vitis HLS 学习笔记--硬件卷积加速 Filter2DKernel

目录 加速器功能 Window2D()函数 实现代码 变量解释 ARRAY_PARTITION DEPENDENCE LOOP_TRIPCOUNT ramp_up 更新Window 更新LineBuffer Filter2D()函数 ARRAY_PARTITION window_stream.read() 计算过程 备注 加速器功能 硬件加速单元从全局内存&#xff08;DDR&a…

Centos7配置IP地址

1、找到网卡名字 使用root用户登陆&#xff0c;输入命令 ifconfig 2、打开配置文件 输入命令&#xff0c;打开配置文件 vi /etc/sysconfig/network-scripts/ifcfg-ens33 3、添加IP地址 3.1修改BOOTPROTO 将“BOOTPROTOdhcp” 改为 “BOOTPROTOstatic” 3.2添加IP地址 在配…

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

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

解读《算者生存:商业分析的方法与实践》:构建企业经营分析框架的必备指南

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

Java:定时任务无法正常执行(scheduling + ShedLock)

目录 一、场景二、代码片段三、排查四、原因五、解决 一、场景 1、使用定时任务(scheduling) 分布式锁(ShedLock)定期执行一段代码 2、configureTasks()对于任务执行周期的更新是正常的 3、但任务方法无法被执行 二、代码片段 三、排查 1、确认Trigger没有问题 2、查看red…

水封式防暴器 我的真诚一直不变

天气在变&#xff0c;服务不变&#xff0c;季节在变&#xff0c;态度不变&#xff0c;时代在变&#xff0c;品质不变&#xff0c;不管世界怎么变&#xff0c;我的真诚一直不变&#xff01; 一、水封式防暴器的用途介绍&#xff1a; FBQ型系列水封式防暴器是安装在抽放瓦斯泵吸…

如何在没有备份的情况下恢复iPhone数据

想要找到没有备份的最佳iPhone数据恢复软件吗&#xff1f;本文介绍了一款专业的iPhone数据恢复软件&#xff0c;无需备份即可恢复iPhone数据。 许多iPhone用户可能对上述情况并不陌生。丢失重要的iPhone数据确实是一件令人沮丧的事情。通常&#xff0c;检索iPhone数据的最佳方…

第九届少儿模特明星盛典 全球赛首席体验官『魏堃明』精彩回顾

2024年1月30日-2月1日&#xff0c;魔都上海迎来了龙年第一场“少儿形体行业美育春晚”&#xff01;由IPA模特委员会主办的第九届少儿模特明星盛典全球总决赛圆满收官&#xff01;近2000名少儿模特选手从五湖四海而来&#xff0c;决战寒假这场高水准&#xff0c;高人气&#xff…

Golang 开发实战day10 - Maps

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f40d; Python工具 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 G…

[leetcode] 53. 最大子数组和

文章目录 题目描述解题方法分治法java代码复杂度分析 题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 示例 1&#xff1a; 输…

如何应对MySQL单表数据量过大:垂直分表与水平分表策略解析

话接上回&#xff0c;单表最大数据建议两千万&#xff0c;那如果开发一个项目&#xff0c;预计注册量达到一个亿怎么办。 单表内放这么多数据&#xff0c;MYSQL底层B树的层级结构就可能会变得很高&#xff0c;磁盘io次数变多&#xff0c;性能会大幅度降低。所以考虑数据库分表…

解析CopyOnWrite机制 以java的CopyOnWriteArrayList为例

什么是CopyOnWrite 写时复制&#xff08;Copy-on-write&#xff0c;简称COW&#xff09;是读写分离的一种实现方式&#xff0c;因为读和写在不同的容器中。 核心思想&#xff1a;线程在修改数据的时&#xff0c;会将原数据复制一份&#xff0c;然后在副本上修改&#xff0c;最…

imx.7交叉编译libX11

背景&#xff1a; 还是之前提到的触摸屏无响应问题&#xff0c;通过GDB调试&#xff0c;发现APP并非人为代码卡死&#xff0c;而是卡在官方的libc.so.6中&#xff0c;这个库出现了一些错误。排除自己代码问题&#xff0c;就剩官方版本问题&#xff0c;移植X11库&#xff0c;或…

提高APP安全性的必备加固手段——深度解析代码混淆技术

​ APP 加固方式 iOSAPP 加固是优化 APK 安全性的一种方法&#xff0c;常见的加固方式有混淆代码、加壳、数据加密、动态加载等。下面介绍一下 iOSAPP 加固的具体实现方式。 混淆代码&#xff1a; 使用 ProGuard 工具可以对代码进行混淆&#xff0c;使得反编译出来的代码很难…

前端本地搭建gninx环境

nginx下载地址&#xff1a; https://nginx.org/en/download.html nginx下载后&#xff0c;解压即用&#xff0c;注意解压目录不要含中文 nginx常用命令 查看版本 nginx -v 开启nginx服务 start nginx 重启服务 nginx -s reload 关闭服务 nginx -s stopnginx目录简析

three.js能够实现的3D动画效果大阅兵,有图有真相。

three.js能够实现许多不同类型的3D交互动画&#xff0c;包括但不限于以下几种&#xff1a; 旋转和缩放&#xff1a;可以通过鼠标或触摸手势来旋转和缩放3D模型或场景。 序列动画&#xff1a;可以创建复杂的动画序列&#xff0c;包括移动、旋转、缩放、颜色变化等。 粒子效果&…

使用Flask部署ppocr模型_3

PaddleOCR环境搭建、模型训练、推理、部署全流程&#xff08;Ubuntu系统&#xff09;_1_paddle 多进程推理-CSDN博客 PP-Structure 文档分析-CSDN博客 接前两篇继续完成Flask部署 一、使用Flask部署ppocr模型 GET方法用于从服务器获取资源&#xff0c;即客户端向服务器请求数据…

单链表讲解

一.链表的概念以及结构 链表是一种物理结构上不连续&#xff0c;逻辑结构上连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表的结构与火车是类似的&#xff0c;一节一节的&#xff0c;数据就像乘客一样在车厢中一样。 与顺序表不同的…