试图通过 cr0 寄存器禁用分页

Trying to disable paging through cr0 register

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

我试过直接使用 LKM 更改值。

void disable_paging(void)
{
    asm("movq %cr0, %rax\n\t"
        "movq [=10=]xFFFFFFFEFFFFFFFF, %rbx\n\t"
        "and  %rbx, %rax\n\t"
        "movq %rax, %cr0\n\t");
}

好吧,预期的结果是位被翻转。实际结果是段错误。

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

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


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

根据 osdev 论坛帖子 x86_64 - disabling paging?

If you disable paging in long mode, you will no longer be in long mode.

如果这确实是真的(而不是只是陷入 #GP 异常或其他什么),那么显然这是一场彻底的灾难!!

从 EIP 而不是 RIP 获取代码是极不可能获取任何东西的,REX 前缀将解码为 inc/dec 如果你碰巧以 EIP 指向低位某处的某个 64 位代码而告终4GiB 的物理地址 space。 (内核地址在较高的规范范围内,但 RIP 的低 32 位极有可能是某些代码的物理地址。)

也相关:Why does long mode require paging - 可能是因为支持未分页的 64 位模式是一项不必要的硬件开销,永远不会有太多实际用途。


我不确定你为什么会遇到 segfault。如果您尝试 运行 user-space 中的此代码,这就是我所期望的,其中 mov %cr0, %raxit's privileged 而出错,并且内核提供 SIGSEGV 作为响应user-space #GP 异常。

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


此外,您正在使用 GNU C Basic asm(没有任何破坏),因此 GCC 的 code-gen 假定寄存器(包括 RAX 和 RBX)未被修改。当然,当您的代码不在 identity-mapped 页面中时,禁用分页也是一种跳转,因此是否向编译器撒谎并不重要。如果这个函数没有内联到任何东西,那么在实践中破坏 RAX 就不会受到伤害。但是破坏 RBX 绝对可以;它是 x86-64 System V 调用约定中的 call-preserved。

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

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

Bits 63:32 of CR0 and CR4 are reserved and must be written with zeros. Writing a nonzero value to any of the upper 32 bits results in a general-protection exception, #GP(0).

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

In long mode, bits 63:32 are reserved and must be written with zero, otherwise a #GP occurs.

因此,至少在可预见的未来,将 RAX 转换为 EAX 是没问题的。新内容往往会添加到 MSR,而不是 CR 位。由于在 Linux 中无法在不崩溃的情况下执行此操作,因此您最好保持简单以应对愚蠢的计算机技巧。


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

以上所有内容都基于您实际清除 paging-enable 位的假设。因此,SIGSEGV 可能只是由于使用 GNU C 基本汇编破坏了寄存器,而根本没有实际更改控制寄存器。

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

您的掩码实际上清除了第 32 位,即高半部分的低位。正确的 AND 掩码是 0x7FFFFFFF。或者 btr , %eax。 T运行将 RAX 转换为 EAX 没问题。

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

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