AARCH64 ERET
Table of Contents
Exception & ERET的使用场景
ERET用来在系统异常处理完成后返回原来的执行流程并继续运行下去
看似简单的功能,实现却并不简单,需要考虑很多场景,不同场景下行为也会有些差异
在ARMv8 TRM中有伪代码,通过分析伪代码,可以从一个角度看清ERET指令的功能。
典型场景:
- SVC,HVC,SMC等软件异常指令
- Synchronous Exception异常
IRQ/FIQ/System Error返回
从上面的场景中,可以看到,在不同的场景下,异常处理返回后需要从不同的地址继续执行
- Data Abort: 需要从当前位置重新执行出错的指令,以便读取到正确的数据
- SVC, HVC, SMC:需要从出错指令的下一条指令开始继续执行,否则将会重新进入特权模式
- Data Abort: 需要从当前位置重新执行出错的指令,以便读取到正确的数据
ERET返回后代码从何处开始执行?
先看ERET的伪代码
伪代码:
// AArch64.ExceptionReturn()
// =========================
AArch64.ExceptionReturn(bits(64) new_pc, bits(32) spsr)
SynchronizeContext();
sync_errors = HaveIESB() && SCTLR[].IESB == '1';
if HaveDoubleFaultExt() then
sync_errors = sync_errors || (SCR_EL3.EA == '1' && SCR_EL3.NMEA == '1' && PSTATE.EL == EL3);
if sync_errors then
SynchronizeErrors();
iesb_req = TRUE;
TakeUnmaskedPhysicalSErrorInterrupts(iesb_req);
// Attempts to change to an illegal state will invoke the Illegal Execution state mechanism
SetPSTATEFromPSR(spsr);
ClearExclusiveLocal(ProcessorID());
SendEventLocal();
if PSTATE.IL == '1' && spsr<4> == '1' && spsr<20> == '0' then
// If the exception return is illegal, PC[63:32,1:0] are UNKNOWN
new_pc<63:32> = bits(32) UNKNOWN;
new_pc<1:0> = bits(2) UNKNOWN;
elsif UsingAArch32() then
// Return to AArch32
// ELR_ELx[1:0] or ELR_ELx[0] are treated as being 0, depending on the target instruction set state
if PSTATE.T == '1' then
new_pc<0> = '0'; // T32
else
new_pc<1:0> = '00'; // A32
else // Return to AArch64
// ELR_ELx[63:56] might include a tag
new_pc = AArch64.BranchAddr(new_pc);
if UsingAArch32() then
// 32 most significant bits are ignored.
BranchTo(new_pc<31:0>, BranchType_ERET);
else
BranchToAddr(new_pc, BranchType_ERET);
通过上面的代码可以发现,ERET并没有对返回的地址做取后一条指令地址的操作,仅仅对异常指令的低位进行了修正.
由此可见,系统在执行ESR指令i之前,系统就已经将LR设置为了需要正确返回的地址了。
再看Exception产生的伪代码
- DataAbort
- da
// AArch64.DataAbort()
// ===================
AArch64.DataAbort(bits(64) vaddress, FaultRecord fault)
route_to_el3 = HaveEL(EL3) && SCR_EL3.EA == '1' && IsExternalAbort(fault);
route_to_el2 = (PSTATE.EL IN {EL0, EL1} && EL2Enabled() && (HCR_EL2.TGE == '1' ||
(HaveRASExt() && HCR_EL2.TEA == '1' && IsExternalAbort(fault)) ||
(HaveNV2Ext() && fault.acctype == AccType_NV2REGISTER) ||
IsSecondStage(fault)));
bits(64) preferred_exception_return = ThisInstrAddr();
if (HaveDoubleFaultExt() && (PSTATE.EL == EL3 || route_to_el3) &&
IsExternalAbort(fault) && SCR_EL3.EASE == '1') then
vect_offset = 0x180;
else
vect_offset = 0x0;
if HaveNV2Ext() && fault.acctype == AccType_NV2REGISTER then
exception = AArch64.AbortSyndrome(Exception_NV2DataAbort, fault, vaddress);
else
exception = AArch64.AbortSyndrome(Exception_DataAbort, fault, vaddress);
if PSTATE.EL == EL3 || route_to_el3 then
AArch64.TakeException(EL3, exception, preferred_exception_return, vect_offset);
elsif PSTATE.EL == EL2 || route_to_el2 then
AArch64.TakeException(EL2, exception, preferred_exception_return, vect_offset);
else
AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);
从DataAbort的伪代码中可以看出,系统在取preferred_exception_return时,使用的是ThisInstrAddr,即ELR的值将设置为出错指令的下一条指令. AArch64.TakeException过程将把preferred_exception_return的值设置到ELR寄存器中
- CallSupervisor
svc
aarch64/exceptions/syscalls/AArch64.CallSupervisor
// AArch64.CallSupervisor()
// ========================
// Calls the Supervisor
AArch64.CallSupervisor(bits(16) immediate)
if UsingAArch32() then AArch32.ITAdvance();
SSAdvance();
route_to_el2 = PSTATE.EL == EL0 && EL2Enabled() && HCR_EL2.TGE == '1';
bits(64) preferred_exception_return = NextInstrAddr();
vect_offset = 0x0;
exception = ExceptionSyndrome(Exception_SupervisorCall);
exception.syndrome<15:0> = immediate;
if UInt(PSTATE.EL) > UInt(EL1) then
AArch64.TakeException(PSTATE.EL, exception, preferred_exception_return, vect_offset);
elsif route_to_el2 then
AArch64.TakeException(EL2, exception, preferred_exception_return, vect_offset);
else
AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);
同理,从SVC指令的伪代码中,可以看到,preferred_exception_return被设置为了NextInstrAddr()下一条指令的地址
总结
AARCH64 CPU在发生异常的时候,会根据异常的Class来将ELR设置为不同的地址,所以,ELR寄存器存储的是异常处理之后需要返回的地址,而不是发生异常的指令地址.