ARM的异常处理过程分析Word文件下载.docx
《ARM的异常处理过程分析Word文件下载.docx》由会员分享,可在线阅读,更多相关《ARM的异常处理过程分析Word文件下载.docx(14页珍藏版)》请在冰豆网上搜索。
CPSR[7]=1
//任何异常模式下都会关闭IRQ中断
PC=exceptionvectoraddress
从上面的代码中我们可以发现CPU自动处理的过程包括如下:
1、
拷贝CPSR到SPSR_<
mode>
2、
设置适当的CPSR位:
改变处理器状态进入ARM状态;
改变处理器模式进入相应的异常模式;
设置中断禁止位禁止相应中断。
3、
更新LR_<
,这个寄存器中保存的是异常返回时的链接地址
4、
设置PC到相应的异常向量
以上的操作都是CPU自动完成,异常的向量表如下:
返回地址问题
异常的返回地址也是需要我们注意的地方,不同的异常模式返回地址也是存在差异的,这主要是因为各种异常产生的机理存在差别所导致的。
这样我们的需要在异常进入处理函数之前或者在返回时调整返回地址,一般采用进入异常处理函数前进行手动调整。
下面每一种异常R14保存的值都给了出来,其中也包含了CPU自动处理的部分,根据保存的R14就可以知道怎样实现地址的返回。
复位异常:
可以看出该模式下的先对来说返回地址也比较简单,不需要做太多的描述。
未定义的指令异常:
返回的方式也比较简单:
MOVS
PC,R14
软中断异常:
预取指令中止异常:
返回需要做下面的调整:
SUBS
PC,R14,#4
数据中止
返回地址需要做下面的调整:
如果需要重新访问数据则:
PC,R14,#8
如果不需要重新访问数据则:
IRQ中断的处理过程:
SUBSPC,R14,#4
IFQ中断:
PC,R14,#4
从上面的代码可以知道,对于每一种异常,保存的返回地址都是不一样的,一般都需要我们手动的跳转,当然调整的时机也需要我们选择,是在进入处理前跳转还是返回时调整都是需要我们程序员控制的。
在ARMDeveloperSuiteDeveloperGuide中对ARM处理器的异常处理操作提供能更加详细的解释,每一种异常下的处理方式如下文描述:
异常返回时另一个非常重要的问题是返回地址的确定,在前面曾提到进入异常时处理器会有一个保存LR
的动作,但是该保存值并不一定是正确的返回地址,下面以一个简单的指令执行流水状态图来对此加以说明。
我们知道在ARM
架构里,PC值指向当前执行指令的地址加8处,也就是说,
当执行指令A(地址0x8000)时,PC
等于指令C
的地址(0x8008)。
假如指令A
是“BL”指令,则当执行该指令时,会把PC(=0x8008)保存到LR
寄存器里面,但是接下去处理器会马上对LR
进行一个自动的调整动作:
LR=LR-0x4。
这样,最终保存在
LR
里面的是
B
指令的地址,所以当从
BL
返回时,LR里面正好是正确的返回地址。
同样的调整机制在所有LR自动保存操作中都存在,比如进入中断响应时,处理器所做的LR
保存中,也进行了一次自动调整,并且调整动作都是LR=LR-0x4。
下面,我们对不同类型的异常的返回地址依次进行说明:
假设在指令A
处(地址0x8000)发生了异常,进入异常响应后,LR
上经过调整保存的地址值应该是B
的地址0x8004。
如果发生的是软件中断,即A
是“SWI”指令
异常是由指令本身引起的,从
SWI
中断返回后下一条执行指令就是B,正好是LR
寄存器保存的地址,
所以只要直接把LR
恢复给PC。
MOVSpc,lr
发生的是Undefinedinstruction异常
异常是由指令本身引起的,从异常返回后下一条执行指令就是B,正好是LR
发生的是IRQ或FIQ中断
因为指令不可能被中断打断,所以A指令执行完以后才能响应中断,此时PC已更新,指向指令D的地址(地址0x800C),LR
上经过调整保存的地址值是C
的地址0x8008。
中断返回后应该执行B指令,所以返回操作是:
SUBSpc,lr,#4
发生的是PrefetchAbort异常
该异常并不是处理器试图从一个非法地址取指令时触发,取出的指令只是被标记为非法,按正常处理流程放在流水线上,在执行阶段触发PrefetchAbort异常,此时LR
上经过调整保存的地址值是B
异常返回应该返回到A指令,尝试重新取指令,所以返回操作是:
5、
发生的是“DataAbort”
CPU访问存储器时触发该异常,此时PC指向指令D的地址(地址0x800C),LR
异常返回后,应回到指令A,尝试重新操作存储器,所以返回操作是:
SUBSpc,lr,#8
以上就是ARM异常的CPU操作部分,接下来就是程序员应该完成的操作。
1.
由于CPU会自动跳转到对应的异常向量中,因此只需要在在各个异常向量中存放对应的操作,最简单的都是存放一个B指令跳转到对应的异常处理函数的操作即可。
但由于B指令的跳转返回只有+-32M,而异常处理函数的地址可能会超过+-32M,因此可以采用另一种方式实现方式:
在异常向量中保存一条指令LDRPC[addr],其中的addr中就保存了异常处理函数的地址,当然addr的相对地址要小于+-32M。
这样也就解决了跳转范围的问题。
2.
接下来就是异常处理函数对应的操作,可以在进入异常处理之前就进行返回地址的调整,这样后面就不用进行处理啦,当然也可以在返回过程中再调整。
一般都是在这个过程中进行调整。
进行压栈操作,保存对应的环境变量。
调用实际的处理过程等。
3.
出栈,恢复CPU的状态和寄存器的值。
由于第一步中已经调整好返回地址,这一步不需要再次调整。
当然如果之前没有调整,这里则需要进行相应的调整。
在uC/OS-II的官网移植中采用通用异常处理函数的方式实现异常的处理,下面我们来分析其中的部分代码:
首先是处理器部分的移植,包括异常向量、异常的ID号,存储异常处理函数地址的地址等:
/*ARM的异常ID号,支持7种类型的异常,每一种异常都存在一个ID号*/
#define
OS_CPU_ARM_EXCEPT_RESET
0x00
OS_CPU_ARM_EXCEPT_UNDEF_INSTR0x01
OS_CPU_ARM_EXCEPT_SWI
0x02
OS_CPU_ARM_EXCEPT_PREFETCH_ABORT0x03
OS_CPU_ARM_EXCEPT_DATA_ABORT
0x04
OS_CPU_ARM_EXCEPT_ADDR_ABORT
0x05
OS_CPU_ARM_EXCEPT_IRQ
0x06
OS_CPU_ARM_EXCEPT_FIQ
0x07
OS_CPU_ARM_EXCEPT_NBR
0x08
/*异常向量地址*/
OS_CPU_ARM_EXCEPT_RESET_VECT_ADDR
(OS_CPU_ARM_EXCEPT_RESET
*0x04+0x00)
//0x00
OS_CPU_ARM_EXCEPT_UNDEF_INSTR_VECT_ADDR
(OS_CPU_ARM_EXCEPT_UNDEF_INSTR
//0x04
OS_CPU_ARM_EXCEPT_SWI_VECT_ADDR
(OS_CPU_ARM_EXCEPT_SWI
//0x08
OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_VECT_ADDR
(OS_CPU_ARM_EXCEPT_PREFETCH_ABORT*0x04+0x00)
//0x0c
OS_CPU_ARM_EXCEPT_DATA_ABORT_VECT_ADDR
(OS_CPU_ARM_EXCEPT_DATA_ABORT
//0x10
/*这个异常是ARM中不支持的异常*/
OS_CPU_ARM_EXCEPT_ADDR_ABORT_VECT_ADDR
(OS_CPU_ARM_EXCEPT_ADDR_ABORT
//0x14
OS_CPU_ARM_EXCEPT_IRQ_VECT_ADDR
(OS_CPU_ARM_EXCEPT_IRQ
//0x18
OS_CPU_ARM_EXCEPT_FIQ_VECT_ADDR
(OS_CPU_ARM_EXCEPT_FIQ
//0x1c
/*存储异常处理函数地址的地址*/
/*ARMexceptionhandlersaddresses
*/
OS_CPU_ARM_EXCEPT_RESET_HANDLER_ADDR
*0x04+0x20)
//0x20
OS_CPU_ARM_EXCEPT_UNDEF_INSTR_HANDLER_ADDR
//0x24
OS_CPU_ARM_EXCEPT_SWI_HANDLER_ADDR
//0x28
OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_HANDLER_ADDR
(OS_CPU_ARM_EXCEPT_PREFETCH_ABORT*0x04+0x20)
//0x2c
OS_CPU_ARM_EXCEPT_DATA_ABORT_HANDLER_ADDR
//0x30
OS_CPU_ARM_EXCEPT_ADDR_ABORT_HANDLER_ADDR
//0x34
OS_CPU_ARM_EXCEPT_IRQ_HANDLER_ADDR
//0x38
OS_CPU_ARM_EXCEPT_FIQ_HANDLER_ADDR
//0x3c
/*存储在异常向量中的内容,实质上是LDRPC,[PC,#0x18]的机器码*/
OS_CPU_ARM_INSTR_JUMP_TO_SELF
0xEAFFFFFE
/*ARM"
JumpToExceptionHandler"
asminstruction
OS_CPU_ARM_INSTR_JUMP_TO_HANDLER
0xE59FF018
异常的初始化函数,首先,完成了在异常向量中存储指令的操作,采用机器码的形式就能避免直接访问寄存器什么的,其次,完成在固定的地址处存放对应异常处理函数的地址。
其中采用了赋值的形式也是需要注意的,采用的强制类型转换和指针相结合的形式。
保证了是修改地址处的内容。
而不是修改地址。
/*初始化异常中断向量*/
void
OS_CPU_InitExceptVect(void)
{
/*
OS_CPU_ARM_EXCEPT_UNDEF_INSTR_VECT_ADDR是对应中断向量表的地址
OS_CPU_ARM_INSTR_JUMP_TO_HANDLER是保存了对应的OS_CPU_ARM_INSTR_JUMP_TO_HANDLER(实质上是一个指令)
实质上就是在异常向量中存放了:
LDRPC[PC,#0x18],也就是让PC指向对应的异常处理地址中的内容,
也就是实现到实际处理函数的跳转。
异常处理地址中存储了实际的异常处理函数的地址
其他的异常也有相同的操作,OS_CPU_ARM_INSTR_JUMP_TO_HANDLER是一个指令的机器码形式
(*(INT32U*)OS_CPU_ARM_EXCEPT_UNDEF_INSTR_VECT_ADDR)
=
OS_CPU_ARM_INSTR_JUMP_TO_HANDLER;
(*(INT32U*)OS_CPU_ARM_EXCEPT_UNDEF_INSTR_HANDLER_ADDR)
=(INT32U)OS_CPU_ARM_ExceptUndefInstrHndlr;
(*(INT32U*)OS_CPU_ARM_EXCEPT_SWI_VECT_ADDR)
(*(INT32U*)OS_CPU_ARM_EXCEPT_SWI_HANDLER_ADDR)
=(INT32U)OS_CPU_ARM_ExceptSwiHndlr;
(*(INT32U*)OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_VECT_ADDR)
(*(INT32U*)OS_CPU_ARM_EXCEPT_PREFETCH_ABORT_HANDLER_ADDR)=(INT32U)OS_CPU_ARM_ExceptPrefetchAbortHndlr;
(*(INT32U*)OS_CPU_ARM_EXCEPT_DATA_ABORT_VECT_ADDR)
(*(INT32U*)OS_CPU_ARM_EXCEPT_DATA_ABORT_HANDLER_ADDR)
=(INT32U)OS_CPU_ARM_ExceptDataAbortHndlr;
(*(INT32U*)OS_CPU_ARM_EXCEPT_ADDR_ABORT_VECT_ADDR)
(*(INT32U*)OS_CPU_ARM_EXCEPT_ADDR_ABORT_HANDLER_ADDR)
=(INT32U)OS_CPU_ARM_ExceptAddrAbortHndlr;
(*(INT32U*)OS_CPU_ARM_EXCEPT_IRQ_VECT_ADDR)
(*(INT32U*)OS_CPU_ARM_EXCEPT_IRQ_HANDLER_ADDR)
=(INT32U)OS_CPU_ARM_ExceptIrqHndlr;
/*在异常向量中存储对应的操作,实质上就是将PC值调转*/
(*(INT32U*)OS_CPU_ARM_EXCEPT_FIQ_VECT_ADDR)
(*(INT32U*)OS_CPU_ARM_EXCEPT_FIQ_HANDLER_ADDR)
=(INT32U)OS_CPU_ARM_ExceptFiqHndlr;
}
异常类型
Mode
异常向量
内容
IRQ异常
IRQ
0x00000018
LDRPC,[PC,#0x18]或者0xE59FF018
IFQ异常
IFQ
0x0000001C
LDRPC,[PC,#0x18]
0x00000038
AddressofOS_CPU_ARM_ExceptIrqHndlr()
0x0000003C
AddressofOS_CPU_ARM_ExceptFiqHndlr()
有必要的讨论一下,为什么在向量中存储的是指令:
LDRPC,[PC,#0x18],我们从上面的地址可以知道,IRQ异常处理函数地址被存储到了0x00000038中,异常向量与该地址之间的差值是0x20,那么为什么在其中存储的值只是0x18呢?
这还要讨论ARM的流水线结构,当前执行的命令相比PC指向的地址差0x08。
也就是当前执行的指令的地址是PC-0x08.当PC指向异常向量以后(取值),还需要等待一个时钟(译码)之后才会被执行(真正意义上的执行操作),而这时PC值已经被更新了。
指向了Vector+0x8的位置,因此我们可以知道,当执行向量中的代码时,这时PC=Vector+0x8,而这时相对于固定的0x20-0x08=0x18,这也就是为什么是LDRPC,[PC,#0x18],而不是LDRPC,[PC,#0x20].
采用上面的例子说明IRQ的向量为0x00000018,而设定好的固定地址用来存储对应异常处理函数地址的地址是0x00000038,当CPU执行完PC=0x00000018以后,还需要译码、才能被执行,这时候PC值已经更新为PC=0x00000018+0x08;
这时候固定地址距离PC的相对位置位0x00000038–PC=0x18,而该地址中保存了IRQ中断的通用处理函数OS_CPU_ARM_ExceptIrqHndlr()的地址,LDRPC,[PC,#0x18]这条指令是指将PC+0x18地址处的内容加载到PC中,实质上也就完成跳转到异常处理函数的操作。
这样处理的好处是因为LDR的加载范围是一个固定值+-32M,我们不能保证异常处理程序的地址刚好在+-32M左右,采用这种LDRPC,ADDR(固定地址)的形式就能实现大范围的跳转操作。
我们仅仅以FIQ中断处理的形式进行讨论,其他的异常有一定的相似性,只是在返回地址上存在差别。
这段代码主要是完成寄存器的压栈,返回地址的调整,保存等操作。
具体的看下面的分析:
AREACODE,CODE,READONLY
CODE32
OS_CPU_ARM_ExceptFiqHndlr
修改中断返回地址,这属于进入真正处理函数前的返回地址调整,具体的返回地址依据前面保存的R14进行相应的修改。
SUB
LR,LR,#4
LRoffsettoreturnfromthisexception:
-4.
压栈操作
STMFD
SP!
{R0-R12,LR}
Pushworkingregisters.
保存链接寄存器
MOV
R2,LR
Savelinkr