assembly - x86 指令是否需要它们自己的编码以及它们的所有参数同时存在于内存中?
问题描述
我试图弄清楚是否可以运行其 RAM 仅由单个物理页面支持的 Linux VM。
为了模拟这一点,我修改了 KVM 中的嵌套页面错误处理程序,以从所有嵌套页面表 (NPT) 条目中删除当前位,除了对应于当前处理的页面错误的条目。
在尝试启动 Linux 客户机时,我观察到使用内存操作数的汇编指令,例如
add [rbp+0x820DDA], ebp
导致页面错误循环,直到我恢复包含指令的页面以及操作数中引用的页面的当前位(在此示例中[rbp+0x820DDA]
)。
我想知道为什么会这样。CPU不应该顺序访问内存页面,即先读取指令然后访问内存操作数吗?还是 x86 要求指令页和所有操作数页都可以同时访问?
我正在测试 AMD Zen 1。
解决方案
是的,它们确实需要机器代码和所有内存操作数。
CPU不应该顺序访问内存页面,即先读取指令然后访问内存操作数吗?
是的,这在逻辑上是这样发生的,但是页面错误异常会中断该两步过程并丢弃任何进度。CPU 没有任何方法可以记住发生页面错误时它处于什么指令的中间。
当页面错误处理程序在处理有效的页面错误后返回时,RIP= 错误指令的地址,因此 CPU从头开始重试执行它。
操作系统修改错误指令的机器代码并期望它iret
在页面错误处理程序(或任何其他异常或中断处理程序)之后执行不同的指令是合法的。因此,AFAIK 在您所说的情况下,从架构上要求 CPU 从 CS:RIP 重新获取代码。(假设它甚至确实返回到出错的 CS:RIP,而不是在等待磁盘出现硬页错误时调度另一个进程,或者在无效页错误时将 SIGSEGV 传递给信号处理程序。)
虚拟机管理程序进入/退出可能在架构上也是必需的。即使它没有在纸上明确禁止,它也不是 CPU 的工作方式。
@torek 评论说,一些(CISC)微处理器部分解码指令并在页面错误时转储微寄存器状态,但 x86 并非如此。
一些指令是可中断的并且可以取得部分进展,例如rep movs
(memcpy in a can) 和其他字符串指令,或收集加载/分散存储。但唯一的机制是更新架构寄存器,如字符串操作的 RCX / RSI / RDI,或收集的目标和掩码寄存器(例如AVX2vpgatherdd
的手册)。不保留操作码/解码会导致一些隐藏的内部寄存器并在 iret 从页面错误处理程序之后重新启动它。这些是执行多个单独数据访问的指令。
还要记住,x86(像大多数 ISA)保证指令是原子的。中断/异常:在中断之前它们要么完全发生,要么根本不发生。 在汇编指令运行时中断它。因此,例如,如果存储部分出现故障,即使没有前缀add [mem], reg
,也需要丢弃负载。lock
最坏的情况下,为向前推进而出现的访客用户空间页面数量可能是 6(加上每个页面的单独访客内核页面表子树):
movsq
或movsw
跨越页面边界的 2 字节指令,因此需要两个页面才能对其进行解码。- qword 源操作数
[rsi]
也是一个分页 - qword 目标操作数
[rdi]
也是页面拆分
如果这 6 页中的任何一个出现错误,我们就会回到原点。
rep movsd
也是一个 2 字节的指令,并且在它的一个步骤上取得进展将具有相同的要求。类似的情况类似于push [mem]
或pop [mem]
可以用未对齐的堆栈构造。
使聚集加载/分散存储“可中断”(使用其进度更新掩码向量)的原因之一(或附带好处)是避免增加此最小占用空间以执行单个指令。还可以提高在一次聚集或分散期间处理多个故障的效率。
@Brandon 在评论中指出来宾将需要其内存中的页表,并且用户空间页面拆分也可以是 1GiB 拆分,因此两侧位于顶级 PML4 的不同子树中。HW page walk 需要触摸所有这些访客页面表页面才能取得进展。这种病态的情况不太可能偶然发生。
TLB(和 page-walker 内部)被允许缓存一些页表数据,并且不需要从头开始重新启动 page-walk,除非操作系统这样做invlpg
或设置了新的 CR3 顶级页面目录。将页面从不存在更改为存在时,这些都不是必需的;纸面上的 x86 保证不需要它(因此不允许对不存在的 PTE 进行“负缓存”,至少对软件不可见)。因此,即使某些客户物理页表页实际上不存在,CPU 也可能不会 VMexit。
可以启用和配置PMU 性能计数器,以便该指令还需要一个 perf 事件来写入该指令的 PEBS 缓冲区。将计数器的掩码配置为仅计算用户空间指令而不是内核,它很可能会在每次返回用户空间时不断尝试溢出计数器并将样本存储在缓冲区中,从而产生页面错误。
推荐阅读
- google-chrome - 如何强制 Chrome 在没有命令行或快捷方式的情况下始终作为应用程序打开
- laravel - 当产品 is_featured 列状态都等于零时,我想使用 laravel 隐藏 html 代码
- c# - Aforge 视频 - 相机被其他设备使用
- node.js - 启动 vscode 调试传递节点环境变量以与 npm 调试模块一起使用
- flutter - “FirebaseFirestore”类型不是“Firestore”类型的子类型
- python - sqlalchemy 查询以获取外键(父)组合的最新数据
- r - .getReactiveEnvironment()$currentContext() 中的 R studio / R 脚本错误:
- java - 数组*不抛出越界异常
- python - 使用 python 创建 Discord Webhook
- javascript - 如何为窗口对象等特定范围内的函数调用创建默认父对象?