swi软件中断:software interrupt。
ARMCPU有7中模式,除了用户模式以外,其他6种都是特权模式,这些特权模式可以直接修改CPSR进入其他模式。usr用户模式不能修改CPSR进入其他模式。Linux应用程序一般运行于用户模式,APP运行于user mode,(受限模式,不可访问硬件),APP想访问硬件,必须切换模式,怎么切换?
发生异常3种模式:
/*1
/* 复位之后, cpu处于svc模式
* 现在, 切换到usr模式
* 设置栈
* 跳转执行
*/
/*2 故意引入一条swi指令*/
/*3 需在_start这里放一条swi指令*/
查看异常向量表swi异常的向量地址是0x8:
我们先切换到usr模式下:
usr模式下的 M0 ~ M4是10000,切换模式的代码如下:
/**5 先进入usr模式*/
mrs r0, cpsr /* 读出cpsr 读到r0 */
/使用bic命令 bitclean 把低4位清零/
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
msr cpsr, r0
/*6 设置栈*/
/* 设置 sp_usr */
ldr sp, =0x33f00000
编译运行,发现可以处理und指令。接下来就可以添加 swi异常,仿照未定义指令做。
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
/*1 添加swi指令*/
ldr pc, swi_addr /* vector 8 : swi */
und_addr:
.word do_und
/*2 仿照und未定义添加指令*/
swi_addr:
.word do_swi
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"
/*3 复制do_und修改为swi */
do_swi:
/* 执行到这里之前:
* 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 3.2. SPSR_svc保存有被中断模式的CPSR
* 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 3.4. 跳到0x08的地方执行程序
*/
/* 3.5 sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* 3.7 lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 3.8 保存现场 */
/* 3.9 处理swi异常 只是打印 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/*3.10 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/*swi处理函数*/
swi_string:
.string "swi exception"
上传代码实验,烧写,发现没有执行。
我们先把下面这些代码注释掉(只保留标号):
/*3 复制do_und 修改为swi */
/* 执行到这里之前:
* 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 3.2. SPSR_svc保存有被中断模式的CPSR
* 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 3.4. 跳到0x08的地方执行程序
*/
/* 3.5 sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* 3.7 lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 3.8 保存现场 */
/* 3.9 处理swi异常 只是打印 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/*3.10 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/* swi处理函数 */
swi_string:
.string "swi exception"
上传编译、烧写执行,可以正常运行,循环打印。
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
,执行后继续执行ldr pc, swi_addr /* vector 8 : swi */
。
表明问题出现在 do_swi函数中,先把下面这句话注释掉 .string "swi exception"
,编译烧写运行,程序可以正常运行。显然程序问题出现在.string "swi exception"
这句话,为什么加上这句话程序就无法执行,查看一下反汇编:
30000064 <swi_string>: //这里地址是64
30000064: 20697773 rsbcs r7, r9, r3, ror r7
30000068: 65637865 strvsb r7, [r3, #-2149]!
3000007c: 6f697470 swivs 0x00697470
30000070: 0000006e andeq r0, r0, lr, rrx
30000082 <reset>: //我们使用的是ARM指令集,应该是4字节对齐,发现这里并不是,问题就在这里
30000082: e3a00453 mov r0, #1392508928 ; 0x53000000
30000086: e3a01000 mov r1, #0 ; 0x0
3000008a: e5801000 str r1, [r0]
3000008e: e3a00313 mov r0, #1275068416 ; 0x4c000000
30000092: e3e01000 mvn r1, #0 ; 0x0
因为这个字符串长度有问题,前面und_string 那里的字符串长度刚刚好,我们不能把问题放在运气上面。添加如下代码:
/*******
以4字节对齐
*/
.align 4
do_swi:
/* 执行到这里之前:
* 3.1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 3.2. SPSR_svc保存有被中断模式的CPSR
* 3.3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 3.4. 跳到0x08的地方执行程序
*/
/* 3.5 sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 3.6 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* 3.7 lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 3.8 保存现场 */
/* 3.9 处理swi异常 只是打印 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/*3.10 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/*****
swi处理函数
*/
swi_string:
.string "swi exception"
.align 4
/**************
表明下面的标号要放在4字节对齐的地方
*/
上传代码编译运行查看反汇编:
30000068 <swi_string>:
30000068: 20697773 rsbcs r7, r9, r3, ror r7
3000006c: 65637865 strvsb r7, [r3, #-2149]!
30000070: 6f697470 swivs 0x00697470
30000074: 0000006e andeq r0, r0, lr, rrx
...
30000080 <reset>: //现在reset放在4自己对齐的地方
30000080: e3a00453 mov r0, #1392508928 ; 0x53000000
30000084: e3a01000 mov r1, #0 ; 0x0
30000088: e5801000 str r1, [r0]
3000008c: e3a00313 mov r0, #1275068416 ; 0x4c000000
30000090: e3e01000 mvn r1, #0 ; 0x0
30000094: e5801000 str r1, [r0]
30000098: e59f0084 ldr r0, [pc, #132] ; 30000124 <.text+0x124>
3000009c: e3a01005 mov r1, #5 ; 0x5
300000a0: e5801000 str r1, [r0]
300000a4: ee110f10 mrc 15, 0, r0, cr1, cr0, {0}
300000a8: e3700103 orr r0, r0, #-1073741824 ; 0xc0000000
300000ac: ee010f10 mcr 15, 0, r0, cr1, cr0, {0}
300000b0: e59f0070 ldr r0, [pc, #112] ; 30000128 <.text+0x128>
300000b4: e59f1070 ldr r1, [pc, #112] ; 3000012c <.text+0x12c>
300000b8: e5801000 str r1, [r0]
300000bc: e3a01000 mov r1, #0 ; 0x0
300000c0: e5910000 ldr r0, [r1]
下载烧写程序执行完全没有问题。
swi可以根据应用程序传入的val来判断为什么调用swi指令,我们的异常处理函数能不能把这个val值读出来?
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 保存现场 */
/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 2
我们要把lr拿出来保存
因为bl printException会破坏lr
mov rX, lr
我把lr保存在那个寄存器?
这个函数 bl printException 可能会修改某些寄存器,但是又会恢复这些寄存器,我得知道他会保护那些寄存器
我们之前讲过ATPCS规则
对于 r4 ~ r11在C函数里他都会保存这几个寄存器,如果用到的话就把他保存起来,执行完C函数再把它释放掉
我们把lr 保存在r4寄存器里,r4寄存器不会被C语言破坏
*/
mov r4, lr
/* 处理swi异常 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
/*1
跳转到printSWIVal
如何才能知道swi的值呢?
我们得读出swi 0x123指令,这条指令保存在内存中,我们得找到他的内存地址
执行完0x123指令以后,会发生一次异常,那个异常模式里的lr寄存器会保存下一条指令的地址
我们把lr寄存器的地址减去4就是swi 0x123这条指令的地址
*/
/*3
我再把r4的寄存器赋给r0让后打印
我们得写出打印函数
mov r0, r4
指令地址减4才可以
swi 0x123
下一条指令bl main 减4就是指令本身
*/
sub r0, r4, #4
bl printSWIVal
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
swi_string:
.string "swi exception"
<syntaxhighlight lang="c" >
在uart.c添加printSWIVal打印函数
void printSWIVal(unsigned int *pSWI)
{
puts("SWI val = ");
printHEx(*pSWI & ~0xff000000); //高8位忽略掉
puts("\n\r");
}
编译实验运行没有问题。
我们再来看看这个程序是怎么跳转的:
/*1
发生swi异常,他是在sdram中,CPU就会跳到0x8的地方
swi 0x123 //执行此命令, 触发SWI异常, 进入0x8执行
*/
/* 2
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
执行这条读内存指令
ldr pc, swi_addr /* vector 8 : swi */
读到swi_addr地址跳转到sdram执行代码 do_swi那段代码
swi_addr:
.word do_swi
*/
/* 3
这段代码被设置栈保存现场 调用处理函数恢复现场,让后就会跳到sdram执行 swi 0x123的下一条指令
do_swi:
/* 执行到这里之前:
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_svc保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_svc未设置, 先设置它 */
ldr sp, =0x33e00000
/* 保存现场 */
/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
mov r4, lr
/* 处理swi异常 */
mrs r0, cpsr
ldr r1, =swi_string
bl printException
sub r0, r4, #4
bl printSWIVal
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
swi_string:
.string "swi exception"
*/