Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

175162
807
85243


ARM64 - 각 익셉션(Exception) 레벨 소개 [Linux][ARM] Core Analysis

64비트의 ARM 아키텍쳐(ARMv8)의 익셉션 레벨에 대해 간단히 살펴보고자 합니다.

각 Exception Level(EL) 특징을 간단하게 적을께요.
1> EL0 -> EL1 -> EL2 -> EL3로 갈수록 execution privilege가 증가해요. 볼 수 있는 코드나 파일에 대한 Permission이 더 있다는 거죠. 
2> EL0는 유일한 unprivileged 특성을 가져요.
3> EL2는 Non-secure 모드에서 가상화를 구현하기 위해서 사용되곤 하는데 자주 쓰지는 않아요.
4> EL3는 secure 와 Non-secure 모드 전환을 위해서 사용되죠.
5> ARMv8에서 EL0, EL1은 필수 구현 사항이며 나머지는 Option이에요.
즉 ARMv8을 탑재한 SoC 업체나 벤더에서 특정 요구 사항에 따라 구현을 안할 수도 있다는 거죠.
만약에 TrustZone을 안 쓰는 자동차 텔레메틱스 용 임베디드 장비를 개발할 때는 구지 EL3까지 구현할 필요는 없어요.

Exception Level 이동에 대해서 잠깐 적으면요.
1> EL1: Supervisor Call(SVC)로 EL0 -> EL1로 이동하며 System Call 개념과 동일해요.
      ARM32 아키텍쳐의 Supervisor Mode와 비슷하다고 보면 되는데요, 보통 리눅스 커널이 구동될 때의 모드죠.   

2> EL2:  Hypervisor Call(HVC)로  EL1 -> EL2이 이동해요.
3> EL3: Secure Monitor Call(SMC)로 레벨 이동을 할 수 있는데요.  EL1 -> EL3, EL2 -> EL3 방향으로 이동 가능하죠.

여기서 중요한 점은 낮은 EL에서 높은 EL로 Shift를 하고 싶을 때는 반드시 Exception을 Trigger해줘야 해요.
그럼 ARM 아키텍쳐(ARMv8)는 Exception을 발생시키면 스택을 푸쉬하는 등 백업을 위한 추가 동작을 해줘야 하고
Exception 발생 시 preempt_count를 보고 스케쥴을 할 수 있기 때문에 시스템 설계 시 고려해야 할 사항이 늘어나요.

반대로 높은 EL는 execution privilege이 낮은 EL보다 높기 때문에 구지 Exception을 Trigger할 필요가 없어요.
ARM32 아키텍쳐에서 말 많이 들어봤잖아요. 커널 모드에서는 모든 파일 접근이 가능하다. (유저 모드 보다)

그래서 부트 Sequence에서 커널 실행 직전 EL을 3으로 설정해놓는 동작이 사실 상 표준으로 잡혀 있어요.
SoC업체에서 TrustZone과 커널을 넘아들면서 부팅 초기화를 해주고 싶을 경우가 많다고 하네요.

각 Exception Level 사용 예를 들면 아래와 같아요.
EL0 : Applications (ex. 카카오톡, T-map)
EL1 : OS Kernel (ex. Android, Linux, QNX)
EL2 : Hypervisor (ex. XEN, KVM)
EL3 : Secure monitor (ex. 솔라시아, Trustonic사의 secure Firmware)


그럼 각 Exception Level Vector는 어디에 구현되어 있냐구요?
아래 코드를 눈 크게 뜨고 보세요. 각 모드별로 Exception Vector가 정의되어 있죠.
#!/bin/sh
[arch/arm64/kernel/entry.S]
ENTRY(vectors)
ventry el1_sync_invalid // Synchronous EL1t
ventry el1_irq_invalid // IRQ EL1t
ventry el1_fiq_invalid // FIQ EL1t
ventry el1_error_invalid // Error EL1t

ventry el1_sync // Synchronous EL1h
ventry el1_irq // IRQ EL1h
ventry el1_fiq_invalid // FIQ EL1h
ventry el1_error_invalid // Error EL1h

ventry el0_sync // Synchronous 64-bit EL0
ventry el0_irq // IRQ 64-bit EL0
ventry el0_fiq_invalid // FIQ 64-bit EL0
ventry el0_error_invalid // Error 64-bit EL0

#ifdef CONFIG_COMPAT
ventry el0_sync_compat // Synchronous 32-bit EL0
ventry el0_irq_compat // IRQ 32-bit EL0
ventry el0_fiq_invalid_compat // FIQ 32-bit EL0
ventry el0_error_invalid_compat // Error 32-bit EL0
#else
ventry el0_sync_invalid // Synchronous 32-bit EL0
ventry el0_irq_invalid // IRQ 32-bit EL0
ventry el0_fiq_invalid // FIQ 32-bit EL0
ventry el0_error_invalid // Error 32-bit EL0
#endif
END(vectors)

위 벡터 중에 el0_sync_compat를 좀 살펴볼께요.
EL0 -> EL1으로는 주로 시스템 콜로 레벨이 바뀐다고 했잖아요.

el0_sync_compat -> el0_svc_compat ->el0_svc_naked -> el0_svc_naked 순서로 호출이 되는데.
아래 코드 조각을 잠깐 보면 "syscall handling" 이란 주석도 보이네요.
el0_svc_compat:
/*
* AArch32 syscall handling
*/
adrp stbl, compat_sys_call_table // load compat syscall table pointer
uxtw scno, w7 // syscall number in w7 (r7)
mov     sc_nr, #__NR_compat_syscalls
b el0_svc_naked

el0_svc_naked 함수에서 시스템 콜 번호를 찾아서 등록된 시스템 콜 함수를 호출해요.
el0_svc_naked: // compat entry point
stp x0, scno, [sp, #S_ORIG_X0] // save the original x0 and syscall number
enable_dbg_and_irq
ct_user_exit 1

ldr x16, [tsk, #TI_FLAGS] // check for syscall hooks
tst x16, #_TIF_SYSCALL_WORK
b.ne __sys_trace
cmp     scno, sc_nr                     // check upper syscall limit
b.hs ni_sys
ldr x16, [stbl, scno, lsl #3] // address in the syscall table
blr x16 // call sys_* routine
b ret_fast_syscall
ni_sys:
mov x0, sp
bl do_ni_syscall
b ret_fast_syscall
ENDPROC(el0_svc)

위 코드가 머리가 들어오지 않는다구요.
아래 콜스택을 잠깐 볼까요? 아래는 sys_sendto란 시스템 콜로 해당 드라이버 코드가 수행된 다음에, 커널 패닉으로 시스템이 돌아가시는 동작인데요. sys_sendto란 시스템 콜을 호출하기 전에 el0_svc_naked(asm) 심볼이 보이네요.
-000|panic(?)
-001|oops_end(inline)
-001|die(?, regs = 0xFFFFFFE4DE6A78D0, err = -563462144)
-002|__do_kernel_fault.part.6(mm = 0xFFFFFFE55A7E4F00, addr = 18446743524274601936, esr = 2516582407, regs = 0xFFFFFFE4DE6A78D0)
-003|__do_kernel_fault(inline)
-003|do_page_fault(addr = 18446743524274601936, esr = 2516582407, regs = 0xFFFFFFE4DE6A78D0)
-004|do_mem_abort(addr = 18446743524274601936, esr = 2516582407, regs = 0xFFFFFFE4DE6A78D0)
-005|el1_da(asm)
 -->|exception
-006|ch_pop_remote_rx_intent(?, size = 40, riid_ptr = 0xFFFFFFE4DE6A7B1C, intent_size = 0xFFFFFFE4DE6A7B20, cookie = 0xFFFFFFE4DE6A7B28)
-007|tx_common(handle = 0xFFFFFFE5775ED200, ?, data = 0x0, iovec = 0xFFFFFFE5791C1580, size = 40, ?, ?, tx_flags = 1)
-008|txv(handle = 0xFFFFFFE5775ED200, ?, iovec = 0xFFFFFFE5791C1580, size = 40, vbuf_provider = 0xFFFFFF9F99B36144, pbuf_provider = 0x0, ?)
-009|ipc_router_glink_xprt_write(?, ?, xprt = 0xFFFFFFE4F4F7A4D0)
-010|ipc_router_write_pkt(inline)
-010|ipc_router_send_to(src = 0xFFFFFFE54FC67000, ?, ?, ?)
-011|ipc_router_sendmsg(?, ?, total_len = 22)
-012|sock_sendmsg_nosec(inline)
-012|sock_sendmsg(sock = 0xFFFFFFE4DC856A80, ?)
-013|SYSC_sendto(inline)
-013|sys_sendto(fd = -116527563208, buff = 531225458560, ?, ?, addr = 531233627424, addr_len = 20)
-014|el0_svc_naked(asm)
 -->|exception
-015|NUX:0x7BB2AFB7C4(asm)
 ---|end of frame

"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
그리고 혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려드리겠습니다!"


핑백

덧글

댓글 입력 영역