转载:http://www.sohu.com/a/118385096_468740

mips栈帧原理

Call stack 是指存放某个程序的正在运行的函数的信息的栈。Call stack 由 stack frames 组成,每个 stack frame 对应于一个未完成运行的函数。

在当今流行的计算机体系架构中,大部分计算机的参数传递,局部变量的分配和释放都是通过操纵程序栈来实现的。栈用来传递函数参数,存储返回值信息,保存寄存器以供恢复调用前处理机状态。 关于栈可见以前的文章: cdecl、stdcall、fastcall函数调用约定区别

每次调用一个函数,都要为该次调用的函数实例分配栈空间。为单个函数分配的那部分栈空间就叫做 stack frame,也就是说,stack frame 这个说法主要是为了描述函数调用关系的。

Stack frame 具体从2点来阐述

第一,它使调用者和被调用者达成某种约定。这个约定定义了函数调用时函数参数的传递方式,函数返回值的返回方式,寄存器如何在调用者和被调用者之间进行共享;

第二,它定义了被调用者如何使用它自己的 stack frame 来完成局部变量的存储和使用。

上图描述的是一种典型的(MIPS O32)嵌入式芯片的 stack frame 组织方式。在这张图中,计算机的栈空间采用的是向下增长的方式(intel 是确定向下增长的,arm可以配置栈增长方向,mips 是可配置还是只能向下增长?),SP(stack pointer) 就是当前函数的栈指针,它指向的是栈底的位置。Current Frame 所示即为当前函数(被调用者)的 frame ,Caller’s Frame 是当前函数的调用者的 frame 。

在没有 BP(base pointer) 寄存器的目标架构中,进入一个函数时需要将当前栈指针向下移动 n 字节,这个大小为n字节的存储空间就是此函数的 stack frame 的存储区域。此后栈指针便不再移动(在linux 内核代码 TODO 里面写着要加上在函数内部调整栈的考虑 – 虽然这通常不会发生),只能在函数返回时再将栈指针加上这个偏移量恢复栈现场。由于不能随便移动栈指针,所以寄存器压栈和出栈都必须指定偏移量,这与 x86 架构的计算机对栈的使用方式有着明显的不同。

RISC计算机一般借助于一个返回地址寄存器 RA(return address) 来实现函数的返回。几乎在每个函数调用中都会使用到这个寄存器,所以在很多情况下 RA 寄存器会被保存在堆栈上以避免被后面的函数调用修改,当函数需要返回时,从堆栈上取回 RA 然后跳转。移动 SP 和保存寄存器的动作一般处在函数的开头,叫做 function prologue;

注意如果当前函数是叶子函数(不存在对其它函数的调用,就不保存ra寄存器,反之就保存)。恢复这些寄存器状态的动作一般放在函数的最后,叫做 function epilogue。关于这些动作可以从IDA 反汇编的结果看出来:

通过上面分析就有思路了:

首先获取当前的栈指针 sp,和指令指针 pc (也叫做 IP)

在mips下sp容易获取 已经约定$29 寄存器作为栈指针,所以可用如下内嵌汇编获取sp:

__asm__ volatile ("move %0, $29" : "=r"(reg));

MIPS没有记录当前PC地址的寄存器,就是说不能像ARM那样读PC寄存器。MIPS使用ra保存函数返回地址,利用这个特性可以获取到当前的PC。 比如:

#pragma GCC push_options

#pragma GCC optimize ("O0")

static unsigned int /*__attribute__((optimize("O0")))*/ * __getpc(void)

{

unsigned int *rtaddr;

__asm__ volatile ("move %0, $31" : "=r"(rtaddr));

return rtaddr;

}

#pragma GCC pop_options

对应的汇编代码是:

注意到上面代码中有 push_options 这是为了防止编译器偷偷把我们的代码给优化成 inline 了,那样就无法获取pc了。在要获取pc的地方调用就可以了:

pc = __getpc();

得到sp 和pc之后剩下的就开始回溯了,具体参考下面函数实现(linux 内核未修改代码,只加入注释):

/*

* TODO for userspace stack unwinding:

* - handle cases where the stack is adjusted inside a function

* (generally doesn't happen)

* - find optimal value for max_instr_check

* - try to find a way to handle leaf functions

*/

static inline int unwind_user_frame(struct stackframe *old_frame,

const unsigned int max_instr_check) //// max_instr_check 函数最大可能的代码长度

{

struct stackframe new_frame = *old_frame;

off_t ra_offset = 0;

size_t stack_size = 0;

unsigned long addr;

if (old_frame->pc == 0 || old_frame->sp == 0 || old_frame->ra == 0)

return -9;

for (addr = new_frame.pc; (addr + max_instr_check > new_frame.pc) /// 上面通过 __getpc 获取的pc指针

&& (!ra_offset || !stack_size); --addr) {

union mips_instruction ip;

if (get_mem(addr, (unsigned long *) &ip)) /// 取出一条指令 ip

return -11;

if (is_sp_move_ins(&ip)) { /// 这条指令是不是 addiu $sp,imme 形式的

int stack_adjustment = ip.i_format.simmediate; /// 如果是求出 imme 立即数,那么本函数栈大小也就知道了

if (stack_adjustment > 0)

/* This marks the end of the previous function,

which means we overran. */

break;

stack_size = (unsigned long) stack_adjustment;

} else if (is_ra_save_ins(&ip)) { /// 是不是 sw / sd $ra, offset($sp) 类似的指令

int ra_slot = ip.i_format.simmediate; /// 如果是获取 offset

if (ra_slot < 0)

/* This shouldn't happen. */

break;

ra_offset = ra_slot;

} else if (is_end_of_function_marker(&ip))

break;

}

if (!ra_offset || !stack_size)

return -1;

if (ra_offset) {

new_frame.ra = old_frame->sp + ra_offset; /// 根据上面的 offset 和当前函数的sp指针得到存放 ra 数值的地址

if (get_mem(new_frame.ra, &(new_frame.ra))) /// 获取ra 的数值,也就是 jal func 这条指令所在的地址

return -13;

}

if (stack_size) {

new_frame.sp = old_frame->sp + stack_size; /// 上个函数的栈指针 貌似这里这么代码是错误的,反正我按我的方式改了能正确运行,

if (get_mem(new_frame.sp, &(new_frame.sp))) /// 这里这么写我也不懂。

return -14;

}

if (new_frame.sp > old_frame->sp)

return -2;

new_frame.pc = old_frame->ra;

*old_frame = new_frame;

return 0;

}

上面代码修改之后可以获取 上一层函数的 pc (jal func :调用本函数的指令不就是上层函数的某一条指令? ra 的值并不是jal func 的地址,而是jal func的地址 +8 ,传说中的 跳转延时槽)指针和sp 。

依此循环调用上面的函数一层层向上回溯,即可。

mips体系堆栈回溯分析与实现的更多相关文章

  1. 嵌入式系统C编程之堆栈回溯【转】

    转自:https://www.cnblogs.com/clover-toeic/p/3949896.html 前言 在嵌入式系统C语言开发调试过程中,常会遇到各类异常情况.一般可按需添加打印信息,以便 ...

  2. 嵌入式系统C编程之堆栈回溯

    前言 在嵌入式系统C语言开发调试过程中,常会遇到各类异常情况.一般可按需添加打印信息,以便观察程序执行流或变量值是否异常.然而,打印操作会占用CPU时间,而且代码中添加过多打印信息时会显得很凌乱.此外 ...

  3. 嵌入式系统C编程之堆栈回溯(二)

    前言 本文作为<嵌入式系统C编程之堆栈回溯>的补充版.文中涉及的代码运行环境如下: 一  异常信号 信号就是软件中断,用于向正在运行的程序(进程)发送有关异步事件发生的信息.Linux应用 ...

  4. Android jni Crash堆栈信息分析

    如何定位Android NDK开发中遇到的错误 NDK编译生成的.so文件作为程序的一部分,在运行发生异常时同样会造成程序崩溃.不同于Java代码异常造成的程序崩溃,在NDK的异常发生时,程序在And ...

  5. jstack 堆栈日志分析

    一.线程的状态 线程间的状态转换:  1. 新建(new):新创建了一个线程对象. 2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法.该状 ...

  6. 字符串的公共前缀对Mysql B+树查询影响回溯分析

        年前项目组接微信公众号. 上线之后,跟微信相关的用cid列的查询会话的SQL变慢了几十倍!思考这个问题思考了非常久.从出现以来一直是我心头的一个结.cid这一列是建了索引的,普通的cid列更新 ...

  7. java 堆栈内存分析详解

    计算机术语里面堆和栈代表不同的存储结构:stack-栈:heap-堆 所以java虚拟机(JVM)中堆和栈是两种内存 堆.栈对比 对比点 堆 栈 JVM中的功能 内存数据区 内存指令区 动静态 运行时 ...

  8. 正则表达式regex回溯分析

    正则表达式的回溯 现在我们来正式认识一下回溯.以字符串“abbc”为例,正则表达式为“ab{1,3}c”,再匹配的时候,a.b.b,匹配完成,这时候,正则表达式会继续用c和b进行比较,发现不符合,这时 ...

  9. 分布式可扩展web体系结构设计实例分析

    Web分布式系统设计准则 下面以一个上传和查询图片的例子来说明分布式web结构的设计考虑和常用的提高性能的方法.该例子提供上传图片和下载图片两个简单功能,并且有一下假设条件?: - 可以存储无上限数量 ...

随机推荐

  1. mysql的主主复制详解

    Mysql双主部署 解释: 所谓双主备份,其实也就是互做主从复制,每台master既是master,又是另一台服务器的slave.这样,任何一方所做的变更,都会通过复制应用到另外一方的数据库中. 要求 ...

  2. Python连接字符串用join还是+

    我们先来看一下用join和+连接字符串的例子 str1 = " ".join(["hello", "world"]) str2 = &quo ...

  3. BZOJ2069 POI2004ZAW(最短路)

    显然这样的路径一定是选择了与1相邻的不同的两点分别作为起点和终点(除1本身).如果能将每一组起点终点都计算到就可以得出最优解了.暴力显然不行.注意到我们每次求出的是单源最短路径,考虑如何充分利用信息. ...

  4. JavaScript无法运行问题

    [出现问题] JavaScript代码在html页面可以正常运行,在js文件夹下无法正常运行 [解决方案] 根据网上查的和自己错误的原因总结下. 1.引用Js的路径不对 ../ 表示上一级目录开始 . ...

  5. 自学Linux Shell5.2-shell内建命令history alias

    点击返回 自学Linux命令行与Shell脚本之路 5.2-shell内建命令history alias 外部命令:有时称为文件系统命令,是存在于bash shell之外的程序,通常位于/bin./u ...

  6. 自学Linux Shell13.2-选项处理(主要getopt、getopts命令)

    点击返回 自学Linux命令行与Shell脚本之路 Bash shell提供了一些不同的方法来从用户处获得数据,包括以下3中方法: 命令行参数(添加在名利后面的数据) 命令行选项(可修改命令行为的单个 ...

  7. awk实例

    AWK-F 以XX为分割df -lh | grep boot | awk '{print $5}' | awk -F '%' '{print $1}'grep "bash" /et ...

  8. ZABBIX 3.4 监控服务器TCP连接状态(六)

    TCP的连接状态对于我们web服务器来说是至关重要的,尤其是并发量ESTAB:或者是syn_recv值,假如这个值比较大的话我们可以认为是不是受到了攻击,或是是time_wait值比较高的话,我们要考 ...

  9. Codeforces Round #548

    没打,简单补档 C.Edgy Trees 容斥,把黑边断掉数联通块,每个联通块贡献$siz^k$ #include<cstdio> #include<cstring> #inc ...

  10. [luoguU42591][小T的面试题]

    luoguU42591 题意: n个不超过n的正整数中,其中有一个数出现了两次,其余的数都只出现了一次, 求这个出现两次的数. 思路: 这个题的亮点在于内存限制1MB.明显不能再用数组储存了,肯定是用 ...