代码仓: https://codechina.csdn.net/fu851523125/arm_unwind_backtrace.git

参考文章:arm上backtrace的分析与实现原理_流风回雪的博客-CSDN博客

2.2 unwind

对于APCS来说,优点是分析起来比较简单,跟踪起来也可以很容易。缺点就是指令过多,栈消耗大,占用的寄存器也过多,比如每次调用 都必须将r11,r12,lr,pc入栈。为了解决这个问题,提出了第二种方案:

使用unwind就能避免这些问题,生产指令的效率要有用的多。unwind是最新的编译器(>gcc-4.5)为arm支持的新特性。它的原理是记录每个函数的入栈指令(一般比APCS的入栈要少的多)到特殊的段.ARM.unwind_idx .ARM.unwind_tab。

所以如果我们要使用unwind,就必须在链接文件中定义这个段

.ARM.exidx : {

__exidx_start = .;

*(.ARM.exidx* .gnu.linkonce.armexidx.*)

__exidx_end = .;

}

我们也可以通过arm-none-eabi-readelf -u xxxxx.elf查看其内容。

以上面两个为例,set_date的函数的地址是0xc007c4a0,而set_time的函数的地址是0xc00a0fb0。

而r11也就是fp地址在unwind_tab段中,也就是位于0xc00a0fa4地址处。

回溯时根据pc值到段中得到对应的编码,解析这些编码计算出lr在栈中的位置,进而计算得到调用者的执行地址。

一般来说,我们使用unwind优势比使用apcs更好,因为采用apcs时,会产生更多的代码指令,对性能有影响,但是使用unwind方式只会产生一个额外的段空间,并不会影响性能,所以大多数情况下,使用unwind更加有利。

unwind回溯的过程可以总结为三部分:

1.根据pc找到函数unwind的段内存地址

2.根据unwind段中信息找到指令相关的编码数据

3.根据入栈地址,分析函数上一级的栈底保存的sp和lr。

03

函数符号表

栈回溯的过程中,往往需要符号表来进行操作,此时需要开启-mpoke-function-name这个编译选项。

使用这个选项编译出的二进制程序中可以包含 C 语言函数名称的信息,以方便函数调用链回溯时记录信息的可读性。

比如在Linux中,系统死机后,可以打印出栈的地址和函数的名称,根据这个进行回溯操作就可以进行使用了。

基本原理就是加上-mpoke-function-name后,在每段代码段后面,都会附加一个函数的符号,我们需要使用的时候,就根据函数的pc指针,然后找到相关的偏移量,之后将这个代码段的符号获取到了。

参考文章: [原创] ARM栈回溯——从理论到实践,开发ida-arm-unwind-plugin-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com

.ARM.exidx 结构

这个 section 连续存放Entry,视为一个Entry数组。先要处理大小端问题,处理好后,每个 Entry 由两个 uint16 组成,相当于如下 struct:

1

2

3

4

struct EHEntry {

  uint32_t Offset;

  uint32_t World1;

};

EHEntry.Offset 意义是函数起始偏移。最高 bit 一定是 0,结合当前偏移(当前 pc )进行使用 prel31 解码,得到 uint64_t。

格式为:

1

2

31----24 23----16 15-----8 7------0 |

0XXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |

EHEntry.Word1 有三种情况:

  • EHEntry.Word1 == 1 ,表示 CannotUnwind

    1

    00000000 00000000 00000000 00000001 |

  • 最高 bit 为 1,则[31:24]必须为 0b10000000(其实是 personality 为 0,属于 inline compact model),余下 X、Y、Z, 3 个 byte 表示字节码。

    1

    2

    31----24 23----16 15-----8 7------0 |

    10000000 | XXXXXXXX | YYYYYYYY | ZZZZZZZZ |

  • 最高 bit 为 0,则整个 Word1 使用 prel31 解码,得到 uint64_t,指向真正的数据。必然会落在 .ARM.extab 里。

    1

    2

    31----24 23----16 15-----8 7------0 |

    0XXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |

prel31 解码

这个东西就是个计算方式,根据当前的绝对偏移(uint32),与当前的内容(uint32)进行运算,求出绝对偏移(uint64)。

对于 ELF 文件,我们可以假想它基址为 0,从而实现解析;

对于内存中的 ELF 片段,可以通过这个计算,根据当前位置寻找到附近的另一个位置,从而避免重定位;

下图代码中,Address 表示当前内容,Place 表示绝对偏移。

1

2

3

4

5

6

static uint64_t PREL31(uint32_t Address, uint32_t Place) {

    uint64_t Location = Address & 0x7fffffff;

    if (Location & 0x04000000)

        Location |= (uint64_t) ~0x7fffffff;

    return Location + Place;

}

.ARM.extab 结构

.ARM.extab 作为 .ARM.exidx 的附属存在,存放数据,但无法直接找到每段数据的入口。入口需要借助上文 Entry.Word1,当 Entry.Word1 的最高 bit 为 0 时,prel31解码后一定会指向 .ARM.extab 的内容,这就是入口。

名词解释:personality ——特性,可能没有中文概念。

先读出第一个 uint32_t,进行初步解析,再根据情况进行进一步解析,有以下的情况:

  • 最高 bit 为 0,表示 generic personality,使用 prel31 解码,使用指向的函数进行 unwind。

    1

    2

    31----24 23----16 15-----8 7------0 |

    0XXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |

  • 最高 bit 为 1,表示 arm compact personality[31:28]必须为 0b1000[27:24] 有且仅有有0、1、2三种情况。

    • 0: inline compact model,X、Y、Z, 3 个 byte 表示字节码。

      1

      2

      31----24 23----16 15-----8 7------0 |

      10000000 | XXXXXXXX | YYYYYYYY | ZZZZZZZZ |

    • 1或者2: [23:16] 表示 more_word(uint_8),表示剩余字节码个数,后面的都是字节码

      1

      2

      3

      31----24 23----16 15-----8 7------0 |

      10000001 | MOREWORD | ........ | ........ |

      10000010 | MOREWORD | ........ | ........ |

字节码的反汇编

根据上文,我们可以得到每一处 Entry 及其 unwind方式。我们关心的是使用字节码进行 unwind (即 personality 为0、1、2)的情况,经过解析可以得到 uint8_t[N] 的字节码 ,解析方式在 "Table 4, ARM-defined frame-unwinding instructions" 文件章节。

参考 llvm-readelf 的实现,它的可读性最好,代码在 https://github.com/llvm/llvm-project/blob/master/llvm/tools/llvm-readobj/ARMEHABIPrinter.h ,其中有大量OpcodeDecoder::Decode_XXXXX 函数可以抄。

纯体力活,没什么好说的,官方表格如下:

Instruction Explanation
00xxxxxx vsp = vsp + (xxxxxx << 2) + 4. Covers range 0x04-0x100 inclusive
01xxxxxx vsp = vsp – (xxxxxx << 2) - 4. Covers range 0x04-0x100 inclusive
10000000 00000000 Refuse to unwind (for example, out of a cleanup) (see remark a)
1000iiii iiiiiiii (i not a ll 0) Pop up to 12 integer registers under masks {r15-r12}, {r11-r4} (see remark b)
1001nnnn ( nnnn != 13,15) Set vsp = r[nnnn]
10011101 Reserved as prefix for ARM register to register moves
10011111 Reserved as prefix for Intel Wireless MMX register to register moves
10100nnn Pop r4-r[4+nnn]
10101nnn Pop r4-r[4+nnn], r14
10110000 Finish (see remark c)
10110001 00000000 Spare (see remark f)
10110001 0000iiii ( i not all 0) Pop integer registers under mask {r3, r2, r1, r0}
10110001 xxxxyyyy Spare (xxxx != 0000)
10110010 uleb128 vsp = vsp + 0x204+ (uleb128 << 2) (for vsp increments of 0x104-0x200, use 00xxxxxx twice)
10110011 sssscccc Pop VFP double-precision registers D[ssss]-D[ssss+cccc] saved (as if) by FSTMFDX (see remark d)
101101nn Spare (was Pop FPA)
10111nnn Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by FSTMFDX (seeremark d)
11000nnn (nnn != 6,7) Intel Wireless MMX pop wR[10]-wR[10+nnn]
11000110 sssscccc Intel Wireless MMX pop wR[ssss]-wR[ssss+cccc] (see remark e)
11000111 00000000 Spare
11000111 0000iiii Intel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0}
11000111 xxxxyyyy Spare (xxxx != 0000)
11001000 sssscccc Pop VFP double precision registers D[16+ssss]-D[16+ssss+cccc] saved (as if) by VPUSH (see remarks d,e)
11001001 sssscccc Pop VFP double precision registers D[ssss]-D[ssss+cccc] saved (as if) by VPUSH (see remark d)
11001yyy Spare (yyy != 000, 001)
11010nnn Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by VPUSH (seeremark d)
11xxxyyy Spare (xxx != 000, 001, 010)

动手:

海思平台,liteos的arm-linux-androideabi-gcc(或者arm-linux-ucliceabi-gcc都可以)

Makefile:

编译选项

常规项

CFLAGS += -Wall -g -Os -fno-builtin -nostdinc -nostdlib -fno-pic -mabort-on-noreturn -march=armv7-a -msoft-float
CFLAGS += -mno-thumb-interwork -mlittle-endian

unwind项

# use unwind for backtrace
#  need __aeabi_unwind_cpp_pr0/__aeabi_unwind_cpp_pr1
# look "readelf -u xxx"
CFLAGS += -funwind-tables -mpoke-function-name

lds链接脚本

要添加

/* use -funwind-tables for unwind backtrace */
    /* Stack unwinding tables */
    . = ALIGN(8);
    .ARM.unwind_idx : {
        __start_unwind_idx = .;
        *(.ARM.exidx*)
        __stop_unwind_idx = .;
    }
    .ARM.unwind_tab : {
        __start_unwind_tab = .;
        *(.ARM.extab*)
        __stop_unwind_tab = .;
    }

start.S:

arm vectors 直接抄

.section ".vectors", "ax"

.globl    _start
_start:
    b    reset
        ldr     pc, _undefined_instruction
        ldr     pc, _software_interrupt
        ldr     pc, _prefetch_abort
        ldr     pc, _data_abort
        ldr     pc, _addr_abort /* unused */
        ldr     pc, _irq
        ldr     pc, _fiq

.globl  _undefined_instruction
        .globl  _software_interrupt
        .globl  _prefetch_abort
        .globl  _data_abort
        .globl  _not_used
        .globl  _irq
        .globl  _fiq

_undefined_instruction: .word undefined_instruction
_software_interrupt:    .word software_interrupt
_prefetch_abort:        .word prefetch_abort
_data_abort:            .word data_abort
_addr_abort:            .word addr_abort
_irq:                   .word irq
_fiq:                   .word fiq

.balignl 16,0xdeadbeef

reset 设置主要的几种异常的sp,用于异常中断时临时保存环境。

reset:
    /* initialize interrupt/exception environments */
    mov    r0, #(CPSR_IRQ_DISABLE |CPSR_FIQ_DISABLE|CPSR_IRQ_MODE)
    msr    cpsr, r0
    ldr    sp, =__irq_stack_top

mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_UNDEF_MODE)
    msr    cpsr, r0
    ldr    sp, =__undef_stack_top

mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_ABT_MODE)
    msr    cpsr, r0
    ldr    sp, =__abt_stack_top

mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_FIQ_MODE)
    msr    cpsr, r0
    ldr    sp, =__fiq_stack_top

/* initialize CPSR (machine state register) */
    mov    r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE)
    msr    cpsr, r0

/* Note: some functions in LIBGCC1 will cause a "restore from SPSR"!! */
    msr    spsr, r0

/* set svc stack, every cpu has OS_EXC_SVC_STACK_SIZE stack */
    ldr    sp, =__svc_stack_top

/*
    * Temporary interrupt stack
    */
    .section ".int_stack", "wa", %nobits
    .align  3

__undef_stack:
    .space OS_EXC_UNDEF_STACK_SIZE
__undef_stack_top:

__abt_stack:
    .space OS_EXC_ABT_STACK_SIZE
__abt_stack_top:

__irq_stack:
    .space OS_EXC_IRQ_STACK_SIZE
__irq_stack_top:

__fiq_stack:
    .space OS_EXC_FIQ_STACK_SIZE
__fiq_stack_top:

__svc_stack:
    .space OS_EXC_SVC_STACK_SIZE
__svc_stack_top:

__exc_stack:
    .space OS_EXC_STACK_SIZE
__exc_stack_top:

异常处理,主要保存相关寄存器,使用__exc_stack防止破坏异常前的所有环境

/* R0-R7 : all used, R8-R14 : r13(sp) r14(lr) r15(pc) */
.macro    save_and_change_to_svc, mode, correction=0
    .if \correction
        sub    lr, lr, #\correction
        .endif

stmfd    sp, {r0, r7}
    mrs    r2, spsr        @ Save CPSR before exception.
    mov    r1, lr            @ Save PC before exception.
    sub    r3, sp, #(8 * 3)    @ Save the start address of working registers.

msr    CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE)      @ Switch to SVC mode, and disable all interrupts
    mov    r5, sp
    ldr    sp, =__exc_stack_top

stmfd     sp!, {r1}    @ Push Exception PC
    stmfd    sp!, {lr}    @ Push SVC LR
    stmfd    sp!, {r5}    @ Push SVC SP
    stmfd    sp!, {r8-r12}    @ Push original R12-R8,
    ldmfd    r3!, {r4-r11}    @ Move original R7-R0 from exception stack to original stack.
    stmfd    sp!, {r4-r11}    
    stmfd    sp!, {r2}    @ Push task`s CPSR (i.e. exception SPSR).

ldr    r0, =\mode
    mov    r1, sp
    b    handle_exc    
.endm

/*
 * exception handlers
 */
        .align  5
undefined_instruction:
    save_and_change_to_svc EXC_UNDEF, 4
    b    .

.align  5
software_interrupt:
    save_and_change_to_svc EXC_SWI, 0
    b    .

.align  5
prefetch_abort:
    save_and_change_to_svc EXC_PABT, 4
    b    .

.align  5
data_abort:
    save_and_change_to_svc EXC_DABT, 8
    b    .

.align  5
addr_abort:
    save_and_change_to_svc EXC_AABT, 0
    b    .

.align  5
irq:
    save_and_change_to_svc EXC_IRQ, 4
    b    .

.align  5
fiq:
    save_and_change_to_svc EXC_FIQ, 4
    b    .

unwind.c

关键代码函数,处理backtrace

部分unwind函数来自https://github.com/chengls/backtrace

这个是ARM Cortex-M系列,现在是ARM Cortex-A系列,prel31还原地址函数有问题,按照前面《prel31 解码》重写,还有FP,ARM Cortex-M 下是R7 ???, ARM Cortex-A下是R11!!!

main.c

测试

最终结果

## Total Size      = 0x00005d2c = 23852 Bytes
## Start Addr      = 0x80000000
## Starting application at 0x80000000 ...
main: running

EXCEPT: Undefined Instruction
R0: 0xFFFFFFFF, R1: 0xFFFFFFFF, R2: 0xFFFFFFFF, R3: 0xFFFFFFFF, R4: 0x0000000A
R5: 0x8FEC8558, R6: 0xFFFFFFFF, R7: 0xFFFFFFFF  R8: 0x00000002, R9: 0x8FEC8EF0
R10: 0x00000000, R11: 0x800048CC R12: 0x0000000A
SP: 0x800048C0, LR: 0x80001304, PC: 0x80000414, CPSR: 0x600001D3
----- Unwind BackTrace -----
Frame(0x80005808) + 0x00000010 = SP(0x800048C0): FP 0x800048CC, LR 0x80001304, PC 0x80000414
0x800003F4(main) @0x80000414
----- Unwind BackTrace End -----
### ERROR ### Please RESET the board ###

由于只有测试main,很简单,所以可以直接看到,

PC: 0x80000414

查看反汇编 test.asm

800003f4 <main>:
800003f4:       e1a0c00d        mov     ip, sp
800003f8:       e92dd800        push    {fp, ip, lr, pc}
800003fc:       e24cb004        sub     fp, ip, #4
80000400:       eb0003c4        bl      80001318 <uart_init>
80000404:       e59f000c        ldr     r0, [pc, #12]   ; 80000418 <main+0x24>
80000408:       eb0003a6        bl      800012a8 <printf>
8000040c:       e3a03000        mov     r3, #0
80000410:       e5c33000        strb    r3, [r3]
80000414:       e7f000f0        udf     #0
80000418:       80001d6f        andhi   r1, r0, pc, ror #26
8000041c:       00000000        andeq   r0, r0, r0
80000420:       70696b73        rsbvc   r6, r9, r3, ror fp
80000424:       6f74615f        svcvs   0x0074615f
80000428:       00000069        andeq   r0, r0, r9, rrx
8000042c:       ff00000c                        ; <UNDEFINED> instruction: 0xff00000c

编译的结果就是 *pUndef = 'A'; 处异常,   使用udf     #0指令进入Undefined Instruction异常

最后

  这是个最初的版本,所以需要实际应用来测试,可能只解析这一层(因为这是save_and_change_to_svc保存的直接异常的地方,所以正常函数时必定可以解析)。

ARM_UNWIND_BACKTRACE相关推荐

  1. ARM_UNWIND_BACKTRACE (2)

    修改点: 1. 所有vrs[7]都要改成vrs[11], 2. unwind_frame() /* We are done if current frame pc is equal to the vi ...

最新文章

  1. 计算机网络面试题(一)
  2. Fragment的详细使用
  3. NLP-基础知识-007(机器学习-朴素贝叶斯)
  4. 鸟哥的Linux私房菜(服务器)- 簡易 APT/YUM 伺服器設定
  5. php 使用 curl 报错,PHP CURL post数据报错 failed creating formpost data
  6. android 监听webView滑动距离和标题栏颜色渐变
  7. 找不到r低版本_R的多进程使用与改进
  8. 学习ASP.NET之前,先了解它
  9. oralce EM企业管理器
  10. 什么叫AI优先?不如你看谷歌CEO的办公位在哪儿
  11. Dos命令将合并两个文本文件的内容
  12. 配置元件--HTTP授权管理器
  13. java 排队实现_Java实现排队论的原理
  14. pandas_空值填充|重复数据|异常数据
  15. 塔菲克蓝牙适配器驱动_TAFIQ蓝牙适配器驱动(TAFIQ蓝牙设备驱动程序)V4.1 正式版...
  16. 个人银行账户管理程序【简化】
  17. 关于站内信的开发思路
  18. hibernate配置映射的问题
  19. 从损失函数的角度详解常见机器学习算法(1)
  20. VC 工程中包含 .c 或cpp文件编译时产生的.pch预编译头错误(C1853)

热门文章

  1. 报错Command line is too long. Shorten the command line xxx【解决办法】
  2. excel 查找两列的共同元素
  3. 自启动管理 - Win10
  4. matlab中的legend函数
  5. 基于PHP的住房公积金在线办理管理系统
  6. centos里的mysql密码重置
  7. 基于残差神经网络的交通标志识别算法研究与应用实现
  8. 使用NPOI2.0.1.0自定义导出的excel文档数字为文本格式不能公式编辑计算的问题解决
  9. python-urlparse :解析url
  10. 如何挂载NFS(一)