TQ2440开发板学习纪实--- 利用Undefined异常模拟BLX指令
Posted smstong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TQ2440开发板学习纪实--- 利用Undefined异常模拟BLX指令相关的知识,希望对你有一定的参考价值。
在博文 《紧急求助!ARM-GCC对于函数指针调用的编译有错误?》中,我提到了GCC在编译函数指针调用的时候,会生成绝对地址跳转指令BLX。而S3C2440A这款CPU不支持BLX指令,从而导致陷入Undefined异常。
本文就利用这个Undefined异常,来模拟BLX指令,从而让使用BLX指令的程序可以正常运行在S3C2440上。
1 Undefined异常的处理流程
ARM9在执行未定义指令时,会跳转到0x00000004处执行,进入Undefined模式,并把下一条指令的地址存入LR。
2 指令模拟的思路
利用Undefined异常处理的一个非常重要的作用就是用来扩展CPU指令集,模拟执行硬件不能直接支持的指令。原理非常简单,在Undefined异常处理中,通过(LR-#4)这个地址就可以获取未定义的指令,然而根据不同的指令写出不同的等价程序即可。
例如本节将要实现的BLX指令,下面是源码事例:
ldr r3, [pc, #32]
ldr r3, [r3]
mov r0, r2
blx r3
其中blx r3的等效源码为:
mov lr, pc
bx r3
需要注意的是:
- 进入Undefined异常处理时,CPU处于Undefined模式,此时的堆栈与异常前模式不同;
- 我们的模拟指令程序应该运行在原模式下,而不是Undefined模式下,否则无法获取原来的寄存器环境;
- 运行模拟指令程序前应保存所有用到的寄存器,运行返回前恢复。否则就会破坏原来的执行环境;
- 保存寄存器的通用方法就是入栈,而入栈保存需要一定的技巧来保证返回的同时恢复所有寄存器;
- 在Undefined模式下需要保存LR(此时存放的是原指令的下一条指令),此时不能通过入栈保存,因为Undfined的栈是独立的。变通方式,是存放LR的值到内存中。
3 BLX模拟实现
/* save the instruction's address after the undefined one. */
.global AddrUnd
AddrUnd:
.word 0x00000000
UndHandler:
sub sp, sp, #8
str r0, [sp]
ldr r0, =AddrUnd
str lr, [r0]
ldr r0, =asm_und_handler
str r0, [sp, #4]
ldmfd sp!, r0, pc^
这段代码非常简单,实现的功能是保存原指令的下一条指令地址到AddrUnd中,然后跳转到asm_und_handler执行。注意此次跳转也会导致运行模式的变化。
另外为了不破坏寄存器环境,必须采用ldmfd sp!, r0, pc的形式来跳转,否则无法恢复r0的值。
实际的指令模拟在asm_und_handler中实现:
.global asm_und_handler
asm_und_handler:
sub sp, sp, #4 /* reserved space for AddrUnd */
stmfd sp!, r0-r12
ldr r0, =AddrUnd
ldr r0, [r0]
str r0, [sp, #52]
ldr r0, [r0, #-4] /* get the undefined instruction */
bic r1, r0, #0xF
ldr r2, =0xE12FFF30
cmp r1, r2
beq handle_BLX
ldmfd sp!, r0-r12, pc
handle_BLX:
ldr r3, =0xFFFFFFF0
bic r0, r0, r3
cmp r0, #0
beq BLX_R0
cmp r0, #1
beq BLX_R1
cmp r0, #2
beq BLX_R2
cmp r0, #3
beq BLX_R3
cmp r0, #4
beq BLX_R4
cmp r0, #5
beq BLX_R5
cmp r0, #6
beq BLX_R6
cmp r0, #7
beq BLX_R7
cmp r0, #8
beq BLX_R8
cmp r0, #9
beq BLX_R9
cmp r0, #10
beq BLX_R10
cmp r0, #11
beq BLX_R11
cmp r0, #12
beq BLX_R12
ldmfd sp!, r0-r12, pc
BLX_R0:
ldmfd sp!, r0-r12, lr
bx r0
BLX_R1:
ldmfd sp!, r0-r12, lr
bx r1
BLX_R2:
ldmfd sp!, r0-r12, lr
bx r2
BLX_R3:
ldmfd sp!, r0-r12, lr
bx r3
BLX_R4:
ldmfd sp!, r0-r12, lr
bx r4
BLX_R5:
ldmfd sp!, r0-r12, lr
bx r5
BLX_R6:
ldmfd sp!, r0-r12, lr
bx r6
BLX_R7:
ldmfd sp!, r0-r12, lr
bx r7
BLX_R8:
ldmfd sp!, r0-r12, lr
bx r8
BLX_R9:
ldmfd sp!, r0-r12, lr
bx r9
BLX_R10:
ldmfd sp!, r0-r12, lr
bx r10
BLX_R11:
ldmfd sp!, r0-r12, lr
bx r11
BLX_R12:
ldmfd sp!, r0-r12, lr
bx r12
因为实际中BLX 可以有
BLX r0
BLX r1
…
BLX r12
等12中方式,所以必须对每种方式都加以模拟。
需要注意的是,必须要在执行 bx r12之前确保寄存器环境与原指令运行时的环境完全相同,还是使用了ldmfd的方式来解决这个问题。因为ldmfd要求按照顺序加载寄存器,所以需要一点小技巧了合理安排栈中寄存器的分布。
本例中,对于除了BLX之外的未定义指令,直接忽略并跳转到下一条指令执行。
4 测试
GCC的C语言编译器对于函数指针形式的调用,会产生处BLX汇编指令,下面就是C源码和编译产生的汇编码。
int(*f)(const char*) = puts;
f("Hello BLX\\n");
对应的汇编码:
ldr r3, .L3+24
str r3, [fp, #-8]
ldr r3, [fp, #-8]
ldr r0, .L3+28
blx r3
S3C2440A这款CPU不支持BLX指令,如果不进行模拟实现,那么就无法实现函数调用的功能。幸好我们上面刚刚实现了BLX的模拟,此时该测试程序顺利执行,并打印出了Hello BLX。
Initialize Keys...[OK]
Initialize LEDs...[OK]
Initialize Beep...[OK]
Hello BLX
I'm idle, waiting for your order
I'm idle, waiting for your order
5 完整源码
本文对应的完整源码v1.0。
以上是关于TQ2440开发板学习纪实--- 利用Undefined异常模拟BLX指令的主要内容,如果未能解决你的问题,请参考以下文章
TQ2440开发板学习纪实--- 设置时钟频率,让CPU运行的更快
TQ2440开发板学习纪实--- 基于中断的UART串口接收
TQ2440开发板学习纪实--- 设置时钟频率,让CPU运行的更快
TQ2440开发板学习纪实(10)--- 实现多任务处理,最简单OS模型