首页 > 解决方案 > 试图通过 cr0 寄存器禁用分页

问题描述

我正在尝试使用 LKM 完全禁用分页(不要问我为什么只是在试验)。

我试过直接用 LKM 改变值。

void disable_paging(void)
{
    asm("movq %cr0, %rax\n\t"
        "movq $0xFFFFFFFEFFFFFFFF, %rbx\n\t"
        "and  %rbx, %rax\n\t"
        "movq %rax, %cr0\n\t");
}

那么预期的结果将是被翻转的位。实际结果是段错误。

标签: assemblylinux-kernelx86-64paging

解决方案


TL:DR:这行不通,但您的尝试并没有禁用分页,因为您清除了第 32 位而不是第 31 位。IDK 为什么这会导致任何用户空间进程的 SIGSEGV。

你从中得到的任何坏处都是在不告诉编译器的情况下破坏 RAX + RBX。


您显然正在为 x86-64 Linux 构建一个以长模式运行的模块。但是长模式需要启用分页。

根据 osdev 论坛主题x86_64 - 禁用分页?

如果在长模式下禁用分页,您将不再处于长模式。

如果这确实是真的(而不仅仅是用#GP异常或其他东西进行陷阱),那么显然这是一场彻底的灾难!

从 EIP 而不是 RIP 获取代码的可能性极小,如果您碰巧最终 EIP 指向物理地址空间低 4GiB 中的某个 64 位代码,REX 前缀将解码为 inc/dec。(内核地址在规范的上限范围内,但 RIP 的低 32 位很可能是某些代码的物理地址。)

也相关:为什么长模式需要分页- 可能是因为支持未分页的 64 位模式是不必要的硬件开销,永远不会得到太多实际使用。


我不确定你为什么会得到一个segfault。如果您尝试在 user-space 中运行此代码,这就是我所期望的mov %cr0, %rax因为它是 privileged 的​​错误,并且内核提供 SIGSEGV 以响应该用户空间#GP异常。

如果您从 LKM 的 init 函数中运行此函数,就像 Brendan 所说的那样,预期的结果将是该内核上的内核崩溃。或者内核可能会捕获它并将 SIGSEGV 传递到modprobe(1).


此外,您使用的是 GNU C Basic asm(没有任何破坏),因此 GCC 的代码生成假定寄存器(包括 RAX 和 RBX)没有被修改。当然,当您的代码不在身份映射页面中时,禁用分页也是一种跳跃,因此是否向编译器制造其他小谎言并不重要。如果这个函数没有内联到任何东西,那么在实践中破坏 RAX 不会受到伤害。但是破坏RBX绝对可以;它在 x86-64 System V 调用约定中保留调用。

顺便说一句,CR0 只有 32 个有效位。你可以and $0x7fffffff, %eax清除它。或者btr $31, %rax,如果您想清除 64 位寄存器中的第 31 位。 https://wiki.osdev.org/CPU_Registers_x86

根据英特尔手册第 3 卷(2019 年 1 月)的第 2.5 节:

CR0 和 CR4 的位 63:32 保留,必须写入零。将非零值写入任何高 32 位都会导致一般保护异常 #GP(0)。

根据 AMD 手册第 2 卷(2017 年 12 月)的第 3.1.1 节:

在长模式下,位 63:32 被保留并且必须写入零,否则会出现#GP。

因此,至少在可预见的将来,将 RAX 截断为 EAX 是可以的。新的东西往往会被添加到 MSR,而不是 CR 位。由于在 Linux 中没有办法做到这一点而不会崩溃,因此您不妨保持简单,以应对愚蠢的计算机技巧。


0xFFFFFFFEFFFFFFFF 清除第 32 位,而不是第 31 位

以上所有内容都基于您实际上正在清除分页启用位的假设。因此,也许 SIGSEGV 仅仅是由于使用 GNU C 基本 asm 破坏了寄存器而根本没有实际更改控制寄存器。

https://wiki.osdev.org/CPU_Registers_x86显示分页是 CR0 的第 31 位,并且高半部分没有真正的位。 https://en.wikipedia.org/wiki/Control_register#CR0说 CR0 是长模式下的 64 位寄存器。(但在高半部分仍然没有任何位可以做任何事情。)

您的掩码实际上清除了第 32 位,即高半部分的低位。正确的 AND 掩码是0x7FFFFFFF. 或btr $31, %eax。将 RAX 截断为 EAX 很好。

实际上会使您的内核在长模式下崩溃,就像您尝试的那样:

// disable paging, should crash
    asm volatile(
        "mov  %%cr0, %%rax        \n\t"   // assembles with no REX prefix, same as mov %cr0,%eax
        "btr  $31, %%eax          \n\t"   // reset (clear) bit 31
        "mov  %%rax, %%cr0        \n\t"
        ::
        : "rax", "memory"
     );

推荐阅读