ARM_UNWIND_BACKTRACE
代码仓: 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]
, r1410110000 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: runningEXCEPT: 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相关推荐
- ARM_UNWIND_BACKTRACE (2)
修改点: 1. 所有vrs[7]都要改成vrs[11], 2. unwind_frame() /* We are done if current frame pc is equal to the vi ...
最新文章
- 计算机网络面试题(一)
- Fragment的详细使用
- NLP-基础知识-007(机器学习-朴素贝叶斯)
- 鸟哥的Linux私房菜(服务器)- 簡易 APT/YUM 伺服器設定
- php 使用 curl 报错,PHP CURL post数据报错 failed creating formpost data
- android 监听webView滑动距离和标题栏颜色渐变
- 找不到r低版本_R的多进程使用与改进
- 学习ASP.NET之前,先了解它
- oralce EM企业管理器
- 什么叫AI优先?不如你看谷歌CEO的办公位在哪儿
- Dos命令将合并两个文本文件的内容
- 配置元件--HTTP授权管理器
- java 排队实现_Java实现排队论的原理
- pandas_空值填充|重复数据|异常数据
- 塔菲克蓝牙适配器驱动_TAFIQ蓝牙适配器驱动(TAFIQ蓝牙设备驱动程序)V4.1 正式版...
- 个人银行账户管理程序【简化】
- 关于站内信的开发思路
- hibernate配置映射的问题
- 从损失函数的角度详解常见机器学习算法(1)
- VC 工程中包含 .c 或cpp文件编译时产生的.pch预编译头错误(C1853)