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

简介

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

  • 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 200int 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 __cplusplusint status;
#endifunw_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 __cpluspluschar *func_name = abi::__cxa_demangle(func_name_cache, 0, 0, &status);
#elseconst char *func_name = func_name_cache;
#endifprintf("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 __cplusplusif (func_name)free((void *)func_name);
#endifframe_id++;}printf("+++++++++++++++++++++++++++\n");
}

<backtrace.h>

#ifndef __BACKTRACE_H
#define __BACKTRACE_H#ifdef __cplusplus
extern "C"
{
#endifvoid 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_cppProgram 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)

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

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

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

相关文章

【C语言】动态内存管理(malloc,free,calloc,realloc)-- 详解

一、动态内存分配 定义&#xff1a;动态内存分配 (Dynamic Memory Allocation) 就是指在程序执行的过程中&#xff0c;动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样&#xff0c;需要预先分配存储空间&#xff0c;而是由系统根据程…

kafka复习:(20):消费者拦截器的使用

一、定义消费者拦截器&#xff08;只消费含"sister"的消息&#xff09; package com.cisdi.dsp.modules.metaAnalysis.rest;import org.apache.kafka.clients.consumer.ConsumerInterceptor; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.…

0103水平分片-jdbc-shardingsphere-中间件

文章目录 1 准备服务器1.1 创建server-order0容器1.2 创建server-order1容器 2、基本水平分片2.1、基本配置2.2、数据源配置2.3、标椎分片表配置2.4、行表达式2.5、分片算法配置2.6、分布式序列算法 3、多表关联3.1、创建关联表3.2、创建实体类3.3、创建Mapper3.4、配置关联表3…

【C++设计模式】用动画片《少年骇客》(Ben10)来解释策略模式

2023年8月25日&#xff0c;周五上午 今天上午学习设计模式中的策略模式时&#xff0c;发现这个有点像很多卡通片里面的变身器... #include<iostream>//alien hero是外星英雄的意思 //在《少年骇客》中&#xff0c;主角可以通过变身器变成10种外星英雄 class AlienHero{ …

手机盖板IR油墨透光率检测仪T03

手机盖板作为手机最外层玻璃面板&#xff0c;其加工一般有落料、倒边、抛光、镀膜、丝印等多道加工工序组成&#xff0c;其中任何一个工序出现差错&#xff0c;都有可能导致手机盖板产生缺陷&#xff0c;例如漏油、透光、IR孔不良、视窗划伤、油墨区划伤、內污、边花等&#xf…

CSS中如何实现元素之间的间距(Margin)合并效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 外边距合并的示例&#xff1a;⭐ 如何控制外边距合并&#xff1a;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff…

C语言实现状态机

关于状态机&#xff0c;基础的知识点可以自行理解&#xff0c;讲解的很多&#xff0c;这里主要是想写一个有限状态机FSM通用的写法&#xff0c;目的在于更好理解&#xff0c;移植&#xff0c;节省代码阅读与调试时间&#xff0c;体现出编程之美。 传统的实现方案 if...else : …

【Spring Boot】什么是深度优先遍历与广度优先遍历?用Spring Boot项目举例说明。

深度优先遍历&#xff08;Depth First Search&#xff0c;DFS&#xff09;和广度优先遍历&#xff08;Breadth First Search&#xff0c;BFS&#xff09;是图的遍历算法。其中&#xff0c;深度优先遍历从某个起始点开始&#xff0c;先访问一个节点&#xff0c;然后跳到它的一个…

mysql 通过 group by 分组查询最大时间的一条数据

SELECT* FROMdemo_table lRIGHT JOIN ( SELECT table_id, MAX( create_time ) AS create_time FROM demo_table GROUP BY table_id ) t ON t.create_time l.create_time AND t.table_id l.table_id

基于GEWE框架实现微信关键字回复

友情链接 geweapi.com 点击即可访问 发送app类型消息 小提示&#xff1a; 发送一些特殊的消息类型注意参数 请求URL&#xff1a; http://域名地址/api/message/sendapp 请求方式&#xff1a; POST 请求头&#xff1a; Content-Type&#xff1a;application/json X-GEWE…

多页面应用,vue cli 配置不生成 html 文件

目录 已解决1&#xff0c;需求2&#xff0c;解决方案1&#xff0c;保持现状2&#xff0c;不生成 html 文件3&#xff0c;将生成的 html 文件放到其他目录。 3&#xff0c;实现1&#xff0c;项目结构2&#xff0c;vue.config.js 核心配置3&#xff0c;打包结果4&#xff0c;vue.…

Nginx入门——Nginx的docker版本和windows版本安装和使用 代理的概念 负载分配策略

目录 引出nginx是啥正向代理和反向代理正向代理反向代理 nginx的安装使用Docker版本的nginx安装下载创建挂载文件获取配置文件创建docker容器拷贝容器中的配置文件删除容器 创建运行容器开放端口进行代理和测试 Windows版本的使用反向代理多个端口运行日志查看启动关闭重启 负载…

【BASH】回顾与知识点梳理(三十八)

【BASH】回顾与知识点梳理 三十八 三十八. 源码概念及简单编译38.1 开放源码的软件安装与升级简介什么是开放源码、编译程序与可执行文件什么是函式库什么是 make 与 configure什么是 Tarball 的软件如何安装与升级软件 38.2 使用传统程序语言进行编译的简单范例单一程序&#…

leetcode304. 二维区域和检索 - 矩阵不可变(java)

前缀和数组 二维区域和检索 - 矩阵不可变题目描述前缀和代码演示 一维数组前缀和 二维区域和检索 - 矩阵不可变 难度 - 中等 原题链接 - 二维区域和检索 - 矩阵不可变 题目描述 给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总…

python3对接godaddy API,实现自动更改域名解析(DDNS)

python3对接godaddy API&#xff0c;实现自动更改域名解析&#xff08;DDNS&#xff09; 文章开始前&#xff0c;先解释下如下问题&#xff1a; ①什么是域名解析&#xff1f; 域名解析一般是指通过一个域名指向IP地址&#xff08;A解析&#xff09;&#xff0c;然后我们访问…

SpringBoot-1-Spring Boot实战:快速搭建你的第一个应用,以及了解原理

SpringBoot-1-Spring Boot实战&#xff1a;快速搭建你的第一个应用&#xff0c;以及了解原理 今日内容 SpringBootWeb入门 前言 我们在之前介绍Spring的时候&#xff0c;已经说过Spring官方(Spring官方)提供很多开源项目&#xff0c;点击projects&#xff0c;看到spring家族…

【前端】深入解析CSS:选择器、显示模式、背景属性和特征剖析

目录 一、前言二、CSS的复合选择器1、后代选择器①、语法②、注意事项 2、子选择器①、语法②、注意事项 3、并集选择器①、语法②、注意事项 4、链接伪类选择器①、语法②、注意事项 三、CSS元素显示模式转换1、转换为块元素display:block2、转换为行内元素display:inline3、转…

带您解读DeepBook经济原理

DeepBook是Sui上的第一个原生流动性层&#xff0c;通过Sui可预测且低廉的gas费&#xff0c;将促进DeepBook上的大规模交易活动。鉴于DeepBook的中央限价订单簿&#xff08;Central Limit Order Book&#xff0c;CLOB&#xff09;架构&#xff0c;交易量越大&#xff0c;资产价格…

java gradle 项目 在idea上 搭建一个简单的thrift实例

前言 Thrift是RPC通信的一种方式&#xff0c;可以通过跨语言进行通信&#xff0c;最近项目需要进行跨语言的通信&#xff0c;因此首先尝试搭建了一个简单的thrift框架&#xff0c;因为网上的实例大都参差不全&#xff0c;通过gpt查询得到的结果对我帮助更大一点&#xff0c;但…

通信原理 | 窗函数 | 矩形窗 | 汉宁窗 | 汉明窗 | 布莱克曼窗 | 补零对频谱的影响

文章目录 矩形窗矩形窗的时域表达式N=32的时域图N=32的频域图时域补零后的时域序列时域补零后的频域序列时域补零到序列长度为4096,对应的频域序列纵轴取对数汉宁窗N=32的情况下的时域序列N=32的频域图时域补零后的时域序列和对应频域序列时域补零到序列长度为4096,对应的频域…