linux获取page大小,截获Linux操作系统异常处理、获取page fault系统调用

在某些情况下,我们可能需要去截获Linux操作系统的一些异常处理,比如截获page fault系统调用。

可以修改内核的情况下

如果我们能够修改内核,那么截获page fault系统调用就会非常简单。以linux 3.8.0内核为例,系统中发生page fault之后,会进入page fault异常处理,调用do_page_fault函数。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函数的内容提取出来,写成一个新的函数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内核的

1 ffffffff8136f6f0 :

2 ffffffff8136f6f0: 66 66 90 data32 xchg %ax,%ax

3 ffffffff8136f6f3: ff 15 07 0a 2b 00 callq *0x2b0a07(%rip) # ffffffff81620100

4 ffffffff8136f6f9: 48 83 ec 78 sub $0x78,%rsp

5 ffffffff8136f6fd: e8 ae 01 00 00 callq ffffffff8136f8b0

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

11 ffffffff8136f718: e9 33 02 00 00 jmpq ffffffff8136f950

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文件中查询,然后用内核模块参数的形式传入。而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

转载自:http://www.cnblogs.com/richardustc/archive/2013/05/03/3057455.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值