arm32 arm64 riscv32 riscv64 基于fp进行栈回溯unwind

编程入门 行业动态 更新时间:2024-10-25 12:16:15

arm32 arm64 riscv32 riscv64 基于fp进行栈回溯<a href=https://www.elefans.com/category/jswz/34/1665017.html style=unwind"/>

arm32 arm64 riscv32 riscv64 基于fp进行栈回溯unwind

文章目录

  • 基于fp的栈回溯源码
  • 栈回溯方法
  • 调用约定与实例分析
  • 栈回溯抽象
  • 栈回溯原理
  • 栈回溯实现
    • 架构无关码
    • 架构相关码
    • 栈回溯实例运行
  • 利用栈回溯知识debug

基于fp的栈回溯源码

  • arm32
  • arm64
  • riscv32
  • riscv64

栈回溯方法

1.基于fp的栈回溯1. 编译器不会优化fp. // 有些编译器默认会把fp省略掉,导致找不到正确的栈帧2. 用固定的abi来做  // fp 和 返回值 的存储位置是固定的,导致找不到正确的栈帧3. 栈是没被破坏的    // 栈帧会破坏了,就无法完成完整的栈回溯
2.特定架构的方法1. arm32 基于 exidx/unwind (-funwind-tables supported in gcc>4.5) , arm32的基于fp的叫做apcs(-mapcs supported in gcc<5.0)2. arm64 基于 dwarf/dwarf23

调用约定与实例分析

  • x86 栈调用分析
  • riscv 栈调用分析
  • aarch64 栈调用分析
  • arm32 栈调用分析
我们分析这些东西,是为了知道 当进行函数调用时, caller 和 callee 做了什么事情
其实我们没有必要分析这些代码,调用是有规范的,这个和具体架构有关系.具体请搜索 函数调用约定 + 架构名注意: arm64/32 x86 riscv32/64 都是满减栈 // 
满减栈,当前指针有值,先移动指针再存
基于栈帧的回溯: gcc 编译器内部已经做了实现__builtin_return_address(0) // 当前函数的返回地址的值,该值是一个地址__builtin_return_address(1) // caller函数的返回地址的值,该值是一个地址__builtin_frame_address(0)  // 当前函数的栈帧值,该值是一个地址__builtin_frame_address(1)  // caller函数的栈帧值,该值是一个地址

栈回溯抽象

  • 参考这里用在线编译器编译如下代码
int g(int x)
{return x + 3;
}int f(int x)
{return g(x);
}int main(void)
{return f(7) + 1;
}
我们分析了这么多,没有抽象出概念,总结出理论,那么很快就会忘掉在分析一个具体的函数栈调用的时候,例如xxx->main->f->g
例如我们分析的函数名为f时候,我们关注4个点1. 栈底2. 栈顶/即栈帧3. 栈大小4. 函数返回地址5. 如何获取到(上一层调用函数)main的栈顶与栈底6. 如何为(下一层被调函数)g准备栈底栈回溯抽象a. 我们函数f中能够获取 1&2的信息,从而我们可以获取f的1-6.即我们可以获取到 main 的栈顶与栈底b. 我们已经获取到main的1&2的信息,从而我们可以获取main的1-6....
以下说的栈底和栈顶都是地址.里面牵扯到了地址和地址中的值,请注意分辨!!!
  • x86
x86
以f为例1.栈底是%ebp (高地址),等于调用函数的栈顶-4字节 // 这个-4 是确定的!!!2.栈顶是%esp(低地址), 运行时决定3.栈大小由运行时决定4.函数返回地址 所在位置 位于 栈底(%ebp) +4字节 偏移位置存储的值 // 存储的值5. main 的栈顶与栈底地址main栈底 : f栈底(%ebp) +0字节 偏移位置存储的值存储的值 // 存储的值main栈顶 : f栈底(%ebp) +4字节6. g 的栈顶g栈底 : f栈顶(%esp)-4字节g栈顶 : 运行时决定
  • riscv64
riscv64
以f为例1.栈底是s0/fp (高地址),等于调用函数的栈顶2.栈顶是sp(低地址), 编译器决定3.栈大小由编译时决定,我们可以在代码中根据当前sp和fp计算出来4.函数返回地址 所在位置 位于 栈底(s0/fp) -8字节 偏移位置存储的值 // 存储的值5. main 的栈顶与栈底main栈底 : f栈底(s0/fp) -16字节 偏移位置存储的值 // 存储的值main栈顶 : f栈底(s0/fp) // 寄存器值6. g 的栈顶g栈底 : f栈顶(sp)g栈顶 : 编译器决定
  • riscv32
riscv32
以f为例1.栈底是s0/fp (高地址),等于调用函数的栈顶2.栈顶是sp(低地址), 编译器决定3.栈大小由编译时决定,我们可以在代码中根据当前sp和fp计算出来4.函数返回地址 所在位置 位于 栈底(s0/fp) -4字节 偏移位置存储的值 // 存储的值5. main 的栈顶与栈底main栈底 : f栈底(s0/fp) -8字节 偏移位置存储的值 // 存储的值main栈顶 : f栈底(s0/fp) // 寄存器值6. g 的栈顶g栈底 : f栈顶(sp)g栈顶 : 编译器决定
  • arm32
arm32  // 通常 ARM 模式下 r11 会作为帧指针,THUMB 模式下 r7 则作为帧指针。// 我们以 ARM 模式为例// 如果要用fp进行栈回溯arm32 ,则 需要编译选项的支持 -marm -mapcs-frame
以f为例1.栈底是fp (高地址),等于调用函数的栈顶2.栈顶是sp(低地址), 运行时决定3.栈大小由运行时决定4.函数返回地址 所在位置 位于 栈底(fp) - 4字节 偏移位置存储的值 // 存储的值5. main 的栈顶与栈底main栈底 : f栈底(fp) - 12字节 偏移位置存储的值 // 存储的值main栈顶 : f栈底(fp) - 8字节  偏移位置存储的值 // 寄存器值6. g 的栈顶g栈底 : f栈顶(sp)-4字节g栈顶 : 编译器决定
  • arm64
arm64
以f为例1.栈底是fp (高地址),等于调用函数的栈顶-32(不确定)字节 // 通常为x29 // 这个-32 是不确定的,编译器根据实际情况确定的2.栈顶是sp(低地址), 编译器决定3.栈大小由编译时决定,我们可以在代码中根据当前sp和fp计算出来4.函数返回地址 所在位置 位于 栈底(fp) +8字节 偏移位置存储的值 // 存储的值5. main 的栈顶与栈底main栈底 : f栈底(fp) 0字节 偏移位置存储的值 // 存储的值main栈顶 : f栈底(fp) +32(不确定) // 寄存器值6. g 的栈顶g栈底 : f栈顶(sp)-32(不确定)g栈顶 : 编译器决定
// 该示例中 标注(不确定) 的 是 编译器编译器确定的

栈回溯原理

注意: 在用户代码中,栈底是一直不变的,栈顶是在变化的!!!
注意: 每个架构中 "从当前fp寻址 caller fp 的方法" 不会随着代码的变化而变化, "从当前fp寻址返回值的方法"也不会随着代码的变化而变化 // arm64中的寻址main栈顶的方法会随着代码的变化而变化
注意: 这两个不变奠定了 栈回溯的 基础!!!注意: 在栈回溯中.我们只需要在用户代码中 1.在f中获取fp寄存器的值,然后根据 "f 寻址 main的栈底(fp)的方法" 可以寻址到 main的栈底2.main的栈底(fp寄存器的值)已经通过1获得,然后根据 "f 寻址 main的栈底(fp)的方法" 可以寻址到上一级xxx的栈底3.xxx的栈底(fp寄存器的值) 已经通过2获取.如果xxx的栈底fp为0.则为回溯根部注意: 其实现在我们已经实现了栈回溯,但是实际应用上我们要取得栈回溯得到的符号名(就是那种在gdb中打bt命令出来的那种东西,或者在linux中调用dump_stack出来的东西)
注意: 我们现在先不做符号名,而是先把符号名有关的地址做出来,这个地址就是与 fp 密切相关的 "返回地址"注意: 我们只是得到了每层的 fp ,但这个跟代码符号没有一点关系,因为 fp 是栈的东西,代码符号是 pc轨迹相关的东西但是我们可以通过 fp 得到 该层栈 中的返回地址 . // 这个返回地址就跟 代码符号相关了!!!注意 : x64/x86/riscv32/riscv64/arm32/arm64 都是先保存 返回地址,再保存caller的栈帧. // arm  :返回地址     所在地址     - caller的栈帧 所在地址  ==  sizeof(unsigned long int)*2// else :返回地址     所在地址     - caller的栈帧 所在地址  ==  sizeof(unsigned long int)
一开始是汇编,然后是c,以此为例子
_start:1. 初始化fp为0 // 为做跟做准备 , 复位时fp都是02. 设置sp3. bl c_code
// 以下是c_code的反汇编伪代码
c_code : 1. 存储 上级函数 的fp2. 存储 c_code 函数的 返回值3. 设置 c_code 函数的 fp
当我们进行栈回溯时,回溯到c_code . 根据c_code 的 fp(非0) 获取到 _start 的 fp(值为0) , 那么表示到了栈回溯的根
所以我们需实现两个函数void * current_fp(void); // 返回当前 fpvoid * caller_fp(void * currentfp); // 根据当前fp计算得到上一层的fpvoid dump_stack(void); // 打印当前函数的堆栈

栈回溯实现

架构无关码

  • 架构无关码 正序
//  正确案例    
#include <stdio.h>// arch must provide get_current_stack_frame & get_caller_stack_frame & get_callee_return_address
// get_current_stack_frame provided by arch ,must be inline !!!void dump_stack(void) {void * fp_current = get_current_stack_frame();void * fp_caller = fp_current;void * ret_callee = NULL;printf("FP:%p,CALLEE:%08x\n",fp_caller,dump_stack);for (;fp_caller;) {ret_callee = get_callee_return_address(fp_caller);fp_caller = get_caller_stack_frame(fp_caller);printf("FP:%p,CALLEE:%08x\n",fp_caller,ret_callee-4);}return ;
}int main(void){dump_stack();return 0;
}
  • 架构无关码 递归
// 注意: 获取栈帧,并获取caller栈帧 时不能进行 函数切换!!!,但是计算栈帧时可以
#include <stdio.h>// arch must provide get_current_stack_frame & get_caller_stack_frame & get_callee_return_address
// get_current_stack_frame provided by arch ,must be inline !!!void  dump_stack_core(void *fp) {void * ret_callee = NULL;void * fp_caller = get_caller_stack_frame(fp);if (fp_caller != NULL){ret_callee = get_callee_return_address(fp_caller);dump_stack_core(fp_caller);}printf("FP:%p,CALLEE:%08x\n",fp_caller,ret_callee == 0 ? ret_callee : ret_callee -4);return ;}void dump_stack(void) {void * fp_current = get_current_stack_frame();dump_stack_core(fp_current);printf("FP:%p,CALLEE:%08x\n",fp_current,get_callee_return_address(fp_current)-4);printf("FP:0x00000000,CALLEE:%08x\n",dump_stack);return ;}int main(void){dump_stack();return 0;
}

架构相关码

  • x86
// To be verified
inline __attribute__((always_inline)) void * get_current_stack_frame(void){void* fp;asm volatile ("mov %0, ebp" : "=r" (fp));return fp;
}inline __attribute__((always_inline)) void * get_caller_stack_frame(void * current_fp)
{void* caller_fp;caller_fp =  (void *)(*(unsigned long int *)(current_fp));return caller_fp;
}inline __attribute__((always_inline)) void * get_callee_return_address(void * current_fp)
{void* callee_ret;callee_ret =  (void *)(*(unsigned long int *)((char *)(current_fp) + sizeof(void*)));return callee_ret;
}
  • x64
// To be verified
inline __attribute__((always_inline)) void * get_current_stack_frame(void){void* fp;asm volatile ("mov %0, rbp" : "=r" (fp));return fp;
}inline __attribute__((always_inline)) void * get_caller_stack_frame(void * current_fp)
{void* caller_fp;caller_fp =  (void *)(*(unsigned long int *)(current_fp));return caller_fp;
}inline __attribute__((always_inline)) void * get_callee_return_address(void * current_fp)
{void* callee_ret;callee_ret =  (void *)(*(unsigned long int *)((char *)(current_fp) + sizeof(void*)));return callee_ret;
}
  • riscv64
// Verified
inline __attribute__((always_inline)) void * get_current_stack_frame(void){void* fp;asm volatile("mv %0, fp" : "=r" (fp));return fp;
}inline __attribute__((always_inline)) void * get_caller_stack_frame(void * current_fp)
{void* caller_fp;caller_fp =  (void *)(*(unsigned long int *)(current_fp - sizeof(void*)*2));return caller_fp;
}inline __attribute__((always_inline)) void * get_callee_return_address(void * current_fp)
{void* callee_ret;callee_ret =  (void *)(*(unsigned long int *)((char *)(current_fp) - sizeof(void*)));return callee_ret;
}
  • riscv32
// Verified
inline __attribute__((always_inline)) void * get_current_stack_frame(void){void* fp;asm volatile("mv %0, fp" : "=r" (fp));return fp;
}inline __attribute__((always_inline)) void * get_caller_stack_frame(void * current_fp)
{void* caller_fp;caller_fp =  (void *)(*(unsigned long int *)(current_fp - sizeof(void*)*2));return caller_fp;
}inline __attribute__((always_inline)) void * get_callee_return_address(void * current_fp)
{void* callee_ret;callee_ret =  (void *)(*(unsigned long int *)((char *)(current_fp) - sizeof(void*)));return callee_ret;
}
  • arm32
// 需要编译选项中添加 -marm -mapcs-frame,使用r11来作为fp,会造成臃肿的反汇编
// Verified
inline __attribute__((always_inline)) void * get_current_stack_frame(void){void* fp;asm volatile ("mov %0, fp" : "=r" (fp));return fp;
}inline __attribute__((always_inline)) void * get_caller_stack_frame(void * current_fp)
{void* caller_fp;caller_fp =  (void *)(*(unsigned long int *)((unsigned long int)current_fp - sizeof(void*)*3));return caller_fp;
}inline __attribute__((always_inline)) void * get_callee_return_address(void * current_fp)
{void* callee_ret;callee_ret =  (void *)(*(unsigned long int *)((unsigned long int)(current_fp) - sizeof(void*)*1));return callee_ret;
}
  • arm64
// Verified
inline __attribute__((always_inline)) void * get_current_stack_frame(void){void* fp;asm volatile ("mov %0, x29" : "=r" (fp));return fp;
}inline __attribute__((always_inline)) void * get_caller_stack_frame(void * current_fp)
{void* caller_fp;caller_fp =  (void *)(*(unsigned long int *)(current_fp));return caller_fp;
}inline __attribute__((always_inline)) void * get_callee_return_address(void * current_fp)
{void* callee_ret;callee_ret =  (void *)(*(unsigned long int *)((char *)(current_fp + sizeof(void *))));return callee_ret;
}

栈回溯实例运行

~$ stacktrace // 正序
FP:0x408ffe90,CALLEE:40006564
FP:0x408ffee0,CALLEE:40007cc4
FP:0x408fff00,CALLEE:40007ce8
FP:0x408fff20,CALLEE:40007d08
FP:0x408fff40,CALLEE:40005e1c
FP:0x408ffff0,CALLEE:40003668
FP:(nil),CALLEE:4000003c$ stacktrace // 递归
FP:(nil),CALLEE:00000000
FP:0x408ffff0,CALLEE:4000003c
FP:0x408fff40,CALLEE:40003668
FP:0x408fff20,CALLEE:40005e1c
FP:0x408fff00,CALLEE:40007d68
FP:0x408ffee0,CALLEE:40007d48
FP:0x408ffeb0,CALLEE:40007d28
FP:0x00000000,CALLEE:40006600然后去  baremetal.elf.asm 中 对比 地址!!!

利用栈回溯知识debug

之前遇到一个bt命令,打印出来的栈不全的问题, 提示说栈被破坏了
那么到底是什么被破坏了呢?1. 每一层的fp2. 每一层的返回值
  • 问题1
函数调用与函数声明 参数类型 不匹配!!!调用的小,用的大,导致fp被破坏
  • 问题2
int main(void){int data[4];memset(data,0,sizeof(data)*4); // 会把栈帧破坏掉
}

更多推荐

arm32 arm64 riscv32 riscv64 基于fp进行栈回溯unwind

本文发布于:2024-02-11 12:50:01,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1681083.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:unwind   fp

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!