现在做第一个实验,模拟未定义模式。
未定义模式,是cpu遇到自己不能识别的指令时候做出的异常处理。
arm指令的机器码一定是按照某种规范要求的,不然你随意写一条指令,cpu不是都可以执行吗?在cpu没有定义该条指令含义的情况下,我们执行了这样未定义的指令,就会进入未定义异常。
现在我们要模拟一个未定义异常,所以我们只要写出一个cpu无法识别的指令即可。
在这之前,要明白一个道理,在内存中执行的机器码,只有0,1两个值,不同的指令被分解为不同的0,1信号的机器码。
所以,我们在运行内存中存放一个32bit的值,这个值又恰恰是上图所不能表示的指令,那么这样,就可以测试未定义异常了。我们采用.word 关键字,.word expression就是在当前位置放一个word型的值,这个值就是expression 。
.word 接收4字节的数据,放在当前地址。
现在我们仔细观察上面的那个图,找到一个未定义的机器码,把这个值放在.word的地方,当cpu运行到.word 地址时,发现这个地址的0,1数据即机器码它不能识别,就发生未定义异常。
所以我们可以使用 .word 0xf3000000来表示一条未定义指令,但大家用的比较多的是.word 0xbadc0de(很形象,表示坏代码)。
现在更改star.S:
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
und_addr:
.word do_und
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
.align 4
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
ldr pc, =sdram
sdram:
/* 清除BSS段 */
bl clean_bss
bl uart0_init
bl print1
/* 故意加入一条未定义指令 */
und_code:
.word 0xdeadc0de /* 未定义指令 */
bl print2
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
cpu处理异常的时候,是有专门地址的,这里的未定义und异常,在地址0x4处,当发生未定义异常时,硬件让程序从地址0x4处执行。这里说明一下汇编中定义字符串:
.string “XXXX” 表示定义的字符串包含最后的 .
.ascii "XXXX"表示定义的字符串不包含最后的 .
.asciiz "XXXX"表示定义的字符串包含最后的 ,z代表zero。
.align 4 放在.string 后面是为了使后面的指令四字节对齐,因为字符串的字节数不一定是四字节对齐的。
进入异常处理前需要保存现场,异常处理之后需要恢复现场。
现在说明下面代码段:
ldr pc, und_addr /* vector 4 : und */und_addr: .word do_und
但是我在想,为什么韦老大说nand启动,怕访问超过4k空间,所以采取上面的操作方式,这样保证肯定无论是否超过4k,程序都可以正常运行。但是,我们的sdram已经重定位完毕了,那样sdram已经是可以访问的了,所以就算超过4k也不会有问题啊?(这个等到后面上文件系统的时候测试,我觉得这里是不存在老师说的那种情况的)。
Question:
去掉star.S中的 bl print1
发现不会进入未定义异常了?这是为什么?
经过分析,是puts函数问题,只有调用了puts函数,才会进入未定义异常。原因。。。
我们上面忽略了一点,就是cond所占用的四个字节条件,
所以,未定义指令要忽略高4位cond条件码。
在测试过程中,我能发现是puts函数的问题,可是,puts函数是我们重定义的库函数,这和stm32 printf映射串口有一点像,具体编译器在操作puts的时候做了什么不得而知,但是可以肯定的是puts让cpsr高四位变成了无条件执行,才触发了未定义异常。现在我们把未定义指令的高四位改成1110,后面加上arm不能识别的指令,就可以进入未定义异常模式了。