截获Linux操作系统异常处理
作者:网络转载 发布时间:[ 2013/5/13 10:24:57 ] 推荐标签:
在某些情况下,我们可能需要去截获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)
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11