연휴를 맞아 몇년 동안 책장에 모셔 놨던 《임베디드 레시피》를 꺼냈다. 이직 후 구매했다가 바쁘다는 핑계로 펼쳐 보지도 못했는데 막상 읽어 보니 ARM AArch32 아키텍처 부분을 제외하고 유용한 실무 내용이 많았다. 그 중에서 마지막 장〈How to debug〉은 AArch32 아키텍처 기준 Exception 상황 몇 가지를 소개하는데 AArch64 와 다른 내용이 꽤 있었다.
- Interrupt Lock에 의한 Watch dog Reset
- Task Lock에 의한 Watch dog Reset
- Data abort (invalid write)
- Stack Corruption과 Prefetch Abort
- Abort in ISR
- Branch/go to address 0
- Watch dog Reset in ISR
이들을 Yocto Linux 를 이용해 AArch64 QEMUARM64 에뮬레이션 환경에서 테스트할 수 있도록 하나씩 커널 모듈 코드로 작성해보자.
Interrupt Lock에 의한 Watch dog Reset
아래 코드는 local_irq_disable() 로 현재 CPU의 IRQ를 비활성화한 뒤 대기한다.
결국 타이머 인터럽트 처리 지연으로 watchdog timeout이 발생해 리셋된다.
static noinline void kfault_irq_lock(void)
{
pr_emerg("kfault-lab: irq-lock: disabling local IRQs and spinning\n");
local_irq_disable();
for (;;)
cpu_relax();
}선점 비활성화 및 Watch dog Reset
IRQ 비활성화 뿐 아니라 선점 비활성화도 Watch dog 리셋 원인이 될 수 있다. 앞선
IRQ 비활성화와 달리 preempt_disable() 은 인터럽트는 발생하지만 무한 루프를
도는 태스크로 인해 CPU의 다른 태스크 처리가 지연되면서 리셋이 발생한다.
static noinline void kfault_task_lock(void)
{
pr_emerg("kfault-lab: task-lock: preemption disabled, IRQs enabled\n");
preempt_disable();
for (;;)
cpu_relax();
}마찬가지로 GDB를 이용해 살펴보면 아래와 같이 루프 지점을 찾아낼 수 있다.
(remote) gef➤ x/20i $pc-12
0xffff80007987044c: ldr w0, [x1, #8]
0xffff800079870450: add w0, w0, #0x1
0xffff800079870454: str w0, [x1, #8]
=> 0xffff800079870458: yield
0xffff80007987045c: b 0xffff800079870458
0xffff800079870460: .inst 0x80fceee8 ; undefined
0xffff800079870464: .inst 0xffff8000 ; undefined
0xffff800079870468: mov x9, x30Data abort (invalid write)
Data Abort는 잘못된 주소 변환, 접근 권한으로 발생한다. 아래의 코드는 유효하지 않은 주소 공간에 강제로 데이터를 write 하여 발생하는 경우다.
static noinline void kfault_data_abort(void)
{
volatile u32 *bad = (volatile u32 *)0xdeadbeefUL;
pr_emerg("kfault-lab: triggering data abort at %px\n", bad);
*bad = 0x4b464c54;
}Stack corruption
Stack corruption은 잘못된 스택 영역 할당 및 call depth 로 발생한다. 아래 코드는
재귀호출을 이용해 stack corruption 상황을 유발한다. 참고로 barrier_data(frame)
은 Tail Call Optimization(새로운 스택 프레임을 생성하지 않고 현재 스택 프레임을
사용하도록 최적화 하는 기법)을 막기 위해 사용된다. 또한 CONFIG_VMAP_STACK 을
사용하는 arm64 커널에서는 스택 경계의 매핑되지 않은 페이지(guard page)에
접근하면 Data abort가 발생한다.
static noinline void kfault_stack_overflow(unsigned int depth)
{
volatile u8 frame[1024];
frame[0] = depth;
if ((depth & 0x7) == 0) {
pr_emerg("kfault-lab: stack-overflow depth=%u\n", depth);
}
kfault_stack_overflow(depth + 1);
/* Prevent tail-call optimization and keep each frame live. */
barrier_data(frame);
}Instruction abort (prefetch abort in AArch32)
Data abort와 마찬가지로 잘못된 주소 변환, 접근 권한으로 발생한다. Data abort가 데이터 읽기·쓰기 중 발생하는 것과 달리 instructino abort는 실행하고자 하는 명령에 접근하다가 발생한다.
static noinline void kfault_prefetch_abort(void)
{
unsigned long pc = 0;
void (*bad)(void);
/* Keep the zero target in a register: branch, rather than a compile-time trap. */
asm volatile("" : "+r"(pc));
bad = (void (*)(void))pc;
pr_emerg("kfault-lab: branching to 0x0 (prefetch/instruction abort)\n");
bad();
}