Armv8 문서를 보면 Arm 코어에서 하드웨어적으로 처리하는 동작을 슈도 코드로 표기한 부분을 볼 수 있습니다.
문서 이름과 해당 절은 다음과 같습니다.
* 문서 이름: DDI0487Fc_armv8_arm.pdf
* 해당 절
Part J Architectural Pseudocode
J1.1 Pseudocode for AArch64 operation J1-7612
J1.1.1 aarch64/debug
J1.1.2 aarch64/exceptions
J1.2 Pseudocode for AArch32 operation
샘플 코드에 익숙한 소프트웨어 개발자들에게 샘플 혹은 슈도 코드는 유익한 정보입니다.
특히 하이퍼바이저나 트러스트존과 같은 Arm Extention 기능의 동작 원리를 알 수 있는 귀중한 정보입니다.
슈도 코드에서 익셉션과 관련된 함수의 코드를 보겠습니다.
AArch64.Abort()
말 그대로 Arm 코어가 어보트를 처리하는 동작을 수행하는 함수인데, 구현부는 다음과 같습니다.
aarch64/exceptions/aborts/AArch64.Abort
// AArch64.Abort()
// ===============
// Abort and Debug exception handling in an AArch64 translation regime.
AArch64.Abort(bits(64) vaddress, FaultRecord fault)
{
if IsDebugException(fault) then
if fault.acctype == AccType_IFETCH then
if UsingAArch32() && fault.debugmoe == DebugException_VectorCatch then
AArch64.VectorCatchException(fault);
else
AArch64.BreakpointException(fault);
else
AArch64.WatchpointException(vaddress, fault);
elsif fault.acctype == AccType_IFETCH then
AArch64.InstructionAbort(vaddress, fault);
else
AArch64.DataAbort(vaddress, fault);
}
디버그 익셉션을 설정하지 않았으면 AArch64.DataAbort() 함수를 호출합니다.
AArch64.DataAbort() 함수
Armv8 아키텍처 기반의 Arm 코어가 데이터 어보트를 처리하는 세부 동작을 나타낸 함수로 구현부는 다음과 같습니다.
aarch64/exceptions/aborts/AArch64.DataAbort
// 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);
}
코드의 내용은 정말 지저분한데, 대부분 루틴은 EL2, EL3와 같이 하이퍼바이저 모드와 세큐어 모드와 관련돼 있습니다.
일반적으로 다음과 같은 조건에서는;
* 시스템에서 EL2, EL3를 설정하지 않은 경우(하이퍼바이저, 세큐어 모드로 EL3를 사용하지 않음)
* 시스템에 유저 애플리케이션이 구동되는 EL0과, 운영체제 커널이 구동되는 EL1만 설정된 경우
슈도 코드는 다음과 같이 간단하게 표현할 수 있습니다.
AArch64.DataAbort(bits(64) vaddress, FaultRecord fault)
{
bits(64) preferred_exception_return = ThisInstrAddr();
AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);
}
AArch64.PCAlignmentFault() 함수
'PC Alignment Fault'는 프로세스의 스택이 코럽션(Stack Corruption) 됐거나,
'b r1' 명령어와 같이 함수 포인터를 실행할 때 r1이 0x7과 같이 쓰레기 값을 저장하면 발생합니다.
PCAlignmentFault() 함수의 구현부는 다음과 같습니다.
aarch64/exceptions/aborts/AArch64.PCAlignmentFault
// AArch64.PCAlignmentFault()
// ==========================
// Called on unaligned program counter in AArch64 state.
AArch64.PCAlignmentFault()
{
bits(64) preferred_exception_return = ThisInstrAddr();
vect_offset = 0x0;
exception = ExceptionSyndrome(Exception_PCAlignment);
exception.vaddress = ThisInstrAddr();
if UInt(PSTATE.EL) > UInt(EL1) then
AArch64.TakeException(PSTATE.EL, exception, preferred_exception_return, vect_offset);
elsif EL2Enabled() && HCR_EL2.TGE == '1' then
AArch64.TakeException(EL2, exception, preferred_exception_return, vect_offset);
else
AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);
}
참고로, HCR_EL2.TGE == '1' 구문은 하이퍼바이저 콘트롤 레지스터의 TGE 비트가 1로 설정됐다는 의미입니다.
일반적으로 다음과 같은 조건에서는;
* 시스템에서 EL2, EL3를 설정하지 않은 경우(하이퍼바이저, 세큐어 모드로 EL3를 사용하지 않음)
* 시스템에 유저 애플리케이션이 구동되는 EL0과, 운영체제 커널이 구동되는 EL1만 설정된 경우
슈도 코드는 다음과 같이 간단하게 표현할 수 있습니다.
Arch64.PCAlignmentFault()
{
bits(64) preferred_exception_return = ThisInstrAddr();
vect_offset = 0x0;
exception = ExceptionSyndrome(Exception_PCAlignment);
exception.vaddress = ThisInstrAddr();
AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);
}
AArch64.TakePhysicalIRQException()
Arm 코어는 인터럽트를 익셉션의 한 종류로 처리하는데, Arm 코어의 내부 처리 과정은 다음 슈도 코드에서 확인할 수 있습니다.
aarch64/exceptions/asynch/AArch64.TakePhysicalIRQException
// AArch64.TakePhysicalIRQException()
// ==================================
// Take an enabled physical IRQ exception.
AArch64.TakePhysicalIRQException()
{
route_to_el3 = HaveEL(EL3) && SCR_EL3.IRQ == '1';
route_to_el2 = (PSTATE.EL IN {EL0, EL1} && EL2Enabled() &&
(HCR_EL2.TGE == '1' || HCR_EL2.IMO == '1'));
bits(64) preferred_exception_return = ThisInstrAddr();
vect_offset = 0x80;
exception = ExceptionSyndrome(Exception_IRQ);
if route_to_el3 then
AArch64.TakeException(EL3, exception, preferred_exception_return, vect_offset);
elsif PSTATE.EL == EL2 || route_to_el2 then
assert PSTATE.EL != EL3;
AArch64.TakeException(EL2, exception, preferred_exception_return, vect_offset);
else
assert PSTATE.EL IN {EL0, EL1};
AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);
}
코드의 내용은 정말 지저분한데, 대부분 루틴은 EL2, EL3와 같이 하이퍼바이저 모드와 세큐어 모드와 관련돼 있습니다.
일반적으로 다음과 같은 조건에서는;
* 시스템에서 EL2, EL3를 설정하지 않은 경우(하이퍼바이저, 세큐어 모드로 EL3를 사용하지 않음)
* 시스템에 유저 애플리케이션이 구동되는 EL0과, 운영체제 커널이 구동되는 EL1만 설정된 경우
슈도 코드는 다음과 같이 간단하게 표현할 수 있습니다.
AArch64.TakePhysicalIRQException()
{
bits(64) preferred_exception_return = ThisInstrAddr();
vect_offset = 0x80;
exception = ExceptionSyndrome(Exception_IRQ);
assert PSTATE.EL IN {EL0, EL1};
AArch64.TakeException(EL1, exception, preferred_exception_return, vect_offset);
}
AArch64.TakeException() 함수의 첫 번째 인자는 EL1인데, 이는 타겟 EL(변경되는 EL)을 뜻합니다.
위 코드는 EL0이나 EL1에서 인터럽트가 발생하면 Arm 코어는 EL1으로 타겟 EL을 변경한다"라고 해석할 수 있습니다.
AArch64.TakeException()
AArch64.TakeException() 함수는 Arm 코어가 익셉션을 처리하는 세부 동작을 잘 표현합니다.
구현부는 다음과 같습니다.
// AArch64.TakeException()
// =======================
// Take an exception to an Exception Level using AArch64.
AArch64.TakeException(bits(2) target_el, ExceptionRecord exception,
bits(64) preferred_exception_return, integer vect_offset)
{
assert HaveEL(target_el) && !ELUsingAArch32(target_el) && UInt(target_el) >= UInt(PSTATE.EL);
sync_errors = HaveIESB() && SCTLR[target_el].IESB == '1';
if HaveDoubleFaultExt() then
sync_errors = sync_errors || (SCR_EL3.EA == '1' && SCR_EL3.NMEA == '1' && target_el == EL3);
if sync_errors && InsertIESBBeforeException(target_el) then
SynchronizeErrors();
iesb_req = FALSE;
sync_errors = FALSE;
TakeUnmaskedPhysicalSErrorInterrupts(iesb_req);
SynchronizeContext();
// If coming from AArch32 state, the top parts of the X[] registers might be set to zero
from_32 = UsingAArch32();
if from_32 then AArch64.MaybeZeroRegisterUppers();
MaybeZeroSVEUppers(target_el);
if UInt(target_el) > UInt(PSTATE.EL) then
boolean lower_32;
if target_el == EL3 then
if EL2Enabled() then
lower_32 = ELUsingAArch32(EL2);
else
lower_32 = ELUsingAArch32(EL1);
elsif IsInHost() && PSTATE.EL == EL0 && target_el == EL2 then
lower_32 = ELUsingAArch32(EL0);
else
lower_32 = ELUsingAArch32(target_el - 1);
vect_offset = vect_offset + (if lower_32 then 0x600 else 0x400);
elsif PSTATE.SP == '1' then
vect_offset = vect_offset + 0x200;
spsr = GetPSRFromPSTATE();
if PSTATE.EL == EL1 && target_el == EL1 && EL2Enabled() then
if HaveNV2Ext() && (HCR_EL2.<NV,NV1,NV2> == '100' || HCR_EL2.<NV,NV1,NV2> == '111') then
spsr<3:2> = '10';
else
if HaveNVExt() && HCR_EL2.<NV,NV1> == '10' then
spsr<3:2> = '10';
if HaveBTIExt() && !UsingAArch32() then
// SPSR[].BTYPE is only guaranteed valid for these exception types
if exception.exceptype IN {Exception_SError, Exception_IRQ, Exception_FIQ,
Exception_SoftwareStep, Exception_PCAlignment,
Exception_InstructionAbort, Exception_Breakpoint,
Exception_VectorCatch, Exception_SoftwareBreakpoint,
Exception_IllegalState, Exception_BranchTarget} then
zero_btype = FALSE;
else
zero_btype = ConstrainUnpredictableBool();
if zero_btype then spsr<11:10> = '00';
if HaveNV2Ext() && exception.exceptype == Exception_NV2DataAbort && target_el == EL3 then
// external aborts are configured to be taken to EL3
exception.exceptype = Exception_DataAbort;
if !(exception.exceptype IN {Exception_IRQ, Exception_FIQ}) then
AArch64.ReportException(exception, target_el);
PSTATE.EL = target_el;
PSTATE.nRW = '0';
PSTATE.SP = '1';
SPSR[] = spsr;
ELR[] = preferred_exception_return;
PSTATE.SS = '0';
PSTATE.<D,A,I,F> = '1111';
PSTATE.IL = '0';
if from_32 then // Coming from AArch32
PSTATE.IT = '00000000';
PSTATE.T = '0'; // PSTATE.J is RES0
if (HavePANExt() && (PSTATE.EL == EL1 || (PSTATE.EL == EL2 && ELIsInHost(EL0))) &&
SCTLR[].SPAN == '0') then
PSTATE.PAN = '1';
if HaveUAOExt() then PSTATE.UAO = '0';
if HaveBTIExt() then PSTATE.BTYPE = '00';
if HaveSSBSExt() then PSTATE.SSBS = SCTLR[].DSSBS;
if HaveMTEExt() then PSTATE.TCO = '1';
BranchTo(VBAR[]<63:11>:vect_offset<10:0>, BranchType_EXCEPTION);
}
슈도 코드의 구문을 보면 코드 한 줄씩 되새김질 하면서 소화해야 할 내용입니다.
AArch64.TakeException() 함수 코드는 다음 포스팅에서 분석한 후 포스팅할 예정입니다.
최근 덧글