如何得到当前程序执行的堆栈

1. 背景

通常我们只是在调试程序的时候,才用 gdb bt命令显示当前进程(或线程)所在的堆栈。实际代码开发中,一般较少需要得到当前程序的堆栈。但是,在调试一些不容易复现、gdb难以跟踪的bug时,或在需要记录部分代被执行的上下文的情景下,就非常有必要得到当前程序运行的堆栈。

2. 原理

要实现上面得到当前程序堆栈的功能,需要依赖glibc中execinfo.h声明的backtrace()函数族。为此我们需要先了解glibc中backtrace是如何实现的。

2.1 底层库的实现

可以参考glibc-2.17/debug/backtrace.c,执行过程是从栈顶遍历到栈底,一层层根据调用关系,取得当前sp的值,并保存在指定的数组里面。

/* By default assume the `next' pointer in struct layout points to thenext struct layout.  */
#ifndef ADVANCE_STACK_FRAME
# define ADVANCE_STACK_FRAME(next) BOUNDED_1 ((struct layout *) (next))
#endif/* By default, the frame pointer is just what we get from gcc.  */
#ifndef FIRST_FRAME_POINTER
# define FIRST_FRAME_POINTER  __builtin_frame_address (0)
#endifint
__backtrace (array, size)void **array;int size;
{struct layout *current;void *__unbounded top_frame;void *__unbounded top_stack;int cnt = 0;top_frame = FIRST_FRAME_POINTER;top_stack = CURRENT_STACK_FRAME;/* We skip the call to this function, it makes no sense to record it.  */current = BOUNDED_1 ((struct layout *) top_frame);while (cnt < size)
{if ((void *) current INNER_THAN top_stack|| !((void *) current INNER_THAN __libc_stack_end))/* This means the address is out of range.  Note that for thetoplevel we see a frame pointer with value NULL which clearly isout of range.  */
break;array[cnt++] = current->return_address;current = ADVANCE_STACK_FRAME (current->next);}return cnt;
}
weak_alias (__backtrace, backtrace)
libc_hidden_def (__backtrace)

2.2 用户态的使用过程

这样,程序开发者就可以直接include execinfo.h头文件,然后调用backtrace()函数。execinfo.h中列出了实现类似功能的一组函数族:

/* Store up to SIZE return address of the current program state inARRAY and return the exact number of values stored.  */extern int backtrace (void **__array, int __size) __nonnull ((1));/* Return names of functions from the backtrace list in ARRAY in a newlymalloc()ed memory block.  */extern char **backtrace_symbols (void *const *__array, int __size)__THROW __nonnull ((1));/* This function is similar to backtrace_symbols() but it writes the resultimmediately to a file.  */extern void backtrace_symbols_fd (void *const *__array, int __size, int __fd)__THROW __nonnull ((1));

从参数中可以想到,需要为array预备一部分存储调用栈的存储空间,后面调用的backtrace()把会进程当前执行的栈信息写到这个array里面去。

3. 实例分析

3.1 测试程序及其输出

具体示例如下execinfo.c:

#include <stdio.h>
#include <execinfo.h>int main(int argc, char * argv[])
{
int i = 0;
void * stack[1024] = {NULL,};backtrace(stack, 1024);for (i = 0; i < 32; i++) {printf("stack %d: %p\n", i, stack[i]);
}return 0;
}

gcc -g -o execinfo execinfo.c 完成之后,可以看一下这个程序的运行结果:
[root@localhost test]# ./execinfo
stack 0: 0x4005cd
stack 1: 0x7fced5bb6c05
stack 2: 0x4004b9
stack 3: (nil)
.......

3.2 反汇编二进制及其分析

我们接着对execinfo反汇编: objdump -alDS ./execinfo >> execinfo.S,得到execinfo.S之后,
看看0x4005cd 对应到哪一行代码:
/home/qxi/test/execinfo.c:34

    backtrace(stack, 1024);4005b9:   48 8d 85 f0 df ff ff    lea    -0x2010(%rbp),%rax4005c0:   be 00 04 00 00          mov    $0x400,%esi4005c5:   48 89 c7                mov    %rax,%rdi4005c8:   e8 83 fe ff ff          callq  400450 <backtrace@plt>
/home/qxi/test/execinfo.c:36for (i = 0; i < 32; i++) {4005cd:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)4005d4:   eb 25                   jmp    4005fb <main+0x7b>
/home/qxi/test/execinfo.c:37 (discriminator 2)

可以看到它指向的是backtrace()执行之后的程序地址,也就是最后一个入栈的值。接着看0x40004b9分别对应哪个函数:

Disassembly of section .text:
0000000000400490 <_start>:
_start():400490:   31 ed                   xor    %ebp,%ebp400492:   49 89 d1                mov    %rdx,%r9400495:   5e                      pop    %rsi400496:   48 89 e2                mov    %rsp,%rdx400499:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp40049d:   50                      push   %rax40049e:   54                      push   %rsp40049f:   49 c7 c0 80 06 40 00    mov    $0x400680,%r84004a6:   48 c7 c1 10 06 40 00    mov    $0x400610,%rcx4004ad:   48 c7 c7 80 05 40 00    mov    $0x400580,%rdi4004b4:   e8 b7 ff ff ff          callq  400470 <__libc_start_main@plt>4004b9:   f4                      hlt4004ba:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

可以看到4004b9记录的是从glibc跳到main()函数之后执行的第一条指令的位置,也就是最早入栈的值。

4. 总结

函数调栈中压栈的值,记录着当前调用返回后会执行的下一个指令(函数)的地址. 结合上面的原理和示例分析,可以看到在应用程序中得到当前程序的调用栈的过程,就是把函数调用过程中一层层入栈的值,从栈顶一个个再次读出的过程。因此利用这个特性,再结合一些其他技术,我们可以用来实现跟踪资源泄漏、锁申请而没有释放等高级功能。

本文转自存储之厨51CTO博客,原文链接:http://blog.51cto.com/xiamachao/2064805 ,如需转载请自行联系原作者

如何得到当前程序执行的堆栈相关推荐

  1. javascript之执行上下文堆栈

    执行上下文堆栈 有三种类型的ECMAScript代码:全局代码,函数代码和eval代码.代码执行在它的执行上下文里. 有唯一的全局上下文,以及可能有多个函数和eval上下文.每一个函数调用,进入到函数 ...

  2. 菜鸟学习笔记:Java基础篇3(面向对象思想、程序执行过程内存分析、面向对象重要概念)

    菜鸟学习笔记:Java面向对象篇上 Java面向对象的思想 Java程序执行过程内存分析 Java垃圾回收机制 构造方法 方法重载(overload) static关键字 this关键字 Java面向 ...

  3. 如何让java程序执行一段时间后停止

    如何让java程序执行一段时间后停止 1.概述 在本文中,我们将学习如何在一段时间后结束长时间运行的任务.我们将探讨这个问题的各种解决方案.此外,还将介绍各种方案缺点. 2.使用循环 假设我们在一个循 ...

  4. JVM原理(二)执行引擎篇(JVM程序执行流程、JIT编译器、JIT编译器优化)

    一.JVM程序执行流程 上一章我们介绍过程序执行通常分为解释执行和编译执行,而Java两种方式都采用了,下面是Java编译成字节码.动态编译和解释为机器码的过程分析: 编译器和解释器的协调工作流程: ...

  5. ucosii任务三要素---执行代码 堆栈 任务控制块

    使用ucosii也有一段时间了,把学习到的总结一下.这篇文章不是对ucosii如何使用的讲解,而是主要看看ucosii内核实现的原理,或者说讲一些RTOS种通用的知识.对于RTOS基础知识的讲解,暂时 ...

  6. 终止js程序执行的方法

    js终止程序执行的方法共有三种 (一)在function里面(普通js方法) (1)return; (2)return false; (二)非function方法里面(如ajax方法) alert(& ...

  7. linux下程序执行的步骤及其作用

    程序执行的步骤及其作用 在linux下使用gcc编程时,从表面上看是简单的命令的执行,但实际上,程序的执行分为四个步骤:预编译,编译,汇编,链接. 预编译 在linux系统下,一个.c文件经过预编译生 ...

  8. 页面动态显示程序执行结果-append

    页面动态显示程序执行结果-append 一般CMS程序安装时会看到: 原理就是用:jquery的append属性:[ajax配合使用即可] 自己简单写了个:知道原理即可: <script src ...

  9. python导包顺序_2019-03-21 python导入包以及Python程序执行顺序理解

    http://codingpy.com/article/python-import-101/ https://segmentfault.com/a/1190000009842139 (一)Python ...

  10. python读文件路径-python获取程序执行文件路径的方法(推荐)

    1.获取当前执行主脚本方法:sys.argv[0]和_ file _ (1)sys.argv 一个传给Python脚本的指令参数列表.sys.argv[0]是脚本的名字.一般得到的是相对路径,用os. ...

最新文章

  1. echarts 地图 scatter点击事件_React实现高亮可点击地图
  2. 【数据分析】多场景下的算法构建
  3. php浏览器类型检测工具,php检测客户端浏览器类型的简单示例
  4. html日期英文状态显示不出来,html 时间控件插件laydate, 显示时分,不显示秒
  5. Iptalbes自动封杀暴力破解(Qmail邮件系统)者的IP地址
  6. Sentinel 1.8.0 年度版本发布,熔断降级重构升级!
  7. Diskpart命令安装系统小结
  8. 关于稀疏矩阵转化为稠密矩阵问题 (scipy.sparse格式和tensor稀疏张量格式)
  9. Halcon 4点单标相机外参
  10. docker安装指定版本的tag镜像
  11. Solana 海湾流(Gulf Stream)海平面(Sealevel)区别
  12. 串口通信以及波特率计算方法
  13. 抖音seo/抖音搜索排名系统/抖音矩阵优化/抖音seo源码开发,轻松进前十
  14. 蓝桥杯之桥本分数式(全排列函数应用)
  15. 电力系统非线性控制_电力系统保护与控制2020年第13期目录
  16. 云分众享,阿里云盘资源搜索工具
  17. 解决通过硬盘或U盘安装ubuntu server出现无法挂载光盘的问题教程
  18. 介绍Zbrush是什么软件
  19. Android Studio 4.1没有GsonFormat插件
  20. 神奇的 Excel 插件:Azure DevOps 插件

热门文章

  1. jsp和java一样具有平台独立性._web开发技术总复习题
  2. 无人驾驶插秧机智能辅助系统_北斗年会 | 智慧农业:插秧“神器”大显身手——雷科防务致力于高精度无人驾驶插秧机前装应用...
  3. soapui返回值类型都有哪些_小程序都有哪些类型,开发小程序效果如何
  4. android 百度地图 64位,百度地图 Android SDK
  5. 添加halcon图像显示控件_Halcon的C#二次开发及经验分享
  6. flutter怎么手动刷新_Flutter 怎样更新?怎样升级? - Flutter - Angular 教程网
  7. Egg 2.20.0 发布,阿里开源的企业级 Node.js 框架
  8. UVA 10733 - The Colored Cubes(Ploya)
  9. 个人Web自动化测试学习点总结
  10. String类型的方法总结