elementui 做的网站,网校培训,淄博个人网站建设,学校网站系统管理请阅读【嵌入式及芯片开发学必备专栏】 请阅读:【Linux 宕机分析 Crash专栏】 文章目录 1.1 栈溢出1.1.1 栈结构1.1.2 汇编实例1.1.3 数组越界栈回踩1.1.4 栈保护区1.1.5 检测栈下溢 上篇文章#xff1a;ARM Linux 系统稳定性分析入门及渐进 2 – Kernel Lockup 下篇文章…请阅读【嵌入式及芯片开发学必备专栏】 请阅读:【Linux 宕机分析 Crash专栏】 文章目录 1.1 栈溢出1.1.1 栈结构1.1.2 汇编实例1.1.3 数组越界栈回踩1.1.4 栈保护区1.1.5 检测栈下溢 上篇文章ARM Linux 系统稳定性分析入门及渐进 2 – Kernel Lockup 下篇文章ARM Linux 系统稳定性分析入门及渐进 4 – 栈分类
1.1 栈溢出
堆和栈的空间必须由程序员静态的分配但计算 堆heap 和 栈stack的空间 大小却不是一件简单的事情即便是对于最小的嵌入式系统。栈一般静态分配并且后进先出开发者静态的指定栈内存空间一般栈向下生长即从高地址到低地址如果栈空间不足发生下溢则栈之下的内存空间被写入。
导致栈溢出的常见的情况有以下几种 (1) 局部数组过大 当系统栈设置比较小时会导致栈溢出。当程序确实需要大数组时可以设置为静态变量或全局变量。
(2) 递归调用层次太多 递归函数在运行时会执行压栈操作当压栈次数太多时也会导致堆栈溢出。
(3) 指针或者数组越界 比方说错误的指针、不加边界检查的数组访问等会造成这种情况。 这两种栈区溢出都会产生 死机 或者 代码跑飞 的风险。当栈区溢出时栈指针指向非法区域这个区域有可能是 全局变量区有可能是别的 task 的栈区。
以函数中数据越界为例 函数 func_a 调用 func_b通过汇编我们知道在执行func_b的指令之前首先会为 func_b进行栈帧分配一般都是进行 SP 指针减去一个立即数据
1.1.1 栈结构
每个进程都会有自己的栈空间而进程中的各个函数也会维护自己本身的一个栈的区域这个区域就是栈帧。那么一个函数的栈帧的区域是如何来界定的呢当然首先会普及ARM的几个特殊寄存器功能
R11frame pointerFP寄存器R12IP寄存器用于暂存SPR13stack pointerSP寄存器R14link registerLR寄存器R15PC寄存器
而在 ARM上函数的栈帧是由 SP寄存器 和 FP寄存器 来界定的参见图 上图描述的是 main 函数调用 func1 函数的栈帧情况从图可知当 main 函数 调用 func1函数时func1 函数会先将 PC、LR、SP、FP 四个寄存器压到栈上边其中 SP 和 FP 的值分别指向 main 函数栈帧的两个边界LR 的值保存的是 func1 调用结束之后的返回值PC 值表示的是当前执行到的指令地址放置的是进入 func1 后的指令地址。紧接着就会在栈上分配一片区域用于放置局部变量等。
如果 func1 中还调用了 func2 子函数那么也会为 func2 创建一个栈帧并且func2 的 SP 和 FP 会指向 func1 栈帧的两个边界。这样当函数返回的时候参数进行出栈也能找到 Caller 函数这个也就是 backtrace 的原理。
1.1.2 汇编实例
反汇编分析某段代码如下图所示
红色部分表明进入到函数时先将几个特殊的寄存器压栈;黄色部分sub sp, sp, #16表明开辟一个4 x 32bit 大小的栈区域;蓝色部分将传入的参数压栈在 ARM ATPCS 中规定寄存器R0-R3用来传参;绿色部分调用子函数。
并不是所有函数调用都需要先 push {fp, ip, lr, pc}当子函数调用过程中并不会去改变这些值的时候就不需要压栈说白了压栈的目的就是为了在使用完的时候能恢复原来的状态。
1.1.3 数组越界栈回踩
从上面的 1.1.1 节内容我们知道ARM 架构上一般栈都是向下增长的如果在 函数 func1 中定义了一个大小为 N 的数组 int test[N]在 for 循环中根据数组下标 i 向数组 test 中写入数据 N 个0xfffff。
int fun1(int a, int b, int c, int loop)
{...int test[N], i;for (int i 0; i loop; i)*(test i) 0xffff;...如果由于某种原因 loop 的值大于 N 的值这样就会出现在 func1 栈空间向数组 test[N] 之后的地址处写入数据, 从栈帧分配可以看到func1的栈帧高地址处存放的是 FP, SP, LR, PC如果 LR 的值被修改为 0xffff那么在 func1 函数返回 main时将会出现致命错误因为 func1 返回时系统会从 LR 地址处取指令给 PC而此时 func1栈帧中保存的 LR 的值已经被修改为 0xffff 了。
1.1.4 栈保护区
栈保护区是一块分配在栈之下的一块内存空间假设栈stack是向下生长的如图 2 所示这样当栈 stack下溢时就能在保护区留下痕迹 trace。通过软件的方法来检查保护区是否还完整即填充保护区相同的数据如0xff 的数据然后检测保护区是否被写入来确定栈下溢情况。有些公司在 task 创建后stack 会被全部初始化为 0xEFEFEFEF。
1.1.5 检测栈下溢
把栈空间填充成固定的值可以检测栈空间的下溢如在程序开始前填充 0xCD。当程序终止时栈内存可以从栈底端搜索模式直到 0xCD 没找到。这样就能得到从栈顶端的栈空间大小。
上篇文章ARM Linux 系统稳定性分析入门及渐进 2 – Kernel Lockup 下篇文章ARM Linux 系统稳定性分析入门及渐进 4 – 栈分类 推荐阅读 https://www.jianshu.com/p/91c5dc0a8bb9