【问题标题】:Trying to disable paging through cr0 register试图通过 cr0 寄存器禁用分页
【发布时间】:2019-07-20 08:58:43
【问题描述】:

我正在尝试使用 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");
}

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

【问题讨论】:

  • 你确定你是在线性地址和物理地址匹配的页面中执行的吗?
  • 您显示的代码试图清除 cr0 的第 32 位,该位始终为 0,因此它不执行任何操作。 PG 是 cr0 的第 31 位。
  • @RomanKwaśniewski : basic 内联汇编语句隐含易变。来自GCC docs没有输出操作数的asm 语句,包括asm goto 语句,是隐式可变的。
  • 在不告诉编译器它们已被修改和破坏的情况下修改寄存器可能会给代码添加细微的错误,尤其是在优化的情况下。需要启用长模式分页。
  • 尝试使用 32 位指令(CR0 是一个 32 位寄存器,将 64 位推入 32 位寄存器没有多大意义)。请注意,预期的结果是计算机重置/重新启动(#PF 然后 #DF 然后三重故障;因为没有任何身份映射,包括 IDT)。

标签: assembly linux-kernel x86-64 paging


【解决方案1】:

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

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


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

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

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

如果这是真的(而不是仅仅使用#GP 异常或其他东西来捕获),那么显然这是一场彻底的灾难!!

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

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


我不知道你为什么会得到一个segfault。如果您尝试在用户空间中运行此代码,这就是我所期望的,其中mov %cr0, %rax 出错是因为it's 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 显示 Paging 是 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"
     );

【讨论】:

  • 没有办法在运行时禁用 Linux 内核中的分页而不崩溃。但是有一个名为CONFIG_MMU 的编译时选项可用于在禁用分页的情况下构建内核,并且在 x86 上受支持。关于如何实际执行此操作的文档很少(仅调整 CONFIG_MMU 是不够的)。关于段错误,在长模式下,在 Intel 和 AMD 处理器上,控制寄存器中的所有保留位必须始终为零。 OP 显示的 4 条指令不会改变架构状态,我认为它们不是故障的原因。
  • @HadiBrais:OP 的指令不会更改 CR0,但会更改 RAX(nitpick:这也是架构状态的一部分)。不告诉 GCC,这显然是个问题。 :P
  • 哦,是的。 rax 不是一个呼叫破坏寄存器吗?但rbx 不是。也像你说的,gcc 不知道rax 被修改了。
  • @HadiBrais:哦,对了,我忘了他们用了 2 个 regs。但是谁知道这个内联到了什么?它没有被称为sys_disable_paging,所以它可能不是直接从系统调用调度中调用的。
猜你喜欢
  • 2021-03-29
  • 1970-01-01
  • 2011-05-24
  • 1970-01-01
  • 2018-11-17
  • 1970-01-01
  • 1970-01-01
  • 2018-12-12
  • 1970-01-01
相关资源
最近更新 更多