Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

11105
637
415734


[ARM][ARMv8] 리눅스 커널: 익셉션 레벨을 읽는 어셈블리 코드 분석 Armv8: Exception Level(EL)

ARM 프로세서의 주요 용법을 배우기 위해 리눅스 커널만큼 좋은 리퍼런스 자료가 없는 것 같다.
ARM 사의 개발자들이 리눅스 커널에 자신의 코드를 메인라인 시키기 때문이다.

이번에는 리눅스 커널에서 64비트 ARMv8 아키텍처에서 ARM의 EL를 읽는 패치를 소개한다.

diff --git a/arch/arm64/kernel/irq.c b/arch/arm64/kernel/irq.c
index 92fa817..0376bac 100644
--- a/arch/arm64/kernel/irq.c
+++ b/arch/arm64/kernel/irq.c
@@ -33,15 +33,38 @@

 unsigned long irq_err_count;

+static inline unsigned long arch_arm64_EL(void)
+{
+       unsigned long EL_value;
+
+       asm volatile(
+                       "mrs  %0, CurrentEL"
+                       : "=r" (EL_value) : : "memory", "cc");
+
+       return EL_value;
+}
+
+
 /* Only access this in an NMI enter/exit */
 DEFINE_PER_CPU(struct nmi_ctx, nmi_contexts);

 DEFINE_PER_CPU(unsigned long *, irq_stack_ptr);

+static unsigned int debug = 1;
+
 int arch_show_interrupts(struct seq_file *p, int prec)
 {
        show_ipi_list(p, prec);
        seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
+
+       if (debug)
+       {
+               unsigned long el_state;
+
+               el_state = arch_arm64_EL();
+               printk(" arm mode = %lx \n", el_state);
+       }
+
        return 0;
 }

위 코드를 Aarch64 빌드 옵션에 맞게 빌드를 한 다음 어셈블리 명령어로 확인해보자.

ffffff80080868e0 <arch_show_interrupts>:
ffffff80080868e0:   a9be7bfd    stp x29, x30, [sp,#-32]!
ffffff80080868e4:   910003fd    mov x29, sp
ffffff80080868e8:   a90153f3    stp x19, x20, [sp,#16]
...
ffffff8008086924:   aa1303e0    mov x0, x19
ffffff8008086928:   9408e852    bl  ffffff80082c0a70 <seq_printf>
ffffff800808692c:   d5384241    mrs x1, currentel
ffffff8008086930:   d0005240    adrp    x0, ffffff8008ad0000 <kallsyms_token_index+0x600>
ffffff8008086934:   913aa000    add x0, x0, #0xea8
ffffff8008086938:   9402b769    bl  ffffff80081346dc <printk>

다음 코드와 같이 'mrs x1, currentel' ARM 어셈블리 명령어로 실행된다.

ffffff800808692c:   d5384241    mrs x1, currentel

현재 익셉션 레벨을 읽어 온 다음에 레지스터 x1에 저장한 후 printk 심볼을 브랜치하는 동작이다.


92static inline bool is_kernel_in_el1(void)
93{
94 return read_sysreg(CurrentEL) == CurrentEL_EL1;
95}


이번에는 현재 실행 중인 EL이 EL1인지 체크하는 패치를 작성하겠다.

diff --git a/arch/arm64/kernel/irq.c b/arch/arm64/kernel/irq.c
index 92fa817..a4d7f3c 100644
--- a/arch/arm64/kernel/irq.c
+++ b/arch/arm64/kernel/irq.c
@@ -33,6 +33,23 @@

 unsigned long irq_err_count;

+static inline unsigned long arch_arm64_EL(void)
+{
+       unsigned long EL_value;
+
+       asm volatile(
+                       "mrs  %0, CurrentEL"
+                       : "=r" (EL_value) : : "memory", "cc");
+
+       return EL_value;
+}
+
+
+static inline bool is_kernel_in_el1(void)
+{
+       return read_sysreg(CurrentEL) == CurrentEL_EL1;
+}
+
 /* Only access this in an NMI enter/exit */
 DEFINE_PER_CPU(struct nmi_ctx, nmi_contexts);

@@ -42,6 +59,15 @@ int arch_show_interrupts(struct seq_file *p, int prec)
 {
        show_ipi_list(p, prec);
        seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
+
+       if (is_kernel_in_el1())
+       {
+               unsigned long el_state;
+
+               el_state = arch_arm64_EL();
+               printk(" arm mode = %lx \n", el_state);
+       }
+
        return 0;
 }

ffffff80080868e0 <arch_show_interrupts>:
ffffff80080868e0:   a9be7bfd    stp x29, x30, [sp,#-32]!
ffffff80080868e4:   910003fd    mov x29, sp
...
ffffff8008086928:   9408e852    bl  ffffff80082c0a70 <seq_printf>
ffffff800808692c:   d5384240    mrs x0, currentel
ffffff8008086930:   f100101f    cmp x0, #0x4
ffffff8008086934:   540000a0    b.eq    ffffff8008086948 <arch_show_interrupts+0x68>
ffffff8008086938:   52800000    mov w0, #0x0                    // #0
ffffff800808693c:   a94153f3    ldp x19, x20, [sp,#16]
ffffff8008086940:   a8c27bfd    ldp x29, x30, [sp],#32
ffffff8008086944:   d65f03c0    ret
ffffff8008086948:   d5384241    mrs x1, currentel
ffffff800808694c:   d0005240    adrp    x0, ffffff8008ad0000 <kallsyms_token_index+0x600>
ffffff8008086950:   913aa000    add x0, x0, #0xea8
ffffff8008086954:   9402b762    bl  ffffff80081346dc <printk>

is_kernel_in_el1() 함수의 코드는 다음과 같이 치환돼 어셈블리 코드로 구성된다.

ffffff800808692c:   d5384240    mrs x0, currentel
ffffff8008086930:   f100101f    cmp x0, #0x4

위 코드를 보면 currentel이 0x4이면 EL1임을 알 수 있다.

CurrentEL_EL1과 CurrentEL_EL2 매크로의 선언부는 다음 코드에서 확인할 수 있다.

https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm64/include/asm/ptrace.h
/* Current Exception Level values, as contained in CurrentEL */
#define CurrentEL_EL1 (1 << 2)
#define CurrentEL_EL2 (2 << 2)

#Reference Armv8: 익셉션 레벨(ELx)


Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자
* 2021년 대한민국 학술원 선정 우수도서



---



덧글

댓글 입력 영역