验证缺页异常机制

渗透技巧 2年前 (2021) admin
479 0 0

问题背景

在x86系列的cpu上开启分页机制后,指令涉及到”内存读写”操作时,用到的”内存地址”都是”虚拟地址”。”mmu硬件”会按照”分段”、”分页”的计算规则,将”虚拟地址”转换成”物理地址”。最终指令可以对”物理地址”读写数据。

上面这段关于内存的”理论知识”,我觉得如果可以实践看一看,就能多一些”感觉”。

本文记录我对下面两个结论的调试和验证过程,也是我对极客时间的操作系统实战 45 讲[1]课程中”内存管理”章节的部分学习和实践,基于第26课代码[2]

“mmu硬件”在转换”虚拟地址”时有可能发生”缺页异常”,包括:

  • 访问”未向内核申请的虚拟地址”会导致”缺页异常”
  • 当第一次访问申请的虚拟地址时,也会产生”缺页异常”;第二次访问同样的虚拟地址时,不会产生”缺页异常”

验证过程

  • 访问”未向内核申请的虚拟地址”会导致”缺页异常”

    按照理论来说:访问未申请的虚拟地址时,”mmu硬件”在转换”虚拟地址”时会发出”缺页异常”(可能因为找不到对应的”页表项”)

    在验证这个理论前,先介绍一下cosmos(课程自制的内核系统名称)相关的代码,以方便读者大概理解文中出现的函数作用

    void test_vadr()
    {
     adr_t vadr = vma_new_vadrs(&initmmadrsdsc, NULL, 0x1000, 0, 0);  // 0x1000是想要申请的空间大小,vadr是申请的内存地址
     if(NULL == vadr)
     {
      kprint("分配虚拟地址空间失败n");
     }
      ...
     hal_memset((void*)vadr, 0, 0x1000);  // 初始化刚申请的内存为0
     ...
    }

    上面test_vadr是cosmos中用来验证”虚拟内存”相关功能的测试函数。

    在cosmos中,想要使用虚拟空间,需要先调用vma_new_vadrs申请一个虚拟地址。

    cosmos相关代码介绍完毕,下面用gdb调试课程代码,来实际验证一下”理论”:

    (gdb) break test_vadr   // 在 test_vadr 函数下断点(选择在test_vadr函数下断点,是因为此时 虚拟内存管理、物理内存管理、缺页异常处理 等功能都已经实现)
    Breakpoint 1 at 0xffff80000202444a: file ../kernel/krlvadrsmem.c, line 289.
    (gdb) continue
    Continuing.

    Breakpoint 1, test_vadr () at ../kernel/krlvadrsmem.c:289
    289 {
    (gdb) break krluserspace_accessfailed   // 在 krluserspace_accessfailed 函数下断点
    Breakpoint 2 at 0xffff800002026588: file ../kernel/krlvadrsmem.c, line 1145.
    (gdb) call hal_memset(0x400,0,0x1)  // 调用hal_memset函数,向 0x400地址 写一个字节内容

    Breakpoint 2, krluserspace_accessfailed (fairvadrs=0) at ../kernel/krlvadrsmem.c:1145 //
    ...
    (gdb) where   // 可以看到 当前代码在../kernel/krlvadrsmem.c文件1145行的krluserspace_accessfailed
    #0  krluserspace_accessfailed (fairvadrs=0) at ../kernel/krlvadrsmem.c:1145
    #1  0xffff8000020135d2 in hal_fault_allocator (faultnumb=14, krnlsframp=0xffff80000008fe18)
        at ../hal/x86/halintupt.c:171
    #2  0xffff80000200d830 in exc_page_fault ()
    ...
    (gdb) continue
    Continuing.
    ^C      // 因为在这里卡死了,所以我按了ctrl+c
    Program received signal SIGINT, Interrupt.
    hal_sysdie (errmsg=0xffff80000203a237 "缺页处理失败n") at ../hal/x86/halcpuctrl.c:185
    185         ;

    从上面的调试信息可以看出来:cpu在执行hal_memset函数访问0x400虚拟地址时,跳到了krluserspace_accessfailed函数,最后卡死在halcpuctrl.c第185行

    验证缺页异常机制
    image

    这里的0x400地址不是通过vma_new_vadrs申请的地址。

    krluserspace_accessfailed函数只有cpu遇到中断并且中断号为14(也就是”缺页异常”)的时候才会被调用

    // 异常处理
    void hal_fault_allocator(uint_t faultnumb, void *krnlsframp) //eax,edx
    {
        ...
        if (faultnumb == 14)        // 缺页异常 中断号是14
        {
            ...
            if (krluserspace_accessfailed(fairvadrs) != 0)
           {
               system_error("缺页处理失败n");
           }
            ...
        }
        ...
    }

    所以可以推测出来:访问0x400时,mmu硬件发出了”缺页中断”,接着cpu处理”缺页中断”就会到krluserspace_accessfailed函数,因为krluserspace_accessfailed函数处理”页表映射”失败,所以会调用system_error函数,最终执行for循环卡死。

    这样可以看出来理论没有问题。

  • 访问”已申请的虚拟地址”

    按照理论:在第一次访问”已申请的虚拟地址”,因为”虚拟内存”和”物理内存”的映射关系还没写到”页表”中,所以”mmu硬件”在转换”虚拟地址”时会发出”缺页异常”,然后”缺页异常”处理程序会写这个映射关系到”页表”中;第二次访问同一个”已申请的虚拟地址”,因为”虚拟内存”和”物理内存”的映射关系已经写到”页表”,”mmu硬件”就可以正常转换,不会发出”缺页异常”。

    下面同样用gdb调试来验证一下:

    (gdb) continue
    Continuing.

    Breakpoint 1, test_vadr () at ../kernel/krlvadrsmem.c:289
    289 {
    (gdb) break krluserspace_accessfailed
    Breakpoint 2 at 0xffff800002026588: file ../kernel/krlvadrsmem.c, line 1145.
    (gdb) x /bx 0x40000
    0x40000: Cannot access memory at address 0x40000
    (gdb) call vma_new_vadrs(&initmmadrsdsc,0x40000,0x1000, 0, 0)   // 向内核申请虚拟空间,地址是0x40000,大小是0x1000
    $1 = 0
    (gdb) call hal_memset(0x40000,0,0x1)    // 第一次访问0x40000时进入到"中断处理函数"

    Breakpoint 2, krluserspace_accessfailed (fairvadrs=0) at ../kernel/krlvadrsmem.c:1145
    ...
    (gdb) finish
    Run till exit from #0  krluserspace_accessfailed (fairvadrs=2097282) at ../kernel/krlvadrsmem.c:1145
    ...
    (gdb) finish
    Run till exit from #0  0xffff8000020135d2 in hal_fault_allocator (faultnumb=14,
        krnlsframp=0xffff80000008fe18) at ../hal/x86/halintupt.c:171
    0xffff80000200d830 in exc_page_fault ()
    (gdb) finish
    Run till exit from #0  0xffff80000200d830 in exc_page_fault ()
    (gdb) where
    #0  test_vadr () at ../kernel/krlvadrsmem.c:289
    ...
    (gdb) call hal_memset(0x40000,0,0x1)    // 第二次访问0x40000时没有产生"中断"
    (gdb) x /bx 0x40000
    0x40000: 0x00

    从上面可以看到:调用vma_new_vadrs申请0x40000地址后,第一次调用hal_memset去访问0x40000时会进入到”缺页异常”处理程序(krluserspace_accessfailed函数)中,第二次再访问0x40000时就不会进入到”缺页异常”处理程序。

总结

通过调试,很容易对cosmos中”虚拟内存”访问时的业务流程做一点研究。

同样我们可以通过”调试”去观察”分页”机制,来验证课程中的知识点。

PS:后面会有一篇文章讲解怎么调试cosmos代码

参考资料

[1]

操作系统实战 45 讲: https://time.geekbang.org/column/intro/100078401?tab=catalog

[2]

第26课代码: https://gitee.com/lmos/cosmos/tree/master/lesson25~26/Cosmos


原文始发于微信公众号(leveryd):验证缺页异常机制

版权声明:admin 发表于 2021年11月30日 下午4:23。
转载请注明:验证缺页异常机制 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...