IMX6ULL裸机学习(10)— 未定义异常和SVC异常实例
一、未定义异常
未定义的指令异常就是 CPU 或协处理器不认识这条指令,执行这样的指令时就会产生“未定义指令异常”。所以我们只要在程序中写入一个不是指令的数据就可以产生未定义异常。
如下所示,使用.word
伪指令,可以在当前位置放一个word型的值,在imx6ull中,即一个32位数据
.word 0x12345678
然后我们修改start.s
文件如下所示,其中其他几个异常暂时没有实现,先用0填充
.text /* .text段保存代码,是只读和可执行的,后面的指令都属于.text段。 */
.global _start /* .global表示_start是一个全局符号,会在链接器链接时用到 */
_start: /* 标签_start,汇编程序的默认入口是_start */
b reset /* 复位异常,复位后从这里开始执行 */
ldr pc,=_undefined_instruction /* 未定义的指令异常,当cpu执行到不认识的指令时,会跳转到这里 */
.word 0 /* ldr pc,=_software_interrupt /* 软中断异常,当cpu执行svc指令时,会跳转到这里 */
.word 0 /* ldr pc,=_prefetch_abort /* 预取中止异常,处理器预取指令的地址不存在,或不允许当前访问时 */
.word 0 /* ldr pc,=_data_abort /* 数据中止异常,处理器数据访问指令的地址不存在,或不允许当前访问时 */
.word 0 /* ldr pc,=_not_used /* 未使用,预留的地址 */
.word 0 /* ldr pc,=_irq /* 中断请求有效,且CPSR中的I位(即中断屏蔽位)为0时,产生IRQ异常 */
.word 0 /* ldr pc,=_fiq /* 快速中断请求有效,且CPSR中的F位(即快中断屏蔽位)为0时,产生FIQ异常 */
reset:
/* 1、设置栈 */
ldr sp, =(0x80000000+0x100000) /* 设置栈顶地址 */
/* 2、设置异常向量表基地址 : VBAR */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0
/* 3、清除bss段 */
ldr r1, =__bss_start /* 将bss段开始地址存入r1寄存器 */
ldr r2, =__bss_end /* 将bss段结束地址存入r2寄存器 */
b clean_bss
clean:
mov r3, #0 /* 将0存入r3寄存器 */
str r3, [r1], #4 /* 将r3中的值存到r1中的值所指向的地址中, 同时r1中的值加4 */
clean_bss:
cmp r1, r2 /* 比较r1和r2内的值是否相等 */
bne clean /* 如果不相等则跳转到clean标签处,如果相等则往下执行 */
/* 4、调用系统初始化函数,在异常处理函数中需要用到uart功能,需要先初始化uart */
bl SystemInit
/* 5、写入一个未定义指令 */
.word 0xffffffff
/* 6、跳转到led函数 */
bl main
/* 7、原地循环 */
b .
_undefined_instruction:
/* 1、设置Undefined模式下的栈 */
ldr sp, =(0x80000000+0x50000)
/* 2、保存现场 */
stmdb sp!, {
r0-r12,lr}
/* 3、调用处理函数 */
bl do_undefined_c
/* 4、恢复现场 */
ldmia sp!, {
r0-r12,pc}^
修改main.c
文件如下所示
#include "uart.h"
#include "led.h"
void SystemInit(void)
{
uart_init();
}
int main(void)
{
led_init();
putstring("imx6ull\r\n");
while(1)
{
putstring("led on\r\n");
led_on();
delay(1000000);
putstring("led off\r\n");
led_off();
delay(1000000);
}
}
void do_undefined_c(unsigned int lr)
{
putstring("Exception: undefinedinstruction.\r\n");
}
接下来make编译,然后烧录到开发板,运行,打印如下所示
二、SVC异常
触发SVC异常是通过如下一个指令触发的,
在ARM指令中,有一条指令:
SVC #VAL
在操作系统中,比如各类RTOS或者Linux,都会使用SVC
指令故意触发异常,从而导致内核的异常处理函数被调用,进而去使用内核的服务。比如Linux中,各类文件操作的函数open
、read
、write
,它的实质都是SVC
指令。
然后我们修改start.s
文件如下所示,其中其他几个异常暂时没有实现,先用0填充
.text /* .text段保存代码,是只读和可执行的,后面的指令都属于.text段。 */
.global _start /* .global表示_start是一个全局符号,会在链接器链接时用到 */
_start: /* 标签_start,汇编程序的默认入口是_start */
b reset /* 复位异常,复位后从这里开始执行 */
ldr pc,=_undefined_instruction /* 未定义的指令异常,当cpu执行到不认识的指令时,会跳转到这里 */
ldr pc,=_software_interrupt /* 软中断异常,当cpu执行svc指令时,会跳转到这里 */
.word 0 /* ldr pc,=_prefetch_abort /* 预取中止异常,处理器预取指令的地址不存在,或不允许当前访问时 */
.word 0 /* ldr pc,=_data_abort /* 数据中止异常,处理器数据访问指令的地址不存在,或不允许当前访问时 */
.word 0 /* ldr pc,=_not_used /* 未使用,预留的地址 */
.word 0 /* ldr pc,=_irq /* 中断请求有效,且CPSR中的I位(即中断屏蔽位)为0时,产生IRQ异常 */
.word 0 /* ldr pc,=_fiq /* 快速中断请求有效,且CPSR中的F位(即快中断屏蔽位)为0时,产生FIQ异常 */
reset:
/* 1、设置栈 */
ldr sp, =(0x80000000+0x100000) /* 设置栈顶地址 */
/* 2、设置异常向量表基地址 : VBAR */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0
/* 3、清除bss段 */
ldr r1, =__bss_start /* 将bss段开始地址存入r1寄存器 */
ldr r2, =__bss_end /* 将bss段结束地址存入r2寄存器 */
b clean_bss
clean:
mov r3, #0 /* 将0存入r3寄存器 */
str r3, [r1], #4 /* 将r3中的值存到r1中的值所指向的地址中, 同时r1中的值加4 */
clean_bss:
cmp r1, r2 /* 比较r1和r2内的值是否相等 */
bne clean /* 如果不相等则跳转到clean标签处,如果相等则往下执行 */
/* 4、调用系统初始化函数,在异常处理函数中需要用到uart功能,需要先初始化uart */
bl SystemInit
/* 5、写入一个未定义指令 */
.word 0xffffffff
/* 6、触发一个SVC异常 */
SVC #1
/* 7、跳转到led函数 */
bl main
/* 8、原地循环 */
b .
_undefined_instruction:
/* 1、设置Undefined模式下的栈 */
ldr sp, =(0x80000000+0x50000)
/* 2、保存现场 */
stmdb sp!, {
r0-r12,lr}
/* 3、调用处理函数 */
bl do_undefined_c
/* 4、恢复现场 */
ldmia sp!, {
r0-r12,pc}^
_software_interrupt:
/* 1、设置SVC模式下的栈 */
ldr sp, =(0x80000000+0x40000)
/* 2、保存现场 */
stmdb sp!, {
r0-r12,lr}
/* 3、调用处理函数 */
bl do_svc_c
/* 4、恢复现场 */
ldmia sp!, {
r0-r12,pc}^
然后再main.c中添加如下函数
void do_svc_c(void)
{
putstring("Exception: software interrupt.\r\n");
}
接下来make编译,然后烧录到开发板,运行,打印如下所示,可以看到,SVC异常已被触发
三、上电时的模式
在上电时, CPU 就处于处于 SVC 模式。
我们将start.c修改为如下所示,添加获取CPSR寄存器的代码
.text /* .text段保存代码,是只读和可执行的,后面的指令都属于.text段。 */
.global _start /* .global表示_start是一个全局符号,会在链接器链接时用到 */
_start: /* 标签_start,汇编程序的默认入口是_start */
b reset /* 复位异常,复位后从这里开始执行 */
ldr pc,=_undefined_instruction /* 未定义的指令异常,当cpu执行到不认识的指令时,会跳转到这里 */
ldr pc,=_software_interrupt /* 软中断异常,当cpu执行svc指令时,会跳转到这里 */
.word 0 /* ldr pc,=_prefetch_abort /* 预取中止异常,处理器预取指令的地址不存在,或不允许当前访问时 */
.word 0 /* ldr pc,=_data_abort /* 数据中止异常,处理器数据访问指令的地址不存在,或不允许当前访问时 */
.word 0 /* ldr pc,=_not_used /* 未使用,预留的地址 */
.word 0 /* ldr pc,=_irq /* 中断请求有效,且CPSR中的I位(即中断屏蔽位)为0时,产生IRQ异常 */
.word 0 /* ldr pc,=_fiq /* 快速中断请求有效,且CPSR中的F位(即快中断屏蔽位)为0时,产生FIQ异常 */
reset:
/* 1、设置栈 */
ldr sp, =(0x80000000+0x100000) /* 设置栈顶地址 */
/* 2、设置异常向量表基地址 : VBAR */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0
/* 3、清除bss段 */
ldr r1, =__bss_start /* 将bss段开始地址存入r1寄存器 */
ldr r2, =__bss_end /* 将bss段结束地址存入r2寄存器 */
b clean_bss
clean:
mov r3, #0 /* 将0存入r3寄存器 */
str r3, [r1], #4 /* 将r3中的值存到r1中的值所指向的地址中, 同时r1中的值加4 */
clean_bss:
cmp r1, r2 /* 比较r1和r2内的值是否相等 */
bne clean /* 如果不相等则跳转到clean标签处,如果相等则往下执行 */
/* 4、调用系统初始化函数,在异常处理函数中需要用到uart功能,需要先初始化uart */
bl SystemInit
/* 5、获取当前CPSR的值存入r1中,r1会作为C函数的参数进行传递 */
mrs r0, cpsr
bl print_cpsr
/* 6、写入一个未定义指令 */
.word 0xffffffff
/* 7、触发一个SVC异常 */
SVC #1
/* 8、跳转到led函数 */
bl main
/* 9、原地循环 */
b .
_undefined_instruction:
/* 1、设置Undefined模式下的栈 */
ldr sp, =(0x80000000+0x50000)
/* 2、保存现场 */
stmdb sp!, {
r0-r12,lr}
/* 3、调用处理函数 */
bl do_undefined_c
/* 4、恢复现场 */
ldmia sp!, {
r0-r12,pc}^
_software_interrupt:
/* 1、设置SVC模式下的栈 */
ldr sp, =(0x80000000+0x40000)
/* 2、保存现场 */
stmdb sp!, {
r0-r12,lr}
/* 3、调用处理函数 */
bl do_svc_c
/* 4、恢复现场 */
ldmia sp!, {
r0-r12,pc}^
然后修改main.c如下所示,添加打印cpsr值的函数
#include "uart.h"
#include "led.h"
void SystemInit(void)
{
uart_init();
}
int main(void)
{
led_init();
putstring("imx6ull\r\n");
while(1)
{
putstring("led on\r\n");
led_on();
delay(1000000);
putstring("led off\r\n");
led_off();
delay(1000000);
}
}
void do_undefined_c(void)
{
putstring("Exception: undefined instruction.\r\n");
}
void do_svc_c(void)
{
putstring("Exception: software interrupt.\r\n");
}
void print_cpsr(unsigned int cpsr_val)
{
putstring("Entered on reset CPSR's value: ");
puthex(cpsr_val);
putstring("\r\n");
}
接下来make编译,然后烧录到开发板,运行,打印如下所示,可以看到,CPSR的值为0x600001D3
我们将其转换为二进制,得到最后5位为10011
通过查看模式表,可以看到,刚好就是SVC模式的值
四、附录
上一篇:IMX6ULL裸机学习(9)— IMX6ULL的异常和中断
下一篇:IMX6ULL裸机学习(11)— 中断和GIC中断控制器
代码存放:https://gitee.com/william_william/imx6ull_noos/tree/master/04_und_svc