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() thensync_errors = sync_errors || (SCR_EL3.EA == '1' && SCR_EL3.NMEA == '1' && PSTATE.EL == EL3);if sync_errors thenSynchronizeErrors();iesb_req = TRUE;TakeUnmaskedPhysicalSErrorInterrupts(iesb_req);// Attempts to change to an illegal state will invoke the Illegal Execution state mechanismSetPSTATEFromPSR(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 UNKNOWNnew_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 stateif PSTATE.T == '1' thennew_pc<0> = '0'; // T32elsenew_pc<1:0> = '00'; // A32else // Return to AArch64// ELR_ELx[63:56] might include a tagnew_pc = AArch64.BranchAddr(new_pc);if UsingAArch32() then// 32 most significant bits are ignored.BranchTo(new_pc<31:0>, BranchType_ERET);elseBranchToAddr(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') thenvect_offset = 0x180;elsevect_offset = 0x0;if HaveNV2Ext() && fault.acctype == AccType_NV2REGISTER thenexception = AArch64.AbortSyndrome(Exception_NV2DataAbort, fault, vaddress);elseexception = AArch64.AbortSyndrome(Exception_DataAbort, fault, vaddress);if PSTATE.EL == EL3 || route_to_el3 thenAArch64.TakeException(EL3, exception, preferred_exception_return, vect_offset);elsif PSTATE.EL == EL2 || route_to_el2 thenAArch64.TakeException(EL2, exception, preferred_exception_return, vect_offset);elseAArch64.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 SupervisorAArch64.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) thenAArch64.TakeException(PSTATE.EL, exception, preferred_exception_return, vect_offset);elsif route_to_el2 thenAArch64.TakeException(EL2, exception, preferred_exception_return, vect_offset);elseAArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);同理,从SVC指令的伪代码中,可以看到,preferred_exception_return被设置为了NextInstrAddr()下一条指令的地址
总结
AARCH64 CPU在发生异常的时候,会根据异常的Class来将ELR设置为不同的地址,所以,ELR寄存器存储的是异常处理之后需要返回的地址,而不是发生异常的指令地址.