Exception -8- (ARM64 Handler)

<kernel v5.4>

 

Context 백업

pt_regs 구조체

arch/arm64/include/asm/ptrace.h

/*
 * This struct defines the way the registers are stored on the stack during an
 * exception. Note that sizeof(struct pt_regs) has to be a multiple of 16 (for
 * stack alignment). struct user_pt_regs must form a prefix of struct pt_regs.
 */
struct pt_regs {
        union {
                struct user_pt_regs user_regs;
                struct {
                        u64 regs[31];
                        u64 sp;
                        u64 pc;
                        u64 pstate;
                };
        };
        u64 orig_x0;
#ifdef __AARCH64EB__
        u32 unused2;
        s32 syscallno;
#else
        s32 syscallno;
        u32 unused2;
#endif

        u64 orig_addr_limit;
        /* Only valid when ARM64_HAS_IRQ_PRIO_MASKING is enabled. */
        u64 pmr_save;
        u64 stackframe[2];
};

exception 발생 시 스택에 저장될 레지스터들이다.

  •  regs[31]
    • x0~x30(lr) 범용 레지스터가 저장된다.
  • sp
    • 중단될 때의 스택 레지스터가 저장된다.
  • pc
    • 중단될 때 복귀할 주소가 저장된다.
  • pstate
    • 중단될 때의 PSTATE 값이 저장된다.
  • orig_x0
  • syscallno
    • syscall 호출 번호
  • orig_addr_limit
    • 유저 태스크의 주소 제한 값이 저장된다.
  • pmr_save
    • irq priority mask 값이 저장된다.
  • stackframe[]

 

user_pt_regs 구조체

arch/arm64/include/uapi/asm/ptrace.h

struct user_pt_regs {
        __u64           regs[31];
        __u64           sp;
        __u64           pc;
        __u64           pstate;
};

pr_regs의 앞부분이 동일하다.

 

kernel_entry 매크로

arch/arm64/kernel/entry.S -1/2-

.       .macro  kernel_entry, el, regsize = 64
        .if     \regsize == 32
        mov     w0, w0                          // zero upper 32 bits of x0
        .endif
        stp     x0, x1, [sp, #16 * 0]
        stp     x2, x3, [sp, #16 * 1]
        stp     x4, x5, [sp, #16 * 2]
        stp     x6, x7, [sp, #16 * 3]
        stp     x8, x9, [sp, #16 * 4]
        stp     x10, x11, [sp, #16 * 5]
        stp     x12, x13, [sp, #16 * 6]
        stp     x14, x15, [sp, #16 * 7]
        stp     x16, x17, [sp, #16 * 8]
        stp     x18, x19, [sp, #16 * 9]
        stp     x20, x21, [sp, #16 * 10]
        stp     x22, x23, [sp, #16 * 11]
        stp     x24, x25, [sp, #16 * 12]
        stp     x26, x27, [sp, #16 * 13]
        stp     x28, x29, [sp, #16 * 14]

        .if     \el == 0
        clear_gp_regs
        mrs     x21, sp_el0
        ldr_this_cpu    tsk, __entry_task, x20  // Ensure MDSCR_EL1.SS is clear,
        ldr     x19, [tsk, #TSK_TI_FLAGS]       // since we can unmask debug
        disable_step_tsk x19, x20               // exceptions when scheduling.

        apply_ssbd 1, x22, x23

        .else
        add     x21, sp, #S_FRAME_SIZE
        get_current_task tsk
        /* Save the task's original addr_limit and set USER_DS */
        ldr     x20, [tsk, #TSK_TI_ADDR_LIMIT]
        str     x20, [sp, #S_ORIG_ADDR_LIMIT]
        mov     x20, #USER_DS
        str     x20, [tsk, #TSK_TI_ADDR_LIMIT]
        /* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */
        .endif /* \el == 0 */
        mrs     x22, elr_el1
        mrs     x23, spsr_el1
        stp     lr, x21, [sp, #S_LR]

        /*
         * In order to be able to dump the contents of struct pt_regs at the
         * time the exception was taken (in case we attempt to walk the call
         * stack later), chain it together with the stack frames.
         */
        .if \el == 0
        stp     xzr, xzr, [sp, #S_STACKFRAME]
        .else
        stp     x29, x22, [sp, #S_STACKFRAME]
        .endif
        add     x29, sp, #S_STACKFRAME

context 전환을 목적으로 레지스터들을 백업한다. el0 또는 AArch32 여부에 따라 약간 차이가 있다.

  • 코드 라인 2~4에서 EL0 AArch32에서 exception 진입한 경우 32비트 레지스터를 사용하기 위해 x0 레지스터의 상위 32비트를 0으로 클리어한다.
  • 코드 라인 5~19에서 context 저장을 목적으로 x0~x29 레지스터를 스택에 백업한다.
  • 코드 라인 21~22에서 EL0에서 exception 진입한 경우 x0~x29 레지스터를 0으로 클리어한다.
  • 코드 라인 23에서 중단된 기존 스택 주소(sp_el0)를 잠시 x21에 백업해둔다.
  • 코드 라인 24~26에서 __entry_task 태스크의 thread_info.flag 값을 x19 레지스터로 알아와서 싱글 스텝 디버깅 비트가 켜져있으면 싱글 스텝 디버거를 끈다.
    • __entry_task는 per-cpu 변수로 선언되었다.
  • 코드 라인 28에서 SSBD(Speculative Store Bypass Disable) 기능을 사용하는 경우 Speculative Store 공격으로 부터 커널을 보호하기 위해 유저와 커널의 전환 시 EL1이 아닌 상위(EL2 or EL3)에서 수행하는 workaround를 적용한다.  사용하지 않는 경우 그냥 skip 한다.
  • 코드 라인 30~31에서 EL0가 아닌 곳에서 exception 진입한 경우 pt_regs 만큼 스택을 키우기 전의 스택 주소를 잠시 x21에 저장한다.
  • 코드 라인 32~37에서 현재 thread_info.addr_limit 값을 스택의 orig_addr_limit 위치에 백업한 후 최대 유저 주소 한계 값을 지정한다.
    • pt_regs.orig_addr_limit <- thread_info.addr_limit
    • thread_info.addr_limit <- USER_DS
      • 예) CONFIG_ARM64_VA_BITS_48 커널 옵션이 사용되는 경우 USER_DS=0x0000_ffff_ffff_ffff
      • 예) CONFIG_ARM64_VA_BITS_52 커널 옵션이 사용되는 경우 USER_DS=0x000f_ffff_ffff_ffff
  • 코드 라인 40~41에서 x22 <- elr_el1 및 x23 <- spsr_el1을 수행한다.
  • 코드 라인 42에서 lr과 pt_regs 만큼 스택 키우기 전의 스택 주소가 담긴 x21 레지스터 값을 pt_regs.x30(lr)과 pt_regs.sp 위치에 저장한다.
  • 코드 라인 49~50에서 EL0에서 진입한 경우 스택의 x0, x1 위치에 0을 저장한다.
  • 코드 라인 51~52에서 EL1에서 진입한 경우 x29와 중단되어 복귀할 주소(pc -> elr_el1)가 담긴 x22 레지스터 값을 pt_regs.stackframe[] 위치에 저장한다.
  • 코드 라인 54에서 pt_regs.stackframe[] 주소를 x29 레지스터에 대입한다.

 

arch/arm64/kernel/entry.S -2/2-

#ifdef CONFIG_ARM64_SW_TTBR0_PAN
        /*
         * Set the TTBR0 PAN bit in SPSR. When the exception is taken from
         * EL0, there is no need to check the state of TTBR0_EL1 since
         * accesses are always enabled.
         * Note that the meaning of this bit differs from the ARMv8.1 PAN
         * feature as all TTBR0_EL1 accesses are disabled, not just those to
         * user mappings.
         */
alternative_if ARM64_HAS_PAN
        b       1f                              // skip TTBR0 PAN
alternative_else_nop_endif

        .if     \el != 0
        mrs     x21, ttbr0_el1
        tst     x21, #TTBR_ASID_MASK            // Check for the reserved ASID
        orr     x23, x23, #PSR_PAN_BIT          // Set the emulated PAN in the saved SPSR
        b.eq    1f                              // TTBR0 access already disabled
        and     x23, x23, #~PSR_PAN_BIT         // Clear the emulated PAN in the saved SPSR
        .endif

        __uaccess_ttbr0_disable x21
1:
#endif

        stp     x22, x23, [sp, #S_PC]

        /* Not in a syscall by default (el0_svc overwrites for real syscall) */
        .if     \el == 0
        mov     w21, #NO_SYSCALL
        str     w21, [sp, #S_SYSCALLNO]
        .endif

        /*
         * Set sp_el0 to current thread_info.
         */
        .if     \el == 0
        msr     sp_el0, tsk
        .endif

        /* Save pmr */
alternative_if ARM64_HAS_IRQ_PRIO_MASKING
        mrs_s   x20, SYS_ICC_PMR_EL1
        str     x20, [sp, #S_PMR_SAVE]
alternative_else_nop_endif

        /*
         * Registers that may be useful after this macro is invoked:
         *
         * x20 - ICC_PMR_EL1
         * x21 - aborted SP
         * x22 - aborted PC
         * x23 - aborted PSTATE
        */
        .endm
  • 코드 라인 1에서 CONFIG_ARM64_SW_TTBR0_PAN 커널 옵션은 보안을 향상 시키기 위해 커널이 유저 영역의 접근을 방지하기 위해 사용된다.
  • 코드 라인 10~12에서 ARM64_HAS_PAN 기능을 갖춘 시스템에서는 skip 한다.
  • 코드 라인 14~20에서 EL0에서 진입한 경우가 아니면 ttbr0_el1 레지스터의 ASID 필드 값이 0인 경우 이미 ttbr0 액세스가 disable 되어 있으므로 x23 레지스터에 PSR_PAN_BIT를 설정하고 skip 한다. ASID 필드 값이 존재하는 경우 x23 레지스터에 PSR_PAN_BIT를 클리어하고 계속 진행한다.
  • 코드 라인 22에서 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하도록 ttbr을 설정한다.
    • x21 레지스터는 매크로에서 임시로 사용되며 크래시된다.
    • 참고: copy_from_user() | 문c
  • 코드 라인 26에서 중단되어 복귀할 주소(pc -> elr_el1)가 담긴 x22 레지스터와 중단될 때의 상태(spsr) 값이 담긴 x23 레지스터를 pt_regs.pc와 pt_regs->pstate 위치에 저장한다.
  • 코드 라인 29~32에서 EL0에서 진입한 경우이면 초기 값으로 NO_SYSCALL(-1) 값을 pt_regs.syscallno 위치에 백업한다.
    • -1은 단지 초기 값일뿐 syscall 번호가 아니다.
  • 코드 라인 37~39에서 EL0에서 진입한 경우이면 현재 스레드 주소를 스택(sp_el0)으로 지정한다.
  • 코드 라인 42~45에서 irq priority masking이 가능한 시스템이면 SYS_ICC_PMR_EL1 레지스터를 읽어 pt_regs.pmr_save위치에 백업해둔다. irq priority masking이 가능한 시스템이 아닌 경우 nop으로 채운다.

 

clear_gp_regs 매크로

arch/arm64/kernel/entry.S

.       .macro  clear_gp_regs
        .irp    n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
        mov     x\n, xzr
        .endr
        .endm

컴파일러로 하여금 x0~x29 레지스터를 0으로 채우게 한다.

  • mov x0, xzr
  • mov z1, xzr
  • mov z29, xzr

 

ldr_this_cpu 매크로

arch/arm64/include/asm/assembler.h

        /*
         * @dst: Result of READ_ONCE(per_cpu(sym, smp_processor_id()))
         * @sym: The name of the per-cpu variable
         * @tmp: scratch register
         */
        .macro ldr_this_cpu dst, sym, tmp
        adr_l   \dst, \sym
alternative_if_not ARM64_HAS_VIRT_HOST_EXTN
        mrs     \tmp, tpidr_el1
alternative_else
        mrs     \tmp, tpidr_el2
alternative_endif
        ldr     \dst, [\dst, \tmp]
        .endm

로컬 cpu에서 per-cpu 변수 @sym의 주소를 @dst에 알아온다. 임시 @tmp 레지스터는 스크래치 된다.

  • tpidr_el1 (또는 tpidr_el2)는 per-cpu offset을 담고 있다.

 

disable_step_tsk 매크로

arch/arm64/include/asm/assembler.h

        .macro  disable_step_tsk, flgs, tmp
        tbz     \flgs, #TIF_SINGLESTEP, 9990f
        mrs     \tmp, mdscr_el1
        bic     \tmp, \tmp, #DBG_MDSCR_SS
        msr     mdscr_el1, \tmp
        isb     // Synchronise with enable_dbg
9990:
        .endm

싱글 스텝 디버깅을 사용하는 중이면 디버거를 끈다.

  • 어떤 태스크의 SINGLESTEP 플래그(@flgs) 비트가 사용중인 경우 MDSCR_EL1(Monitor Debug System Control Register EL1) 레지스터의 SS(Software Step control) 비트를 disable 한다.

 

get_current_task 매크로

arch/arm64/include/asm/assembler.h

/*
 * Return the current task_struct.
 */
.       .macro  get_current_task, rd
        mrs     \rd, sp_el0
        .endm

현재 태스크를 @rd 레지스터로 가져온다.

  • 스택의 가장 아래에 struct task_struct가 존재한다.

 

 


Context 복구

kernel_exit 매크로

arch/arm64/kernel/entry.S -1/2-

        .macro  kernel_exit, el
        .if     \el != 0
        disable_daif

        /* Restore the task's original addr_limit. */
        ldr     x20, [sp, #S_ORIG_ADDR_LIMIT]
        str     x20, [tsk, #TSK_TI_ADDR_LIMIT]

        /* No need to restore UAO, it will be restored from SPSR_EL1 */
        .endif

        /* Restore pmr */
alternative_if ARM64_HAS_IRQ_PRIO_MASKING
        ldr     x20, [sp, #S_PMR_SAVE]
        msr_s   SYS_ICC_PMR_EL1, x20
        /* Ensure priority change is seen by redistributor */
        dsb     sy
alternative_else_nop_endif

        ldp     x21, x22, [sp, #S_PC]           // load ELR, SPSR
        .if     \el == 0
        ct_user_enter
        .endif

#ifdef CONFIG_ARM64_SW_TTBR0_PAN
        /*
         * Restore access to TTBR0_EL1. If returning to EL0, no need for SPSR
         * PAN bit checking.
         */
alternative_if ARM64_HAS_PAN
        b       2f                              // skip TTBR0 PAN
alternative_else_nop_endif

        .if     \el != 0
        tbnz    x22, #22, 1f                    // Skip re-enabling TTBR0 access if the PSR_PAN_BIT is set
        .endif

        __uaccess_ttbr0_enable x0, x1

        .if     \el == 0
        /*
         * Enable errata workarounds only if returning to user. The only
         * workaround currently required for TTBR0_EL1 changes are for the
         * Cavium erratum 27456 (broadcast TLBI instructions may cause I-cache
         * corruption).
         */
        bl      post_ttbr_update_workaround
        .endif
1:
        .if     \el != 0
        and     x22, x22, #~PSR_PAN_BIT         // ARMv8.0 CPUs do not understand this bit
        .endif
2:
#endif
  • 코드 라인 2~7에서 el0 exception에서 진입하였던 경우가 아니면 PSTATE의 DAIF 플래그들을 마스크하여 인터럽트 및 Abort Exception들을 허용하지 않게 한다. 그런 후 스택에 백업해둔 tsk->addr_limit을 복구한다.
  • 코드 라인 13~18에서 Pesudo-NMI 기능을 사용할 수 있는 시스템의 경우 스택에 백업해둔 pt_regs->pmr_save를 SYS_ICC_PMR_EL1 레지스터로 복구한다. 그런 후 redistributor가 변경된 값을 액세스 할 수 있도록 dsb 명령을 통해 operation이 완료될 때까지 대기한다.
  • 코드 라인 20에서 중단 점으로 복귀하기 위해 pt_regs->pc와 spsr 값을 x21, x22 레지스터로 읽어온다.
  • 코드 라인 21~23에서 el0 exception에서 진입하였던 경우 context 트래킹 디버그를 지원한다.
SW TTBR0 PAN 동작 – 커널의 유저 영역 분리
  • 코드 라인 30~32에서 ARMv8.1 아키텍처 이상에서 커널에서 유저 공간의 접근을 막는 PAN(Privilege Access Never) 기능을 지원하는 경우 SW  TTBR0 PAN을 skip 한다.
  • 코드 라인 34~36에서 el0 exception에서 진입하였던 경우가 아니면 중단 점에서의 PSTATE 레지스터의 PAN 비트가 설정되어 동작 중인 경우 재설정을 피하기 위해 1f 레이블로 이동한다.
  • 코드 라인 38에서 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.
  • 코드 라인 40~48에서 유저 복귀 전에 Cavium 칩의 erratum을 지원한다.
    • 내부적으로 TTBRx_el1을 갱신하는 경우 ic iallu; dsb nsh; isb 등의 베리어등을 수행해야 한다.
  • 코드 라인 49~52에서 1: 레이블에서는 el0 exception에서 진입하였던 경우가 아니면 ARMv8.0 아키텍처는 PSTATE의 PAN 비트를 모르므로 클리어한다.

 

arch/arm64/kernel/entry.S -2/2-

        .if     \el == 0
        ldr     x23, [sp, #S_SP]                // load return stack pointer
        msr     sp_el0, x23
        tst     x22, #PSR_MODE32_BIT            // native task?
        b.eq    3f

#ifdef CONFIG_ARM64_ERRATUM_845719
alternative_if ARM64_WORKAROUND_845719
#ifdef CONFIG_PID_IN_CONTEXTIDR
        mrs     x29, contextidr_el1
        msr     contextidr_el1, x29
#else
        msr contextidr_el1, xzr
#endif
alternative_else_nop_endif
#endif
3:
#ifdef CONFIG_ARM64_ERRATUM_1418040
alternative_if_not ARM64_WORKAROUND_1418040
        b       4f
alternative_else_nop_endif
        /*
         * if (x22.mode32 == cntkctl_el1.el0vcten)
         *     cntkctl_el1.el0vcten = ~cntkctl_el1.el0vcten
         */
        mrs     x1, cntkctl_el1
        eon     x0, x1, x22, lsr #3
        tbz     x0, #1, 4f
        eor     x1, x1, #2      // ARCH_TIMER_USR_VCT_ACCESS_EN
        msr     cntkctl_el1, x1
4:
#endif
        apply_ssbd 0, x0, x1
        .endif

        msr     elr_el1, x21                    // set up the return data
        msr     spsr_el1, x22
        ldp     x0, x1, [sp, #16 * 0]
        ldp     x2, x3, [sp, #16 * 1]
        ldp     x4, x5, [sp, #16 * 2]
        ldp     x6, x7, [sp, #16 * 3]
        ldp     x8, x9, [sp, #16 * 4]
        ldp     x10, x11, [sp, #16 * 5]
        ldp     x12, x13, [sp, #16 * 6]
        ldp     x14, x15, [sp, #16 * 7]
        ldp     x16, x17, [sp, #16 * 8]
        ldp     x18, x19, [sp, #16 * 9]
        ldp     x20, x21, [sp, #16 * 10]
        ldp     x22, x23, [sp, #16 * 11]
        ldp     x24, x25, [sp, #16 * 12]
        ldp     x26, x27, [sp, #16 * 13]
        ldp     x28, x29, [sp, #16 * 14]
        ldr     lr, [sp, #S_LR]
        add     sp, sp, #S_FRAME_SIZE           // restore sp

        .if     \el == 0
alternative_insn eret, nop, ARM64_UNMAP_KERNEL_AT_EL0
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
        bne     5f
        msr     far_el1, x30
        tramp_alias     x30, tramp_exit_native
        br      x30
5:
        tramp_alias     x30, tramp_exit_compat
        br      x30
#endif
        .else
        eret
        .endif
        sb
        .endm
  • 코드 라인 1~3에서 el0에서 exception 진입한 경우 sp_el0를 복원한다.
  • 코드 라인 4~5에서 중단점에서의 PSTATE 값에 MODE32 비트가 설정된 경우 3f 레이블로 이동한다.
  • 코드 라인 7~16에서 Cortext-A53 아키텍처에서 잘못된 load가 발생할 수 있는 에러를 방지하기 위해 erratum 84715 코드를 지원하며, 설명은 생략한다.
  • 코드 라인 18~32에서 Cortex-A76 아키텍처의 AArch32에서 Generic 타이머의 잘못된 읽기가 발생할 수 있는 에러를 방지하기 위해 erratum 1165522 코드를 지원하며, 설명은 생략한다.
  • 코드 라인 33에서 SSBD(Speculative Store Bypass Disable) 기능을 사용하는 경우 Speculative Store 공격으로 부터 커널을 보호하기 위해 유저와 커널의 전환 시 EL1이 아닌 상위(EL2 or EL3)에서 수행하는 workaround를 적용한다.  사용하지 않는 경우 그냥 skip 한다.
레지스터들 복원
  • 코드 라인 36~37에서 중단 점 pc를 elr_el1 레지스터에 기록하고, 중단 점에서의 PSTATE 값을 spsr_el1에 다시 기록하여 복원한다.
  • 코드 라인 38~52에서 스택에 백업해둔 x0~x29까지의 레지스터 정보를 다시 복원한다.
  • 코드 라인 53~54에서 스택에 백업해둔 lr(x30) 레지스터를 복원하고, 스택을 pt_regs 사이즈만큼 pop 한다.
trampoline 사용 – 유저에서 커널 영역 분리
  • 코드 라인 56~66에서 el0에서 exception 진입한 경우이다. 유저 영역에서 커널 영역을 완전히 분리하여 액세스를 방지하기 위해 trampoline 벡터를 이용하여 tramp_vectors를 vbar_el1에 지정하고, 커널 영역은 분리한 후 exception 발생하였던 중단점으로 복귀한다.
  • 코드 라인 70에서 Speculation barrier 명령을 수행한다. (dsb, isb)

 

ct_user_enter 매크로

arch/arm64/kernel/entry.S

        .macro ct_user_enter
#ifdef CONFIG_CONTEXT_TRACKING
        bl      context_tracking_user_enter
#endif
        .endm

디버그를 위해 context 트래킹 커널 옵션을 사용하는 경우 유저 복귀 전 trace 출력을 지원한다.

 

sb 매크로

arch/arm64/include/asm/assembler.h

/*
 * Speculation barrier
 */
        .macro  sb
alternative_if_not ARM64_HAS_SB
        dsb     nsh
        isb
alternative_else
        SB_BARRIER_INSN
        nop
alternative_endif
        .endm

Speculation barrier로 시스템이 지원하지 않으면 dsb, isb를 사용하고, 지원하는 경우 sb 전용 명령을 사용한다.

 


SSBD(Speculative Store Bypass Disable)

Speculative Store 공격으로 부터 커널을 보호하기 위해 유저와 커널의 전환 시 EL1이 아닌 상위(EL2 or EL3)에서 수행하는 workaround를 적용한다.

 

apply_ssbd 매크로

arch/arm64/kernel/entry.S

.       // This macro corrupts x0-x3. It is the caller's duty
        // to save/restore them if required.
        .macro  apply_ssbd, state, tmp1, tmp2
#ifdef CONFIG_ARM64_SSBD
alternative_cb  arm64_enable_wa2_handling
        b       .L__asm_ssbd_skip\@
alternative_cb_end
        ldr_this_cpu    \tmp2, arm64_ssbd_callback_required, \tmp1
        cbz     \tmp2,  .L__asm_ssbd_skip\@
        ldr     \tmp2, [tsk, #TSK_TI_FLAGS]
        tbnz    \tmp2, #TIF_SSBD, .L__asm_ssbd_skip\@
        mov     w0, #ARM_SMCCC_ARCH_WORKAROUND_2
        mov     w1, #\state
alternative_cb  arm64_update_smccc_conduit
        nop                                     // Patched to SMC/HVC #0
alternative_cb_end
.L__asm_ssbd_skip\@:
#endif
        .endm

SSBD 기능을 적용할 수 있는 시스템에서 유저와 커널의 전환 시 상위(EL2 or EL3)에서 수행하게 한다. 적용되지 않는 시스템은 그냥 skip 한다.

  • 코드 라인 2~5에서 시스템이 SSBD 기능을 지원하는 경우 nop을 수행하고, 지원하지 않는 경우 branch 명령대로 .L__asm_ssbd_skip 레이블로 이동한다.
    • alternative_cb을 통해 부트업 타임에 arm64_enable_wa2_handling() 함수를 수행하여 SSBD 기능이 동작하는 경우 다음 branch 명령을 nop으로 대체한다.
  • 코드 라인 6~7에서 per-cpu 변수인 arm64_ssbd_callback_required 값이 0인 경우 .L__asm_ssbd_skip 레이블로 이동한다.
  • 코드 라인 8~9에서 현재 태스크에 TIF_SSBD 플래그가 설정된 경우가 아니면 .L__asm_ssbd_skip 레이블로 이동한다.
  • 코드 라인 10~14에서 w0 레지스터에 ARM_SMCCC_ARCH_WORKAROUND_2를 준비하고, w1 레지스터에 @state를 지정한 뒤 EL2의 hvc 또는 EL3의 smc를 호출한다.

 

arm64_update_smccc_conduit()

arch/arm64/kernel/cpu_errata.c

void __init arm64_update_smccc_conduit(struct alt_instr *alt,
                                       __le32 *origptr, __le32 *updptr,
                                       int nr_inst)
{
        u32 insn;

        BUG_ON(nr_inst != 1);

        switch (psci_ops.conduit) {
        case PSCI_CONDUIT_HVC:
                insn = aarch64_insn_get_hvc_value();
                break;
        case PSCI_CONDUIT_SMC:
                insn = aarch64_insn_get_smc_value();
                break;
        default:
                return;
        }

        *updptr = cpu_to_le32(insn);
}

SSBD 기능을 적용할 수 있는 시스템에서 hvc 또는 smc 명령으로 대체한다.

 

arm64_enable_wa2_handling()

arch/arm64/kernel/cpu_errata.c

void __init arm64_enable_wa2_handling(struct alt_instr *alt,
                                      __le32 *origptr, __le32 *updptr,
                                      int nr_inst)
{
        BUG_ON(nr_inst != 1);
        /*
         * Only allow mitigation on EL1 entry/exit and guest
         * ARCH_WORKAROUND_2 handling if the SSBD state allows it to
         * be flipped.
         */
        if (arm64_get_ssbd_state() == ARM64_SSBD_KERNEL)
                *updptr = cpu_to_le32(aarch64_insn_gen_nop());
}

SSBD 기능을 적용할 수 있는 시스템에서 nop으로 대체한다.

 


EL1 Exception

커널 동작 중 sync exception

el1_sync

arch/arm64/kernel/entry.S

        .align  6
el1_sync:
        kernel_entry 1
        mrs     x1, esr_el1                     // read the syndrome register
        lsr     x24, x1, #ESR_ELx_EC_SHIFT      // exception class
        cmp     x24, #ESR_ELx_EC_DABT_CUR       // data abort in EL1
        b.eq    el1_da
        cmp     x24, #ESR_ELx_EC_IABT_CUR       // instruction abort in EL1
        b.eq    el1_ia
        cmp     x24, #ESR_ELx_EC_SYS64          // configurable trap
        b.eq    el1_undef
        cmp     x24, #ESR_ELx_EC_PC_ALIGN       // pc alignment exception
        b.eq    el1_pc
        cmp     x24, #ESR_ELx_EC_UNKNOWN        // unknown exception in EL1
        b.eq    el1_undef
        cmp     x24, #ESR_ELx_EC_BREAKPT_CUR    // debug exception in EL1
        b.ge    el1_dbg
        b       el1_inv

el1_ia:
        /*
         * Fall through to the Data abort case
         */
el1_da:
        /*
         * Data abort handling
         */
        mrs     x3, far_el1
        inherit_daif    pstate=x23, tmp=x2
        untagged_addr   x0, x3
        mov     x2, sp                          // struct pt_regs
        bl      do_mem_abort

        kernel_exit 1
el1_pc:
        /*
         * PC alignment exception handling. We don't handle SP alignment faults,
         * since we will have hit a recursive exception when trying to push the
         * initial pt_regs.
         */
        mrs     x0, far_el1
        inherit_daif    pstate=x23, tmp=x2
        mov     x2, sp
        bl      do_sp_pc_abort
        ASM_BUG()
el1_undef:
        /*
         * Undefined instruction
         */
        inherit_daif    pstate=x23, tmp=x2
        mov     x0, sp
        bl      do_undefinstr
        kernel_exit 1
el1_dbg:
        /*
         * Debug exception handling
         */
        cmp     x24, #ESR_ELx_EC_BRK64          // if BRK64
        cinc    x24, x24, eq                    // set bit '0'
        tbz     x24, #0, el1_inv                // EL1 only
        gic_prio_kentry_setup tmp=x3
        mrs     x0, far_el1
        mov     x2, sp                          // struct pt_regs
        bl      do_debug_exception
        kernel_exit 1
el1_inv:
        // TODO: add support for undefined instructions in kernel mode
        inherit_daif    pstate=x23, tmp=x2
        mov     x0, sp
        mov     x2, x1
        mov     x1, #BAD_SYNC
        bl      bad_mode
        ASM_BUG()
ENDPROC(el1_sync)

el1에서 동기 exception이 발생하여 진입한 경우이다.

  • 코드 라인 3에서 context 전환을 위해 레지스터들을 스택에 pt_regs 구조체 만큼 백업한다.
  • 코드 라인 4~18에서 esr_el1(Exception Syndrom Register EL1) 레지스터를 읽어와서(x1) EC(Exception Class) 필드 값에 따라 다음과 같이 처리한다.
    • ESR_ELx_EC_DABT_CUR (0x25)
      • Data Abort인 경우 el1_da 레이블로 이동한다.
    • ESR_ELx_EC_IABT_CUR (0x21)
      • Instruction Abort인 경우 el1_ia 레이블로 이동한다.
    • ESR_ELx_EC_SYS64 (0x18)
      • Configurable Trap인 경우 el1_undef 레이블로 이동한다.
    • ESR_ELx_EC_PC_ALIGN (0x22)
      • PC Alignment Exception인 경우 el1_pc 레이블로 이동한다.
    • ESR_ELx_EC_UNKNOWN (0x00)
      • Unknown Exception인 경우 el1_undef 레이블로 이동한다.
    • ESR_ELx_EC_BREAKPT_CUR (0x31)
    • ESR_ELx_EC_SOFTSTP_CUR (0x33)
    • ESR_ELx_EC_WATCHPT_CUR (0x35)
      • 싱글 스텝 디버거를 사용하는 몇 종류의 Exception(0x31~)들인 경우 el1_dbg 레이블로 이동한다.
    • 그 외는 모두 처리 불가능한 abort exception이며, el1_inv 레이블로 이동한다.
el1_ia & el1_da 레이블 – 명령 및 데이터 abort exception
  • 코드 라인 28에서 far_el1(Fault Address Register EL1) 레지스터에서 fault 발생한 가상 주소를 읽어온다.
  • 코드 라인 29에서 중단된 곳의 PSTATE 값(x23) 중 DAIF 비트를 현재 PSTATE에 반영(복구)한다.
  • 코드 라인 30~32에서 do_mem_abort() 함수를 호출하고, 세 개의 인자는 다음과 같다.
    • 첫 번째 인자(x0) 읽어온 fault 가상 주소에 address 태그가 있으면 제거한 가상 주소
    • 두 번째 인자(x1) ESR 값
    • 세 번째 인자(x2) 스택의 pt_regs
  • 코드 라인 34에서 exception을 빠져나가서 중단점으로 복귀하기 위해 context를 복구한다.
    • 스택에 저장해둔 레지스터들을 다시 읽어들인다.
el1_pc 레이블 – PC alignment exception
  • 코드 라인 41~44에서 dp_sp_pc_abort() 함수를 호출하여 “SP/PC alignment exception” 출력하고 스스로 다운(die)된다.
    • 참고로 초기 pt_reg를 푸시하려고 할 때 재귀 예외가 발생하는 문제로 스택 alignment exceptin은 처리하지 않고, pc alignment excption만 처리한다.
  • 코드 라인 45에서 스스로 break 디버그 exception을 발생시킨다. exception이 발생하면 ESR_ELx 레지스터에는 다음과 같은 정보가 담긴다.
    • ESR_ELx.EC = 0x3c
    • ESR_ELx.ISS = BUG_BRK_IMM(0x800)
el1_undef 레이블 – 아키텍처 미정의 명령 exception
  • 코드 라인 50~52에서 do_undefinstr() 함수를 호출하여 아키텍처가 디코드하여 수행할 수 없는 명령(instruction)에 대해 다음과 같이 구분하여 처리한다.
    • 해당 명령에 대해 등록된 후크가 있는 경우 해당 후크 함수를 호출한다.
      • 후크 함수의 등록은 register_undef_hook() 함수를 사용한다.
      • 예) ssbd 기능 호출 시 아키텍처에 hvc 또는 smc 명령이 없으면 이를 emulation 한다.
    • 유저 application에서 아키텍처에 없는 명령을 사용한 경우 SIGILL 시그널을 발생시켜 application을 kill 시킨다.
    • 커널 코드에서 아키텍처에 없는 명령을 사용한 경우 시스템을 die() 처리한다. (커널 옵션에 따라 리셋)
  • 코드 라인 53에서 exception을 빠져나가서 중단점으로 복귀하기 위해 context를 복구한다.
el1_dbg 레이블 – 디버그 exception
  • 코드 라인 58~60에서 Exception 신드롬 레지스터의 exception class 코드 값이 짝수인 경우 싱글 스텝 디버그 처리를 하지 않고, el1_inv 레이블로 이동한다. 그런데 ESR_ELx_EC_BRK64(0x3c)의 경우는 짝수지만 강제로 홀수로 만들어 el1_inv 레이블로 보내지 않는다.
  • 코드 라인 61에서 Pesudo-NMI를 지원하는 시스템인 경우 irq 우선순위를 통과시키도록 허용한다.
  • 코드 라인 62~64에서 다음 exception class 들에 대해 싱글 스텝 디버깅 처리를 위한 do_debug_exception() 함수를 호출한다.
    • ESR_ELx_EC_BREAKPT_CUR(0x31)
    • ESR_ELx_EC_SOFTSTP_CUR(0x33)
    • ESR_ELx_EC_WATCHPT_CUR(0x35)
    • ESR_ELx_EC_BRK64(0x3c)
  • 코드 라인 65에서 exception을 빠져나가서 중단점으로 복귀하기 위해 context를 복구한다.
el1_inv 레이블 –  그 외 처리 불가능 abort exception
  • 코드 라인 68~72에서 bad_mode() 함수를 호출한 후 panic() 처리한다. (커널 옵션에 따라 리셋)

 

inherit_daif 매크로

arch/arm64/include/asm/assembler.h

.       /* Only on aarch64 pstate, PSR_D_BIT is different for aarch32 */
        .macro  inherit_daif, pstate:req, tmp:req
        and     \tmp, \pstate, #(PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT)
        msr     daif, \tmp
        .endm

PSTATE 값이 담긴 @pstate 레지스터의 DAIF 플래그만을 PSTATE에 반영한다. 임시로 사용된 @tmp 레지스터는 크래시된다.

 

untagged_addr 매크로

arch/arm64/include/asm/asm-uaccess.h

/*
 * Remove the address tag from a virtual address, if present.
 */
        .macro  untagged_addr, dst, addr
        sbfx    \dst, \addr, #0, #56
        and     \dst, \dst, \addr
        .endm

FAR(Fault Address Register)에서 읽어온 가상 주소의 상위 8비트 address 태그가 존재하면 제거한다.

  • 코드 라인 2에서 @addr 값의 하위 56비트를 @dst로 복사한다.
    • @addr 값은 FAR(Fault Address Register)에서 읽은 가상 주소가 담겨 있고, 상위 8비트는 unknown 상태일 수도 있다.
  • 코드 라인 3에서 위의 값을 다시 @addr 값과 and 하여 unknown 비트들을 제거한다.

 

gic_prio_kentry_setup 매크로

arch/arm64/kernel/entry.S

        .macro  gic_prio_kentry_setup, tmp:req
#ifdef CONFIG_ARM64_PSEUDO_NMI
        alternative_if ARM64_HAS_IRQ_PRIO_MASKING
        mov     \tmp, #(GIC_PRIO_PSR_I_SET | GIC_PRIO_IRQON)
        msr_s   SYS_ICC_PMR_EL1, \tmp
        alternative_else_nop_endif
#endif
        .endm

Pesudo-NMI를 지원하는 시스템인 경우 nmi 뿐만 아니라 일반 irq도 허용하도록 pmr에 기록한다. @tmp 레지스터에는 플래그 값을 반환한다.

 


커널 동작 중 irq exception

el1_irq

arch/arm64/kernel/entry.S

        .align  6
el1_irq:
        kernel_entry 1
        gic_prio_irq_setup pmr=x20, tmp=x1
        enable_da_f

#ifdef CONFIG_ARM64_PSEUDO_NMI
        test_irqs_unmasked      res=x0, pmr=x20
        cbz     x0, 1f
        bl      asm_nmi_enter
1:
#endif

#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_off
#endif

        irq_handler

#ifdef CONFIG_PREEMPT
        ldr     x24, [tsk, #TSK_TI_PREEMPT]     // get preempt count
alternative_if ARM64_HAS_IRQ_PRIO_MASKING
        /*
         * DA_F were cleared at start of handling. If anything is set in DAIF,
         * we come back from an NMI, so skip preemption
         */
        mrs     x0, daif
        orr     x24, x24, x0
alternative_else_nop_endif
        cbnz    x24, 1f                         // preempt count != 0 || NMI return path
        bl      arm64_preempt_schedule_irq      // irq en/disable is done inside
1:
#endif

#ifdef CONFIG_ARM64_PSEUDO_NMI
        /*
         * When using IRQ priority masking, we can get spurious interrupts while
         * PMR is set to GIC_PRIO_IRQOFF. An NMI might also have occurred in a
         * section with interrupts disabled. Skip tracing in those cases.
         */
        test_irqs_unmasked      res=x0, pmr=x20
        cbz     x0, 1f
        bl      asm_nmi_exit
1:
#endif

#ifdef CONFIG_TRACE_IRQFLAGS
#ifdef CONFIG_ARM64_PSEUDO_NMI
        test_irqs_unmasked      res=x0, pmr=x20
        cbnz    x0, 1f
#endif
        bl      trace_hardirqs_on
1:
#endif

        kernel_exit 1
ENDPROC(el1_irq)

커널 동작 중 발생한 인터럽트(irq, nmi)를 처리한다.

  • 코드 라인 3에서 커널에서 exception이 발생하여 진입하였다. context를 스택에 백업한다.
  • 코드 라인 4에서 Pesudo-NMI를 지원하는 시스템에서 priority mask 레지스터에 @pmr 값을 기록한다.
  • 코드 라인 5에서 DAIF 중 I(irq)를 제외하고 enable(unmask) 한다.
  • 코드 라인 7~12에서 Pesudo-NMI 기능을 사용하는 시스템에서 nmi 진입 코드를 수행한다. Pesudo-NMI 기능을 사용하지 않는 시스템이거나 irq들이 unmask 상태인 경우 nmi 진입과 관련된 코드를 수행하지 않고 skip 한다.
  • 코드 라인 14~16에서 트레이스 출력이 허용된 경우에만 수행한다.
  • 코드 라인 18에서 irq 전용 스택으로 전환하고 인터럽트 컨트롤러의 irq 핸들러를 호출한다. 완료 후 태스크 스택으로 복원한다.
  • 코드 라인 20~33에서 커널 preemption이 가능한 커널 옵션을 사용하는 시스템에서 thread_info->preempt_count 값을 읽어 0인 경우 preemption이 요청되었다. 이러한 경우 우선 순위가 더 높은 태스크를 처리하기 위해 현재 태스크를 슬립하고 리스케줄한다. 만일 nmi 처리를 끝내고 돌아온 경우라면 DAIF 플래그 중 하나라도 설정되어 있다. 이 경우도 preemption 처리를 skip 한다.
  • 코드 라인 35~45에서 Pesudo-NMI 기능을 사용하는 시스템에서 nmi 처리가 끝난 경우 nmi 완료 코드를 수행한다. Pesudo-NMI 기능을 사용하지 않는 시스템이거나 irq들이 unmask 상태인 경우 nmi 처리 완료와 관련된 코드를 수행하지 않고 skip 한다.
  • 코드 라인 47~54에서 hard irq on에 대한 트레이스 출력을 수행한다.
  • 코드 라인 56에서 스택으로부터 context를 복원하고 exception이 발생하였던 중단점으로 돌아간다.

 

gic_prio_irq_setup 매크로

arch/arm64/kernel/entry.S

        .macro  gic_prio_irq_setup, pmr:req, tmp:req
#ifdef CONFIG_ARM64_PSEUDO_NMI
        alternative_if ARM64_HAS_IRQ_PRIO_MASKING
        orr     \tmp, \pmr, #GIC_PRIO_PSR_I_SET
        msr_s   SYS_ICC_PMR_EL1, \tmp
        alternative_else_nop_endif
#endif
        .endm

Pesudo-NMI를 지원하는 시스템에서 priority mask 레지스터에 @pmr 값을 기록한다.

  • 실제 기록 시 @pmr 값에 GIC_PRIO_PSR_I_SET(0x10) 비트를 더해 SYS_ICC_PMR_EL1에 기록한다.
  • GIC_PRIO_PSR_I_SET 값은 PSTATE의 I 비트 복원이 잘안되는 현상이 있어 정확한 복원을 위해 비트 확인이 가능하도록 추가하였다.

 

enable_da_f  매크로

arch/arm64/include/asm/assembler.h

        /* IRQ is the lowest priority flag, unconditionally unmask the rest. */
.       .macro enable_da_f
        msr     daifclr, #(8 | 4 | 1)
        .endm

PSTATE의 DA_F 플래그들을 클리어한다.

  • I(irq) 플래그는 처리하지 않는다.
  • 참고로 IRQ exception된 경우 아키텍처는 자동으로 I를 마스크하며 exception 진입된다.

 

test_irqs_unmasked 매크로

arch/arm64/kernel/entry.S

.       /*
         * Set res to 0 if irqs were unmasked in interrupted context.
         * Otherwise set res to non-0 value.
         */
        .macro  test_irqs_unmasked res:req, pmr:req
alternative_if ARM64_HAS_IRQ_PRIO_MASKING
        sub     \res, \pmr, #GIC_PRIO_IRQON
alternative_else
        mov     \res, xzr
alternative_endif
        .endm
#endif

Priority masking이 가능한 시스템인 경우 @pmr 값을 확인하여 irq가 unmask 상태인지 확인한다.

  • Priority masking이 가능한 시스템이 아닌 경우 항상 0을 반환한다.

 

irq_handler 매크로

arch/arm64/kernel/entry.S

/*
 * Interrupt handling.
 */
.       .macro  irq_handler
        ldr_l   x1, handle_arch_irq
        mov     x0, sp
        irq_stack_entry
        blr     x1
        irq_stack_exit
        .endm

인터럽트를 처리하기 위해 인터럽트 컨트롤러의 핸들러 함수를 호출한다.

  • 인터럽트 컨트롤러가 초기화될 때 전역 (*handle_arch_irq) 후크 함수에 해당 인터럽트 컨트롤러의 핸들러 함수가 대입된다.
    • 예) gic-v3 -> gic_of_init()

 


IRQ 전용 스택 사용

irq_stack_ptr

arch/arm64/kernel/irq.c

DEFINE_PER_CPU(unsigned long *, irq_stack_ptr);

컴파일 타임에 static하게 irq 스택을 가리키는 포인터가 cpu 마다 사용된다.

  • 초기화 함수는 init_IRQ() -> init_irq_stacks() 함수이다.
  • CONFIG_VMAP_STACK을 사용하는 경우 vmalloc 영역에 irq 스택을 할당하여 사용한다.

 

irq_stack

arch/arm64/kernel/irq.c

/* irq stack only needs to be 16 byte aligned - not IRQ_STACK_SIZE aligned. */
DEFINE_PER_CPU_ALIGNED(unsigned long [IRQ_STACK_SIZE/sizeof(long)], irq_stack);
  • CONFIG_VMAP_STACK을 사용하지 않는 경우 cpu 마다 컴파일 타임에 준비한 irq_stack을 사용한다.

 

irq_stack_entry 매크로

arch/arm64/kernel/entry.S

        .macro  irq_stack_entry
        mov     x19, sp                 // preserve the original sp

        /*
         * Compare sp with the base of the task stack.
         * If the top ~(THREAD_SIZE - 1) bits match, we are on a task stack,
         * and should switch to the irq stack.
         */
        ldr     x25, [tsk, TSK_STACK]
        eor     x25, x25, x19
        and     x25, x25, #~(THREAD_SIZE - 1)
        cbnz    x25, 9998f

        ldr_this_cpu x25, irq_stack_ptr, x26
        mov     x26, #IRQ_STACK_SIZE
        add     x26, x25, x26

        /* switch to the irq stack */
        mov     sp, x26
9998:
        .endm

인터럽트 컨트롤러의 핸들러 함수를 호출하기 전에 irq 스택으로 전환한다.

  • 코드 라인 2에서 스택을 x19에 백업해둔다.
  • 코드 라인 9~12에서 현재 스택이 thread_info->stack에서 지정한 태스크용 스택 범위 밖에 있는 경우 이미 irq 스택을 사용 중이므로 9998: 레이블로 이동한다.
  • 코드 라인 14~19에서 irq 스택으로 전환한다.

 

irq_stack_exit 매크로

arch/arm64/kernel/entry.S

        /*
         * x19 should be preserved between irq_stack_entry and
         * irq_stack_exit.
         */
.       .macro  irq_stack_exit
        mov     sp, x19
        .endm

인터럽트 컨트롤러의 핸들러 함수의 호출 이후 태스크 스택으로 복원한다.

  • 이 매크로와 한쌍인 irq_stack_entry 매크로에서 x19 레지스터를 사용하여 기존 스택을 백업해두었었다.

 


EL0 Exception

유저 동작 중 sync exception

el0_sync

arch/arm64/kernel/entry.S -1/3-

/*
 * EL0 mode handlers.
 */
        .align  6
el0_sync:
        kernel_entry 0
        mrs     x25, esr_el1                    // read the syndrome register
        lsr     x24, x25, #ESR_ELx_EC_SHIFT     // exception class
        cmp     x24, #ESR_ELx_EC_SVC64          // SVC in 64-bit state
        b.eq    el0_svc
        cmp     x24, #ESR_ELx_EC_DABT_LOW       // data abort in EL0
        b.eq    el0_da
        cmp     x24, #ESR_ELx_EC_IABT_LOW       // instruction abort in EL0
        b.eq    el0_ia
        cmp     x24, #ESR_ELx_EC_FP_ASIMD       // FP/ASIMD access
        b.eq    el0_fpsimd_acc
        cmp     x24, #ESR_ELx_EC_SVE            // SVE access
        b.eq    el0_sve_acc
        cmp     x24, #ESR_ELx_EC_FP_EXC64       // FP/ASIMD exception
        b.eq    el0_fpsimd_exc
        cmp     x24, #ESR_ELx_EC_SYS64          // configurable trap
        ccmp    x24, #ESR_ELx_EC_WFx, #4, ne
        b.eq    el0_sys
        cmp     x24, #ESR_ELx_EC_SP_ALIGN       // stack alignment exception
        b.eq    el0_sp
        cmp     x24, #ESR_ELx_EC_PC_ALIGN       // pc alignment exception
        b.eq    el0_pc
        cmp     x24, #ESR_ELx_EC_UNKNOWN        // unknown exception in EL0
        b.eq    el0_undef
        cmp     x24, #ESR_ELx_EC_BREAKPT_LOW    // debug exception in EL0
        b.ge    el0_dbg
        b       el0_inv

#ifdef CONFIG_COMPAT
        .align  6
el0_sync_compat:
        kernel_entry 0, 32
        mrs     x25, esr_el1                    // read the syndrome register
        lsr     x24, x25, #ESR_ELx_EC_SHIFT     // exception class
        cmp     x24, #ESR_ELx_EC_SVC32          // SVC in 32-bit state
        b.eq    el0_svc_compat
        cmp     x24, #ESR_ELx_EC_DABT_LOW       // data abort in EL0
        b.eq    el0_da
        cmp     x24, #ESR_ELx_EC_IABT_LOW       // instruction abort in EL0
        b.eq    el0_ia
        cmp     x24, #ESR_ELx_EC_FP_ASIMD       // FP/ASIMD access
        b.eq    el0_fpsimd_acc
        cmp     x24, #ESR_ELx_EC_FP_EXC32       // FP/ASIMD exception
        b.eq    el0_fpsimd_exc
        cmp     x24, #ESR_ELx_EC_PC_ALIGN       // pc alignment exception
        b.eq    el0_pc
        cmp     x24, #ESR_ELx_EC_UNKNOWN        // unknown exception in EL0
        b.eq    el0_undef
        cmp     x24, #ESR_ELx_EC_CP15_32        // CP15 MRC/MCR trap
        b.eq    el0_cp15
        cmp     x24, #ESR_ELx_EC_CP15_64        // CP15 MRRC/MCRR trap
        b.eq    el0_cp15
        cmp     x24, #ESR_ELx_EC_CP14_MR        // CP14 MRC/MCR trap
        b.eq    el0_undef
        cmp     x24, #ESR_ELx_EC_CP14_LS        // CP14 LDC/STC trap
        b.eq    el0_undef
        cmp     x24, #ESR_ELx_EC_CP14_64        // CP14 MRRC/MCRR trap
        b.eq    el0_undef
        cmp     x24, #ESR_ELx_EC_BREAKPT_LOW    // debug exception in EL0
        b.ge    el0_dbg
        b       el0_inv

el0에서 동기 exception이 발생하여 진입한 경우이다.

  • 코드 라인 3에서 context 전환을 위해 레지스터들을 스택에 pt_regs 구조체 만큼 백업한다.
  • 코드 라인 4~29에서 esr_el1(Exception Syndrom Register EL1) 레지스터를 읽어와서 EC(Exception Class) 필드 값에 따라 다음과 같이 처리한다.
    • ESR_ELx_EC_SVC64 (0x15)
      • EL0 AArch64 application에서 syscall 호출한 경우 el0_svc 레이블로 이동한다.
    • ESR_ELx_EC_DABT_LOW (0x24)
      • EL0에서 Data Abort인 경우 el0_da 레이블로 이동한다.
    • ESR_ELx_EC_IABT_LOW (0x20)
      • EL0에서 Instruction Abort인 경우 el0_ia 레이블로 이동한다.
    • ESR_ELx_EC_FP_ASIMD (0x07)
      • Advanced SIMD 또는 부동 소숫점 명령 사용 시 트랩된 경우 el0_fpsimd_acc 레이블로 이동한다.
    • ESR_ELx_EC_SVE (0x19)
      • SVE 기능으로 트랩된 경우 el0_sve_acc 레이블로 이동한다.
    • ESR_ELx_EC_FP_EXC64 (0x2c)
      • 부동 소숫점 명령으로 exception 발생 시 el0_fpsimd_exc 레이블로 이동한다.
    • ESR_ELx_EC_SYS64 (0x18)
      • MSR 및 MRS 명령에 의해 트랩되었고
    • ESR_ELx_EC_WFx (0x1)
      • WFI 또는 WFE 명령으로 트랩된 경우가 아니면 el0_sys 레이블로 이동한다.
    • ESR_ELx_EC_SP_ALIGN (0x26)
      • SP Alignment Exception인 경우 el0_sp 레이블로 이동한다.
    • ESR_ELx_EC_PC_ALIGN (0x22)
      • PC Alignment Exception인 경우 el0_pc 레이블로 이동한다.
    • ESR_ELx_EC_UNKNOWN (0x00)
      • Unknown Exception인 경우 el0_undef 레이블로 이동한다.
    • ESR_ELx_EC_BREAKPT_LOW (0x30)
    • ESR_ELx_EC_SOFTSTP_LOW (0x32)
    • ESR_ELx_EC_WATCHPT_LOW (0x34)
    • ESR_ELx_EC_BRK64 (0x3c)
      • EL0에서 싱글 스텝 디버거를 사용한 트랩인 경우 el0_dbg 레이블로 이동한다.
    • 그 외는 모두 처리 불가능한 abort exception이며, el0_inv 레이블로 이동한다.
  • 코드 라인 34에서 context 전환을 위해 레지스터들을 스택에 pt_regs 구조체 만큼 백업한다.
  • 코드 라인 35~63에서 esr_el1(Exception Syndrom Register EL1) 레지스터를 읽어와서 EC(Exception Class) 필드 값에 따라 다음과 같이 처리한다.
    • ESR_ELx_EC_SVC32 (0x11)
      • AArch32 application에서 syscall 호출한 경우 el0_svc_compat 레이블로 이동한다.
    • ESR_ELx_EC_DABT_LOW (0x24)
      • EL0에서 Data Abort인 경우 el0_da 레이블로 이동한다.
    • ESR_ELx_EC_IABT_LOW (0x20)
      • EL0에서 Instruction Abort인 경우 el0_ia 레이블로 이동한다.
    • ESR_ELx_EC_FP_ASIMD (0x07)
      • Advanced SIMD 또는 부동 소숫점 명령 사용 시 트랩된 경우 el0_fpsimd_acc 레이블로 이동한다.
    • ESR_ELx_EC_FP_EXC32 (0x28)
      • 부동 소숫점 명령으로 exception 발생 시 el0_fpsimd_exc 레이블로 이동한다.
    • ESR_ELx_EC_PC_ALIGN (0x22)
      • PC Alignment Exception인 경우 el0_pc 레이블로 이동한다.
    • ESR_ELx_EC_UNKNOWN (0x00)
      • Unknown Exception인 경우 el0_undef 레이블로 이동한다.
    • ESR_ELx_EC_CP15_32 (0x03)
    • ESR_ELx_EC_CP15_64 (0x04)
      • CP15 명령으로 트랩된 경우 el0_cp15 레이블로 이동한다.
    • ESR_ELx_EC_CP14_MR (0x05)
    • ESR_ELx_EC_CP14_LS (0x06)
    • ESR_ELx_EC_CP14_64 (0x0c)
      • CP14 명령으로 트랩된 경우 el0_undef 레이블로 이동한다.
    • ESR_ELx_EC_BREAKPT_LOW (0x30)
    • ESR_ELx_EC_SOFTSTP_LOW (0x32)
    • ESR_ELx_EC_WATCHPT_LOW (0x34)
    • ESR_ELx_EC_BRK64 (0x3c)
      • EL0에서 싱글 스텝 디버거를 사용한 트랩인 경우 el0_dbg 레이블로 이동한다.
    • 그 외는 모두 처리 불가능한 abort exception이며, el0_inv 레이블로 이동한다.

 

arch/arm64/kernel/entry.S -2/3-

el0_svc_compat:
        gic_prio_kentry_setup tmp=x1
        mov     x0, sp
        bl      el0_svc_compat_handler
        b       ret_to_user

        .align  6
el0_cp15:
        /*
         * Trapped CP15 (MRC, MCR, MRRC, MCRR) instructions
         */
        ct_user_exit_irqoff
        enable_daif
        mov     x0, x25
        mov     x1, sp
        bl      do_cp15instr
        b       ret_to_user
#endif

el0_da:
        /*
         * Data abort handling
         */
        mrs     x26, far_el1
        ct_user_exit_irqoff
        enable_daif
        untagged_addr   x0, x26
        mov     x1, x25
        mov     x2, sp
        bl      do_mem_abort
        b       ret_to_user
el0_ia:
        /*
         * Instruction abort handling
         */
        mrs     x26, far_el1
        gic_prio_kentry_setup tmp=x0
        ct_user_exit_irqoff
        enable_da_f
#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_off
#endif
        mov     x0, x26
        mov     x1, x25
        mov     x2, sp
        bl      do_el0_ia_bp_hardening
        b       ret_to_user
el0_fpsimd_acc:
        /*
         * Floating Point or Advanced SIMD access
         */
        ct_user_exit_irqoff
        enable_daif
        mov     x0, x25
        mov     x1, sp
        bl      do_fpsimd_acc
        b       ret_to_user
el0_sve_acc:
        /*
         * Scalable Vector Extension access
         */
        ct_user_exit_irqoff
        enable_daif
        mov     x0, x25
        mov     x1, sp
        bl      do_sve_acc
        b       ret_to_user
el0_fpsimd_exc:
        /*
         * Floating Point, Advanced SIMD or SVE exception
         */
        ct_user_exit_irqoff
        enable_daif
        mov     x0, x25
        mov     x1, sp
        bl      do_fpsimd_exc
        b       ret_to_user
el0_svc_compat 레이블 – AArch32에서 SVC 호출
  • 코드 라인 2에서 Pesudo-NMI를 지원하는 시스템인 경우 irq 우선순위를 통과시키도록 허용한다.
  • 코드 라인 3~4에서 el0_svc_compat_handler() 함수를 호출하여 syscall 서비스를 수행한다.
  • 코드 라인 5에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_cp15 레이블 – CP15 (MRC, MCR, MRRC, MCRR) 트랩
  • 코드 라인 12에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 13에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 14~16에서 do_cp15instr() 함수를 호출한다.
    • 유저 application에서 타이머 카운터(CNTVCT) 레지스터 값을 읽기 위해 트랩을 사용하였다.
  • 코드 라인 17에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_da 레이블 – Data Abort exception
  • 코드 라인 24에서 far_el1(Fault Address Register EL1) 레지스터에서 fault 발생한 가상 주소를 읽어온다.
  • 코드 라인 25에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 26에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 27~30에서 do_mem_abort() 함수를 호출하고, 세 개의 인자는 다음과 같다.
    • 첫 번째 인자(x0) 읽어온 fault 가상 주소에 address 태그가 있으면 제거한 가상 주소
    • 두 번째 인자(x1) ESR(Exception Syndrom Register) 값
    • 세 번째 인자(x2) 스택의 pt_regs
  • 코드 라인 31에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_ia 레이블 – Instruction Abort exception
  • 코드 라인 36에서 far_el1(Fault Address Register EL1) 레지스터에서 fault 발생한 가상 주소를 읽어온다.
  • 코드 라인 37에서 Pesudo-NMI를 지원하는 시스템인 경우 irq 우선순위를 통과시키도록 허용한다.
  • 코드 라인 38에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 39에서 PSTATE의 DA_F를 클리어하여 인터럽트만 제외하고 exception을 허용하게 한다.
  • 코드 라인 40~42에서 트레이스 출력이 허용된 경우에만 수행한다.
  • 코드 라인 43~46에서 do_el0_ia_bp_hardening() 함수를 호출하고, 세 개의 인자는 다음과 같다.
    • 첫 번째 인자(x0) 읽어온 fault 가상 주소에 address 태그가 있으면 제거한 가상 주소
    • 두 번째 인자(x1) ESR(Exception Syndrom Register) 값
    • 세 번째 인자(x2) 스택의 pt_regs
  • 코드 라인 47에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_fpsimd_acc 레이블 – 부동 소숫점 또는 Advanced SIMD 트랩
  • 코드 라인 52에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 54에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 54~56에서 do_fpsimd_acc() 함수를 호출한다. 함수 내부는 경고만 출력한다.
  • 코드 라인 57에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_sve_acc 레이블 – SVE 호출
  • 코드 라인 62에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 63에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 64~66에서 do_sve_acc() 함수를 호출하여 태스크의 첫 호출인 경우 SVE를 지원하기 위해 준비 작업을 수행한다.
    • SVE(Scalable Vector Extension)는 ARMv8.2 아키텍처 이상에서 Advanced SIMD의 벡터를 더 wide한 벡터를 지원한다.
  • 코드 라인 67에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_fpsimd_exc 레이블 – 부동 소숫점 또는 Advanced SIMD exception
  • 코드 라인 72에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 73에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 74~76에서 do_fpsimd_exc() 함수를 호출하여 현재 태스크에 SIGFPE 시그널을 발생시킨다.
  • 코드 라인 77에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.

 

arch/arm64/kernel/entry.S -3/3-

el0_sp:
        ldr     x26, [sp, #S_SP]
        b       el0_sp_pc
el0_pc:
        mrs     x26, far_el1
el0_sp_pc:
        /*
         * Stack or PC alignment exception handling
         */
        gic_prio_kentry_setup tmp=x0
        ct_user_exit_irqoff
        enable_da_f
#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_off
#endif
        mov     x0, x26
        mov     x1, x25
        mov     x2, sp
        bl      do_sp_pc_abort
        b       ret_to_user
el0_undef:
        /*
         * Undefined instruction
         */
        ct_user_exit_irqoff
        enable_daif
        mov     x0, sp
        bl      do_undefinstr
        b       ret_to_user
el0_sys:
        /*
         * System instructions, for trapped cache maintenance instructions
         */
        ct_user_exit_irqoff
        enable_daif
        mov     x0, x25
        mov     x1, sp
        bl      do_sysinstr
        b       ret_to_user
el0_dbg:
        /*
         * Debug exception handling
         */
        tbnz    x24, #0, el0_inv                // EL0 only
        mrs     x24, far_el1
        gic_prio_kentry_setup tmp=x3
        ct_user_exit_irqoff
        mov     x0, x24
        mov     x1, x25
        mov     x2, sp
        bl      do_debug_exception
        enable_da_f
        b       ret_to_user
el0_inv:
        ct_user_exit_irqoff
        enable_daif
        mov     x0, sp
        mov     x1, #BAD_SYNC
        mov     x2, x25
        bl      bad_el0_sync
        b       ret_to_user
ENDPROC(el0_sync)
el0_sp 레이블 – SP alignment exection
  • 코드 라인 2~3에서 스택에 저장한 중단점에서의 스택 값을 읽은 후 아래 el0_sp_pc: 레이블로 이동한다.
el0_pc 레이블 – PC alignment exection
  • 코드 라인 5에서 far_el1(Fault Address Register EL1) 레지스터에서 fault 발생한 가상 주소를 읽어온다.
  • 코드 라인 6에서 el0_sp와 el0_pc가 공동으로 사용하는 el0_sp_pc 레이블이다.
  • 코드 라인 10에서 Pesudo-NMI를 지원하는 시스템인 경우 irq 우선순위를 통과시키도록 허용한다.
  • 코드 라인 11에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 12에서 PSTATE의 DA_F를 클리어하여 인터럽트만 제외하고 exception을 허용하게 한다.
  • 코드 라인 13~15에서 트레이스 출력이 허용된 경우에만 수행한다.
  • 코드 라인 16~19에서 do_sp_pc_abort() 함수를 호출하여 현재 태스크에 SIGKILL을 발생시킨다. 그리고, 세 개의 인자는 다음과 같다.
    • 첫 번째 인자(x0) 읽어온 fault 가상 주소에 address 태그가 있으면 제거한 가상 주소
    • 두 번째 인자(x1) ESR(Exception Syndrom Register) 값
    • 세 번째 인자(x2) 스택의 pt_regs
  • 코드 라인 20에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_undef 레이블 – Undefined instruction exception
  • 코드 라인 25에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 26에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 27~28에서 do_undefinstr() 함수를 호출하여 특정 명령에 대해 등록된 후크 함수를 호출한다. 만일 그러한 후크 함수가 없으면 현재 태스크에 SIGILL을 발생시킨다.
  • 코드 라인 29에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_sys 레이블 – 시스템 명령 트랩
  • 코드 라인 34에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 35에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 36~38에서 do_sysinstr() 함수를 호출하여 특정 시스템 명령에 대해 등록된 후크 함수를 호출한다. 만일 그러한 후크 함수가 없으면 현재 태스크에 SIGILL을 발생시킨다.
  • 코드 라인 39에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_dbg 레이블 – 디버그 트랩
  • 코드 라인 44에서 el0인 경우 el0_inv 레이블로 이동한다.
  • 코드 라인 45에서 far_el1(Fault Address Register EL1) 레지스터에서 fault 발생한 가상 주소를 읽어온다.
  • 코드 라인 46에서 Pesudo-NMI를 지원하는 시스템인 경우 irq 우선순위를 통과시키도록 허용한다.
  • 코드 라인 47에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 48~51에서 do_debug_exception() 함수를 호출한다.
  • 코드 라인 52에서 PSTATE의 DA_F를 클리어하여 인터럽트만 제외하고 exception을 허용하게 한다.
  • 코드 라인 53에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.
el0_inv 레이블 – 그 외 처리 불가능 abort exception
  • 코드 라인 55에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 56에서 PSTATE의 DAIF를 클리어하여 인터럽트 등을 허용하게 한다.
  • 코드 라인 57~60에서 bad_el0_sync 함수를 호출하여 현재 태스크에 SIGILL을 발생시킨다.
  • 코드 라인 61에서 exception을 빠져나가서 유저 중단점으로 복귀하기 위해 pending 작업을 처리한 후 context를 복구한다.

 


유저 동작 중 irq exception

el0_irq

arch/arm64/kernel/entry.S

        .align  6
el0_irq:
        kernel_entry 0
el0_irq_naked:
        gic_prio_irq_setup pmr=x20, tmp=x0
        ct_user_exit_irqoff
        enable_da_f

#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_off
#endif

#ifdef CONFIG_HARDEN_BRANCH_PREDICTOR
        tbz     x22, #55, 1f
        bl      do_el0_irq_bp_hardening
1:
#endif
        irq_handler

#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_on
#endif
        b       ret_to_user
ENDPROC(el0_irq)

64비트 유저 동작 중 발생한 인터럽트(irq, nmi)를 처리한다.

  • 코드 라인 3에서 64비트 유저에서 exception이 발생하여 진입하였다. context를 스택에 백업한다.
  • 코드 라인 4에서 32비트 유저 irq exception도 이곳에서 공통으로 처리하기 위해 진입을 위한 el0_irq_naked 레이블이다.
  • 코드 라인 5에서 Pesudo-NMI를 지원하는 시스템에서 priority mask 레지스터에 @pmr 값을 기록한다.
  • 코드 라인 6에서 유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.
  • 코드 라인 7에서 PSTATE의 DAIF 중 I(irq)를 제외하고 enable(unmask) 한다.
  • 코드 라인 9~11에서 hardirq off에 대해 트레이스 출력이 허용된 경우에만 수행한다.
  • 코드 라인 13~17에서 BP hardning을 위해 do_el0_irq_bp_hardening() 함수를 호출한다.
  • 코드 라인 18에서 irq 전용 스택으로 전환하고 인터럽트 컨트롤러의 irq 핸들러를 호출한다. 완료 후 태스크 스택으로 복원한다.
  • 코드 라인 20~22에서 hardirq on에 대해 트레이스 출력이 허용된 경우에만 수행한다.
  • 코드 라인 23에서 스택으로부터 context를 복원하고 exception이 발생하였던 중단점으로 돌아간다.

 

AArch32 EL0 irq exception

el0_irq_compat

arch/arm64/kernel/entry.S

        .align  6
el0_irq_compat:
        kernel_entry 0, 32
        b       el0_irq_naked

32bit 유저 동작 중 발생한 인터럽트(irq, nmi)를 처리한다.

  • 코드 라인 3에서 32비트 유저에서 exception이 발생하여 진입하였다. context를 스택에 백업한다.
  • 코드 라인 4에서 el0_irq_naked 레이블로 이동한다.

 


BP(Branch Predictor) Hardening

일부 고성능 ARM64 시스템의 경우 BP(Branch Predictor)를 사용한 Speculation 부채널 공격(side channel Attck)을 통해 시스템의 보안이 뚫리는 경우가 발생하여 이를 보완하기 위해 BP를 숨기는 기능을 수행한다.

 

do_el0_ia_bp_hardening()

arch/arm64/mm/fault.c

asmlinkage void __exception do_el0_ia_bp_hardening(unsigned long addr,
                                                   unsigned int esr,
                                                   struct pt_regs *regs)
{
        /*
         * We've taken an instruction abort from userspace and not yet
         * re-enabled IRQs. If the address is a kernel address, apply
         * BP hardening prior to enabling IRQs and pre-emption.
         */
        if (!is_ttbr0_addr(addr))
                arm64_apply_bp_hardening();

        local_daif_restore(DAIF_PROCCTX);
        do_mem_abort(addr, esr, regs);
}

유저 모드에서 instruction abort exception이 발생한 경우 bp hardning 기능을 수행한 후 메모리 fault 처리를 수행한다.

  • 코드 라인 10~11에서 fault가 발생한 주소가 유저 주소인 경우 bp hardning을 수행한다.
  • 코드 라인 13에서 PSTATE의 DAIF를 모두 클리어해 인터럽트 등을 허용한다.
  • 코드 라인 14에서 fault 처리를 수행한다.

 

is_ttbr0_addr()

arch/arm64/mm/fault.c

static inline bool is_ttbr0_addr(unsigned long addr)
{
        /* entry assembly clears tags for TTBR0 addrs */
        return addr < TASK_SIZE;
}

유저 가상 주소인지 여부를 반환한다.

 

arm64_apply_bp_hardening()

arch/arm64/include/asm/mmu.h

static inline void arm64_apply_bp_hardening(void)
{
        struct bp_hardening_data *d;

        if (!cpus_have_const_cap(ARM64_HARDEN_BRANCH_PREDICTOR))
                return;

        d = arm64_get_bp_hardening_data();
        if (d->fn)
                d->fn();
}

ARM64_HARDEN_BRANCH_PREDICTOR 기능(capability)을 가진 시스템에서 workround가 필요한 경우 호출된다.

  • Qualcom FALKOR 아키텍처의 경우다음 함수가 호출된다.
    • qcom_link_stack_sanitization()
  • 그 외 smccc 호출을 통해 해당 아키텍처가 workaround가 필요한 경우 다음 함수중 하나가 호출된다.
    • call_hvc_arch_workaround_1()
    • call_smc_arch_workaround_1()

 

arm64_get_bp_hardening_data()

arch/arm64/include/asm/mmu.h

static inline struct bp_hardening_data *arm64_get_bp_hardening_data(void)
{
        return this_cpu_ptr(&bp_hardening_data);
}

 

bp_hardening_data

arch/arm64/kernel/cpu_errata.c

DEFINE_PER_CPU_READ_MOSTLY(struct bp_hardening_data, bp_hardening_data);

 

bp_hardening_data 구조체

arch/arm64/include/asm/mmu.h

struct bp_hardening_data {
        int                     hyp_vectors_slot;
        bp_hardening_cb_t       fn;
};
  • hyp_vectors_slot
    • KVM을 사용하는 경우 사용될 4개의 하이퍼 바이저 벡터용 슬롯
  •  fn
    • Branch Predict 숨김 기능을 위해 workaround 기능을 수행할 콜백 함수가 담긴다.

 


유저 복귀

ct_user_exit_irqoff 매크로

arch/arm64/kernel/entry.S

/*
 * Context tracking subsystem.  Used to instrument transitions
 * between user and kernel mode.
 */
        .macro ct_user_exit_irqoff
#ifdef CONFIG_CONTEXT_TRACKING
        bl      enter_from_user_mode
#endif
        .endm

유저 모드에서 커널 모드 진입에 따른 디버그용 Context 트래킹을 호출한다.

  • 유저 사용 시간 산출 및 트레이스 출력을 수행한다.

 

ret_to_user & finish_ret_to_user 레이블

arch/arm64/kernel/entry.S

/*
 * "slow" syscall return path.
 */
ret_to_user:
        disable_daif
        gic_prio_kentry_setup tmp=x3
        ldr     x1, [tsk, #TSK_TI_FLAGS]
        and     x2, x1, #_TIF_WORK_MASK
        cbnz    x2, work_pending
finish_ret_to_user:
        enable_step_tsk x1, x2
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
        bl      stackleak_erase
#endif
        kernel_exit 0
ENDPROC(ret_to_user)

exception을 빠져나가서 유저 중단점으로 복귀하기 전에 pending 작업을 처리한 후 context를 복구한다. pending 작업들에는 리스케줄, 시그널 처리 등이 있다.

  • 코드 라인 2에서 PSTATE의 DAIF를 마스크하여 인터럽트 등을 허용하지 않는다.
  • 코드 라인 3에서 Pesudo-NMI를 지원하는 시스템인 경우 irq 우선순위를 통과시키도록 허용한다.
  • 코드 라인 4~6에서 thread_info->flag에 _TIF_WORK_MASK에 해당하는 플래그들이 있는 경우 이에 대한 pending 작업을 수행한다.
  • 코드 라인 8에서 현재 태스크에 싱글 스텝 기능이 꺼져 있으면 활성화시킨다.
  • 코드 라인 9~11에서 보안을 위해 위해 syscall 호출후 복귀전에 커널 스택의 빈 공간을 STACKLEAK_POISON(-0xBEEF) 값으로 클리어한다.
  • 코드 라인 12에서 스택으로부터 context를 복원하고 exception이 발생하였던 중단점으로 돌아간다.

 

_TIF_WORK_MASK

arch/arm64/include/asm/thread_info.h

#define _TIF_WORK_MASK          (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \
                                 _TIF_NOTIFY_RESUME | _TIF_FOREIGN_FPSTATE | \
                                 _TIF_UPROBE | _TIF_FSCHECK)

커널에서 유저로 Context 복귀하기 전에 수행할 작업에 대한 플래그들이다.

 

work_pending 레이블

arch/arm64/kernel/entry.S

/*
 * Ok, we need to do extra processing, enter the slow path.
 */
work_pending:
        mov     x0, sp                          // 'regs'
        bl      do_notify_resume
#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_on               // enabled while in userspace
#endif
        ldr     x1, [tsk, #TSK_TI_FLAGS]        // re-check for single-step
        b       finish_ret_to_user

pending된 작업을 처리한다. (Slowpath 작업이므로 EL0 복귀 시에만 수행한다.)

  • 코드 라인 2~3에서 pending된 작업을 처리한다. 인자로 pt_regs와 thread_info->flag를 사용한다.
  • 코드 라인 4~6에서 hard irq on에 대한 트레이스 출력을 수행한다.
  • 코드 라인 7~8에서 thread_info->flags 값을 x1 레지스터로 다시 읽어들인 후 finish_ret_to_user 레이블로 이동한다.

 

enable_step_tsk 매크로

arch/arm64/include/asm/assembler.h

        /* call with daif masked */
        .macro  enable_step_tsk, flgs, tmp
        tbz     \flgs, #TIF_SINGLESTEP, 9990f
        mrs     \tmp, mdscr_el1
        orr     \tmp, \tmp, #DBG_MDSCR_SS
        msr     mdscr_el1, \tmp
9990:
        .endm

현재 태스크에 싱글 스텝 기능이 꺼져 있으면 활성화시킨다.

  • 코드 라인 3에서 @flgs(thread_info->flag) 값에 TIF_SINGLESTEP 값이 없으면 9990 레이블로 이동한다.
  • 코드 라인 4~6에서 MDSCR_EL1(Monitor Debug System Control Register)의 SS 비트를 설정하여 software step 기능을 활성화한다. @tmp 레지스터는 스크래치 레지스터로 이용된다.

 

참고

 

3 thoughts to “Exception -8- (ARM64 Handler)”

  1. 안녕하세요, 문영일님! 16차 이파란입니다.

    코드 라인 26에서 중단되어 복귀할 주소(pc -> elr_el1)가 담긴 x22 레지스터와 중단될 때의 상태(spsr) 값이 담긴 x23 레지스터를 pt_regs.pc와 pt_regs->psr 위치에 저장한다.
    >> pt_regs->psr 가 pt_regs->pstate 인가요?

    ARM64 pt_regs 구조체
    struct pt_regs {
    union {
    struct user_pt_regs user_regs;
    struct {
    u64 regs[31];
    u64 sp;
    u64 pc;
    u64 pstate;
    };
    };
    u64 orig_x0;
    s32 syscallno;
    u32 unused2;
    u64 orig_addr_limit;
    u64 pmr_save;
    u64 stackframe[2];
    }

  2. 안녕하세요? 이파란님.

    말씀하신 것처럼 pt_regs->psr이 오타고, pt_regs->pstate가 맞습니다. 본문에서도 정정했습니다.
    arm32에서 psr이라고 했었었죠. ^^;
    aarch64에서는 pstate인데 그걸 실수로 psr이라고 그냥 설명했군요.

    감사합니다. ^^

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다