AARCH64 ERET

Table of Contents

Exception & ERET的使用场景

ERET用来在系统异常处理完成后返回原来的执行流程并继续运行下去
看似简单的功能,实现却并不简单,需要考虑很多场景,不同场景下行为也会有些差异
在ARMv8 TRM中有伪代码,通过分析伪代码,可以从一个角度看清ERET指令的功能。

典型场景:

  1. SVC,HVC,SMC等软件异常指令
  2. Synchronous Exception异常
  3. IRQ/FIQ/System Error返回

    从上面的场景中,可以看到,在不同的场景下,异常处理返回后需要从不同的地址继续执行

    • Data Abort: 需要从当前位置重新执行出错的指令,以便读取到正确的数据
    • SVC, HVC, SMC:需要从出错指令的下一条指令开始继续执行,否则将会重新进入特权模式

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寄存器存储的是异常处理之后需要返回的地址,而不是发生异常的指令地址.

Contact me via :)
虚怀乃若谷,水深则流缓。