前面博文提到了,如果address不屬於進程的地址空間,那麼do_page_fault()函數繼續執行bad_area標記處的語句。如果錯誤發生在使用者態,則發送一個SIGSEGV訊號給current進程並結束函數:
/*
* Something tried to access memory that isn't in our memory map..
* Fix it, but check if it's kernel or user first..
*/
bad_area:
up_read(&mm->mmap_sem);
bad_area_nosemaphore:
/* User mode accesses just cause a SIGSEGV */
if (error_code & 4) {
/*
* Valid to do another page fault here because this one came
* from user space.
*/
if (is_prefetch(regs, address, error_code))
return;
tsk->thread.cr2 = address;
/* Kernel addresses are always protection faults */
tsk->thread.error_code = error_code | (address >= TASK_SIZE);
tsk->thread.trap_no = 14;
force_sig_info_fault(SIGSEGV, si_code, address, tsk);
return;
}
#ifdef CONFIG_X86_F00F_BUG
/*
* Pentium F0 0F C7 C8 bug workaround.
*/
if (boot_cpu_data.f00f_bug) {
unsigned long nr;
nr = (address - idt_descr.address) >> 3;
if (nr == 6) {
do_invalid_op(regs, 0);
return;
}
}
#endif
force_sig_info_fault()函數確信進程不忽略或阻塞SIGSEGV訊號,並通過si_code局部變數傳遞附加資訊的同時把該訊號發送給使用者態進程。si_code變數已被置為SEGV_MAPERR(如果異常是由於一個不存在的頁框引起,上篇博文剛開始的時候),或置為SEGV_ACCERR(如果異常是由於對現有頁框的無效訪問引起)。
如果異常發生在核心態(error_code的第2位被清0,即error_code & 4為0),仍然有兩種可選的情況:
(1)異常的引起是由於把某個線性地址作為系統調用的參數傳遞給核心。
(2)異常是因一個真正的核心缺陷所引起的。
函數這樣區分這兩種可選的情況:
no_context:
/* Are we prepared to handle this kernel fault? */
if (fixup_exception(regs))
return;
/*
* Valid to do another page fault here, because if this fault
* had been triggered by is_prefetch fixup_exception would have
* handled it.
*/
if (is_prefetch(regs, address, error_code))
return;
在第一種情況中,代碼跳到一段“修正代碼”處,這段代碼的典型操作就是向當前進程發送SIGSEGV訊號,或用一個適當的出錯碼終止系統調用處理常式:
int fixup_exception(struct pt_regs *regs)
{
const struct exception_table_entry *fixup;
#ifdef CONFIG_PNPBIOS
if (unlikely((regs->xcs & ~15) == (GDT_ENTRY_PNPBIOS_BASE << 3)))
{
extern u32 pnp_bios_fault_eip, pnp_bios_fault_esp;
extern u32 pnp_bios_is_utter_crap;
pnp_bios_is_utter_crap = 1;
printk(KERN_CRIT "PNPBIOS fault.. attempting recovery./n");
__asm__ volatile(
"movl %0, %%esp/n/t"
"jmp *%1/n/t"
: : "g" (pnp_bios_fault_esp), "g" (pnp_bios_fault_eip));
panic("do_trap: can't hit this");
}
#endif
fixup = search_exception_tables(regs->eip);
if (fixup) {
regs->eip = fixup->fixup;
return 1;
}
return 0;
}
const struct exception_table_entry *search_exception_tables(unsigned long addr)
{
const struct exception_table_entry *e;
e = search_extable(__start___ex_table, __stop___ex_table-1, addr);
if (!e)
e = search_module_extables(addr);
return e;
}
在第二種情況中,函數把CPU寄存器和核心態堆棧的全部轉儲列印到控制台,並輸出到一個系統訊息緩衝區,然後調用函數do_exit()殺死當前進程。這就是所謂按所顯示的訊息命名的“核心漏洞(Kernel oops)”錯誤。這片代碼我就不多講了,如果你是核心高手,這些輸出值可由核心編程高手用於推測引發此錯誤的條件,進而發現並糾正錯誤:
/*
* Oops. The kernel tried to access some bad page. We'll have to
* terminate things with extreme prejudice.
*/
bust_spinlocks(1);
if (oops_may_print()) {
#ifdef CONFIG_X86_PAE
if (error_code & 16) {
pte_t *pte = lookup_address(address);
if (pte && pte_present(*pte) && !pte_exec_kernel(*pte))
printk(KERN_CRIT "kernel tried to execute "
"NX-protected page - exploit attempt? "
"(uid: %d)/n", current->uid);
}
#endif
if (address < PAGE_SIZE)
printk(KERN_ALERT "BUG: unable to handle kernel NULL "
"pointer dereference");
else
printk(KERN_ALERT "BUG: unable to handle kernel paging"
" request");
printk(" at virtual address %08lx/n",address);
printk(KERN_ALERT " printing eip:/n");
printk("%08lx/n", regs->eip);
}
page = read_cr3();
page = ((unsigned long *) __va(page))[address >> 22];
if (oops_may_print())
printk(KERN_ALERT "*pde = %08lx/n", page);
/*
* We must not directly access the pte in the highpte
* case, the page table might be allocated in highmem.
* And lets rather not kmap-atomic the pte, just in case
* it's allocated already.
*/
#ifndef CONFIG_HIGHPTE
if ((page & 1) && oops_may_print()) {
page &= PAGE_MASK;
address &= 0x003ff000;
page = ((unsigned long *) __va(page))[address >> PAGE_SHIFT];
printk(KERN_ALERT "*pte = %08lx/n", page);
}
#endif
tsk->thread.cr2 = address;
tsk->thread.trap_no = 14;
tsk->thread.error_code = error_code;
die("Oops", regs, error_code);
bust_spinlocks(0);
do_exit(SIGKILL);