arm32 arm64 riscv32 riscv64 Stack-Backtrace-Abwickeln basierend auf fp

Stack-Traceback-Quellcode basierend auf fp

Stack-Traceback-Methode

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

Aufrufkonvention und Beispielanalyse

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

Stack-Traceback-Abstraktion

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栈顶 : 编译器决定
// 该示例中 标注(不确定) 的 是 编译器编译器确定的

Stack-Backtracing-Prinzip

注意: 在用户代码中,栈底是一直不变的,栈顶是在变化的!!!
注意: 每个架构中 "从当前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都是0
    2. 设置sp
    3. bl c_code
// 以下是c_code的反汇编伪代码
c_code : 
	1. 存储 上级函数 的fp
    2. 存储 c_code 函数的 返回值
    3. 设置 c_code 函数的 fp
当我们进行栈回溯时,回溯到c_code . 根据c_code 的 fp(0) 获取到 _start 的 fp(值为0) , 那么表示到了栈回溯的根
所以我们需实现两个函数
    void * current_fp(void); // 返回当前 fp
    void * caller_fp(void * currentfp); // 根据当前fp计算得到上一层的fp
	void dump_stack(void); // 打印当前函数的堆栈

Stack-Backtrace-Implementierung

Architekturunabhängiger Code

  • Architekturunabhängiger Code mit positiver Sequenz
//  正确案例    
#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;
}
  • Architekturunabhängige Coderekursion
// 注意: 获取栈帧,并获取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;
}

Architekturbezogener Code

  • 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;
}

Stack-Traceback-Instanz wird ausgeführt

~$ 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 中 对比 地址!!!

Debuggen Sie mithilfe von Stack-Backtrace-Wissen

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

Guess you like

Origin blog.csdn.net/u011011827/article/details/131238185