您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么截獲Linux操作系統異常處理”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
在某些情況下,我們可能需要去截獲Linux操作系統的一些異常處理,比如截獲page fault異常處理。
可以修改內核的情況下:
如果我們能夠修改內核,那么截獲page fault異常處理就會非常簡單。以linux 3.8.0內核為例,系統中發生page fault之后,會進入page fault異常處理,調用do_page_fault函數。do_page_fault的代碼如下:
dotraplinkage void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code) { exception_enter(regs); __do_page_fault(regs, error_code); exception_exit(regs); }
我們把do_page_fault函數的內容提取出來,寫成一個新的函數default_do_page_fault。再增加一個函數指針do_page_fault_handler,初始化為default_do_page_fault。將原來的do_page_fault內部改為調用函數指針do_page_fault_handler。修改之后的代碼如下:
void default_do_page_fault(struct pt_regs *regs, unsigned long error_code) { exception_enter(regs); __do_page_fault(regs, error_code); exception_exit(regs); } EXPORT_SYMBOL(default_do_page_fault); typedef void (*do_page_fault_handler_t)(struct pt_regs *, unsigned long); do_page_fault_handler_t do_page_fault_handler = default_do_page_fault; EXPORT_SYMBOL(do_page_fault_handler); dotraplinkage void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code){ do_page_fault_handler(regs, error_code); }
由于do_page_fault_handler被EXPORT_SYMBOL導出,我們在內核模塊中可以很方便地訪問它。只要將do_page_fault_handler的值設置為自定義的page fault異常處理函數,就能完成截獲功能。如果想要恢復原來的異常處理函數,只需要再次把do_page_fault_handler設置為default_do_page_fault即可。
不能修改內核的情況下:
但是有些情況下,我們不能直接修改內核代碼,需要在已經編譯好的內核上完成截獲功能。
開始的時候,我考慮在do_page_fault函數開始處插入跳轉代碼,跳轉到自定義的page fault處理函數中。但是實踐的時候發現,內核不允許直接修改do_page_fault的代碼。
經過一番調查,又想到一個新的辦法,即通過更改IDT表的方式來截獲page fault。
內核原有的IDT表肯定是不能直接寫的,所以我申請了一個頁,將原來的IDT表復制過來,再更改頁面異常對應的ISR(Interrupt Service Routine)。page fault的ISR名稱為page_fault,它將寄存器壓棧,將error number壓棧,然后調用do_page_fault,待do_page_fault返回之后再恢復寄存器,退出異常處理。
在Linux內核中,ISR是用匯編寫的。例如,x86_64 Linux的ISR源碼位于內核源碼arch/x86/kernel/entry_64.S中,X86_32的位于arch/x86/kernel/entry_32.S中。如果去讀entry_64.S或者entry_32.S,你會發現這兩個文件非常復雜,利用了很多的匯編宏和宏定義,無法方便地基于它們寫一個自定義的ISR出來。
我的解決辦法是將內核編譯出來,反匯編vmlinux.o,然后查找page_fault,找到其匯編代碼。下面的匯編代碼就是linux-3.8.0 X86_64內核的:
ffffffff8136f6f0 <page_fault>: ffffffff8136f6f0: 66 66 90 data32 xchg %ax,%ax ffffffff8136f6f3: ff 15 07 0a 2b 00 callq *0x2b0a07(%rip) # ffffffff81620100 <pv_irq_ops+0x30> ffffffff8136f6f9: 48 83 ec 78 sub $0x78,%rsp ffffffff8136f6fd: e8 ae 01 00 00 callq ffffffff8136f8b0 <error_entry> ffffffff8136f702: 48 89 e7 mov %rsp,%rdi ffffffff8136f705: 48 8b 74 24 78 mov 0x78(%rsp),%rsi ffffffff8136f70a: 48 c7 44 24 78 ff ff movq $0xffffffffffffffff,0x78(%rsp) ffffffff8136f711: ff ff ffffffff8136f713: e8 1f 2e 00 00 callq ffffffff81372537 <do_page_fault> 11 ffffffff8136f718: e9 33 02 00 00 jmpq ffffffff8136f950 <error_exit> 12 ffffffff8136f71d: 0f 1f 00 nopl (%rax)
我仿照著寫了一個,名為my_page_fault:
asmlinkage void my_page_fault(void); asm(" .text"); asm(" .type my_page_fault,@function"); asm("my_page_fault:"); //the first 3 bytes of the routine basically do nothing, //but I decide to keep them because kernel may rely on them for some special purpose asm(" .byte 0x66"); asm(" xchg %ax, %ax"); asm(" callq *addr_adjust_exception_frame"); asm(" sub $0x78, %rsp"); asm(" callq *addr_error_entry"); asm(" mov %rsp, %rdi"); asm(" mov 0x78(%rsp), %rsi"); asm(" movq $0xffffffffffffffff, 0x78(%rsp)"); asm(" callq my_do_page_fault"); asm(" jmpq *addr_error_exit"); asm(" nopl (%rax)");
其中第9行addr_adjust_exception_frame是(pv_irq_ops+0x30)地址處存儲的值;第11行addr_error_entry是error_entry的地址;第16行addr_error_exit是error_exit的地址。這幾個值需要從System.map文件中查詢,然后用內核模塊參數的形式傳入。而my_do_page_fault則是我們自己定義的page fault處理函數。
如果需要截獲X86_32的page fault,可以參考這個C文件。不過需要注意的是,新版內核有所變動,這里的代碼需要根據自己的情況做一些調整。
有了自定義的ISR之后,就可以將這個ISR填到IDT中,加載新的IDT表之后,自定義的page fault處理函數就開始發揮作用了。這個過程主要有以下幾個步驟:
用store_idt(&default_idtr)保存現有的IDT寄存器值
從default_idtr中讀出IDT表首地址和表的大小
申請一個頁面
將原來的idt表拷貝到新申請的頁面中
利用pack_gate將my_page_fault(注意不是my_do_page_fault)填入到對應的IDT項中
在idtr中填寫新的IDT表地址和大小,用load_idt(&idtr)加載新的IDT表到當前CPU
利用smp_call_function,將新的IDT表加載到其他CPU上。
如果想恢復原來的IDT表,則用load(&default_idtr)和smp_call_function加載原來的IDT表,釋放申請的頁面。
“怎么截獲Linux操作系統異常處理”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。