转自:https://blog.csdn.net/jasonchen_gbd/article/details/44066815?utm_source=blogxgwz8

版权声明:本文为博主原创文章,转载请附上原博链接。 https://blog.csdn.net/jasonchen_gbd/article/details/44066815
内核中的dump_stack()
获得内核中当前进程的栈回溯信息需要用到的最重要的三个内容就是:

栈指针:sp寄存器,用来跟踪程序执行过程。

返回地址:ra寄存器,用来获取函数的返回地址。

程序计数器:epc,用于定位当前指令的位置。

本文的内容都是基于mips体系架构的,如果你不搞mips,就只看个大致流程就可以了,不然可能会被某些内容误导。在ARM中,这三个寄存器分别为SP、LR和PC寄存器。

dump_stack()用于回溯函数调用关系,他需要做的工作很简单:

1.   从进程栈中找到当前函数(callee)的返回地址。

2.   根据函数返回地址,从代码段中定位该地址位于哪个函数中,找到的函数即为caller函数。

3.   打印caller函数的函数名。

4.   重复前3个步骤。直到返回值为0或不在内核记录的符号表范围内。

在编译程序的时候,所有函数所需要的栈空间的大小都已经计算出来,如果函数需要保存返回地址,返回地址在该函数的栈空间中保存的位置也都计算出来了。所以,我们想得到返回地址,只需得到每个函数栈即可,而所有函数栈都放在进程的栈中,栈顶为sp。

返回地址是caller函数中将要执行的指令,是指向代码段的,这个更容易得到,因为代码段在编译时就确定了。

当前函数的位置通过pc的值可以得到。

例如,现在有func0调用func1,func1又调用func2,在func2执行过程中,进程栈空间大致如下:

左图为栈空间,栈顶为sp,右图为程序代码的部分内容。右图中的实曲线表示出了函数之间的调用和返回关系。调用关系通过跳转指令完成,返回地址通过左图每个函数栈空间中存储的返回地址指定。这样我们就可以得到函数的调用关系,并通过每个函数的地址打印出函数名。

那dump_stack的工作流程就很清楚了。我就不帖代码了,因为基本上都是体系结构相关的操作。

需要说明的一个地方是,通过函数的地址来打印函数名是通过格式控制符%pS来打印的:

printk("[<%p>] %pS\n", (void *) ip,(void *) ip);

在内核代码树的lib/vsprintf.c中的pointer函数中,说明了printk中的%pS的意思:

case 'S':
return symbol_string(buf, end, ptr, spec, *fmt);
即'S'表示打印符号名,而这个符号名是kallsyms里获取的。

可以看一下kernel/kallsyms.c中的kallsyms_lookup()函数,它负责通过地址找到函数名,分为两部分:

1. 如果地址在编译内核时得到的地址范围内,就查找kallsyms_names数组来获得函数名。

2. 如果这个地址是某个内核模块中的函数,则在模块加载后的地址表中查找。

kallsyms_lookup()最终返回字符串“函数名+offset/size[mod]”,交给printk打印。

关于内核符号表kallsyms_names可参考我的另一篇文章点击打开链接。

实现应用程序中的dump_stack()
按照如上所述,实现一个用户态程序的dump_stack好像不是什么难事,因为上面说的步骤在用户态都可以完成,程序运行的方式也基本上是相同的。

那我们实现一个dump_stack需要做的事情只有两点:

1.   获得程序当前运行时间点的pc值和栈指针sp。这样就可以得到每个函数栈中的返回地址。

2.   构造和内核符号表相同的应用程序符号表。

需要注意,不同用户进程都拥有自己的虚拟地址空间,所以栈回溯只能在本进程中完成。

具体实现当然也是体系结构相关的。既然原理都知道了,那我就直接给出代码供参考(mips的)。代码见https://github.com/castoz/backtrace。

其中backtrace.c实现了栈回溯,uallsyms.c用于生成符号表,main.c中为测试代码。
backtrace.c中提供了两个接口供其他文件调用:
show_backtrace():打印函数的回溯信息。
addr_to_name(addr):打印addr对应的函数名。
uallsyms.c文件直接使用内核中的scripts/kallsyms.c,只需要做少量修改,具体的改动为:
1. 符号基准地址改为__start。
2. 需要记录的符号范围改为在_init到_fini之间或_init到_end之间。
3. 维护uallsyms_addresses、uallsyms_num_syms和uallsyms_names三个全局变量,不使用压缩算法,所以不需要其他三个全局变量。
4. 在生成的汇编代码中删除"#include <asm/types.h>"一行,因为在编译时不需要。

测试文件main.c的内容:

#include <stdio.h>
#include "backtrace.h"

int func2(int a, int b);
int func1(int a, int b);
int func0(int a, int b);

int func2(int a, int b)
{
int c = a * b;
printf("%s: c = %d\n", __FUNCTION__, c);
show_backtrace();
return c;
}

int func1(int a, int b)
{
int c = func2(a, b);
printf("%s: c = %d\n", __FUNCTION__, c);
return c;
}

int func0(int a, int b)
{
int c = func1(a, b);
printf("%s: c = %d\n", __FUNCTION__, c);
return c;
}

int main()
{
int a = 4, b = 5;
int (*funcptr)(int, int) = func0;

int c = func0(a, b);
printf("%s: c = %d\n", __FUNCTION__, c);

printf("funcptr's name = %s\n", addr_to_name((unsigned long)funcptr));
return 0;
}
执行make all生成可执行文件testbt,放到mips的系统上运行。
运行结果:
root@openwrt:/tmp# ./testbt
func2: c = 20
4362
Call trace:
=>show_backtrace()+0x20
=>func2()+0x34
=>func1()+0x10
=>func0()+0x10
=>main()+0x14

func1: c = 20
func0: c = 20
main: c = 20
funcptr's name = func0
---------------------
作者:落尘纷扰
来源:CSDN
原文:https://blog.csdn.net/jasonchen_gbd/article/details/44066815
版权声明:本文为博主原创文章,转载请附上博文链接!

内核中dump_stack()的实现,并在用户态模拟dump_stack()【转】的更多相关文章

  1. 用户态使用 glibc/backtrace 追踪函数调用堆栈定位段错误【转】

    转自:https://blog.csdn.net/gatieme/article/details/84189280 版权声明:本文为博主原创文章 && 转载请著名出处 @ http:/ ...

  2. Linux内核中的软中断、tasklet和工作队列具体解释

    [TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...

  3. 内核通信之Netlink源码分析-用户内核通信原理3

    2017-07-06 上节主讲了用户层通过netlink和内核交互的详细过程,本节分析下用户层接收数据的过程…… 有了之前基础知识的介绍,用户层接收数据只涉及到一个核心调用readmsg(), 其他的 ...

  4. [中英对照]Device Drivers in User Space: A Case for Network Device Driver | 用户态设备驱动: 以网卡驱动为例

    前文初步介绍了Linux用户态设备驱动,本文将介绍一个典型的案例.Again, 如对Linux用户态设备驱动程序开发感兴趣,请阅读本文,否则请飘过. Device Drivers in User Sp ...

  5. 例说linux内核与应用数据通信(四):映射设备内核空间到用户态

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途]         一个进程的内存映象由以下几部分组成:代码段.数据段.BSS段和 ...

  6. 用户态tcp协议栈调研

    一.各种用户态socket的对比 1.MTCP 简单介绍: 韩国高校的一个科研项目,在DPDK的2016年的技术开发者大会上有讲,所以intel将这个也放到了官方上,所以一般搜索DPDK的用户态的协议 ...

  7. 内核中dump_stack的实现原理(1) —— 栈回溯

    环境 Aarch64 Qemu aarch64-linux-gnu-gcc linux-4.14   概述     栈回溯的目的是将函数的调用栈打印出来,对于分析函数调用和debug系统异常会很有帮助 ...

  8. 在linux系统中实现各项监控的关键技术(2)--内核态与用户态进程之间的通信netlink

    Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 ...

  9. Linux内核中进程上下文、中断上下文、原子上下文、用户上下文的理解【转】

    转自:http://blog.csdn.net/laoliu_lcl/article/details/39972459 进程上下文和中断上下文是操作系统中很重要的两个概念,这两个概念在操作系统课程中不 ...

随机推荐

  1. XenServer中虚拟机和快照导出与导入

    我们在工作中经常会遇到,把Xenserver中的虚拟机或者快照导出,然后导入到另一台Xenserver,或者导出来备份下来,以防虚拟机出现故障. 下面介绍一下用xe命令如何导出/导入虚拟机或快照,当然 ...

  2. Memcache在.Net中的使用

    一.Memcache基本概念(socket服务器) 本质:是一个在内存上存储的hash表,key的最大值是255字符,最长过期时间为30天 特点:惰性删除,没有监控数据过期的机制,实现最基本的key- ...

  3. ACM-ICPC 2018 南京赛区网络预赛 I Skr (马拉车+hash去重)或(回文树)

    https://nanti.jisuanke.com/t/30998 题意 给一串由0..9组成的数字字符串,求所有不同回文串的权值和.比如说“1121”这个串中有“1”,“2”,“11”,“121” ...

  4. Oracle的 listagg() WITHIN GROUP ()函数使用

    1.使用条件查询  查询部门为20的员工列表 -- 查询部门为20的员工列表    SELECT t.DEPTNO,t.ENAME FROM SCOTT.EMP t where t.DEPTNO = ...

  5. Linux 三剑客 -- awk sed grep

    本文由本人收集整理自互联网供自己与网友参考,参考文章均已列出,如有侵权,请告知! 顶配awk,中配sed,标配grep awk 参考 sed 参考 grep 参考 在线查看linux命令速记表 app ...

  6. windows10 下使用Pycharm2016 基于Anaconda3 Python3.6 安装Mysql驱动总结

    本文记录:在PyCharm2016.3.3 中基于Anaconda3 Python3.6版本安装Python for Mysql驱动.尝试了安装Mysql-Connector成功,但是连接数据库时驱动 ...

  7. Ubuntu Server Download

    点击查看所有版本(中国站点) http://mirror.lzu.edu.cn/ubuntu-releases/

  8. JDK源码之ArrayList

    序言 ArrayList底层通过数组实现. ArrayList即动态数组,实现了动态的添加和减少元素 需要注意的是,容量拓展,是创建一个新的数组,然后将旧数组上的数组copy到新数组,这是一个很大的消 ...

  9. 设置 img 在 div 中水平居中和垂直居中

    Ø  前言 写 html + css 时,img 标签的垂直与水平居中,应该是很常见的设计.实现的方式很多,但是容易遗忘,下面分别整理了几种实现方式. 1.   css 代码 <style ty ...

  10. 【演变】Ajax(AjAj)到WebSocket

    提出问题:A  =>  服务器  =>  B           B端浏览器如何知道服务器有A发来的数据? 解决方案: 第1种:频繁轮询    间隔1秒B向服务器讨要数据,就算数据为空.[ ...