科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道利用异常表处理Linux内核态缺页异常 (3)

利用异常表处理Linux内核态缺页异常 (3)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

首先让我们来看看传给这个函数调用的两个参数:它们都是通过entry.S在堆栈中建立的(arch/i386/kernel/entry.S),参数regs指向保存在堆栈中的寄存器,error_code中存放着异常的出错码,具体的堆栈布局参见图一(堆栈的生成过程请参考《Linux内核源代码情景分析》一书)

作者:asdkj 来源:赛迪网技术社区 2007年10月28日

关键字: 异常 内核 Linux

  • 评论
  • 分享微博
  • 分享邮件

首先让我们来看看传给这个函数调用的两个参数:它们都是通过entry.S在堆栈中建立的(arch/i386/kernel/entry.S),参数regs指向保存在堆栈中的寄存器,error_code中存放着异常的出错码,具体的堆栈布局参见图一(堆栈的生成过程请参考《Linux内核源代码情景分析》一书)

利用异常表处理Linux内核态缺页异常 (3)

该函数首先从CPU的控制寄存器CR2中获取出现缺页异常的虚拟地址。由于缺页异常处理程序需要处理的缺页异常类型很多,分支也很复杂。基于本文的主旨,我们只关心以下的几种内核缺页异常处理的情况:

1." 程序要访问的内核地址空间的内容不在内存中,先跳转到标号vmalloc_fault,如果当前访问的内容所对应的页目录项不在内存中,再跳转到标号no_context;

2. 缺页异常发生在中断或者内核线程中,跳转到标号no_context;

3. 程序在核心态运行时访问用户空间的数据,被访问的数据不在内存中

a) 出现异常的虚拟地址在进程的某个vma中,但是系统内存无法分配空闲页框(page frame),则先跳转到标号out_of_memory,再跳转到标号no_context;

b) 出现异常的虚拟地址不属于进程任一个vma,而且不属于堆栈扩展的范畴,则先跳转到标号bad_area,最终也是到达标号no_context。

从上面的这几种情况来看,我们关注的焦点最后集中到标号no_context处,即对函数search_exception_table的调用。这个函数的作用就是通过发生缺页异常的指令(regs->eip)在异常表(exception table)中寻找下一条可以继续运行的指令(fixup)。这里提到的异常表包含一些地址对,地址对中的前一个地址表示出现异常的指令的地址,后一个表示当前一个指令出现错误时,程序可以继续得以执行的修复地址。

如果这个查找操作成功的话,缺页异常处理程序将堆栈中的返回地址(regs->eip)修改成修复地址并返回,随后,发生异常的进程将按照fixup中安排好的指令继续执行下去。当然,如果无法找到与之匹配的修复地址,系统只有打印出出错信息并停止运作。

那么,这个所谓的修复地址又是如何生成的呢?是系统自动生成的吗?答案当然是否定的,这些修复指令都是编程人员通过as提供的扩展功能写进内核源码中的。下面我们就来分析一下其实现机制。

异常表的实现机制

笔者取include/asm-i386/uaccess.h中的宏定义__copy_user编写了一段程序作为例子加以讲解。

/* hello.c */
  #include 
  #include 
  
  #define __copy_user(to,from,size) do
 { int __d0, __d1; __asm__ __volatile__
( "0: rep; movsl\n" " movl %3,%0\n" "1: rep; movsb\n"
 "2:\n" ".section .fixup,\"ax\"\n" 
"3: lea 0(%3,%0,4),%0\n" " jmp 2b\n" 
".previous\n" ".section __ex_table,\"a\"\n"
 " .align 4\n"
 " .long 0b,3b\n" " .long 1b,2b\n"
 ".previous" : "=&c"(size), "=&D" (__d0), 
"=&S" (__d1) : "r"(size & 3), "0"(size / 4), "1"(to),
 "2"(from) : "memory"); } while (0)
  
  int main(void)
  {
   const char *string = "Hello, world!";
   char buf[20];
   unsigned long n, m;
  
   m = n = strlen(string);
   __copy_user(buf, string, n);
   buf[m] = '\0';
   printf("%s\n", buf);
   exit(0);
  }
  

先看看本程序的执行结果:

$ gcc hello.c -o hello
   $ ./hello
  Hello, world!
 

显然,这就是一个简单的"hello world"程序,那为什么要写得这么复杂呢?程序中的一大段汇编代码在内核中才能体现出其价值,笔者将其加入到上面的程序中,是为了后面的分析而准备的。

系统在核心态运行的时候,参数是通过寄存器来传递的,由于寄存器所能够传递的信息有限,所以传递的参数大多数是指针。要使用指针所指向的更大块的数据,就需要将用户空间的数据拷贝到系统空间来。上面的__copy_user在内核中正是扮演着这样的一个拷贝数据的角色,当然,内核中这样的宏定义还很多,笔者也只是取其中的一个来讲解,读者如果感兴趣的话可以看完本文以后自行学习。

如果读者对于简单的嵌入式汇编还不是很了解的话,可以参考《Linux内核源代码情景分析》一书。下面我们将程序编译成汇编程序来加以分析:

$ gcc -S hello.c
  /* hello.s */
   movl -60(%ebp), %eax
   andl $3, %eax
   movl -60(%ebp), %edx
   movl %edx, %ecx
   shrl $2, %ecx
   leal -56(%ebp), %edi
   movl -12(%ebp), %esi
  #APP
   0: rep; movsl
   movl %eax,%ecx
  1: rep; movsb
  2:
  .section .fixup,"ax"
  3: lea 0(%eax,%ecx,4),%ecx
   jmp 2b
  .previous
  .section __ex_table,"a"
   .align 4
   .long 0b,3b
   .long 1b,2b
  .previous
  #NO_APP
   movl %ecx, %eax
 

从上面通过gcc生成的汇编程序中,我们可以很容易的找到访问用户地址空间的指令,也就是程序中的标号为0和1的两条语句。而程序中伪操作.section的作用就是定义了.fixup和__ex_table这样的两个段,那么这两段在可执行程序中又是如何安排的呢?下面就通过objdump给读者一个直观的概念:

 $ objdump --section-headers hello
  hello:   file format elf32-i386
  
  Sections:
  Idx Name     Size   VMA    LMA    File off Algn
   0 .interp    00000013 080480f4 080480f4 000000f4 2**0
           CONTENTS, ALLOC, LOAD, READONLY, DATA
   ………………………………
   9 .init     00000018 080482e0 080482e0 000002e0 2**2
           CONTENTS, ALLOC, LOAD, READONLY, CODE
   10 .plt     00000070 080482f8 080482f8 000002f8 2**2
           CONTENTS, ALLOC, LOAD, READONLY, CODE
   11 .text     000001c0 08048370 08048370 00000370 2**4
           CONTENTS, ALLOC, LOAD, READONLY, CODE
   12 .fixup    00000009 08048530 08048530 00000530 2**0
           CONTENTS, ALLOC, LOAD, READONLY, CODE
   13 .fini     0000001e 0804853c 0804853c 0000053c 2**2
           CONTENTS, ALLOC, LOAD, READONLY, CODE
   14 .rodata    00000019 0804855c 0804855c 0000055c 2**2
           CONTENTS, ALLOC, LOAD, READONLY, DATA
   15 __ex_table  00000010 08048578 08048578 00000578 2**2
           CONTENTS, ALLOC, LOAD, READONLY, DATA
   16 .data     00000010 08049588 08049588 00000588 2**2
           CONTENTS, ALLOC, LOAD, DATA
           CONTENTS, READONLY
   ………………………………
   26 .note     00000078 00000000 00000000 0000290d 2**0
           CONTENTS, READONLY
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章