swi异常

文章目录

    • 1 swi异常简介
    • 2 swi程序示例
      • 2.1 start.S中要做的事情
      • 2.2 异常向量表及CPU模式表
      • 2.3 swi异常处理程序示例

1 swi异常简介

swi软件中断:software interrupt。

ARMCPU有7中模式,除了用户模式以外,其他6种都是特权模式,这些特权模式可以直接修改CPSR进入其他模式。usr用户模式不能修改CPSR进入其他模式。Linux应用程序一般运行于用户模式,APP运行于user mode,(受限模式,不可访问硬件),APP想访问硬件,必须切换模式,怎么切换?

发生异常3种模式:

  • 中断是一种异常
  • und也是
  • swi + 某个值(使用软中断切换模式)

2 swi程序示例

2.1 start.S中要做的事情

/*1
	/* 复位之后, cpu处于svc模式
	 * 现在, 切换到usr模式
	 * 设置栈
	 * 跳转执行
 */

/*2 故意引入一条swi指令*/

/*3 需在_start这里放一条swi指令*/

2.2 异常向量表及CPU模式表

查看异常向量表swi异常的向量地址是0x8:
swi异常_第1张图片
我们先切换到usr模式下:
swi异常_第2张图片
swi异常_第3张图片

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异常,仿照未定义指令做。

2.3 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"
*/

你可能感兴趣的:(Linux)