内核中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 进程上下文和中断上下文是操作系统中很重要的两个概念,这两个概念在操作系统课程中不 ...
随机推荐
- varStatus="vs"
通过在百度上查看,发现是属于JSTL 标签.我具体说一下用途吧 就是删除/新增的时候,序号总是连续的从1开始排列,序号的数据与数据库没有关系,序号并不等于id,在数据库中的id仍然是原来的那样,不连续 ...
- flask 渲染jinja2模版和传参
渲染模版(html文件) A.模版文件(html)放入到template目录下,项目启动的时候会从template目录里查找, B.从flask中导入“render_tempalte”函数 C.在视图 ...
- Kafka权威指南 读书笔记之(五)深入Kafka
集中讨论以下3 个有意思的话题 :• Kafka 如何进行复制:• Kafka 如何处理来自生产者和消费者的请求 :• Kafka 的存储细节,比如文件格式和索引. 集群成员关系 Kafka 使用 Z ...
- Java高并发秒杀API之web层
第1章 设计Restful接口 1.1前端交互流程设计 1.2 学习Restful接口设计 什么是Restful?它就是一种优雅的URI表述方式,用来设计我们资源的访问URL.通过这个URL的设计,我 ...
- SVN快速入门笔记【转】
1. SVN版本控制软件目的 协作开发 远程开发 版本回退 2. 什么是SVN subVersion 支持平台操作 支持版本回退 3. 获取SVN软件 属于C/S结构软件(客户端与服务端) serve ...
- 在Java中调用Python
写在前面 在微服务架构大行其道的今天,对于将程序进行嵌套调用的做法其实并不可取,甚至显得有些愚蠢.当然,之所以要面对这个问题,或许是因为一些历史原因,或者仅仅是为了简单.恰好我在项目中就遇到了这个问题 ...
- GreenDao 直接执行SQL的方法
try { DaoSession session=MyApplication.getInstances().getDaoSession(); long fromId=-1; String strSql ...
- JDK8新特性03 Lambda表达式03_Java8 内置的四大核心函数式接口
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.functio ...
- Javaweb——————sql常用思维导图
- HTML 实例学习(基础)
1.HTML <html> 标签 注意:对于中文网页需要使用 <meta charset="utf-8"> 声明编码,否则会出现乱码.有些浏览器会设置 GB ...