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"
);