内核中dump_stack()的实现,并在用户态模拟dump_stack()【转】
转自: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()【转】的更多相关文章
- 用户态使用 glibc/backtrace 追踪函数调用堆栈定位段错误【转】
转自:https://blog.csdn.net/gatieme/article/details/84189280 版权声明:本文为博主原创文章 && 转载请著名出处 @ http:/ ...
- Linux内核中的软中断、tasklet和工作队列具体解释
[TOC] 本文基于Linux2.6.32内核版本号. 引言 软中断.tasklet和工作队列并非Linux内核中一直存在的机制,而是由更早版本号的内核中的"下半部"(bottom ...
- 内核通信之Netlink源码分析-用户内核通信原理3
2017-07-06 上节主讲了用户层通过netlink和内核交互的详细过程,本节分析下用户层接收数据的过程…… 有了之前基础知识的介绍,用户层接收数据只涉及到一个核心调用readmsg(), 其他的 ...
- [中英对照]Device Drivers in User Space: A Case for Network Device Driver | 用户态设备驱动: 以网卡驱动为例
前文初步介绍了Linux用户态设备驱动,本文将介绍一个典型的案例.Again, 如对Linux用户态设备驱动程序开发感兴趣,请阅读本文,否则请飘过. Device Drivers in User Sp ...
- 例说linux内核与应用数据通信(四):映射设备内核空间到用户态
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 一个进程的内存映象由以下几部分组成:代码段.数据段.BSS段和 ...
- 用户态tcp协议栈调研
一.各种用户态socket的对比 1.MTCP 简单介绍: 韩国高校的一个科研项目,在DPDK的2016年的技术开发者大会上有讲,所以intel将这个也放到了官方上,所以一般搜索DPDK的用户态的协议 ...
- 内核中dump_stack的实现原理(1) —— 栈回溯
环境 Aarch64 Qemu aarch64-linux-gnu-gcc linux-4.14 概述 栈回溯的目的是将函数的调用栈打印出来,对于分析函数调用和debug系统异常会很有帮助 ...
- 在linux系统中实现各项监控的关键技术(2)--内核态与用户态进程之间的通信netlink
Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 ...
- Linux内核中进程上下文、中断上下文、原子上下文、用户上下文的理解【转】
转自:http://blog.csdn.net/laoliu_lcl/article/details/39972459 进程上下文和中断上下文是操作系统中很重要的两个概念,这两个概念在操作系统课程中不 ...
随机推荐
- MySQL_参数设置
1.max_allowed_packet 描述:有时备份数据库时,将本地psc文件备份到数据库时,会遇见备份不成功的情况 分析:这时要考虑,是否由于max_allowed_packet这个参数过小,导 ...
- Python中集合的操作
Python集合的基本详情 集合是无序的 集合是可变数据类型 集合属于不可哈希范围 集合自动去重 集合的操作 set1 = {1, 2, 3, 4, 5} set2 = {4, 5, 6, 7, 8} ...
- Hbase准生产配置
hbase-site.xml <?xml version="1.0"?><?xml-stylesheet type="text/xsl" hr ...
- Python package下载中遇到ReadTimeoutError: HTTPSConnectionPool?
问题描述: Python package下载中遇到ReadTimeoutError: HTTPSConnectionPool? 问题解决: 方法1:继续重复下载 pip install virtual ...
- .NET获取当前程序所在电脑的CPU和内存使用率
using System; using System.Diagnostics; using System.Text; using System.Runtime.InteropServices; nam ...
- Java高并发秒杀API之web层
第1章 设计Restful接口 1.1前端交互流程设计 1.2 学习Restful接口设计 什么是Restful?它就是一种优雅的URI表述方式,用来设计我们资源的访问URL.通过这个URL的设计,我 ...
- JVM简析
JVM结构图 1.程序计数器 是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支. ...
- java实现《剑指offer》(二)11~20 更新中
11.二进制中1的个数 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. (1)最优解 public class Solution { public int NumberOf1(int ...
- 分布式中的 transaction log
分布式中的 transaction log 在分布式系统中,有很多台node组成一个cluster,对于client 的一个写操作请求而言,在什么样的情况下,cluster告诉client此次写操作请 ...
- 对XML文件进行的修改
XmlDocument xml = new XmlDocument(); xml.Load(Way);//获取相对路径 var Exports = xml.GetElementsByTagName(& ...