이번 포스트에서는 EL0에서 익셉션이 발생할 때의 익셉션 벡터 핸들러 코드에 대해 분석합니다.
익셉션 벡터 테이블 분석하기
먼저 다음 익셉션 벡터 테이블을 봅시다.

위에서 보이는 표는 리눅스 커널이 구동되는 EL1 기준으로 작성된 익셉션 벡터 테이블입니다.
EL0, 즉 유저 애플리케이션이 실행하는 도중 IRQ 타입 익셉션이 발생하면 테이블의 볼드체로 표기된 주소로 ARM 프로세서의 프로그램 카운터가 브랜치됩니다.
간단히 익셉션 벡터 테이블을 리뷰했으니, 이제 코드를 분석하겠습니다.
익셉션 벡터 핸들러 코드 분석하기
익셉션 벡터 테이블의 베이스 주소가 0xffffff8008082000라고 가정하고 익셉션 벡터 핸들러의 코드를 분석하겠습니다.
ffffff8008082480: d503201f nop
ffffff8008082484: d503201f nop
ffffff8008082488: d10503ff sub sp, sp, #0x140
ffffff800808248c: 8b2063ff add sp, sp, x0
ffffff8008082490: cb2063e0 sub x0, sp, x0
ffffff8008082494: 37700080 tbnz w0, #14, ffffff80080824a4 <vectors+0x4a4>
ffffff8008082498: cb2063e0 sub x0, sp, x0
ffffff800808249c: cb2063ff sub sp, sp, x0
ffffff80080824a0: 140005c8 b ffffff8008083bc0 <el0_irq>
ffffff80080824a4: d51bd040 msr tpidr_el0, x0
ffffff80080824a8: cb2063e0 sub x0, sp, x0
ffffff80080824ac: d51bd060 msr tpidrro_el0, x0
ffffff80080824b0: d0006060 adrp x0, ffffff8008c90000 <overflow_stack+0xd50>
ffffff80080824b4: 910ac01f add sp, x0, #0x2b0
ffffff80080824b8: d538d080 mrs x0, tpidr_el1
ffffff80080824bc: 8b2063ff add sp, sp, x0
ffffff80080824c0: d53bd040 mrs x0, tpidr_el0
ffffff80080824c4: cb2063e0 sub x0, sp, x0
ffffff80080824c8: f274cc1f tst x0, #0xfffffffffffff000
ffffff80080824cc: 54001861 b.ne ffffff80080827d8 <__bad_stack>
ffffff80080824d0: cb2063ff sub sp, sp, x0
ffffff80080824d4: d53bd060 mrs x0, tpidrro_el0
ffffff80080824d8: 140005ba b ffffff8008083bc0 <el0_irq>
ffffff80080824dc: d503201f nop
EL0에서 IRQ 익셉션이 발생하면 ARM 프로세서는 0xffffff8008082000+0x480 주소로 프로그램 카운터를 브랜치합니다. 이를 다른 관점으로 설명하면, ffffff8008082480 주소로 프로그램 카운터가 브랜치한다라고 볼 수 있습니다. 프로그램 카운터가 ffffff8008082480로 브랜치하면 ffffff8008082480 주소에 있는 명령어를 실행합니다.
ffffff8008082480 주소에 보이는 여러 명령어가 있는데, 핵심 동작은 다음과 같이 요약할 수 있습니다.
* 스택 공간 확보
* tpidrro_el0 레지스터를 x0레지스터에 저장
* el0_irq 레이블로 브랜치
이어서, 사실 상 EL0에서 인터럽트가 발생할 때의 익셉션 벡터 핸들러인 el0_irq 레이블의 코드를 분석하겠습니다.
el0_irq 레이블 코드 분석
el0_irq 레이블의 구현부는 다음과 같습니다.
01 el0_irq:
02 kernel_entry 0
03 el0_irq_naked:
04 gic_prio_irq_setup pmr=x20, tmp=x0
05 ct_user_exit_irqoff
06 enable_da_f
07
08 #ifdef CONFIG_TRACE_IRQFLAGS
09 bl trace_hardirqs_off
10 #endif
11
12 #ifdef CONFIG_HARDEN_BRANCH_PREDICTOR
13 tbz x22, #55, 1f
14 bl do_el0_irq_bp_hardening
15 1:
16 #endif
17 irq_handler
18
19 #ifdef CONFIG_TRACE_IRQFLAGS
20 bl trace_hardirqs_on
21 #endif
22 b ret_to_user
23 ENDPROC(el0_irq)
01~06번째 줄을 보겠습니다.
01 el0_irq:
02 kernel_entry 0
03 el0_irq_naked:
04 gic_prio_irq_setup pmr=x20, tmp=x0
05 ct_user_exit_irqoff
06 enable_da_f
인터럽트 핸들러를 처리하기 전에 전처리를 하는 동작인데, 06번째 줄은 PSTATE 레지스터의 DA_F 플래그들을 클리어하는 동작을 수행합니다.
이어서 17번째 줄을 봅시다.
17 irq_handler
리눅스 커널의 IRQ 서브시스템을 호출하는데, 이후 서브 루틴에서 인터럽트 핸들러가 호출됩니다.
마지막으로 22번째 줄을 보겠습니다.
22 b ret_to_user
EL0(유저 애플리케이션)에서 인터럽트(익셉션)이 발생했으니, 이제 유저 공간을 복귀하기 위해 ret_to_user 레이블을 호출합니다.
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자

최근 덧글