Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

11105
637
415734


[Arm프로세서] XEN 하이퍼바이저: EL2 익셉션 벡터 핸들러 코드 분석 Arm: Virtualization

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 스팩 문서에서 명시된 정보를 기반으로 어셈블리 명령어를 구현하는 것입니다.

덧글

  • JohnLee 2023/01/13 15:27 # 삭제 답글

    운인지 운명인지 모르겠지만, 요 며칠 제가 배우고, 참여할 프로젝트가 서로서로 엮이는 걸 보니 참 놀라는 나날입니다.
    지거국대학 전자공학부에서 잘 선택하지 않는 임베디드를 전공으로 잡고, 군대에서 웹코딩 찍어먹다 WSL을 처음 접하게 되어 남들보다 조금 일찍 VM의 존재를 알게되었고,
    리눅스 커널을 배워둬야지 하다 이 블로그에 들어오게 되었고, 석사 학위 없으면 나는 취업이 안 되겠다하여 인턴으로 들어온 연구실이 마침 또 차량 임베디드이고,
    이제 졸업 프로젝트를 해야해서 친구없이 들어온 프로젝트는 클라우드 스케줄링...

    연구실, 졸프를 모아보니 딱 여기 블로그에서 소개하고 있는 차량에서의 가상화라니... 뭔가 참 기묘하고 놀랍고 멋진 일이지만 한편으론 또 갈 길이 더 길어진 것 같아 걱정이 듭니다...
    인턴 1년, 석사 2년 안에 얼마나 성장할 수 있을까요...
  • JohnLee 2023/01/13 15:41 # 삭제 답글

    할게 많아져서 데드락에 걸린 것 마냥 뭐부터 해야할지 모르겠습니다. 저자님의 책을 일단 보고 있지만 코드를 볼 때만 잠깐 이해되는 것 같으니 뜬 구름을 잡고 있는 것 같기도 하고요
    이럴 때 차근차근히 공부하면 된다고는 하지만 RPG게임 마냥 퀘스트로 길을 안내해주면 얼마나 좋을까요 ㅠㅠ '그거 깨면 이거해, 이거 다 하면 저거 하면 돼' 처럼 말입니다.
    이만... 지나가는 학부생의 한탄이었습니다... ㅠㅠ
  • AustinKim 2023/01/14 12:18 #

    제 책을 읽은 독자 분들이 하시는 말씀이요.. 한 번 읽으면 잘 이해가 가지 않는데, 두 번째 부터는 읽은 내용이 잘 이해가 가고 머릿 속에 또렷히 남는다고 하네요. 2~3번 정도 읽으시고 실습을 하시면 훨씬 좋아지실 것입니다.

    화이팅하세요.
댓글 입력 영역