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년 대한민국 학술원 선정 우수도서

---
최근 덧글