XEN 하이퍼바이저의 익셉션 벡터 핸들러를 해석하는 방법을 소개했으니 이어서 익셉션 벡터 핸들러의 코드를 분석하겠습니다.
먼저 2~14번째 줄을 보겠습니다.
2 26a800: 17fffc00 b 269800 <hyp_sync_invalid>
3 26a804: d503201f nop
4 26a808: d503201f nop
...
5 26a880: 17fffbfb b 26986c <hyp_irq_invalid>
6 26a884: d503201f nop
7 26a888: d503201f nop
...
8 26a900: 17fffbf6 b 2698d8 <hyp_fiq_invalid>
9 26a904: d503201f nop
10 26a908: d503201f nop
...
11 26a980: 17fffbf1 b 269944 <hyp_error_invalid>
12 26a984: d503201f nop
13 26a988: d503201f nop
2~14번째 줄은 익셉션 레벨과 상관없이 같은 스택을 사용하도록 설정한 경우에 동작하는데, XEN 하이퍼바이저는 이 유형의 익셉션을 지원하지 않습니다. 이점을 염두에 두고 코드를 분석하겠습니다.
익셉션이 발생하면 익셉션의 종류 별로 2번째 줄, 5번째 줄, 8번째 줄 그리고 11번째 줄에 있는 주소로 프로그램 카운터가 브랜치됩니다. 그런데 해당 익셉션 벡터 핸들러의 코드는 hyp_sync_invalid 혹은 hyp_irq_invalid 레이블과 같이 *_invalid라는 접미사가 있는 레이블로 브랜치하는 명령어로 구성돼 있습니다. hyp_sync_invalid와 hyp_sync_invalid 레이블의 서브 루틴에서는 디버깅 정보를 출력하고 크래시를 유발하는 동작을 수행합니다.
이어서 14~25번째 줄을 분석하겠습니다.
14 26aa00: 17fffc07 b 269a1c <hyp_sync>
15 26aa04: d503201f nop
16 26aa08: d503201f nop
...
17 26aa80: 17fffc05 b 269a94 <hyp_irq>
18 26aa84: d503201f nop
19 26aa88: d503201f nop
...
20 26ab00: 17fffb76 b 2698d8 <hyp_fiq_invalid>
21 26ab04: d503201f nop
22 26ab08: d503201f nop
...
23 26ab80: 17fffb8c b 2699b0 <hyp_error>
24 26ab84: d503201f nop
25 26ab88: d503201f nop
14~25번째 줄은 하이퍼바이저의 코드가 실행 중인 EL2에서 익셉션이 유발됐을때 처리되는
익셉션 벡터 핸들러입니다.
익셉션의 종류별로 세부 코드를 분석하겠습니다. 먼저 14~16번째 줄을 보겠습니다.
14 26aa00: 17fffc07 b 269a1c <hyp_sync>
15 26aa04: d503201f nop
16 26aa08: d503201f nop
여기서 EL2의 Synchronous 익셉션은 어떻게 유발될까요? 이 질문에는 다음과 같이 답할 수 있습니다.
"EL2에서 실행되는 하이퍼바이저에서 메모리 어보트를 유발하는 명령어가 실행된 경우에
Synchronous 익셉션이 유발됩니다."
Arm 아키텍처 관점으로 분석하면 EL2에서 Synchronous 익셉션이 유발될 때 14번째 줄에 보이는 0x26aa00 주소로 프로그램 카운터가 브랜치됩니다. 14번째 줄에 "b hyp_sync"이라는 명령어가 있는데, 이 명령어가 실행되면 hyp_sync 레이블로 브랜치합니다.
hyp_sync 레이블의 서브 루틴에서는 디버깅 정보를 출력하고 크래시를 유발하는 동작이 실행됩니다.
이어서 17~19번째 줄을 보겠습니다.
17 26aa80: 17fffc05 b 269a94 <hyp_irq>
18 26aa84: d503201f nop
19 26aa88: d503201f nop
EL2에서 실행되는 하이퍼바이저에서 'IRQ 인터럽트'가 발생하면 17번째 줄에 보이는 0x26aa80 주소로 프로그램 카운터가 브랜치됩니다. 17번째 줄의 코드는 hyp_irq 레이블로 브랜치하는 명령어입니다.
이어서 20~22번째 줄을 보겠습니다.
20 26ab00: 17fffb76 b 2698d8 <hyp_fiq_invalid>
21 26ab04: d503201f nop
22 26ab08: d503201f nop
하이퍼바이저가 구동되는 EL2에서 'FIQ 인터럽트'가 유발되면 20번째 줄에 보이는 0x26ab00 주소로 프로그램 카운터가 브랜치됩니다. 그런데 XEN 하이퍼바이저는 FIQ를 지원하지 않으므로 hyp_fiq_invalid라는 레이블로 브랜치되며, 이후 크래시를 유발하는 코드를 실행합니다.
[정보]
대부분 운영체제에서는 FIQ는 시큐어 월드(Secure World)의 Trusted OS에서 받아서 처리하도록 시스템을 설정합니다.
이어서 SError 익셉션이 유발되면 실행되는 23~25번째 줄 코드를 분석하겠습니다.
23 26ab80: 17fffb8c b 2699b0 <hyp_error>
24 26ab84: d503201f nop
25 26ab88: d503201f nop
EL2에서 SError 익셉션이 유발되면 23번째 줄에 보이는 주소로 프로그램 카운터가 바뀝니다. 'b 2699b0 <hyp_error>' 명령어가 실행되면 hyp_error 레이블로 브랜치되는데, hyp_error 레이블은 크래시를 유발하는 코드를 실행합니다.
이번에는 XEN 하이퍼바이저의 익셉션 벡터 핸들러 코드 중에서 가장 중요한 동작을 수행하는 26~28번째 줄을 보겠습니다.
26 26ac00: 17fffbc5 b 269b14 <guest_sync>
27 26ac04: d503201f nop
28 26ac08: d503201f nop
게스트 운영체제가 실행되는 EL1에서 hvc, wfi, wfe 명령어를 실행했을 때 26번째 줄에 있는 0x26ac00 주소로 프로그램 카운터가 브랜치됩니다. 이와 같이 EL1에서 실행되는 게스트 OS가 'hvc, wfi, wfe' 명령어를 실행해 EL2로 진입하는 동작을 "게스트 Exit"이라고 부릅니다.
소프트웨어적으로 EL1에서 EL2으로 진입하는 시작점이 26번째 줄이라고 볼 수 있습니다.
[정보]
하이퍼바이저를 분석할 때 가장 많이 리뷰하는 루틴이 게스트 Exit으로 실행되는 익셉션 벡터 핸들러입니다.
이번에는 게스트 OS에서 ‘IRQ Interrupt’ 익셉션이 유발될 때 실행되는 29~31번째 줄을 분석하겠습니다.
29 26ac80: 17fffbfe b 269c78 <guest_irq>
30 26ac84: d503201f nop
31 26ac88: d503201f nop
29번째 줄과 같이 guest_irq이란 레이블로 브랜치합니다.
EL1에서 실행되는 게스트 OS에서 설정된 인터럽트가 발생하면 일반적으로 EL1에서 받아 처리합니다. 만약 HCR_EL2 레지스터의 VI와 IMO 비트가 1로 설정된 경우에만, EL2가 EL1에서 설정된 인터럽트를 받게 됩니다.
이어서 EL1에서 FIQ 인터럽트 익셉션이 유발되면 실행하는 32~34번째 줄을 보겠습니다.
32 26ad00: 17fffc13 b 269d4c <guest_fiq_invalid>
33 26ad04: d503201f nop
34 26ad08: d503201f nop
32번째 줄과 같이 guest_fiq_invalid 레이블로 브랜치됩니다. XEN 하이퍼바이저는 FIQ를 지원하지 않으므로 guest_fiq_invalid 레이블에서는 크래시를 유발하는 루틴이 실행됩니다.
이어서 게스트 Exit이 실행되면 호출되는 guest_sync 레이블의 코드를 분석하겠습니다.
https://github.com/xen-project/xen/blob/stable-4.15/xen/arch/arm/arm64/entry.S
01 guest_sync:
02 /*
03 * Save x0, x1 in advance
04 */
05 stp x0, x1, [sp, #-(UREGS_kernel_sizeof - UREGS_X0)]
06
07 /*
08 * x1 is used because x0 may contain the function identifier.
09 * This avoids to restore x0 from the stack.
10 */
11 mrs x1, esr_el2
12 lsr x1, x1, #HSR_EC_SHIFT /* x1 = ESR_EL2.EC */
13 cmp x1, #HSR_EC_HVC64
14 b.ne guest_sync_slowpath /* Not a HVC skip fastpath. */
15
16 mrs x1, esr_el2
17 and x1, x1, #0xffff /* Check the immediate [0:16] */
18 cbnz x1, guest_sync_slowpath /* should be 0 for HVC #0 */
먼저 11~12번째 줄을 보겠습니다.
11 mrs x1, esr_el2
12 lsr x1, x1, #HSR_EC_SHIFT /* x1 = ESR_EL2.EC */
11번째 줄은 익셉션 클래스의 정보를 담고 있는 익셉션 신드롬 레지스터인 esr_el2의 값을 x1 레지스터에 로딩하는 동작입니다. 11번째 줄에서 esr_el2 레지스터의 값을 x1 레지스터에 로딩하는 이유는 무엇일까요? esr_el2 레지스터의 [31:26] 비트에 익셉션이 유발된 세부 원인을 나타내는 익셉션 클래스 비트 정보가 저장돼 있기 때문입니다.
12번째 줄은 x1 레지스터의 값을 #HSR_EC_SHIFT 크기 만큼 오른쪽으로 비트 시프트 연산을 해 x1 레지스터에 저장합니다. 여기서 HSR_EC_SHIFT는 다음 매크로 선언부와 같이 26입니다.
https://github.com/xen-project/xen/blob/stable-4.15/xen/include/asm-arm/processor.h
#define HSR_EC_SHIFT 26
11번째 줄에서 esr_el2 레지스터의 값을 x1 레지스터에 로딩한 다음에 오른쪽으로 26만큼 비트 시프트(12번째 줄)를 하면, 익셉션 클래스를 나타내는 정수값을 x1 레지스터가 저장하게 됩니다.
11~12번째 줄의 동작은 12번째 줄에 보이는 주석처럼 다음과 같이 표기할 수 있습니다.
'x1 = ESR_EL2.EC'
ESR_EL2은 EL2에서 엑세스하는 익셉션 신드롬 레지스터이고, EC는 익셉션 클래스(Exception Class)을 나타냅니다.
이어서 13~14번째 줄을 보겠습니다.
13 cmp x1, #HSR_EC_HVC64
14 b.ne guest_sync_slowpath /* Not a HVC skip fastpath. */
13번째 줄은 x1 레지스터가 담고 있는 익셉션 클래스가 #HSR_EC_HVC64인지 비교하는 명령어입니다. 13번째 명령어의 실행 결과는 14번째 줄에 영향을 주는데, x1 레지스터의 값이 #HSR_EC_HVC64가 아니면 guest_sync_slowpath() 함수로 분기합니다.
[정보]
여기서 HSR_EC_HVC64 매크로의 정체는 무엇일까요? 다음 코드와 같이 0x16입니다.
https://github.com/xen-project/xen/blob/stable-4.15/xen/include/asm-arm/processor.h
#define HSR_EC_HVC64 0x16
그렇다면 HSR_EC_HVC64를 0x16으로 설정한 근거는 무엇일까요? 의문을 풀기 위해서는 Armv8 아키텍처의 스팩 문서를 볼 필요가 있습니다. 다음은 익셉션 클래스와 관련된 Arm 스팩 문서의 내용입니다.
<출처: DDI0487F_b_armv8_arm.pdf>
D1.10.4 Exception classes and the ESR_ELx syndrome registers
010110 HVC instruction execution in AArch64 state, when HVC is not disabled
010110는 익셉션 클래스를 나타내는 비트 맵 정보로 2진수 포멧인데 16진수로는 0x16입니다. 이처럼 Arm 스팩 문서에서 명시된 정보를 기반으로 어셈블리 명령어를 구현하는 것입니다.
최근 덧글