Exception은 오류만 뜻하지 않는다
일반 프로그램은 EL0에서 실행되며 kernel memory나 장치를 직접 만질 수 없다. 파일 쓰기처럼 kernel 권한이 필요한 일을 요청하면 SVC 명령으로 EL1의 정해진 입구를 호출한다. page fault, interrupt, breakpoint도 같은 큰 틀의 exception이지만 발생 원인과 돌아갈 위치가 다르다.
프로그램이 의도적으로 kernel 서비스를 요청한다.
현재 명령을 완료하려면 page table 수정이나 signal 처리가 필요하다.
장치가 CPU의 현재 명령과 독립적으로 처리를 요청한다.
breakpoint·single-step 같은 디버그 사건이다.
CPU가 EL1 진입 순간 보존하는 최소 상태
| 상태 | 역할 | Linux에서의 사용 |
|---|---|---|
| ELR_EL1 | 돌아갈 instruction 주소 | pt_regs.pc로 옮겨 signal·ptrace·복귀에 사용한다. |
| SPSR_EL1 | exception 전 PSTATE | 원래 EL, interrupt mask, condition flag를 복원한다. |
| ESR_EL1 | Exception Class와 syndrome | SVC, instruction abort, data abort, debug 중 어느 handler로 갈지 고른다. |
| FAR_EL1 | fault 관련 virtual address | page fault라면 VMA와 page table을 찾는 주소가 된다. |
| VBAR_EL1 | vector table 시작 주소 | 현재 EL·stack·AArch64 여부에 따른 entry slot을 선택한다. |
중요: CPU가 모든 general-purpose register를 자동 저장하는 것은 아니다. Linux의 kernel_entry assembly가 x0~x30과 관련 상태를 stack의 struct pt_regs 형태로 만든다.
Vector slot에서 C handler까지
개념 호출 흐름
EL0 executes SVC #0
→ VBAR_EL1의 EL0t synchronous slot
→ entry.S: kernel_entry 0
→ entry-common.c: el0t_64_sync_handler(regs)
→ ESR_EL1.EC == SVC64
→ el0_svc(regs)entry.S는 register 저장과 stack·보안 상태 전환을 담당하고, entry-common.c는 ESR의 Exception Class를 읽어 C handler를 선택한다. 두 파일의 책임을 섞어 읽으면 호출 경로를 놓치기 쉽다.
kernel_entry와 el0t_64_sync_handler()가 첫 번째 핵심 지점이다.
write()가 실제 system call 함수에 도달하는 길
- userspace wrapper가 x0~x5에 인수, x8에 system call 번호를 넣고
svc #0을 실행한다. el0t_64_sync_handler()가 ESR의 EC를 판별해el0_svc()를 호출한다.el0_svc()가do_el0_svc()로 넘긴다.do_el0_svc()는 x8 값을el0_svc_common()에 전달한다.invoke_syscall()이 번호 범위를 검사하고sys_call_table[scno]를 선택한다.- 반환값은 x0 위치인
regs->regs[0]에 기록된다. - 진입 시 보존한 상태를 검사하고
kernel_exit 0과ERET으로 EL0에 돌아간다.
Linux v6.18.37의 실제 연결점은 do_el0_svc(), el0_svc_common(), invoke_syscall()이다.
인수와 반환값은 pt_regs를 통해 보인다
| AArch64 ABI | System call 의미 | 저장 위치 |
|---|---|---|
| x0~x5 | 최대 6개 인수 | regs->regs[0..5] |
| x8 | system call 번호 | regs->regs[8] |
| x0 | 성공 값 또는 음수 errno | 복귀 전 regs->regs[0] |
| PC/PSTATE | 복귀 주소와 상태 | regs->pc, regs->pstate |
strace는 이 ABI 경계를 보여 주지만 kernel 내부 함수 호출을 보여 주지는 않는다. 내부 흐름은 tracepoint, ftrace 또는 BPF를 추가해야 한다.
Data abort는 system call과 다른 분기다
EL0 load/store가 translation·permission·tag 검사에 실패하면 ESR의 Data Abort EC와 ISS, FAR이 채워진다. el0t_64_sync_handler()는 이를 do_mem_abort()로 보내고, fault status table이 translation fault·permission fault·MTE tag fault 등에 맞는 handler를 고른다.
Page fault 경로
el0t_64_sync_handler
→ do_mem_abort(FAR_EL1, ESR_EL1, regs)
→ fault_info[DFSC].fn
→ do_page_fault
→ lock_vma_under_rcu / find_and_lock_anon_vma
→ handle_mm_fault
→ page table 생성 또는 SIGSEGVdo_mem_abort()와 do_page_fault()를 이어서 읽는다.
실제 entry code가 추가로 처리하는 것
register 저장만 하는 것이 아니다. context tracking, lockdep, RCU, tracing, MTE의 TCO 상태, pointer authentication key, single-step, signal과 rescheduling 여부가 진입·복귀 경계에서 정리된다. 그래서 entry 경로에는 noinstr가 많이 붙고 일반 C 함수처럼 임의 instrumentation을 넣으면 안 된다.
분석 원칙: entry assembly에 printk를 넣는 방식으로 실험하지 않는다. tracepoint·function graph·kprobe가 허용되는 지점부터 관찰한다.
Linux v6.18.37 소스 지도
| 파일 | 핵심 심볼 | 확인 질문 |
|---|---|---|
| arch/arm64/kernel/entry.S | kernel_entry, kernel_exit | 무엇을 stack에 저장하고 언제 ERET하는가. |
| entry-common.c | el0t_64_sync_handler, el1h_64_sync_handler | ESR EC가 어느 C handler를 선택하는가. |
| syscall.c | do_el0_svc, invoke_syscall | x8 번호가 syscall table로 어떻게 연결되는가. |
| arch/arm64/mm/fault.c | do_mem_abort, do_page_fault | DFSC와 FAR이 page fault나 signal로 어떻게 바뀌는가. |
| arch/arm64/include/asm/ptrace.h | struct pt_regs | 저장된 register layout이 무엇인가. |
장비에서 호출 경로를 재현한다
System call 관찰
strace -e write ./demo
sudo trace-cmd record -e raw_syscalls ./demo
sudo trace-cmd report
sudo perf trace -e write ./demo그다음 ftrace의 function graph filter에 do_el0_svc 대신 instrumentation 가능한 syscall 하위 함수를 좁혀 넣는다. entry의 noinstr 함수가 보이지 않는 것은 도구 실패가 아니라 의도된 제약일 수 있다.
fault 실험은 보호된 test process에서 unmapped address나 read-only page write를 만들고 perf trace, signal handler의 siginfo_t.si_code, kernel trace를 함께 저장한다.
자주 틀리는 해석
SVC가 곧 함수 호출인 것은 아니다. Exception Level과 stack·PSTATE를 바꾸는 architecture event다.- ESR은 원인의 모든 정보를 주지 않는다. FAR validity, instruction syndrome와 page table 상태를 함께 봐야 한다.
- page fault는 항상 오류가 아니다. demand paging과 copy-on-write의 정상 경로이기도 하다.
strace출력만으로 kernel 내부 병목을 확정할 수 없다.
근거 자료
- Local sourceLinux v6.18.37 commit
0c503cf3dde2 - ArchitectureA-profile Architecture Reference Manual
- RegistersA-profile System Registers
- Linux ABILinux arm64 documentation