Linux 栈回溯

目录

  • 前言
  • 一、什么是栈回溯?
  • 二、栈回溯的实现原理
  • 三、参考阅读

前言

  日常工作中,我们在开发软件程序时,经常会遇到程序奔溃的问题,导致程序奔溃的原因有很多,我们一般都是定位到相关代码,再去查询具体原因。而定位到bug相关代码往往需要依赖栈回溯这个功能,知道程序是在哪里挂掉的。

一、什么是栈回溯?

  在Linux系统中,栈回溯(stack trace)是用于跟踪程序执行期间函数调用的一种技术,记录了程序在出现错误或异常时,从当前位置开始追溯回到程序的执行起点,包括每个函数的调用关系和相应的返回地址。栈回溯可以帮助开发人员快速定位问题所在。Linux系统提供了几种获取栈回溯的方法:

  1. backtrace函数:该函数定义在execinfo.h头文件中,可以获取当前线程的栈回溯信息。使用该函数需要在编译时添加-g选项以启用调试符号。使用方法:
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>void printStackTrace() {void *callstack[128];int i, frames;char **strs;frames = backtrace(callstack, 128);strs = backtrace_symbols(callstack, frames);printf("Stack Trace:\n");for (i = 0; i < frames; i++) {printf("%s\n", strs[i]);}free(strs);
}void func1() {printf("Entering func1...\n");printStackTrace();printf("Exiting func1...\n");
}void func2() {printf("Entering func2...\n");func1();printf("Exiting func2...\n");
}void func3() {printf("Entering func3...\n");func2();printf("Exiting func3...\n");
}int main() {printf("Entering main...\n");func3();printf("Exiting main...\n");return 0;
}

编译指令如下:

gcc -rdynamic stacktrace.c -o stacktrace
./stacktrace

通过添加 -rdynamic 参数,您将为可执行文件生成完整的符号表,从而使 backtrace_symbols 函数能够正确解析堆栈帧的信息。

  1. pstack命令:该命令可以打印指定进程或线程的栈回溯信息。为了使用 pstack 工具,您的系统必须安装有 gdb(GNU Debugger)。在大多数 Linux 发行版中,gdb 已经预装或可以通过包管理器轻松安装。pstack使用方法:
pstack <pid>
pstack -T <pid>
  1. gdb调试器:该调试器可以在程序崩溃时获取栈回溯信息。使用方法:
gdb <executable>
(gdb) run
<program crashes>
(gdb) bt

 无论使用哪种方法,栈回溯都可以提供关于程序崩溃或异常的有价值的信息,帮助开发人员快速定位问题所在。

二、栈回溯的实现原理

  在 Linux ARM64 系统上,实现栈回溯的原理与其他架构类似,主要涉及到寄存器、栈帧和符号表等概念。

  1. 寄存器:ARM64 架构有一组通用寄存器,用于存储函数调用的参数、局部变量和返回值等。在栈回溯过程中,关键的寄存器是程序计数器(Program Counter,PC),它保存着当前指令的地址。栈回溯需要从 PC 寄存器中获取每个函数调用的返回地址。
	ARM64 架构中的 CPU 寄存器是用于存储和处理数据的关键组件。寄存器在计算过程中用于存储
操作数、中间结果和控制信息。以下是 ARM64 架构中常见的 CPU 寄存器:1. 通用寄存器(General-Purpose Registers):-3164 位的通用寄存器,用来存储整数类型数据。- 这些寄存器被用于算术运算、数据传输、函数参数传递和临时存储等。- 寄存器命名为 x0-x30,其中 x30(栈帧指针)一般作为函数的帧指针使用。2. 程序计数器(Program Counter):- 存储当前执行的指令的地址。- 通常使用 `pc` 表示,是一个 64 位的寄存器。3. 标志寄存器(Flags Register):- 存储运算结果的条件信息,例如是否溢出、是否相等等。- 在 ARM64 架构中,标志寄存器叫做 Condition Flags Register(CPSR)。4. 浮点寄存器(Floating-Point Registers):-32128 位的浮点寄存器,用于存储浮点数和进行浮点运算。- 寄存器命名为 v0-v31,每个寄存器可以存储一个 128 位的浮点数或者多个较小精度的浮点数。5. SIMD 寄存器(Single Instruction, Multiple Data Registers):-32128 位的 SIMD 寄存器,用于存储数据并执行 SIMD(单指令多数据)运算。- 在 ARM64 架构中,SIMD 寄存器被称为向量寄存器(Vector Registers)。- 寄存器命名为 v0-v31,每个寄存器可以存储一个 128 位的向量或者多个较小精度的元素。除了上述常见的寄存器之外,ARM64 架构还有一些特殊用途的寄存器,如堆栈指针寄存器
(Stack Pointer Register,SP)等。这些寄存器在程序的执行过程中起着重要的作用,用于处理数据、控制程序流程、传递参数等。
编程时,需要根据需求合理使用寄存器来优化性能和实现所需的功能。

在这里插入图片描述

	x0-x30 是 ARM64 架构中的通用寄存器,共有 31 个寄存器,用于存储整数类型数据以及执行
各种操作。下面对这些寄存器进行详细说明:1. x0-x30 (x0~x30)- 这些寄存器是通用寄存器,每个寄存器的大小为 64 位。- 在函数调用中,寄存器 x0-x7 用于函数参数的传递,后续的参数(这几个寄存器存满了)存储在栈上。- 寄存器 x8 保留给系统调用使用。- 寄存器 x9-x15 可用作临时寄存器。- 寄存器 x16-x17 用作特殊用途寄存器。- 寄存器 x18 保留给全局数据指针使用(例如 TLS 模型)。- 寄存器 x19-x28 用作通用寄存器,可以用于存储数据和进行算术运算。- 寄存器 x29(FP,Frame Pointer)用作栈帧指针,指向当前函数的栈帧的起始位置。- 寄存器 x30(LR,Link Register)用于存储函数调用时的返回地址。总体而言,x0-x30 寄存器在 ARM64 架构中用于存储数据、进行算术运算、传递函数参数、控制程序流程等。在函数调用过程中,这些寄存器的使用通过约定规则来进行管理,有助于提高运行效率和优化编译代码。开发者需要根据编程需求合理使用这些寄存器,并遵循相关规则来保证程序的正确性和性能。
  1. 栈帧:在函数调用过程中,通过栈帧(Stack Frame)来保存函数的局部变量、返回地址和其他调用相关信息。每个栈帧由保护区域(Saved Registers)和局部变量区域(Local Variables)组成。栈帧中包含了被调用函数的返回地址,在函数返回时会恢复到该地址继续执行。

  2. 栈回溯算法:栈回溯的实现通常使用递归算法或迭代算法。下面是一种迭代的栈回溯算法:

    • 在当前堆栈帧中获取当前函数的返回地址。
    • 根据地址和可执行文件的调试符号表信息,匹配到对应的函数名称和行号。
    • 打印或记录匹配到的函数名称和行号。
    • 对于从地址中解析的下一个返回地址,重复上述步骤,直到回溯到最顶层的函数或者到达设定的回溯层数。
  3. 符号表:符号表是一个映射关系,将函数名与对应的地址关联起来。在栈回溯过程中,通过符号表可以将地址解析为函数名和行号等调试信息。在 Linux 系统中,可以使用调试符号表文件(例如 ELF 文件)来获取符号表信息。

  需要注意的是,栈回溯的准确性和可读性受到符号表的影响。如果可执行文件没有包含调试符号信息,或者没有正确设置符号文件的路径,栈回溯可能只能提供地址而无法解析为函数名和行号。因此,在构建可执行文件时,建议开启调试符号信息的生成并妥善保存符号表文件。

三、参考阅读

ARM体系结构:https://zhuanlan.zhihu.com/p/577979125?utm_id=0
内核中dump_stack:https://www.cnblogs.com/pengdonglin137/p/11109427.html
dump_stack:https://blog.csdn.net/weixin_52849254/article/details/130559085

  dump_stack 函数是 Linux 内核中的一个调试函数,用于在内核代码中打印当前的函数调用堆栈信息。它用于诊断和调试内核中发生的问题,如内核崩溃或死锁等。

  dump_stack 函数的原型定义在 kernel/lib/dump_stack.c 头文件中:

void dump_stack(void);__dump_stack();dump_stack_print_info(KERN_DEFAULT);show_stack(NULL, NULL) //arch\arm64\kernel\traps.c

  通过调用 dump_stack 函数,可以在内核日志中输出当前的函数调用堆栈信息。这对于定位问题非常有用,特别是在内核崩溃时,您可以通过查看内核日志中的堆栈跟踪信息来确定哪些函数导致了问题。

  要在内核代码中使用 dump_stack 函数,只需在适当的位置调用它即可。例如,在驱动程序中遇到错误或异常情况时,可以在相应的错误处理路径中调用 dump_stack 函数,以便在内核日志中获取有关问题的更多信息。

注:推荐一本讲调试技巧的书《Debug Hacks中文版—深入调试的技术和工具》,非常nice!!!

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

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

相关文章

C# 使用PanGu分词

写在前面 这是官方介绍&#xff1a;盘古分词是一个中英文分词组件。作者eaglet 曾经开发过KTDictSeg 中文分词组件&#xff0c;拥有大量用户。作者基于之前分词组件的开发经验&#xff0c;结合最新的开发技术重新编写了盘古分词组件。 盘古分词组件需要配合其字典文件使用&am…

虚幻学习笔记—文本内容处理

一、前言 本文使用的虚幻引擎5.3.2&#xff0c;在虚幻中已经集成了很多可以直接处理多样化文本的蓝图&#xff0c;比如格式化动态显示、浮点数多样化等。 二、实现 2.1、格式化文本显示动态内容&#xff1a;在设置某个文本时可以使用“Format Text”蓝图设置自定义可以的显示…

1.6锁的升级过程

一、偏向锁 轻量级锁 当有新的线程进来时 其实就是竞争不激烈&#xff0c;但是确实存在多个锁竞争的情况&#xff0c;而且是&#xff0c;大家都很有序的进行&#xff0c;一释放&#xff0c;下一个线程就拿到锁&#xff0c;很有顺序的获取锁&#xff0c;基本上通过自旋的方式代…

一、Oceanbase基础

一、集群相关概念 集群&#xff1a;整个分布式数据库。Region&#xff1a;表示区域&#xff0c;是地域的逻辑概念&#xff0c;如1个城市&#xff0c;1个集群可以有多个Region&#xff0c;用于跨城市远 距离容灾。Zone&#xff1a;表示分区&#xff0c;是机房或机架的逻辑概念…

Git指定分支或文件回退到指定版本

文章目录 一、分支回滚1.1、使用 git reset 命令1.2、使用 git revert 命令1.3、使用 git checkout 命令 二、文件回滚2.1、回滚未提交文件2.2、回滚已提交文件2.2.1、首先查看文件的历史版本2.2.2、找到你想要还原的版本2.2.3、将文件还原到你想要还原的版本2.2.4、提交代码 三…

Scrapy爬虫异步框架之持久化存储(一篇文章齐全)

1、Scrapy框架初识&#xff08;点击前往查阅&#xff09; 2、Scrapy框架持久化存储 3、Scrapy框架内置管道&#xff08;点击前往查阅&#xff09; 4、Scrapy框架中间件&#xff08;点击前往查阅&#xff09; Scrapy 是一个开源的、基于Python的爬虫框架&#xff0c;它提供了…

Android应用程序开发实战篇----期末总结1

项目1&#xff08;了解移动电子商务开发&#xff09; 1,任务一&#xff1a;了解移动电子商务 移动电子商务&#xff1a;利用无线终端进行的电子商务活动。 移动电子商务的特点&#xff1a;方便&#xff0c;摆脱时空性&#xff0c;安全&#xff0c;潜在用户规模大&#xff0c;…

vue day2

1、指令修饰符&#xff1a;.指明一些指令后缀&#xff0c;不同后缀封装不同处理操作 按键修饰符&#xff1a;keyup.enter v-model修饰符&#xff1a; v-model.trim&#xff1a;去首位空格 v-model.number&#xff1a;转数字 事件修饰符&#xff1a; 阻止事件冒泡&#xff1…

新版idea如何开启多台JVM虚拟机

1.看看自己的项目 2.可能开始的时候啥也没有&#xff0c;就点Run Configuration Type 3.再点击Edit Configurations... 4.点击号添加SpringBoot 5.主类选择一下&#xff0c;一般就一个&#xff0c;点他选了就行。 6.然后点击Modify Options 选择添加add VM Options 7.点击appl…

云服务器哪家便宜?亚马逊AWS等免费云服务器推荐

在这数字化的时代&#xff0c;云计算技术越来越广泛应用于各种场景&#xff0c;尤其是云服务器&#xff0c;作为一种全新的服务器架构正在逐渐取代传统的物理服务器&#xff0c;“云服务器哪家便宜”等用户相关问题也受到越来越多的关注。自从亚马逊最早推出了首个云计算服务—…

如何解决主从数据库同步延迟问题?

如何解决主从数据库同步延迟问题&#xff1f; 前言 最近&#xff0c;系统上频繁出现主从延迟的问题&#xff0c;因此针对主从架构、主从同步以及主从延迟问题进行了一次学习。 主从架构浅析 在了解主从延迟之前&#xff0c;我们有必要对主从架构有一些简单的认识。在如今的…

2023-11-26 LeetCode每日一题(统计子串中的唯一字符)

2023-11-26每日一题 一、题目编号 828. 统计子串中的唯一字符二、题目链接 点击跳转到题目位置 三、题目描述 我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符&#xff0c;并返回唯一字符的个数。 例如&#xff1a;s “LEETCODE” &#xff0c;则其…

深度学习及其基本原理

深度学习的 Ups and Downs概念区分神经网络的构成深度学习基本原理深度学习的普遍近似定理扩展&#xff1a;反卷积网络——可视化每一层提取的特征 深度学习的 Ups and Downs 1958&#xff1a;感知机&#xff08;线性模型&#xff09;1969&#xff1a;感知机有局限性1980s&…

SpringCloud-高级篇(五)

一&#xff1a;分布式事务理论基础 原子性&#xff08;Atomicity&#xff09; 原子性是指事务是一个不可分割的工作单位&#xff0c;事务中的操作要么都发生&#xff0c;要么都不发生。 一致性&#xff08;Consistency&#xff09; 事务前后数据的完整性必须保持一致。 隔离性&…

【九章斩题录】Leetcode:面试题 01.03. URL化(C/C++)

精品题解 &#x1f525; 《九章斩题录》 &#x1f448; 猛戳订阅 面试题 01.03. URL化 &#x1f4da; 题目&#xff1a;URL化。编写一种方法&#xff0c;将字符串中的空格全部替换为%20。假定该字符串尾部有足够的空间存放新增字符&#xff0c;并且知道字符串的“真实”长度。…

【JDK21】详解虚拟线程

目录 1.概述 2.虚拟线程是为了解决哪些问题 2.1.线程切换的巨大代价 2.2.哪些情况会造成线程的切换 2.3.线程资源是有限的 3.虚拟线程 4.适用场景 1.概述 你发任你发&#xff0c;我用JAVA8&#xff1f;JDK21可能要对这句话say no了。 现在Oracle JDK是每4个版本&#x…

最新版小权云黑系统 骗子添加查询源码

小权云黑系统添加骗子&#xff0c;查询骗子&#xff0c;可添加团队后台方便审核用&#xff0c;在线反馈留言系统&#xff0c;前台提交骗子&#xff0c;后台需要审核才能过&#xff0c;后台使用光年UI界面&#xff0c;新增导航列表&#xff0c;可给网站添加导航友链&#xff0c;…

qt国际化多语言

vs + qt 方法 一 (1)生成.pro文件 如果报错: cannot find any qt projects to export 则执行如下: 然后重新生成 pro文件。 (2)生成ts文件 (方法1)在项目文件(xxx.pro) 文件添加: TRANSLATIONS += en.ts zh_CN.ts 然后打开cmd命令,进入项目目录,执行 l…

Spring中的循环依赖问题

目录 1、什么是Spring的循环依赖&#xff1f; 2、如何避免循环依赖问题&#xff1f; 3、Spring的三级缓存 小结 1、什么是Spring的循环依赖&#xff1f; Spring框架中的循环依赖问题是指两个或多个bean之间相互依赖&#xff0c;形成闭环&#xff0c;导致无法完成实例化的问…

【力扣】907.子数组的最小值之和

【力扣】907.子数组的最小值之和 文章目录 【力扣】907.子数组的最小值之和1. 题目介绍2. 解法2.1 方法一&#xff1a;单调栈2.2 方法二&#xff1a;动态规划 3. Danger参考 1. 题目介绍 给定一个整数数组 arr&#xff0c;找到 min(b) 的总和&#xff0c;其中 b 的范围为 arr …