- 未定义指令:故名思意就是没有定义的异常,CPU不认识的指令,当执行这些指令时CPU将会出错
- 未定义指令有什么作用呢?
- 很多时候,我们故意在代码里插入一些伪造的指令,故意让CPU执行到它时触发错误。
- 这在调试时很有用,比如想打断点:怎么实现呢?有很多种方法:硬件监视点(watch point,数量有限)、软件断点(数量无限)。
- 软件断点就是使用
未定义指令
来实现的
- 产生未定义的原因有两个: 要么是程序出错,要么是故意插入未定义的指令
- 想让程序执行到某个地址A时停下来,可以这样做:
- 地址A上原来的指令是
xxx
- 我们故意把它改成
yyy
,改成一条CPU无法识别的指令 - 当CPU执行到地址A上的
yyy
指令时,触发异常 - 在异常处理函数里,打印更多调试信息(跟程序员交互,显示更多寄存器的值、状态等)
- 调试完毕后,恢复地址A上的指令为
xxx
- 从地址A重新执行程序
- 地址A上原来的指令是
A7的异常向量表:
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
- 从向量表可以看出,A7支持哪些异常
- 可以在代码中插入一条未定义指令: .word 0xffffffff看看会发生什么
因为我们将异常向量表放在程序最开头,程序烧写在flash上,然后会被片内ROM拷贝到内存的某个地址,而这个地址一般不为0(ARM9中,异常向量表的地址只能是0、0xFFFF0000;A7中可以通过VBAR修改异常向量表基地址),所以需要修改向量表的基地址:
MRC p15, 0, <Rt>, c12, c0, 0 ; Read VBAR into Rt
MCR p15, 0, <Rt>, c12, c0, 0 ; Write Rt to VBAR
// VBAR(vector base address)是一个协处理器(寄存器)
/* 设置异常向量表基地址 : VBAR */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 //将异常向量表基地址设置为_start
- 实现未定义指令异常:
```c
//#define STACK_BASE (0xc0000000 + 0x100000) // stm32mp157
define STACK_BASE (0x80000000 + 0x100000) // imx6ull
define STACK_SIZE (2048)
.text
.global _start
_start:
b reset
ldr pc, =do_undefined
.word 0 // ldr pc, _software_interrupt //.word 0 : 在当前位置放一个word型的值,该值为0
.word 0 // ldr pc, _prefetch_abort
.word 0 // ldr pc, _data_abort
.word 0 // ldr pc, _not_used
.word 0 // ldr pc, _irq
.word 0 // ldr pc, _fiq
reset: / 设置sp / / 对于STM32MP157设置链接地址为0xC0200000, 对于IMX6ULL设为0x80200000 / ldr sp, =STACK_BASE
adr r0, _start
bl SystemInit
bl uart_init
/* 设置异常向量表基地址 : VBAR */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0
.word 0xffffffff
/* 调用main函数 */
//bl main
ldr pc, =main
do_undefined: / 设置SP_und / ldr sp, =STACK_BASE - STACK_SIZE // 设置SP_und在SP_user的下方
/* 保存现场 */
stmdb sp!, {R0-R3,R12,LR} // 将寄存器进行压栈; und模式中, LR保存的地址是未定义指令地址+4(即下一条指令的地址)
/* 调用处理函数 */
bl do_undefined_c // 调用实际的处理函数(这里将会更新LR寄存器的值)
/* 恢复现场 */
ldmia sp!, {R0-R3,R12,PC}^ //恢复压栈的寄存器值 ^ : 还要将SPSR的值恢复到CPSR ; LR刚好指向未定义指令的下一条指令,所以不用再+offset
```