我仿照着写了一个,名为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表,释放申请的页面。

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