日本好好热aⅴ|国产99视频精品免费观看|日本成人aV在线|久热香蕉国产在线

  • <cite id="ikgdy"><table id="ikgdy"></table></cite>
    1. 西西軟件園多重安全檢測下載網(wǎng)站、值得信賴的軟件下載站!
      軟件
      軟件
      文章
      搜索

      首頁編程開發(fā)其它知識 → 截獲Linux操作系統(tǒng)異常處理、獲取page fault系統(tǒng)調(diào)用

      截獲Linux操作系統(tǒng)異常處理、獲取page fault系統(tǒng)調(diào)用

      相關(guān)軟件相關(guān)文章發(fā)表評論 來源:西西整理時間:2013/5/3 20:33:17字體大。A-A+

      作者:RichardUSTC點擊:101次評論:0次標簽: 異常處理

      在某些情況下,我們可能需要去截獲Linux操作系統(tǒng)的一些異常處理,比如截獲page fault系統(tǒng)調(diào)用。

      可以修改內(nèi)核的情況下

      如果我們能夠修改內(nèi)核,那么截獲page fault系統(tǒng)調(diào)用就會非常簡單。以linux 3.8.0內(nèi)核為例,系統(tǒng)中發(fā)生page fault之后,會進入page fault異常處理,調(diào)用do_page_fault函數(shù)。do_page_fault的代碼如下:

      1 dotraplinkage void __kprobes
      2 do_page_fault(struct pt_regs *regs, unsigned long error_code)
      3 {
      4     exception_enter(regs);
      5     __do_page_fault(regs, error_code);
      6     exception_exit(regs);
      7 }

      我們把do_page_fault函數(shù)的內(nèi)容提取出來,寫成一個新的函數(shù)default_do_page_fault。再增加一個函數(shù)指針do_page_fault_handler,初始化為default_do_page_fault。將原來的do_page_fault內(nèi)部改為調(diào)用函數(shù)指針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導出,我們在內(nèi)核模塊中可以很方便地訪問它。只要將do_page_fault_handler的值設(shè)置為自定義的page fault異常處理函數(shù),就能完成截獲功能。如果想要恢復原來的異常處理函數(shù),只需要再次把do_page_fault_handler設(shè)置為default_do_page_fault即可。

      不能修改內(nèi)核的情況下

      但是有些情況下,我們不能直接修改內(nèi)核代碼,需要在已經(jīng)編譯好的內(nèi)核上完成截獲功能。

      開始的時候,我考慮在do_page_fault函數(shù)開始處插入跳轉(zhuǎn)代碼,跳轉(zhuǎn)到自定義的page fault處理函數(shù)中。但是實踐的時候發(fā)現(xiàn),內(nèi)核不允許直接修改do_page_fault的代碼。

      經(jīng)過一番調(diào)查,又想到一個新的辦法,即通過更改IDT表的方式來截獲page fault。

      內(nèi)核原有的IDT表肯定是不能直接寫的,所以我申請了一個頁,將原來的IDT表復制過來,再更改頁面異常對應的ISR(Interrupt Service Routine)。page fault的ISR名稱為page_fault,它將寄存器壓棧,將error number壓棧,然后調(diào)用do_page_fault,待do_page_fault返回之后再恢復寄存器,退出異常處理。

      在Linux內(nèi)核中,ISR是用匯編寫的。例如,x86_64 Linux的ISR源碼位于內(nèi)核源碼arch/x86/kernel/entry_64.S中,X86_32的位于arch/x86/kernel/entry_32.S中。如果去讀entry_64.S或者entry_32.S,你會發(fā)現(xiàn)這兩個文件非常復雜,利用了很多的匯編宏和宏定義,無法方便地基于它們寫一個自定義的ISR出來。

      我的解決辦法是將內(nèi)核編譯出來,反匯編vmlinux.o,然后查找page_fault,找到其匯編代碼。下面的匯編代碼就是linux-3.8.0 X86_64內(nèi)核的

       1 ffffffff8136f6f0 <page_fault>:
       2 ffffffff8136f6f0:       66 66 90                data32 xchg %ax,%ax
       3 ffffffff8136f6f3:       ff 15 07 0a 2b 00       callq  *0x2b0a07(%rip)        # ffffffff81620100 <pv_irq_ops+0x30>
       4 ffffffff8136f6f9:       48 83 ec 78             sub    $0x78,%rsp
       5 ffffffff8136f6fd:       e8 ae 01 00 00          callq  ffffffff8136f8b0 <error_entry>
       6 ffffffff8136f702:       48 89 e7                mov    %rsp,%rdi
       7 ffffffff8136f705:       48 8b 74 24 78          mov    0x78(%rsp),%rsi
       8 ffffffff8136f70a:       48 c7 44 24 78 ff ff    movq   $0xffffffffffffffff,0x78(%rsp)
       9 ffffffff8136f711:       ff ff 
      10 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

       1 asmlinkage void my_page_fault(void);
       2 asm("   .text");
       3 asm("   .type my_page_fault,@function");
       4 asm("my_page_fault:");
       5 //the first 3 bytes of the routine basically do nothing,
       6 //but I decide to keep them because kernel may rely on them for some special purpose
       7 asm("   .byte 0x66");
       8 asm("   xchg %ax, %ax"); 
       9 asm("   callq *addr_adjust_exception_frame");
      10 asm("   sub $0x78, %rsp");
      11 asm("   callq *addr_error_entry");
      12 asm("   mov %rsp, %rdi");
      13 asm("   mov 0x78(%rsp), %rsi");
      14 asm("   movq $0xffffffffffffffff, 0x78(%rsp)");
      15 asm("   callq my_do_page_fault");
      16 asm("   jmpq *addr_error_exit");
      17 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文件中查詢,然后用內(nèi)核模塊參數(shù)的形式傳入。而my_do_page_fault則是我們自己定義的page fault處理函數(shù)。

      如果需要截獲X86_32的page fault,可以參考這個C文件。不過需要注意的是,新版內(nèi)核有所變動,這里的代碼需要根據(jù)自己的情況做一些調(diào)整。

      有了自定義的ISR之后,就可以將這個ISR填到IDT中,加載新的IDT表之后,自定義的page fault處理函數(shù)就開始發(fā)揮作用了。這個過程主要有以下幾個步驟:

      用store_idt(&default_idtr)保存現(xiàn)有的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表,釋放申請的頁面。

      讀完文章之后,可以參考我的github中的代碼:https://github.com/RichardUSTC/intercept-page-fault-handler

      轉(zhuǎn)載自:http://www.cnblogs.com/richardustc/archive/2013/05/03/3057455.html

        相關(guān)評論

        閱讀本文后您有什么感想? 已有人給出評價!

        • 8 喜歡喜歡
        • 3 頂
        • 1 難過難過
        • 5 囧
        • 3 圍觀圍觀
        • 2 無聊無聊

        熱門評論

        最新評論

        發(fā)表評論 查看所有評論(0)

        昵稱:
        表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
        字數(shù): 0/500 (您的評論需要經(jīng)過審核才能顯示)