Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

8179
1390
307630


[ARMv8] EL1: el1_sync - 익셉션 벡터 코드와 ARM 스팩 문서 분석 익셉션(Exception) 소개

이번 포스트에서는 ARMv8 아키텍처 기반에서 구현된 익셉션 벡터의 코드를 리뷰해보겠습니다.
물론 리눅스 커널에서 확인된 코드입니다.

ARMv8 아키텍처의 익셉션 벡터 코드

다음은 ARMv8 아키텍처 기반의 익셉션 벡터 코드입니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/entry.S
01 /*
02  * Exception vectors.
03  */
04     .pushsection ".entry.text", "ax"
05 
06     .align  11
07 ENTRY(vectors)
08     kernel_ventry   1, sync_invalid         // Synchronous EL1t
09     kernel_ventry   1, irq_invalid          // IRQ EL1t
10    kernel_ventry   1, fiq_invalid          // FIQ EL1t
11    kernel_ventry   1, error_invalid        // Error EL1t
12
13    kernel_ventry   1, sync             // Synchronous EL1h
14    kernel_ventry   1, irq              // IRQ EL1h
15    kernel_ventry   1, fiq_invalid          // FIQ EL1h
16    kernel_ventry   1, error            // Error EL1h
17
18    kernel_ventry   0, sync             // Synchronous 64-bit EL0
19    kernel_ventry   0, irq              // IRQ 64-bit EL0
20    kernel_ventry   0, fiq_invalid          // FIQ 64-bit EL0
21    kernel_ventry   0, error            // Error 64-bit EL0

ARM 프로세서가 EL1에서 실행될 때 익셉션이 발생하면 처리되는 코드는 13~16번째 줄이며,
13~16번째 줄 코드는 각각 다음과 같은 레이블로 변환돼 컴파일됩니다. 아래 코드에서 볼드체 주석으로 표기된 부분이 실제 레이블입니다.

13    kernel_ventry   1, sync             // Synchronous EL1h // el1_sync
14    kernel_ventry   1, irq              // IRQ EL1h  // el1_irq
15    kernel_ventry   1, fiq_invalid          // FIQ EL1h // el1_fiq_invalid
16    kernel_ventry   1, error            // Error EL1h // el1_error

이 중에서 13번째 줄에 보이는 el1_sync 레이블의 구현부를 분석해봅시다.
사실, ARMv8에서 발생하는 주요 익셉션의 대부분은 el1_irq과 el1_irq 레이블에 구현돼 있으니 이 코드를 잘 분석할 필요가 있습니다.

EL1에서 Synchronous 익셉션 벡터 코드

익셉션 벡터 테이블의 시작 주소 기준으로(VBAR_ELn + 0x200) 바이트에 Synchronous 익셉션의 코드가 위치합니다.

이번 포스트에서 분석할 익셉션 벡터 테이블의 시작 주소는 ffffff8008082000이므로,
EL1에서 Synchronous 익셉션이 발생하면 다음 코드 기준으로 ffffff8008082200 주소로 프로그램 카운터가 브랜치합니다.

ffffff8008082200:   d10503ff    sub sp, sp, #0x140
ffffff8008082204:   8b2063ff    add sp, sp, x0
ffffff8008082208:   cb2063e0    sub x0, sp, x0
ffffff800808220c:   37700080    tbnz    w0, #14, ffffff800808221c <vectors+0x21c>
ffffff8008082210:   cb2063e0    sub x0, sp, x0
ffffff8008082214:   cb2063ff    sub sp, sp, x0
ffffff8008082218:   140003aa    b   ffffff80080830c0 <el1_sync>
ffffff800808221c:   d51bd040    msr tpidr_el0, x0
ffffff8008082220:   cb2063e0    sub x0, sp, x0
ffffff8008082224:   d51bd060    msr tpidrro_el0, x0
ffffff8008082228:   d0006060    adrp    x0, ffffff8008c90000 <overflow_stack+0xd50>
ffffff800808222c:   910ac01f    add sp, x0, #0x2b0
ffffff8008082230:   d538d080    mrs x0, tpidr_el1
ffffff8008082234:   8b2063ff    add sp, sp, x0
ffffff8008082238:   d53bd040    mrs x0, tpidr_el0
ffffff800808223c:   cb2063e0    sub x0, sp, x0
ffffff8008082240:   f274cc1f    tst x0, #0xfffffffffffff000
ffffff8008082244:   54002ca1    b.ne    ffffff80080827d8 <__bad_stack>
ffffff8008082248:   cb2063ff    sub sp, sp, x0
ffffff800808224c:   d53bd060    mrs x0, tpidrro_el0
ffffff8008082250:   1400039c    b   ffffff80080830c0 <el1_sync>

위 코드의 핵심은 "예외 처리 루틴을 체크한 다음에 el1_sync 레이블로 브랜치하는 동작"입니다.

다음은 el1_sync 레이블 코드의 구현부입니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/entry.S
01 el1_sync:
02     kernel_entry 1
03     mrs x1, esr_el1         // read the syndrome register
04     lsr x24, x1, #ESR_ELx_EC_SHIFT  // exception class
05     cmp x24, #ESR_ELx_EC_DABT_CUR   // data abort in EL1
06     b.eq    el1_da
07     cmp x24, #ESR_ELx_EC_IABT_CUR   // instruction abort in EL1
08     b.eq    el1_ia
09     cmp x24, #ESR_ELx_EC_SYS64      // configurable trap
10    b.eq    el1_undef
11    cmp x24, #ESR_ELx_EC_PC_ALIGN   // pc alignment exception
12    b.eq    el1_pc
13    cmp x24, #ESR_ELx_EC_UNKNOWN    // unknown exception in EL1
14    b.eq    el1_undef
15    cmp x24, #ESR_ELx_EC_BREAKPT_CUR    // debug exception in EL1
16    b.ge    el1_dbg
17    b   el1_inv

---
02 번째 줄에 보이는 kernel_entry라는 매크로가 보이는데 이 코드는 나중에 분석하겠습니다.
---

먼저 03번째 줄을 분석해봅시다.

03     mrs x1, esr_el1         // read the syndrome register

이 코드는 신드롬 레지스터를 읽어서 x1 레지스터에 저장하는 기능인데, 컴파일이 되면 다음과 같은 명령어로 변환됩니다.

mrs     x1,#0x3,#0x0,c5,c2,#0x0   ; x1, ESR_EL1 // mrs x1, esr_el1

---
여기서 신드롬 레지스터는 익셉션이 발생한 세부 원인(Reason)을 담고 있는데, ARM 스팩 문서에는 다음과 같이 명시돼 있습니다.

출처: DEN0024A_v8_architecture_PG.pdf
10.2.6 The Exception Syndrome Register
The Exception Syndrome Register, ESR_ELn, contains information which allows the exception handler to determine the reason for the exception. It is updated only for synchronous exceptions and SError.

신드롬 레지스터에 대해서는 다른 포스트에서 집중 분석할 예정입니다.
---

이어서 04번째 줄을 봅시다.

04     lsr x24, x1, #ESR_ELx_EC_SHIFT  // exception class

신드롬 레지스터의 값을 저장한 x1 레지스터를 왼쪽으로 ESR_ELx_EC_SHIFT 만큼 비트 쉬프트해서 x24 레지스터에 저장합니다.
ESR_ELx_EC_SHIFT은 10진수로 26인데, x1 레지스터를 26만큼 왼쪽으로 비트 쉬프트를 해서 x24 레지스터에 저장하는 동작입니다.

이해를 돕기 위해 04번째 줄이 컴파일된 명령어를 다음과 같이 소개합니다.

lsr     x24,x1,#0x1A     ; x24,x1,#26

위 코드로 보아 #ESR_ELx_EC_SHIFT는 26임을 알 수 있는데, 주석과 같이 x24 레지스터는 익셉션 클래스를 저장하게 됩니다.

코드의 오른쪽을 보면 exception class란 주석이 보이는데, 이는 익셉션의 세부 원인(Exception Cause)을 뜻합니다.

---
exception class에 대한 내용은 역시 ARM 스팩 문서에서 확인할 수 있습니다.

출처: DEN0024A_v8_architecture_PG.pdf
10.2.6 The Exception Syndrome Register
...
• Bits [31:26] of ESR_ELn indicate the exception class which allows the handler to distinguish between the various possible exception causes (such as unallocated
instruction, exceptions from MCR/MRC to CP15, exception from FP operation, SVC, HVC or SMC executed, Data Aborts, and alignment exceptions).

익셉션이 발생한 세부 원인(distinguish between the various possible exception causes)에 대한 정보를 Bits [31:26]에 담고 있다는 내용입니다.
---

역시 ARMv8 아키텍처 스팩 문서에 명시된 대로 구현돼 있다는 사실을 알 수 있습니다.

Exception from a Data abort

이어서 05~06번째 줄을 보겠습니다.

05     cmp x24, #ESR_ELx_EC_DABT_CUR   // data abort in EL1
06     b.eq    el1_da

익셉션 클래스의 정보를 담고 있는 x24 레지스터와 #ESR_ELx_EC_DABT_CUR(37)가 같은 지 비교 연산을 수행해, 그 결과 true이면
el1_da 레이블을 브랜치하는 동작입니다.

위 코드에 해당하는 어셈블리 명령어는 다음과 같습니다.

cmp     x24,#0x25        ; x24,#37 // 100101
b.eq    0xFFFFFF8008082970   ; el1_da

---

#ESR_ELx_EC_DABT_CUR(37)에 대한 내용은 역시 ARM 스팩 문서에서 확인할 수 있습니다.

---
출처: DDI0487A_g_armv8_arm.pdf
Table D1-8 ESR_ELx.EC field encoding

100101: Data Abort taken without a change in Exception levelh
...
EC == 100101, Exception from a Data abort

This value is valid for all described registers.

Data Abort that caused entry from a current Exception level, where the current
Exception level must be using AArch64.

Used for MMU faults generated by data accesses, alignment faults other than those
caused by the Stack Pointer misalignment, and synchronous external aborts, including
synchronous parity or ECC errors. Not used for debug related exceptions. 
---

Instruction Abort 

이어서 07~08번째 줄을 보겠습니다.

07     cmp x24, #ESR_ELx_EC_IABT_CUR   // instruction abort in EL1
08     b.eq    el1_ia

익셉션 클래스의 정보를 담고 있는 x24 레지스터와 #ESR_ELx_EC_IABT_CUR(33)가 같은 지 비교 연산을 수행해, 그 결과 true이면
el1_ia 레이블을 브랜치하는 동작입니다.

위 코드에 해당하는 어셈블리 명령어는 다음과 같습니다.

cmp     x24,#0x21        ; x24,#33
b.eq    0xFFFFFF8008082970   ; el1_da

el1_ia은 el1_da 레이블과 같은 심벌로 변환되므로 어셈블리 명령어 기준으로 el1_da 레이블을  브랜치합니다.

---
el1_ia 레이블의 구현부는 다음과 같습니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/entry.S
el1_ia:
    /*
     * Fall through to the Data abort case
     */
el1_da:
    /*
     * Data abort handling
     */
    mrs x3, far_el1
    inherit_daif    pstate=x23, tmp=x2
---

ESR_ELx_EC_IABT_CUR(33)에 대한 내용은 역시 ARM 스팩 문서에서 확인할 수 있습니다.

---
출처: DDI0487A_g_armv8_arm.pdf
Table D1-8 ESR_ELx.EC field encoding

100001: Instruction Abort taken without a change in Exception levelg
...
EC == 100001, Exception from an Instruction abort

This value is valid for all described registers.

Instruction Abort from the current Exception level, where the current Exception level
must be using AArch64.

Used for MMU faults generated by instruction accesses and synchronous external
aborts, including synchronous parity or ECC errors. Not used for debug related
exceptions.
---

MSR, MRS, or System instruction execution

이어서 09~10번째 줄을 보겠습니다.

09     cmp x24, #ESR_ELx_EC_SYS64      // configurable trap
10    b.eq    el1_undef

익셉션 클래스의 정보를 담고 있는 x24 레지스터와 #ESR_ELx_EC_SYS64(24)가 같은 지 비교 연산을 수행해, 그 결과 true이면
el1_undef 레이블로 브랜치하는 동작입니다.

위 코드에 해당하는 어셈블리 명령어는 다음과 같습니다.

cmp     x24,#0x18        ; x24,#24  // 011000
b.eq    0xFFFFFF8008082A08   ; el1_undef

#ESR_ELx_EC_SYS64(24)에 대한 내용은 다음 ARM 스팩 문서에서 확인할 수 있습니다.

---
출처: DDI0487A_g_armv8_arm.pdf
Table D1-8 ESR_ELx.EC field encoding

011000: MSR, MRS, or System instruction execution, that is not reported using EC 0x00, 0x01, or 0x07
...
EC == 001100, Exception from an MCRR or MRRC access

This value is valid for all described registers. Exceptions from MCRR/MRRC to CP14 from AArch32 as a result of configurable
traps, enables, or disables.
---
Stack Pointer Alignment exception

이어서 11~12번째 줄을 보겠습니다.

11    cmp x24, #ESR_ELx_EC_PC_ALIGN   // pc alignment exception
12    b.eq    el1_pc

익셉션 클래스의 정보를 담고 있는 x24 레지스터와 #ESR_ELx_EC_PC_ALIGN(38)가 같은 지 비교 연산을 수행해, 그 결과 true이면
el1_pc 레이블로 브랜치하는 동작입니다. 위 코드는 컴파일되면 다음과 같이 변환됩니다.

cmp     x24,#0x26        ; x24,#38  // 100110
b.eq    0xFFFFFF80080829F0   ; el1_sp_pc

#ESR_ELx_EC_PC_ALIGN(38)에 대한 내용은 다음 ARM 스팩 문서에서 확인할 수 있습니다.

---
출처: DDI0487A_g_armv8_arm.pdf
Table D1-8 ESR_ELx.EC field encoding

100110: Stack Pointer Alignment exception
...
EC == 100110, Exception from an Illegal execution state, or a PC or SP alignment fault

This value is valid for all described registers.
SP alignment fault.
---

Exceptions with an unknown reason

이어서 13~14번째 줄을 보겠습니다.

13    cmp x24, #ESR_ELx_EC_UNKNOWN    // unknown exception in EL1
14    b.eq    el1_undef

익셉션 클래스의 정보를 담고 있는 x24 레지스터와 #ESR_ELx_EC_UNKNOWN(0)가 같은 지 비교 연산을 수행해, 그 결과 true이면
el1_undef 레이블로 브랜치하는 동작입니다. 위 코드는 컴파일되면 다음과 같이 변환됩니다.

cmp     x24,#0x0         ; x24,#0
b.eq    0xFFFFFF8008082A08   ; el1_undef

#ESR_ELx_EC_UNKNOWN(0)에 대한 내용은 다음 ARM 스팩 문서에서 확인할 수 있습니다.

13    cmp x24, #ESR_ELx_EC_UNKNOWN    // unknown exception in EL1
14    b.eq    el1_undef

---
출처: DDI0487A_g_armv8_arm.pdf
Table D1-8 ESR_ELx.EC field encoding

000000: Unknown reason
...
EC == 000000, Exceptions with an unknown reason

This value is valid for all described registers.

Unknown or Uncategorized Reason - generally used for exceptions as a result of
erroneous execution.
---



Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


덧글

댓글 입력 영역