<kernel v5.4>
Data Abort 핸들러
__dabt_usr
arch/arm/kernel/entry-armv.S
.align 5 __dabt_usr: usr_entry uaccess=0 kuser_cmpxchg_check mov r2, sp dabt_helper b ret_from_exception UNWIND(.fnend ) ENDPROC(__dabt_usr)
user 모드에서 data abort exception을 만나 진입하게 되면 data abort 핸들러를 수행한 후 다시 user 모드로 복귀한다.
- 코드 라인 3에서 전체 레지스터를 스택에 pt_regs 구조체 순서로 백업한다.
- 코드 라인 4에서 atomic 연산을 지원하지 못하는 아키텍처에서 atomic 하게 처리해야 하는 구간에서 인터럽트를 맞이하고 복귀할 때 그 atomic operation 구간의 시작부분으로 다시 돌아가도록 pt_regs의 pc를 조작한다.
- 코드 라인 5~6에서 r2 레지스터에 스택 위치를 담고 data abort 핸들러를 호출한다.
- 코드 라인 7에서 스택에 백업해둔 레지스터들을 다시 불러 읽은 후 user 모드로 복귀한다.
dabt_helper 매크로
arch/arm/kernel/entry-armv.S
.macro dabt_helper @ @ Call the processor-specific abort handler: @ @ r2 - pt_regs @ r4 - aborted context pc @ r5 - aborted context psr @ @ The abort handler must return the aborted address in r0, and @ the fault status register in r1. r9 must be preserved. @ #ifdef MULTI_DABORT ldr ip, .LCprocfns mov lr, pc ldr pc, [ip, #PROCESSOR_DABT_FUNC] #else bl CPU_DABORT_HANDLER #endif .endm
data abort 핸들러 함수를 호출한다.
- 코드 라인 13~16에서 data abort 핸들러가 2 개 이상 있어야 하는 경우 MULTI_DABORT가 설정된다.
- 코드 라인 17~19에서 빌드 타임에 정해진 아키텍처의 data abort 핸들러를 호출한다.
.LCprocfns
arch/arm/kernel/entry-armv.S
#ifdef MULTI_DABORT .LCprocfns: .word processor #endif
CPU_DABORT_HANDLER
arch/arm/include/asm/glue-df.h
#ifdef CONFIG_CPU_ABRT_EV7 # ifdef CPU_DABORT_HANDLER # define MULTI_DABORT 1 # else # define CPU_DABORT_HANDLER v7_early_abort # endif #endif
빌드타임에 아키텍처가 ARMv7으로 정해진 경우 data abort 핸들러 함수는 v7_early_abort() 이다.
v7_early_abort()
arch/arm/mm/abort-ev7.S
/* * Function: v7_early_abort * * Params : r2 = pt_regs * : r4 = aborted context pc * : r5 = aborted context psr * * Returns : r4 - r11, r13 preserved * * Purpose : obtain information about current aborted instruction. */
.align 5 ENTRY(v7_early_abort) mrc p15, 0, r1, c5, c0, 0 @ get FSR mrc p15, 0, r0, c6, c0, 0 @ get FAR uaccess_disable ip @ disable userspace access /* * V6 code adjusts the returned DFSR. * New designs should not need to patch up faults. */ #if defined(CONFIG_VERIFY_PERMISSION_FAULT) /* * Detect erroneous permission failures and fix */ ldr r3, =0x40d @ On permission fault and r3, r1, r3 cmp r3, #0x0d bne do_DataAbort mcr p15, 0, r0, c7, c8, 0 @ Retranslate FAR isb mrc p15, 0, ip, c7, c4, 0 @ Read the PAR and r3, ip, #0x7b @ On translation fault cmp r3, #0x0b bne do_DataAbort bic r1, r1, #0xf @ Fix up FSR FS[5:0] and ip, ip, #0x7e orr r1, r1, ip, LSR #1 #endif b do_DataAbort ENDPROC(v7_early_abort)
data abort exception 상황이 발생 시 DFSR(Data Fault Status Register)에서 읽은 상태 코드를 갖고 doDataAbort() 함수를 호출하여 관련 fault 처리기를 호출한다.
- 코드 라인 3에서 DFSR(Data Fault Status Register)를 통해 data abort exception에 대한 상태 코드를 읽어 r1 레지스터에 대입한다.
- 코드 라인 4에서 DFAR(Data Fault Address Register)를 통해 data abort exception 당시의 물리 주소를 읽어 r0 레지스터에 대입한다.
- 코드 라인 12~30에서 CONFIG_VERIFY_PERMISSION_FAULT 커널 옵션은 스냅드래곤의 qsd8x60 SoC의 오류를 교정하기 위해 사용된다.
- 코드 라인 32에서 data fault 상태 코드에 따른 fault 처리기를 호출한다.
do_DataAbort()
arch/arm/mm/fault.c
/* * Dispatch a data abort to the relevant handler. */
asmlinkage void do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { const struct fsr_info *inf = fsr_info + fsr_fs(fsr); if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs)) return; pr_alert("8<--- cut here ---\n"); pr_alert("Unhandled fault: %s (0x%03x) at 0x%08lx\n", inf->name, fsr, addr); show_pte(KERN_ALERT, current->mm, addr); arm_notify_die("", regs, inf->sig, inf->code, (void __user *)addr, fsr, 0); }
DFSR(Data Fault Status Register)로 부터 읽은 5비트로 구성된 fault 상태 코드에 따른 fault 핸들러 함수를 호출한다.
- 코드 라인 4~7에서 fsr을 인덱스로 fsr_info[] 배열에서 fsr_info 구조체에 설정된 fault 핸들러 함수를 실행한다. 만일 결과가 성공(0)인 경우 함수를 빠져나간다.
- 코드 라인 9~12에서 “Unhandled fault: ……” 크리티컬 메시지 와 pte 정보를 출력한다.
- 코드 라인 14~15에서 관련 태스크의 종료 처리를 요청한다.
- 유저 태스크인 경우 시그널을 보내 종료 처리를 하고, 커널 태스크인경우 시스템의 die() 처리를 진행한다.
- 유저 태스크에 보낼 시그널은 fsr_info 구조체에 설정된 sig 값을 가져온다. (SIGSEGV, SIGBUS, SIGKILL 중 하나)
- 유저 태스크인 경우 시그널을 보내 종료 처리를 하고, 커널 태스크인경우 시스템의 die() 처리를 진행한다.
fsr_info[]
arch/arm/mm/fsr-2level.c
static struct fsr_info fsr_info[] = { . /* * The following are the standard ARMv3 and ARMv4 aborts. ARMv5 * defines these to be "precise" aborts. */ { do_bad, SIGSEGV, 0, "vector exception" }, { do_bad, SIGBUS, BUS_ADRALN, "alignment exception" }, { do_bad, SIGKILL, 0, "terminal exception" }, { do_bad, SIGBUS, BUS_ADRALN, "alignment exception" }, { do_bad, SIGBUS, 0, "external abort on linefetch" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" }, { do_bad, SIGBUS, 0, "external abort on linefetch" }, { do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" }, { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, { do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" }, { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, { do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" }, /* * The following are "imprecise" aborts, which are signalled by bit * 10 of the FSR, and may not be recoverable. These are only * supported if the CPU abort handler supports bit 10. */ { do_bad, SIGBUS, 0, "unknown 16" }, { do_bad, SIGBUS, 0, "unknown 17" }, { do_bad, SIGBUS, 0, "unknown 18" }, { do_bad, SIGBUS, 0, "unknown 19" }, { do_bad, SIGBUS, 0, "lock abort" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 21" }, { do_bad, SIGBUS, BUS_OBJERR, "imprecise external abort" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 23" }, { do_bad, SIGBUS, 0, "dcache parity error" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 25" }, { do_bad, SIGBUS, 0, "unknown 26" }, { do_bad, SIGBUS, 0, "unknown 27" }, { do_bad, SIGBUS, 0, "unknown 28" }, { do_bad, SIGBUS, 0, "unknown 29" }, { do_bad, SIGBUS, 0, "unknown 30" }, { do_bad, SIGBUS, 0, "unknown 31" }, };
DFSR(Data Fault Status Register)로 부터 읽은 5비트로 구성된 fault 상태 코드에 따라 설정된 fault 핸들러 함수를 dispatch하기 위해 사용된다.
- do_bad()
- 기타 exception으로 아무것도 수행하지 않고 1을 반환한다.
- do_translation_fault()
- 섹션 변환 실패 시 호출한다.
- do_page_fault()
- 2 가지의 경우로 페이지 변환 실패 또는 페이지 권한 위반 시 호출한다.
- do_sect_fault()
- 섹션 페이지의 권한 위반 시 호출한다.
fsr_fs()
arch/arm/mm/fault.h
static inline int fsr_fs(unsigned int fsr) { return (fsr & FSR_FS3_0) | (fsr & FSR_FS4) >> 6; }
DFSR(Data Fault Status Register)로부터 읽어온 fsr 값에서 bit[0~3]과 bit[10]으로 5개의 bit를 연달아 구성해서 반환한다.
- 예) fsr=0x0000_040a -> 0x1a
__dabt_invalid
arch/arm/kernel/entry-armv.S
__dabt_invalid: inv_entry BAD_DATA b common_invalid ENDPROC(__dabt_invalid)
허용하지 않은 모드에서 data abort exception 핸들러에 진입하여 실패 처리를 위한 루틴이다
- 코드 라인 2에서 레지스터를 스택에 백업한다.
- 스택에 레지스터들을 백업하기 위한 공간(struct pt_regs)을 확보하고 r1~r14(lr)까지 백업해둔다. r1 레지스터에는 reason 값을 대입한다.
- 코드 라인 3에서 common_invalid 레이블로 이동하야 스택의 pt_regs 위치에 레지스터들을 백업하고 “Oops” 출력 및 panic() 처리한다.
__dabt_svc
arch/arm/kernel/entry-armv.S
.align 5 __dabt_svc: svc_entry uaccess=0 mov r2, sp dabt_helper THUMB( ldr r5, [sp, #S_PSR] ) @ potentially updated CPSR svc_exit r5 @ return from exception UNWIND(.fnend ) ENDPROC(__dabt_svc)
svc 모드에서 data abort exception을 만나 진입하게 되면 data abort 핸들러를 수행한 후 다시 svc 모드로 복귀한다.
- 코드 라인 3에서 전체 레지스터를 스택에 pt_regs(svc_pt_regs) 구조체 순서로 백업한다.
- 코드 라인 4~5에서 r2 레지스터에 스택 위치를 담고 data abort 핸들러를 호출한다.
- 코드 라인 7에서 스택에 백업해둔 레지스터들을 다시 불러 읽은 후 svc 모드로 복귀한다.
Prefetch Abort 핸들러
__pabt_usr
arch/arm/kernel/entry-armv.S
.align 5 __pabt_usr: usr_entry mov r2, sp @ regs pabt_helper UNWIND(.fnend ) /* fall through */ /* * This is the return code to user mode for abort handlers */ ENTRY(ret_from_exception) UNWIND(.fnstart ) UNWIND(.cantunwind ) get_thread_info tsk mov why, #0 b ret_to_user UNWIND(.fnend ) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
user 모드에서 pre-fetch abort exception을 만나 진입하게 되면 pre-fetch abort 핸들러를 수행한 후 다시 user 모드로 복귀한다.
- 처리가 유사한 __dabt_usr 소스 설명 참고
pabt_helper 매크로
arch/arm/kernel/entry-armv.S
.macro pabt_helper @ PABORT handler takes pt_regs in r2, fault address in r4 and psr in r5 #ifdef MULTI_PABORT ldr ip, .LCprocfns mov lr, pc ldr pc, [ip, #PROCESSOR_PABT_FUNC] #else bl CPU_PABORT_HANDLER #endif .endm
pre-fetch abort 핸들러 함수를 호출한다.
- 처리가 유사한 dabt_helper 소스 설명 참고
CPU_PABORT_HANDLER
arch/arm/include/asm/glue-pf.h
#ifdef CONFIG_CPU_PABRT_V7 # ifdef CPU_PABORT_HANDLER # define MULTI_PABORT 1 # else # define CPU_PABORT_HANDLER v7_pabort # endif #endif
빌드타임에 아키텍처가 ARMv7으로 정해진 경우 pre-fetch abort 핸들러 함수는 v7_pabort() 이다.
v7_pabort
arch/arm/mm/pabort-v7.S
/* * Function: v7_pabort * * Params : r2 = pt_regs * : r4 = address of aborted instruction * : r5 = psr for parent context * * Returns : r4 - r11, r13 preserved * * Purpose : obtain information about current prefetch abort. */
.align 5 ENTRY(v7_pabort) mrc p15, 0, r0, c6, c0, 2 @ get IFAR mrc p15, 0, r1, c5, c0, 1 @ get IFSR b do_PrefetchAbort ENDPROC(v7_pabort)
pre-fetch abort exception 상황이 발생 시 IFSR에서 읽은 상태 코드를 갖고 do_PrefetchAbort() 함수를 호출하여 관련 fault 처리기를 호출한다.
do_PrefetchAbort()
arch/arm/mm/fault.c
asmlinkage void do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs) { const struct fsr_info *inf = ifsr_info + fsr_fs(ifsr); if (!inf->fn(addr, ifsr | FSR_LNX_PF, regs)) return; pr_alert("Unhandled prefetch abort: %s (0x%03x) at 0x%08lx\n", inf->name, ifsr, addr); arm_notify_die("", regs, inf->sig, inf->code, (void __user *)addr, ifsr, 0); }
pre-fetch abort exception 상황이 발생 시 PFSR(Pre-fetch Fault Status Register)에 기록된 5비트의 상태 코드를 인덱스로 ifsr_info[] 배열에 설정된 fault 처리기를 호출한다.
- 처리가 유사한 do_DataAbort() 소스 설명 참고
ifsr_info[]
arch/arm/mm/fsr-2level.c
static struct fsr_info ifsr_info[] = { { do_bad, SIGBUS, 0, "unknown 0" }, { do_bad, SIGBUS, 0, "unknown 1" }, { do_bad, SIGBUS, 0, "debug event" }, { do_bad, SIGSEGV, SEGV_ACCERR, "section access flag fault" }, { do_bad, SIGBUS, 0, "unknown 4" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" }, { do_bad, SIGSEGV, SEGV_ACCERR, "page access flag fault" }, { do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" }, { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, { do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" }, { do_bad, SIGBUS, 0, "unknown 10" }, { do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" }, { do_bad, SIGBUS, 0, "external abort on translation" }, { do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" }, { do_bad, SIGBUS, 0, "unknown 16" }, { do_bad, SIGBUS, 0, "unknown 17" }, { do_bad, SIGBUS, 0, "unknown 18" }, { do_bad, SIGBUS, 0, "unknown 19" }, { do_bad, SIGBUS, 0, "unknown 20" }, { do_bad, SIGBUS, 0, "unknown 21" }, { do_bad, SIGBUS, 0, "unknown 22" }, { do_bad, SIGBUS, 0, "unknown 23" }, { do_bad, SIGBUS, 0, "unknown 24" }, { do_bad, SIGBUS, 0, "unknown 25" }, { do_bad, SIGBUS, 0, "unknown 26" }, { do_bad, SIGBUS, 0, "unknown 27" }, { do_bad, SIGBUS, 0, "unknown 28" }, { do_bad, SIGBUS, 0, "unknown 29" }, { do_bad, SIGBUS, 0, "unknown 30" }, { do_bad, SIGBUS, 0, "unknown 31" }, };
IFSR(Pre-fetch Fault Status Register)로 부터 읽은 5비트로 구성된 fault 상태 코드에 따라 설정된 fault 핸들러 함수를 dispatch하기 위해 사용된다.
- do_bad()
- 기타 exception으로 아무것도 수행하지 않고 1을 반환한다.
- do_translation_fault()
- 섹션 변환 실패 시 호출한다.
- do_page_fault()
- 2 가지의 경우로 페이지 변환 실패 또는 페이지 권한 위반 시 호출한다.
- do_sect_fault()
- 섹션 페이지의 권한 위반 시 호출한다.
__pabt_invalid
arch/arm/kernel/entry-armv.S
__pabt_invalid: inv_entry BAD_PREFETCH b common_invalid ENDPROC(__pabt_invalid)
허용하지 않은 모드에서 pre-fetch abort exception 핸들러에 진입하여 실패 처리를 위한 루틴이다
- 처리가 유사한 __dabt_invalid 소스 설명 참고
__pabt_svc
arch/arm/kernel/entry-armv.S
__pabt_svc: svc_entry trace=0 mov r2, sp @ regs pabt_helper svc_exit r5 @ return from exception UNWIND(.fnend ) ENDPROC(__pabt_svc)
svc 모드에서 pre-fetch abort exception을 만나 진입하게 되면 pre-fetch abort 핸들러를 수행한 후 다시 svc 모드로 복귀한다.
- 처리가 유사한 __dabt_svc 소스 설명 참고
Fault 처리 관련 -1-
do_bad()
arch/arm/mm/fault.c
/* * This abort handler always returns "fault". */
static int do_bad(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { return 1; }
특별한 처리 없이 항상 실패로 반환한다.
Fault 처리 관련 -2-
do_translation_fault()
arch/arm/mm/fault.c
/* * First Level Translation Fault Handler * * We enter here because the first level page table doesn't contain * a valid entry for the address. * * If the address is in kernel space (>= TASK_SIZE), then we are * probably faulting in the vmalloc() area. * * If the init_task's first level page tables contains the relevant * entry, we copy the it to this task. If not, we send the process * a signal, fixup the exception, or oops the kernel. * * NOTE! We MUST NOT take any locks for this case. We may be in an * interrupt or a critical region, and should only copy the information * from the master page table, nothing more. */
#ifdef CONFIG_MMU static int __kprobes do_translation_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { unsigned int index; pgd_t *pgd, *pgd_k; pud_t *pud, *pud_k; pmd_t *pmd, *pmd_k; if (addr < TASK_SIZE) return do_page_fault(addr, fsr, regs); if (user_mode(regs)) goto bad_area; index = pgd_index(addr); pgd = cpu_get_pgd() + index; pgd_k = init_mm.pgd + index; if (pgd_none(*pgd_k)) goto bad_area; if (!pgd_present(*pgd)) set_pgd(pgd, *pgd_k); pud = pud_offset(pgd, addr); pud_k = pud_offset(pgd_k, addr); if (pud_none(*pud_k)) goto bad_area; if (!pud_present(*pud)) set_pud(pud, *pud_k); pmd = pmd_offset(pud, addr); pmd_k = pmd_offset(pud_k, addr); #ifdef CONFIG_ARM_LPAE /* * Only one hardware entry per PMD with LPAE. */ index = 0; #else /* * On ARM one Linux PGD entry contains two hardware entries (see page * tables layout in pgtable.h). We normally guarantee that we always * fill both L1 entries. But create_mapping() doesn't follow the rule. * It can create inidividual L1 entries, so here we have to call * pmd_none() check for the entry really corresponded to address, not * for the first of pair. */ index = (addr >> SECTION_SHIFT) & 1; #endif if (pmd_none(pmd_k[index])) goto bad_area; copy_pmd(pmd, pmd_k); return 0; bad_area: do_bad_area(addr, fsr, regs); return 0; }
첫 번째 레벨에서 translation fault가 발생한 경우 유저 영역이거나 커널 영역이면서 테이블 엔트리가 비어있는 경우 do_page_fauilt() 함수에서 처리하게 한다. 커널 영역인 경우 해당 주소의 테이블 엔트리(pgd, pud, pmd)가 present 플래그만 빠져있는 경우라면 커널 엔트리에서 유저 엔트리로 복사한다.
- 코드 라인 11~12에서 fault 주소 addr이 user 주소 공간인 경우 do_page_fault() 함수를 호출한다.
- 코드 라인 14~15에서 kernel address space이지만 user mode에서 fault된 경우 bad_area 레이블로 이동하여 do_bad_area() 함수를 호출한다.
- 코드 라인 17에서 fault 주소로 pgd 엔트리의 인덱스 번호를 알아온다.
- 코드 라인 19~20에서 유저 테이블의 pgd 엔트리 및 커널 테이블의 pgd 엔트리 주소를 산출한다.
- 코드 라인 22~23에서 커널용 pgd 엔트리 값이 0으로 비어 있는 경우 bad_area 레이블로 이동하여 do_bad_area() 함수를 호출한다.
- 코드 라인 24~25에서 유저용 pgd 엔트리가 present 설정이 없는 경우 커널용 pgd 엔트리 값을 유저용 pgd 엔트리에 복사한다.
- 코드 라인 27~28에서 유저용 pud 엔트리 및 커널용 pud 엔트리 주소를 산출한다.
- 코드 라인 30~31에서 커널용 pud 엔트리 값이 0으로 비어 있는 경우 bad_area 레이블로 이동하여 do_bad_area() 함수를 호출한다.
- 코드 라인 32~33에서 유저용 pud 엔트리가 present 설정이 없는 경우 커널용 pud 엔트리 값을 유저용 pud 엔트리에 복사한다.
- 코드 라인 35~36에서 유저용 pmd 엔트리 및 커널용 pmd 엔트리 주소를 산출한다.
- 코드 라인 52에서 pmd 엔트리가 홀 수 섹션인 경우 짝 수 섹션 단위로 절삭한다.
- 코드 라인 54~55에서 커널용 pmd 엔트리 값이 0으로 비어 있는 경우 bad_area 레이블로 이동하여 do_bad_area() 함수를 호출한다.
- 코드 라인 57~58에서 커널용 pmd 엔트리 값을 유저용 pmd 엔트리에 복사하고 성공(0)으로 복귀한다.
- 코드 라인 60~62에서 bad_area: 레이블에서는 do_bad_area() 함수를 호출한 후 성공(0)으로 복귀한다.
cpu_get_pgd()
arch/arm/include/asm/proc-fns.h
#define cpu_get_pgd() \ ({ \ unsigned long pg; \ __asm__("mrc p15, 0, %0, c2, c0, 0" \ : "=r" (pg) : : "cc"); \ pg &= ~0x3fff; \ (pgd_t *)phys_to_virt(pg); \ }) #endif
유저 테이블의 가상 주소를 산출한다.
- 코드 라인 4~7에서 물리 주소를 담고 있는 TTBR0 값을 읽어와서 lsb 14비트를 절삭한 물리 주소를 가상 주소로 반환한다.
Fault 처리 관련 -3-
do_sect_fault()
arch/arm/mm/fault.c
/* * Some section permission faults need to be handled gracefully. * They can happen due to a __{get,put}_user during an oops. */
static int do_sect_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { do_bad_area(addr, fsr, regs); return 0; }
섹션 접근 권한 fault가 발생된 경우 do_bad_area() 함수를 호출하여 다음과 같은 처리를 한다.
- 유저 모드에서 exception된 경우 해당 유저 태스크는 SIGSEGV 시그널을 받아서 종료된다.
- 유저 모드가 아닌 모드에서 exception된 경우 die() 처리를 한다. 만일 ex_table(exception table)에 별도의 fixup 코드가 있는 경우 해당 코드를 실행시킨다
do_bad_area()
arch/arm/mm/fault.c()
void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { struct task_struct *tsk = current; struct mm_struct *mm = tsk->active_mm; /* * If we are in kernel mode at this point, we * have no context to handle this fault with. */ if (user_mode(regs)) __do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs); else __do_kernel_fault(mm, addr, fsr, regs); }
유저 모드에서 exception 된 경우 __do_user_fault() 함수를 호출하고 그 외의 모드는 __do_kernel_fault() 함수를 처리한다.
- 코드 라인 10~11에서 유저 모드에서 exception되어 매핑되지 않은 영역에 접근하려 하는 경우 해당 유저 태스크에 SIGSEGV 시그널을 전송하여 태스크를 종료시킨다.
- 코드 라인 12~13에서 유저 모드가 아닌 모드에서 exception되어 매핑되지 않은 영역에 접근하려 하는 경우 die() 처리를 한다. 단 ex_table(exception table)에 별도의 fixup 코드가 있는 경우 해당 코드를 실행시킨다.
- get_user() 등의 매크로 함수에서 ex_table(exception table)에 fixup 코드를 등록하여 사용한다.
__do_user_fault()
arch/arm/mm/fault.c
/* * Something tried to access memory that isn't in our memory map.. * User mode accesses just cause a SIGSEGV */
static void __do_user_fault(unsigned long addr, unsigned int fsr, unsigned int sig, int code, struct pt_regs *regs) { struct task_struct *tsk = current; if (addr > TASK_SIZE) harden_branch_predictor(); #ifdef CONFIG_DEBUG_USER if (((user_debug & UDBG_SEGV) && (sig == SIGSEGV)) || ((user_debug & UDBG_BUS) && (sig == SIGBUS))) { printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n", tsk->comm, sig, addr, fsr); show_pte(tsk->mm, addr); show_regs(regs); } #endif #ifndef CONFIG_KUSER_HELPERS if ((sig == SIGSEGV) && ((addr & PAGE_MASK) == 0xffff0000)) printk_ratelimited(KERN_DEBUG "%s: CONFIG_KUSER_HELPERS disabled at 0x%08lx\n", tsk->comm, addr); #endif tsk->thread.address = addr; tsk->thread.error_code = fsr; tsk->thread.trap_no = 14; force_sig_info(sig, code, (void __user *)addr); }
유저 모드에서 커널 영역의 매핑되지 않은 페이지를 엑세스하려할 때 해당 유저 태스크에 인수로 요청 받은 sigSIGSEGV, SIGBUS, SIGKILL 중 하나 시그널을 전달한다.
__do_kernel_fault()
arch/arm/mm/fault.c
/* * Oops. The kernel tried to access some page that wasn't present. */
static void __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, struct pt_regs *regs) { /* * Are we prepared to handle this kernel fault? */ if (fixup_exception(regs)) return; /* * No handler, we'll have to terminate things with extreme prejudice. */ bust_spinlocks(1); pr_alert("8<--- cut here ---\n"); pr_alert("Unable to handle kernel %s at virtual address %08lx\n", (addr < PAGE_SIZE) ? "NULL pointer dereference" : "paging request", addr); show_pte(KERN_ALERT, mm, addr); die("Oops", regs, fsr); bust_spinlocks(0); do_exit(SIGKILL); }
유저 모드가 아닌 모드에서 커널 영역의 매핑되지 않은 페이지를 엑세스하려할 때 ex_table(exception table) 에 등록한 별도의 fixup 코드를 실행시킨다. 만일 등록된 별도의 fixup 코드가 없는 경우 “Unable to handle kernel…” 메시지와 함께 pte 정보를 출력하고 die() 처리한다.
- 코드 라인 8~9에서 ex_table(exception table) 에 등록한 별도의 fixup 코드가 있는 경우 이를 실행하고 복귀한다.
- 코드 라인 15~21에서 “Unable to handle kernel…” 메시지와 함께 pte 정보를 출력하고 die() 처리한다
Fault 처리 관련 -4-
do_page_fault()
arch/arm/mm/fault.c
static int __kprobes do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) { struct task_struct *tsk; struct mm_struct *mm; int sig, code; vm_fault_t fault; unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE; if (kprobe_page_fault(regs, fsr)) return 0; tsk = current; mm = tsk->mm; /* Enable interrupts if they were enabled in the parent context. */ if (interrupts_enabled(regs)) local_irq_enable(); /* * If we're in an interrupt or have no user * context, we must not take the fault.. */ if (faulthandler_disabled() || !mm) goto no_context; if (user_mode(regs)) flags |= FAULT_FLAG_USER; if ((fsr & FSR_WRITE) && !(fsr & FSR_CM)) flags |= FAULT_FLAG_WRITE;
abort exception되어 fault 처리를 하는데 exception 되기 전의 모드에 따라 처리를 다음과 같이 수행한다.
- 유저 모드
- vma에 포함되지 않은 영역인 경우 태스크 die 처리
- vma에 포함되었지만 페이지 테이블 엔트리가 present 플래그가 없어 즉, 매핑되지 않은 영역은 mm_fault 처리하여 실제 메모리를 할당하고 매핑시킨다.
- lazy alloc, swap, file 매핑 등
- 유저 모드가 아닌 모드(커널 모드)
- vma에 포함되지 않은 영역인 경우 die 처리
- vma에 포함되었지만 페이지 테이블 엔트리가 present 플래그가 없어 즉, 매핑되지 않은 영역은 mm_fault 처리하여 실제 메모리를 할당하고 매핑시킨다. (vmalloc 영역에서의 lazy alloc)
- 코드 라인 8에서 allow retry와 killable 플래그를 기본 플래그로 대입한다.
- 코드 라인 10~11에서 페이지 fault 처리에 앞서 커널 디버거 kprobe를 지원하고 동작중인 경우 kprobe용 fault 핸들러 함수를 처리할 수 있게 한다.
- 코드 라인 13~14에서 현재 태스크의 메모리 디스크립터를 가리킨다.
- 코드 라인 17~18에서 exception되기 전에 인터럽트가 가능한 상태인 경우 local irq를 enable한다.
- 코드 라인 24~25에서 atomic 하게 처리해야 하는 스케쥴링하는 동안이거나 메모리 디스크립터가 지정되지 않은 경우 no_context: 레이블로 이동하여 커널 fault를 처리 한다. (die)
- 코드 라인 27~28에서 user 모드에서 exception된 경우 user 플래그 표시를 한다.
- 코드 라인 29~30에서 fault 상태 값에 FSR_WRITE가 있는 경우 write 플래그를 추가한다.
/* * As per x86, we may deadlock here. However, since the kernel only * validly references user space from well defined areas of the code, * we can bug out early if this is from code which shouldn't. */ if (!down_read_trylock(&mm->mmap_sem)) { if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc)) goto no_context; retry: down_read(&mm->mmap_sem); } else { /* * The above down_read_trylock() might have succeeded in * which case, we'll have missed the might_sleep() from * down_read() */ might_sleep(); #ifdef CONFIG_DEBUG_VM if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc)) goto no_context; #endif } fault = __do_page_fault(mm, addr, fsr, flags, tsk); /* If we need to retry but a fatal signal is pending, handle the * signal first. We do not need to release the mmap_sem because * it would already be released in __lock_page_or_retry in * mm/filemap.c. */ if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) { if (!user_mode(regs)) goto no_context; return 0; } /* * Major/minor page fault accounting is only done on the * initial attempt. If we go through a retry, it is extremely * likely that the page will be found in page cache at that point. */ perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr); if (!(fault & VM_FAULT_ERROR) && flags & FAULT_FLAG_ALLOW_RETRY) { if (fault & VM_FAULT_MAJOR) { tsk->maj_flt++; perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, regs, addr); } else { tsk->min_flt++; perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, regs, addr); } if (fault & VM_FAULT_RETRY) { /* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk * of starvation. */ flags &= ~FAULT_FLAG_ALLOW_RETRY; flags |= FAULT_FLAG_TRIED; goto retry; } } up_read(&mm->mmap_sem);
- 코드 라인 6~10에서 매핑과 관련한 세마포어 락을 시도하여 실패하면 천천히 다시 락을 획득한다. 단 유저 모드가 아니면서 ex_table에도 해당 주소가 없는 경우는 no_context: 레이블로 이동하여 커널 fault를 처리 한다. (die)
- 코드 라인 11~23에서 preemption point를 수행하며 유저 모드가 아니면서 CONFIG_DEBUG_VM 커널 옵션을 사용하는 경우 ex_table에도 해당 주소가 없는 경우는 no_context: 레이블로 이동하여 커널 fault를 처리 한다. (die)
- 코드 라인 25~35에서 페이지 fault 처리를 수행하고 수행 후 fault retry 요청이 있지만 시그널 지연중인 경우는 그냥 함수를 빠져나간다. 단 커널 모드에서 exception된 경우 no_context 레이블로 이동한다.
- 코드 라인 43에서 CONFIG_PERF_EVENTS 커널 옵션을 사용하는 경우 커널에서 제공하는 s/w perf 카운터 중 하나인 PERF_COUNT_SW_PAGE_FAULTS 카운터를 증가시킨다.
- 코드 라인 44~61에서 vm_fault 에러이면서 retry를 허용한 경우 retry 플래그를 제거 하고, retry 중(FAULT_FLAG_TRIED)이라고 설정한다음 다시 한 번 시도한다. 그리고 major/minor fault인지 여부에 따라 maj_flt 또는 min_flt를 증가시키고 해당 perf 카운터도 증가시킨다.
/* * Handle the "normal" case first - VM_FAULT_MAJOR */ if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS)))) return 0; /* * If we are in kernel mode at this point, we * have no context to handle this fault with. */ if (!user_mode(regs)) goto no_context; if (fault & VM_FAULT_OOM) { /* * We ran out of memory, call the OOM killer, and return to * userspace (which will retry the fault, or kill us if we * got oom-killed) */ pagefault_out_of_memory(); return 0; } if (fault & VM_FAULT_SIGBUS) { /* * We had some memory, but were unable to * successfully fix up this page fault. */ sig = SIGBUS; code = BUS_ADRERR; } else { /* * Something tried to access memory that * isn't in our memory map.. */ sig = SIGSEGV; code = fault == VM_FAULT_BADACCESS ? SEGV_ACCERR : SEGV_MAPERR; } __do_user_fault(addr, fsr, sig, code, regs); return 0; no_context: __do_kernel_fault(mm, addr, fsr, regs); return 0; }
유저 페이지 fault에 대한 처리를 수행한다.
- 코드 라인 4~5에서 높은 확률로 fault 에러, badmap, badaccess가 없는 경우 함수를 종료한다.
- 코드 라인 11~12에서 user 모드가 아닌 모드에서 exception된 경우 함수를 종료한다.
- 코드 라인 14~22에서 OOM fault로 인해 매핑을 못한 경우 OOM 킬러가 동작하는 경우 OOM kill 처리를 수행한다.
- 메모리를 많이 사용하는 태스크를 평가하여 kill 한다
- 코드 라인 24~42에서 SIGBUS fault인 경우 시그널에 SIGBUS, code에 BUS_ADRERR을 담고 user fault 핸들러를 수행하고, SIGBUS가 아닌 경우 시그널에 SIGSEGV, code에 bad access 여부에 따라 SEGV_ACCERR 또는 SEGV_MAPERR를 담고 user fault 핸들러를 수행하고 복귀한다.
- 코드 라인 44~46에서 no_context: 레이블이다. kernel fault 핸들러를 수행하고 복귀한다.
__do_page_fault()
arch/arm/mm/fault.c
static vm_fault_t __kprobes __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, unsigned int flags, struct task_struct *tsk) { struct vm_area_struct *vma; vm_fault_t fault; vma = find_vma(mm, addr); fault = VM_FAULT_BADMAP; if (unlikely(!vma)) goto out; if (unlikely(vma->vm_start > addr)) goto check_stack; /* * Ok, we have a good vm_area for this * memory access, so we can handle it. */ good_area: if (access_error(fsr, vma)) { fault = VM_FAULT_BADACCESS; goto out; } return handle_mm_fault(vma, addr & PAGE_MASK, flags); check_stack: /* Don't allow expansion below FIRST_USER_ADDRESS */ if (vma->vm_flags & VM_GROWSDOWN && addr >= FIRST_USER_ADDRESS && !expand_stack(vma, addr)) goto good_area; out: return fault; }
유저 영역에 대한 fault 여부를 다음과 같이 확인한다.
- 유저 태스크에 등록된 vma 영역을 벗어난 경우 VM_FAULT_BADMAP
- 사용 권한이 없는 vma 영역인 경우 VM_FAULT_BADACCESS
- vma 영역이지만 매핑만 하지 않은 경우이므로 물리 메모리를 할당하고 lazy alloc 처리, swap 또는 file 로딩 후 매핑처리한다.
- 코드 라인 8~11에서 vma 영역에서 찾을 수 없는 경우 VM_FAULT_BADMAP을 반환한다.
- 코드 라인 12~13에서 찾은 vma 영역 아래에 위치한 경우 check_stack 레이블로 이동한다.
- 코드 라인 19~23에서 access 권한 에러인 경우 VM_FAULT_BADACCESS를 반환한다.
- 코드 라인 25에서 vma 영역에 해당하지만 메모리 매핑이 아직 안된 페이지를 처리 하러 handle_mm_fault() 함수를 호출한다.
- 코드 라인 27~31에서 check_stack: 레이블이다. vma가 밑으로 증가되는 스택이고 요청 주소가 유저 영역 하한 주소인 FIRST_USER_ADDRESS(arm에서는 2 페이지) 이상이고 스택이 확장된 스택이 아니면 good_area 레이블로 이동한다.
Undefined Instruction 핸들러
__und_usr
arch/arm/kernel/entry-armv.S
.align 5 __und_usr: usr_entry uaccess=0 mov r2, r4 mov r3, r5 @ r2 = regs->ARM_pc, which is either 2 or 4 bytes ahead of the @ faulting instruction depending on Thumb mode. @ r3 = regs->ARM_cpsr @ @ The emulation code returns using r9 if it has emulated the @ instruction, or the more conventional lr if we are to treat @ this as a real undefined instruction @ badr r9, ret_from_exception @ IRQs must be enabled before attempting to read the instruction from @ user space since that could cause a page/translation fault if the @ page table was modified by another CPU. enable_irq tst r3, #PSR_T_BIT @ Thumb mode? bne __und_usr_thumb sub r4, r2, #4 @ ARM instr at LR - 4 1: ldrt r0, [r4] ARM_BE8(rev r0, r0) @ little endian instruction @ r0 = 32-bit ARM instruction which caused the exception @ r2 = PC value for the following instruction (:= regs->ARM_pc) @ r4 = PC value for the faulting instruction @ lr = 32-bit undefined instruction function adr lr, BSYM(__und_usr_fault_32) b call_fpe
user 모드에서 undefined instruction exception을 만나 진입하게 되면 VFP 예외 처리, FPE 수행 등을 수행한 후 다시 user 모드로 복귀한다.
- 코드 라인 3에서 전체 레지스터를 스택에 pt_regs 구조체 순서로 백업한다.
- 코드 라인 5~6에서 exception된 주소가 담기 r4 레지스터를 r2 레지스터에 담고, cpsr을 담고 있는 r5 레지스터를 r3 레지스터에 담는다.
- 코드 라인 16에서 레지스터 r9에 ret_from_exception 레이블의 주소를 담는다.
- 코드 라인 21에서 irq를 enable한다.
- 코드 라인 23~24에서 cpsr의 thumb 모드 비트가 설정된 경우 __und_usr_thumb 레이블로 이동한다.
- 코드 라인 25~26에서 exception된 instruction 코드를 r0 레지스터로 읽어온다.
- exception 당시 pc – 4의 주소이다.
- 코드 라인 33~34에서 __und_usr_fault_32 레이블의 주소를 돌아갈 주소로 지정하기 위해 lr 레지스터에 대입하고 Floating Point 관련 예외 처리 또는 에뮬레이션을 수행하기 위해 call_fpe 레이블로 이동한다.
- 참고: VFP & FPE | 문c
__und_usr_fault_32
arch/arm/kernel/entry-armv.S
__und_usr_fault_32: mov r1, #4 b 1f __und_usr_fault_16_pan: uaccess_disable ip __und_usr_fault_16: mov r1, #2 1: mov r0, sp badr lr, ret_from_exception b __und_fault ENDPROC(__und_usr_fault_32) ENDPROC(__und_usr_fault_16)
r1 레지스터에 4를 담고 ret_from_exception 레이블 주소를 복귀 주소로 lr 레지스터에 저장한 후 fault 처리를 위해 __und_fault 레이블로 이동한다.
__und_invalid
arch/arm/kernel/entry-armv.S
__und_invalid: inv_entry BAD_UNDEFINSTR @ @ XXX fall through to common_invalid @
허용하지 않은 모드에서 undefined instruction exception 핸들러에 진입하여 실패 처리를 위한 루틴이다. 이어서 common_invalid: 레이블을 계속 진행한다.
- 처리가 유사한 __dabt_invalid 소스 설명 참고
__und_svc
arch/arm/kernel/entry-armv.S
.align 5 __und_svc: #ifdef CONFIG_KPROBES @ If a kprobe is about to simulate a "stmdb sp..." instruction, @ it obviously needs free stack space which then will belong to @ the saved context. svc_entry MAX_STACK_SIZE #else svc_entry #endif @ @ call emulation code, which returns using r9 if it has emulated @ the instruction, or the more conventional lr if we are to treat @ this as a real undefined instruction @ @ r0 - instruction @ #ifndef CONFIG_THUMB2_KERNEL ldr r0, [r4, #-4] #else mov r1, #2 ldrh r0, [r4, #-2] @ Thumb instruction at LR - 2 cmp r0, #0xe800 @ 32-bit instruction if xx >= 0 blo __und_svc_fault ldrh r9, [r4] @ bottom 16 bits add r4, r4, #2 str r4, [sp, #S_PC] orr r0, r9, r0, lsl #16 #endif badr r9, __und_svc_finish mov r2, r4 bl call_fpe mov r1, #4 @ PC correction to apply __und_svc_fault: mov r0, sp @ struct pt_regs *regs bl __und_fault __und_svc_finish: get_thread_info tsk ldr r5, [sp, #S_PSR] @ Get SVC cpsr svc_exit r5 @ return from exception UNWIND(.fnend ) ENDPROC(__und_svc)
svc 모드에서 undefined instruction exception을 만나 진입하게 되면 VFP 예외 처리, FPE 수행 등을 수행한 후 다시 svc 모드로 복귀한다.
- 코드 라인 3~10에서 전체 레지스터를 스택에 pt_regs(svc_pt_regs) 구조체 순서로 백업한다.
- kprobes를 사용 시 MAX_STACK_SIZE(64) 바이트 만큼의 공간을 스택에 추가로 확보한다.
- 코드 라인 19에서 exception된 instruction 코드를 r0 레지스터에 가져온다.
- exception 당시 pc – 4의 주소이다.
- 코드 라인 30~32에서 __und_svc_finish 레이블의 주소를 r9에 담고 r4를 r2에 담고 VFP 예외 처리 및 FPE 수행 등을 처리하기 위해 call_fpe 함수를 호출한다.
- 코드 라인 34~37에서 r1에 4를 더하고, r0에 스택위치를 대입한 후 __und_fault() 함수를 호출한다.
- 코드 라인 40~42에서 r5 레지스터에 백업해두었던 pt_regs의 psr 값을 담은 후 스택에 백업해 둔 레지스터들을 복구하고 svc 모드로 빠져나간다.
__und_fault
arch/arm/kernel/entry-armv.S
__und_fault: @ Correct the PC such that it is pointing at the instruction @ which caused the fault. If the faulting instruction was ARM @ the PC will be pointing at the next instruction, and have to @ subtract 4. Otherwise, it is Thumb, and the PC will be @ pointing at the second half of the Thumb instruction. We @ have to subtract 2. ldr r2, [r0, #S_PC] sub r2, r2, r1 str r2, [r0, #S_PC] b do_undefinstr ENDPROC(__und_fault)
복귀 주소에 correction(4) 만큼을 뺀다. undefined 훅에 등록된 명령인 경우 정상적으로 함수를 리턴하고 그렇지 않은 경우 유저 모드인 경우 task의 kill 처리를 위한 시그널을 요청하고, 그렇지 않은 경우 시스템을 die 처리한다.
- 코드 라인 8~10에서 스택에 위치한 pt_regs의 pc 값을 읽어 r1 (correction)값을 뺀 후 다시 pt_regs의 pc 위치에 저장한다.
- 코드 라인 11에서 설치된 undefined 훅에 등록된 명령인 경우 함수를 리턴하고 그렇지 않은 경우 유저 모드인 경우 task의 kill 처리를 위한 시그널을 요청하고, 그렇지 않은 경우 시스템을 die 처리한다.
do_undefinstr()
arch/arm/kernel/traps.c
asmlinkage void do_undefinstr(struct pt_regs *regs) { unsigned int instr; siginfo_t info; void __user *pc; pc = (void __user *)instruction_pointer(regs); if (processor_mode(regs) == SVC_MODE) { #ifdef CONFIG_THUMB2_KERNEL if (thumb_mode(regs)) { instr = __mem_to_opcode_thumb16(((u16 *)pc)[0]); if (is_wide_instruction(instr)) { u16 inst2; inst2 = __mem_to_opcode_thumb16(((u16 *)pc)[1]); instr = __opcode_thumb32_compose(instr, inst2); } } else #endif instr = __mem_to_opcode_arm(*(u32 *) pc); } else if (thumb_mode(regs)) { if (get_user(instr, (u16 __user *)pc)) goto die_sig; instr = __mem_to_opcode_thumb16(instr); if (is_wide_instruction(instr)) { unsigned int instr2; if (get_user(instr2, (u16 __user *)pc+1)) goto die_sig; instr2 = __mem_to_opcode_thumb16(instr2); instr = __opcode_thumb32_compose(instr, instr2); } } else { if (get_user(instr, (u32 __user *)pc)) goto die_sig; instr = __mem_to_opcode_arm(instr); } if (call_undef_hook(regs, instr) == 0) return; die_sig: #ifdef CONFIG_DEBUG_USER if (user_debug & UDBG_UNDEFINED) { pr_info("%s (%d): undefined instruction: pc=%p\n", current->comm, task_pid_nr(current), pc); __show_regs(regs); dump_instr(KERN_INFO, regs); } #endif arm_notify_die("Oops - undefined instruction", regs, SIGILL, ILL_ILLOPC, pc, 0, 6); } NOKPROBE_SYMBOL(do_undefinstr)
undefined 훅에 등록된 명령인 경우 정상적으로 함수를 리턴한다. 만일 등록되지 않은 경우 다음과 같이 처리한다.
- 유저 모드인 경우 task의 kill 처리를 위한 시그널을 요청
- 유저 모드가 아닌(커널) 경우 시스템을 die 처리한다.
- 코드 라인 7에서 스택에 백업한 pt_regs 구조체의 pc 주소 값을 대입한다.
- 코드 라인 9~20에서 exception 되기 전의 모드가 svc 모드가 아닌 경우 __mem_to_opcode_arm() 함수를 사용하여 명령어 값을 가져온다.
- 코드 라인 32~36에서 exception 되기 전의 모드가 svc 모드가 아닌 경우 __mem_to_opcode_arm() 함수를 사용하여 명령어 값을 가져오되 단 usr 모드인 경우 die_sig 레이블로 이동한다.
- 코드 라인 38~39에서 undefined hook이 설치된 경우 인스트럭션과 모드 등을 비교하여 해당 훅 함수를 호출한다. 호출 결과가 성공(0)인 경우 함수를 빠져나간다.
- 코드 라인 41~51에서 “Oops – undefined instruction” 메시지를 출력하며 die() 함수를 호출한다.
- 커널인 경우 시스템 die, 유저 모드인 경우 태스크만 die 처리한다.
Undefined Hook
call_undef_hook()
arch/arm/kernel/traps.c
static int call_undef_hook(struct pt_regs *regs, unsigned int instr) { struct undef_hook *hook; unsigned long flags; int (*fn)(struct pt_regs *regs, unsigned int instr) = NULL; raw_spin_lock_irqsave(&undef_lock, flags); list_for_each_entry(hook, &undef_hook, node) if ((instr & hook->instr_mask) == hook->instr_val && (regs->ARM_cpsr & hook->cpsr_mask) == hook->cpsr_val) fn = hook->fn; raw_spin_unlock_irqrestore(&undef_lock, flags); return fn ? fn(regs, instr) : 1; }
undefined hook이 설치된 경우 인스트럭션과 모드 등을 비교하여 해당 훅 함수를 호출한다. 성공=0, 매칭된 훅 함수가 없거나 실패=1
- register_undef_hook() 함수를 통해 hook이 추가된다.
- CONFIG_TLS_REG_EMUL 커널 옵션을 사용하는 경우 SMP를 사용하는 ARMv6+ 이전 아키텍처를 위해 TLS 에뮬레이션 목적으로 hook를 하나 설치한다.
- late_initcall(arm_mrc_hook_init); -> get_tp_trap() 함수
Die 처리
arm_notify_die()
arch/arm/kernel/traps.c
void arm_notify_die(const char *str, struct pt_regs *regs, int signo, int si_code, void __user *addr, unsigned long err, unsigned long trap) { if (user_mode(regs)) { current->thread.error_code = err; current->thread.trap_no = trap; force_sig_info(signo, si_code, addr); } else { die(str, regs, err); } }
exception 되기 전의 모드가 유저 모드인 경우 태스크에 signal을 보내 해당 태스크만 죽이고 커널 모드인 경우 die() 함수를 호출한다.
참고
- Exception -1- (ARM32 Vector) | 문c
- Exception -2- (ARM32 Handler 1) | 문c
- Exception -3- (ARM32 Handler 2) | 문c – 현재 글
- Exception -4- (ARM32 VFP & FPE) | 문c
- Exception -5- (Extable) | 문c
- Exception -6- (MM Fault Handler) | 문c
- Exception -7- (ARM64 Vector)
- Exception -8- (ARM64 Handler)