嵌入式开发之堆栈调试打印

2023-11-04

简介

打印堆栈的常用方法包括:

  • glibc中的backtrace函数
  • gcc内置函数__builtin_return_address
  • 第三方库libunwind

1 glibc中的backtrace

1. 1函数原型

#include <execinfo.h>

/*
 * 功能: 获取当前线程的调用堆栈并存放在buffer中(指向字符串数组的指针)
 * @param buffer: 存放当前线程的调用堆栈
 * @param size: 指定buffer中可以保存多少个 void* 元素(void* 元素实际上是从堆栈中获取的返回地址)
 * @return 实际返回的 void* 元素个数
 * */
int backtrace(void **buffer, int size);

/*
 * 功能: 将backtrace函数获取的信息转化为一个字符串数组
 * @param buffer: backtrace获取的堆栈指针
 * @param size: backtrace返回值
 * @return: 一个指向字符串数组的指针, 包含 size 个 char* 元素, 每个元素包含了一个相对于buffer中对应元素的可打印信息(函数名、函数偏移地址和实际返回地址)
 * */
char **backtrace_symbols(void *const *buffer, int size);

/*
 * 功能: 与backtrace_symbols函数功能相同, 但是不会malloc内存, 而是将结果写入文件描述符为fd的文件中
 * */
void backtrace_symbols_fd(void *const *buffer, int size, int fd);

1.2问题

backtrace()backtrace_symbols()都是不可重入的函数,原因在于它们内部都使用了malloc函数,而malloc内部是有锁的。假设某个线程正在调用malloc分配堆空间,此时程序捕捉到信号发生中断,而信号处理函数中恰好也调用了malloc函数就会发生死锁导致该线程hang住,随后所有调用malloc的其他线程也会相继hang住。

backtrace_symbols_fd函数是可重入的,我们用它代替backtrace_symbols打印堆栈信息。

1.3使用注意事项

  • backtrace的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数-fomit-frame-pointer后可能不能正确得到程序栈信息
  • backtrace_symbols的实现需要符号名称的支持,在gcc编译时需要加入-rdynamic参数
  • 内联函数没有栈帧,它在编译过程中被展开在调用位置
  • 尾调用优化Tail-Call Optimization将复用当前函数栈而不再生成新的函数栈,这将导致栈信息不能被正确获取

1.4例子

#include <libgen.h>
#include <execinfo.h>

void show_backtrace(void)
{
#define SIZE 200
    int nptrs;
    void *buffer[SIZE];
    char **strings;

    nptrs = backtrace(buffer, SIZE);
    printf("backtrace() returned %d address\n", nptrs);
    
    // backtrace_symbols函数不可重入, 可以使用backtrace_symbols_fd替换
    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL)
    {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

    for (int j = 0; j < nptrs; j++)
    {
        printf("%s\n", strings[j]);
    }

    free(strings);
}

这种方法使用起来很方便,也不需要引入什么第三方库,但是这种方法要求交叉编译器必须是支持glibc的,比如海思的hi3536是uclibc的,就没有backtrace接口,只能使用别的方法。

2 gcc内置函数

我们可以使用gcc内置函数__builtin_return_address(level)打印出一个函数的堆栈地址,其中level表示堆栈中第几层调用地址。

#include <cstdio>

void f() {
    printf("%p,%p\n", __builtin_return_address(0), __builtin_return_address(1));
}

void g() {
    f();
}
int main() {
    g();
}

3 第三方库libunwind

3.1下载

下载地址:http://download-mirror.savannah.gnu.org/releases/libunwind/

这里我下载1.5版本,文件名为:libunwind-1.5.0.tar.gz。

3.2安装

$tar -zxvf libunwind-1.5.0.tar.gz
$cd libunwind-1.5.0
$CFLAGS=-fPIC ./configure --prefix=$(pwd)/.libs
$make CFLAGS=-fPIC
$make CFLAGS=-fPIC install 

如果是编译arm版本,需要在configure的时候指定交叉编译链用 --host=xxxxxx,比如海思hi3536:

$CFLAGS=-fPIC ./configure --host=arm-hisiv500-linux --prefix=$(pwd)/.libs

3.3测试代码

<backtrace.cpp>

#include "backtrace.h"

#ifdef __cplusplus
#include <cxxabi.h>
#endif
#include <dlfcn.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <ucontext.h>


void show_backtrace(void)
{
    unw_cursor_t cursor;
    unw_context_t uc;
    unw_word_t ip, sp;
    char func_name_cache[4096];
    func_name_cache[sizeof(func_name_cache) - 1] = 0;
    unw_word_t unw_offset;
    unw_proc_info_t unw_proc;
    int frame_id = 0;
#ifdef __cplusplus
    int status;
#endif

    unw_getcontext(&uc);
    unw_init_local(&cursor, &uc);
    printf("++++++++ backtrace ++++++++\n");
    while (unw_step(&cursor) > 0)
    {
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        unw_get_reg(&cursor, UNW_REG_SP, &sp);

        unw_get_proc_info(&cursor, &unw_proc);
        unw_get_proc_name(&cursor, func_name_cache, sizeof(func_name_cache) - 1,
                          &unw_offset);
#ifdef __cplusplus
        char *func_name = abi::__cxa_demangle(func_name_cache, 0, 0, &status);
#else
        const char *func_name = func_name_cache;
#endif
        printf("Frame #%02d: (%s+0x%llx) [0x%llx]\n", frame_id,
                      func_name ? func_name : func_name_cache,
                      static_cast<unsigned long long>(unw_offset),
                      static_cast<unsigned long long>(unw_proc.start_ip));
#ifdef __cplusplus
        if (func_name)
            free((void *)func_name);
#endif
        frame_id++;
    }
    printf("+++++++++++++++++++++++++++\n");
}

<backtrace.h>

#ifndef __BACKTRACE_H
#define __BACKTRACE_H

#ifdef __cplusplus
extern "C"
{
#endif

void show_backtrace();

#ifdef __cplusplus
}
#endif

#endif /* __BACKTRACE_H */

<main.cpp>

#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#include "backtrace.h"

typedef void (*sighandler_t)(int);

static const char *program_exec = "trace_cpp";

void crash()
{
    volatile int i = *(int *)7;
    (void)i;
}

void foo2(void)
{
    crash();
}

void foo1(void)
{
    foo2();
}

static void signal_handler(int signo)
{
    printf("Aborting (signal %d) [%s]\n", signo, program_exec);
    show_backtrace();
    exit(EXIT_FAILURE);
}

static void signal_setup(sighandler_t handler)
{
    struct sigaction sa;
    sigset_t mask;

    sigemptyset(&mask);
    sa.sa_handler = handler;
    sa.sa_mask = mask;
    sa.sa_flags = 0;
    sigaction(SIGBUS, &sa, NULL);
    sigaction(SIGILL, &sa, NULL);
    sigaction(SIGFPE, &sa, NULL);
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
    sigaction(SIGPIPE, &sa, NULL);
}

int main(int argc, char **argv)
{
    signal_setup(signal_handler);

    foo1();

    return 0;
}

编译:

可以通过命令行编译,或者通过cmake构建文件进行编译,然后链接libunwind的静态库以及头文件即可生成测试可执行文件。

运行log:

/mnt/hi3536-workspace # ./trace_cpp
Aborting (signal 11) [trace_cpp]
++++++++ backtrace ++++++++
Frame #00: (signal_handler(int)+0x2c) [0x10eac]
Frame #01: (_setjmp+0xc) [0x10eac]
Frame #02: (crash()+0x10) [0x10e64]
Frame #03: (foo2()+0xc) [0x10e8c]
Frame #04: (foo1()+0xc) [0x10e9c]
Frame #05: (main+0x20) [0x10fa4]
Frame #06: (__uClibc_main+0x298) [0x10fa4]
+++++++++++++++++++++++++++

分析:

从log中,我们看到最上面的一个接口并且是我们测试代码中用到的是,Frame #02,后面的地址是0x10e64,偏移地址是0x10,我们用addr2line在pc上找到具体出问题的地方:

zl@zl-Lenovo:~/vstdio-workspace/hi3536-webapp/backtrace-test/build$ arm-hisiv500-linux-addr2line -C -f -e trace_cpp 0x10e74
crash()
/home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:18

注意addr2line必须要用执行测试代码的交叉编译链里面的,从结果结合我们的测试程序,很明显打印的堆栈信息可以分析出出问题的文件,以及行号,以及出问题的接口。

4 扩展

其实对于异常崩溃的调试,可以通过上面的方法,崩溃的时候打印堆栈信息,然后结合工程源码来找到出问题的地方,另外其实还可以用linux非常非常强大的工具,gdb来运行程序调试。比如上面的应用,我们用gdb来运行:

/mnt/hi3536-workspace # gdb trace_cpp
GNU gdb (GDB) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-hisiv500-linux".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from trace_cpp...done.
(gdb) r
Starting program: /mnt/hi3536-workspace/trace_cpp

Program received signal SIGSEGV, Segmentation fault.
0x00010e74 in crash () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:18
18      /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp: No such file or directory.
(gdb) pt
The history is empty.
(gdb) bt
#0  0x00010e74 in crash () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:18
#1  0x00010e98 in foo2 () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:24
#2  0x00010ea8 in foo1 () at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:29
#3  0x00010fc4 in main (argc=1, argv=0xbefffe04) at /home/zl/vstdio-workspace/hi3536-webapp/backtrace-test/main.cpp:61
(gdb)

可以很清楚的看到出问题的接口,以及位于哪个文件的哪一行。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌入式开发之堆栈调试打印 的相关文章

  • 异步回调到BackgroundWorker

    我想使用 NET FTP 库 http netftp codeplex com http netftp codeplex com 该库提供 BeginOpenRead string AsyncCallback object 使用异步编程模型
  • 将 LUIS 与 FormFlow 集成

    我创建了一个机器人 里面有一个 FormFlow 现在 如果您输入 我想启动产品 LUIS 将告诉它必须转到哪个对话框 internal static IDialog
  • 从变量使用 OLE DB 源命令的 EzAPI 等效项是什么?

    tl dr 使用 来自变量的 SQL 命令 数据访问模式的 OLE DB 源并分配变量的 EzAPI 代码是什么 Preamble 每月一次 我们需要使用生产数据的子集刷新我们的公共测试站点 我们已确定 根据我们的需求 SSIS 解决方案最
  • 我应该如何以非 root 身份读取 Linux 上的 Intel PCI 非核心性能计数器?

    我想要一个库 允许对 Linux 可执行文件的关键部分进行 自我分析 就像人们可以使用一个部分计时一样获取当日时间 http linux die net man 2 gettimeofday or RDTSC http www strchr
  • 未定义的参考错误 - rand

    我正在创建一个命令行 C 测试应用程序 可执行 以便在我的 root Android 设备上运行 该可执行文件使用多个预构建的 C 库 其中之一使用 rand 在链接状态期间我收到错误 rand 的未定义引用 为了检查路径是否设置正确 我尝
  • C# 3 新功能帖子(与 .Net 3.5 功能无关)[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions Net F
  • 获取在 Unity 中实现接口的所有类型

    如果您只想知道解决方案 请跳至更新 我有一个应用程序 它使用以下代码来获取并运行许多工作方法 var type typeof IJob var types AppDomain CurrentDomain GetAssemblies Sele
  • PostgreSQL:42883 运算符不存在:没有时区的时间戳 = 文本

    我正在使用 Npgsql 3 0 3 0 和 PetaPoco 最新版本 当我运行这个命令时 var dateCreated DateTime Now just an example var sql new Sql WHERE date c
  • Microsoft.Web.Administration 内存泄漏

    拥有一个 Windows 服务 除其他外 还可以监视 IIS 应用程序池 如果任何池已配置应用程序但未运行 则该池 池 将启动 这已经运行良好一段时间了 最近发现该服务存在内存泄漏 查看内存转储 罪魁祸首是用于检查应用程序池的 Micros
  • 如何从 nuget 包中排除子目录和内容

    所以我有一个网站正在尝试打包用于 Octopus Deploy 我有以下文件夹结构 Web Views WantThis Dontwantthis WantThis1 WantThis2 lots more Scripts 我试图排除 Do
  • C# 如何在没有 GacUtil 的情况下在 GAC 中注册程序集?

    我需要使用批处理文件在 GAC 中注册程序集 有没有办法找到安装位置GacUtil exe或者有没有办法在没有 GacUtil 的情况下注册程序集 Your bestbet is to use a powershell script tha
  • const int 列表而不是 enum

    我开始研究大型 C 代码库 并发现使用带有多个 const ints 字段的静态类 这个类的行为与枚举完全一样 我想将类转换为实际的枚举 但权力被拒绝 我想转换它的主要原因是这样我可以将枚举作为数据类型而不是 int 这对可读性有很大帮助
  • 当 MSB 位等于 0 时如何以十六进制格式打印它们

    我需要使用打印变量HEX格式 问题是当我的变量很小时 MSB 等于 0 因此不会打印它们 ex uint16 t var 10 0x000A h gt 我需要打印 000A 但无论我做什么它总是打印 A 我怎样才能让它发挥作用 您可以添加前
  • allocator.construct 循环是否等于 std::uninitialized_copy?

    在此背景下T是某种类型并且allocator是该类型的分配器对象 默认情况下是std allocator
  • DateTimeFormat.AbbreviatedMonthNames 在月份名称末尾添加一个点

    昨晚 我们将 Web 服务层从物理 Windows 2008 r2 迁移到虚拟 Windows 2012 我们的日志中收到大量有关 DateTime 无效格式的事件 这很奇怪 因为我们仔细检查了区域设置 长话短说 CultureInfo G
  • CGAL:如何有效计算多面体的面面积?

    我有一个多面体 其面是三角形 我知道在 CGAL 中 Triangle 3 类提供了 squared area 方法 通过它我们可以计算三角形的面积 有什么方法可以将其应用到多面体方面吗 或者关于如何计算每个面的面积有什么想法吗 这是一个例
  • 什么时候适合在 C++ 中使用 static(在未命名的命名空间上)?

    我一整天都在阅读有关未命名命名空间的文章 大多数文章都解释了何时应该在 static 关键字上使用未命名命名空间 但我仍然有一个大问题什么时候适合使用静态 毕竟它还没有完全弃用 那么带有静态函数的头文件我现在应该将它们放入未命名的命名空间中
  • ASP .NET Core IIS 托管用户身份名称为空且 IsAuthenticated=false

    我在 IIS 上运行 ASP NET Core dll 使用 AspNetCoreModule 使用以前的 ASP NET 我可以通过以下方式获取用户身份名称 HttpContext Current User Identity Name 因
  • 是一对一的关系不好的策略

    用户始终拥有一个钱包 一个钱包始终属于一位用户 由于我想分离与钱夹相关的属性 我创建了 Wallet 对象并能够跟踪钱交易 我创建了 public Wallet Entity
  • qt 如何知道按钮被点击?

    我正在尝试编写一个程序 用声音进行一些操作 我的问题是我有 3 个播放按钮和 3 个标签 我希望无论我单击 播放 按钮 都应该播放按钮附近标签中名称的声音 我有一个没有任何参数的播放插槽 那么 如何分别连接到每个播放按钮和每个标签呢 实际上

随机推荐

  • Unity3d学习之路-简单巡逻兵

    简单巡逻兵 简单巡逻兵 游戏规则与游戏要求 游戏UML类图 游戏实现 巡逻兵部分 巡逻兵预制体 巡逻兵创建 巡逻兵巡逻与追捕 玩家部分 区域部分 订阅与发布模式部分 发布事件类 订阅者 水晶触碰 玩家
  • 2021年 至 2023年 mysql国家法定节假日脚本

    2021年 至 2023年 mysql国家法定节假日脚本 查阅相关资料 根据日历表核对数据 获取2021 2022 2023年数据 建表语句 DROP TABLE IF EXISTS public holiday info CREATE T
  • java代码实现导出或者下载xml、word、pdf、excel功能

    java代码实现导出或者下载xml word pdf excel功能 写在前面 将用户操作日志以xml word pdf excel格式的文件导出 1 导出xml 导出xml使用JAXB的注解实现 实体如下 import javax xml
  • 目标检测——mAP

    mean Average Precision 对于一张图片中的c类目标 算法检测出来 T 个c类的目标 而真值是 TP FN 个c类的目标 检测结果中有 TP 个结果和真值的 IOU 达到某个设定的阈值 那么Precision定义为 对所有
  • 机器学习—使用Gradient Descent预测房价—c++实现

    1 Introduction 之前写了一篇梯度下降的c语言实现 听吴恩达机器学习的入门课 于是考虑自己写一个简易的机器学习的例子 我们打算做一个model为f x W x B这样简单的模型 背景可以理解为房子的面积对应不同的价格 x为房子的
  • BUG:使用/var/log/messages初步定位软件莫名退出问题

    BUG 使用 var log messages定位问题 1 var log 目录下文件和目录简介 var log messages 包括整体系统信息 也包含系统启动期间的日志 此外 mail cron daemon kern和auth等内容
  • 使用谷歌提供的解析插件gsonformat安装到Android studio方法

    转载地址 https www cnblogs com tianmanyi p 6028624 html Android Studio菜单栏File gt Settings gt plugins 这个是Android Studio搜索和安装插
  • C语言——输入两个数,输出较大值(函数调用实现)

    C语言函数调用 输入两个数 要求输出其较大值 一个返回值 两个参数 名字getMaxFromTwoData 函数体 正常实现 三目运算符 函数调用过程 1 在定义函数中指定的形参 在未出现函数调用时 不占用存储单元 发生调用时 函数形参被临
  • 解决Tomcat下IntelliJ IDEA报错java.lang.NoClassDefFoundError: javax/servlet/ServletContextListener

    解决Tomcat下IntelliJ IDEA报错java lang NoClassDefFoundError javax servlet ServletContextListener 笔者在做代码重构的时候 以前记得运行正常的代码 如今一直
  • 解决Pycharm导入模块时提示Cannot find reference

    问题描述 今天在学习python时候遇到了一个导入模块时提醒Cannot find reference 的问题 要导入的这个模块是正常的 解决方案 在pycharm中设置source路径 File gt Setting gt Project
  • 数据结构与算法Python版期末在线考试OJ部分

    1 二叉树路径 10分 题目内容 给定一个二叉查找树的节点插入顺序 请重新构建这个二叉查找树 并按从左至右顺序返回所有根节点至叶节点的路径 输入格式 一行整数 以空格分隔 注 测试用例中不包含重复的数字 输出格式 按照叶节点由左至右顺序 以
  • 【亲测可用】使用pm2部署nuxt项目

    1 科普 Nuxt是一个基于vue js的应用框架 可以做到服务器端渲染 解决vue动态生成页面 难以SEO优化的难题 详情我这里不赘述了 想要了解的可以查看这里 Nuxt教程 PM2是一个进程管理工具 用于启动 维护Node的应用程序 非
  • Finalshell连接Linux超时之Connection timed out: connect

    BUG原因 每次重启finalshell 还是 CentOS ip地址存在变化的可能 目录 前言 报错 摸索 解决措施 前言 1 福利 花了2小时才解决的BUG 希望本篇文章能帮你10分钟解决 2 tips ipconfig或ip addr
  • cp can‘t stat ..........

    在我把usr src linux source 5 13 0 tar bz2复制的时候出现了错误 就是这个提示 cp cannot stat linux source 5 13 0 tar bz2 No such file or direc
  • Linux性能调优之sar详解

    什么是sar sar是一个采集 报告和存储计算机负载信息的工具 有的时候 我们要通过对系统的cpu负载等性能数值的查看 来判排查系统产生某种故障 经常死机或者运行速度突然变慢 的原因 但是 简单的top uptime w等命令只可以查看当前
  • linux :ubuntu 安装搜狗输入法

    1 安装Fcitx输入框架 sudo apt install fcitx 2 搜狗输入法官网下载Linux版本搜狗输入法 32位和64位根据自己情况 3 输入指令安装 sudo dpkg i sogoupinyin 2 3 1 0112 a
  • 虚拟机的内存泄漏和内存溢出

    文章目录 内存泄漏 内存溢出 集合引起的内存泄漏 内存泄漏 内存泄漏的根本原因是长生命周期的对象持有短生命周期对象的引用 尽管短生命周期的对象已经不再需要 但由于长生命周期对象持有它的引用而导致不能被回收 以发生的方式来分类 内存泄漏可以分
  • google protobuf使用

    http www cnblogs com youxin p 4073703 html If you get the source from github you need to generate the configure script f
  • chatgpt赋能python:如何实现Python代码执行完后再执行

    如何实现Python代码执行完后再执行 当我们在编写Python代码时 可能会需要在代码执行完之后再执行一些任务 这种情况非常常见 例如在爬虫中 可能需要在爬取完网页内容后再将其存入数据库 Python提供了多种方法来实现这个目的 方法一
  • 嵌入式开发之堆栈调试打印

    简介 打印堆栈的常用方法包括 glibc中的backtrace函数 gcc内置函数 builtin return address 第三方库libunwind 1 glibc中的backtrace 1 1函数原型 include