<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 중 하나)
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 레이블로 이동한다.
__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() 함수를 호출한다.
참고