RFC-0224:用户空间 J 扩展指针掩码 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 为了适应 RISC-V 指针掩码扩展,需要进行的更改。 |
问题 |
|
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-03-09 |
审核日期(年-月-日) | 2023-09-05 |
摘要
本文档提出了在 Fuchsia 用户空间中支持 RISC-V J 扩展指针掩码功能所需的更改。
设计初衷
RISC-V J 扩展旨在让 RISC-V 成为传统解释型或 JIT 编译型语言或需要大型运行时库或语言级虚拟机的语言的理想目标平台。示例包括但不限于 C#、Go、Haskell、Java、JavaScript、OCaml、PHP、Python、R、Ruby、Scala、Smalltalk 或 WebAssembly。J 扩展中的一个显著特征是指针掩码 (PM)。这是一项硬件功能,启用后,MMU 会在内存访问时忽略有效地址的顶部 N 位。这与 ARMv8.0 CPU 上的 Top-Byte-Ignore (TBI) 功能非常相似。PM 的直接用途之一是在用户空间中启用硬件辅助的 AddressSanitizer (HWASan),其中标记存储在顶部字节中以进行内存跟踪。
术语
本文档中使用的许多术语都可以从 RFC-0143:用户空间 Top-Byte-Ignore 中扩展而来。
地址 - 地址是 64 位整数,表示用户地址空间边界内的某个位置。系统绝不会标记地址。
Pointer - 可解引用内存的位置,可能有标记,也可能没有。这相当于 RISC-V Base ISA 中定义的有效地址。
标记 - 指针的上位,通常用于元数据。RISC-V 指针掩码支持不同的标记大小。
Zjpm - 这是 J-extension 中指针掩码功能的正式标识符。
设计
符合标记指针 ABI
Zjpm 支持标记的用户空间指针。内核对这些指针的处理遵循 RFC-0143 中规定的相同规则。即:
内核会忽略从系统调用收到的用户指针上的标记。
在接受地址的系统调用中传递标记指针是错误的。
当内核接受带标记的指针(无论是通过系统调用还是故障),它会尝试保留标记,以便用户代码稍后可以观察到它。
内核本身永远不会生成标记指针。
比较用户空间指针时,内核会忽略可能存在的任何标记。
此外,Zjpm 将由内核启动选项控制。与 ARM TBI 类似,启用后,Zjpm 将针对所有用户空间进程处于开启状态。
RISC-V 不会为写入调试寄存器的值提供语义含义,因此调试程序可以自由地将带标记的值写入调试寄存器。对于观察点等功能,指针比较是通过 mcontext6
寄存器的 match
字段控制的,该字段可以控制为与标记的确切值匹配,也可以忽略标记(就像指针掩码一样)。
将标记设置为 8 位(适用于 Sv39 和 Sv48)
Zjpm 最直接的用例是在 RISC-V 上启用内存错误检测工具,例如 HWASan,该工具依赖于将元数据存储到指针的顶部位。ARM TBI 仅支持 8 位标记大小。不过,Zjpm 的灵活性更高,允许在内存访问时忽略可变数量的顶部位。此位数可通过 CSR 寄存器在不同模式下进行控制。
最简单的方法就是遵循我们现在的做法。ARM TBI 和 HWASan 已经在使用 8 位标记,但实际上,对于大于(或小于)8 位的标记,没有任何即时或可预见的需求。
只有 Sv39 和 Sv48 支持 8 位标记大小。这是因为 Sv57 仅支持掩盖前 7 位。如果未来支持 Sv57,则需要重新考虑代码大小。
实现
实现的大部分内容都与 ARM TBI 的实现非常相似。通过在 upm
CSR 中设置 uenable
位,可以在 U 模式下启用 Zjpm。标记大小可通过 upm
中的 ubits
位字段进行设置。
现有的系统调用基础架构应该已设置为接受固定标记大小的标记用户空间指针。
ZX_FEATURE_KIND_ADDRESS_TAGGING
功能将有一个额外的标志(类似于 ZX_RISCV64_FEATURE_ADDRESS_TAGGING_ZJPM_8BIT
),用于指示 Zjpm 是否已启用以及代码大小。ZX_RISCV64_FEATURE_ADDRESS_TAGGING_ZJPM_8BIT
表示前 8 位肯定会被掩码,但将来我们可能需要添加其他标志,表示更多高位被掩码。例如,我们可以添加 ZX_RISCV64_FEATURE_ADDRESS_TAGGING_ZJPM_16BIT
之类的标志来指示掩码了前 16 位,但 ZX_FEATURE_KIND_ADDRESS_TAGGING
还会设置 8 位等效标志,以确保检查 8 位标志的兼容代码适用于未来的系统。
Zjpm 还取决于 Zicsr 扩展是否已启用,该扩展提供了用于修改 CSR 寄存器的指令。
性能
性能影响应该可以忽略不计,我们将使用现有的微基准进行验证。
测试
用于测试 ARM TBI 的同一套测试也应应用于 Zjpm。这些测试在很大程度上应该与启用了哪种地址标记模式无关。
缺点、替代方案和未知情况
Zjpm 比 ARM TBI 更灵活,因此我们可以支持更多掩码选项。
目前,似乎没有任何真实硬件支持最新版本的 Zjpm,因此内核如何发现硬件支持的内容尚未明确指定。在我们了解硬件将受到哪些实际限制之前,此处提出的保守型单功能始终开启模式将是最佳支持方式(如果有)。QEMU 应支持指针掩码,并且可以使用“x-j”cpu 属性启用。
将代码始终设置为某个其他静态值
代码植入 ABI 支持不同的代码植入大小(即,我们不局限于 8 位)。在实践中,我们实际上很少使用虚拟地址的顶部位。在 x86 上,我们实际上只使用底部的 48 位,但这只是针对我们目前的目标做出的假设,未来可能会发生变化。目前,用于指示标记大小的位字段长度为 5 位,这意味着只能忽略指针的前 31 位。此字段上方剩余的位是 WPRI,因此此字段将来可以扩展以容纳更大的值。
对于 HWASan 等工具,内存标记算法不一定取决于标记大小为 8 位。将代码大小增加到 16 位左右可以显著降低代码比较中出现假正例的几率,但这意味着需要将较大的代码存储到影子内存中,这并不是很理想。目前,使用 8 位时假正例概率也非常小。
将标记作为可修改的值公开给用户空间
此选项暗示了向用户公开指针遮盖功能的方法。也就是说,用户可以 (1) 在运行时启用/停用指针掩码,以及 (2) 更改代码大小。这可能不太理想,因为没有立即执行此操作的需要,并且需要添加更多系统调用来切换这些值。不过,代码植入 ABI 为未来支持此功能提供了空间。
指令提取的指针掩码
Zjpm 的一项强大功能是针对指令提取启用 PM,包括因顺序执行、控制转移(例如分支和直接/间接跳转以及 uret
/sret
/mret
)而导致的 PC 单调递增。此提案仅概述了数据指针的指针掩码规则,但为日后探索此选项留出了空间。
与其他理想的硬件功能交互
Zjpm 仅引入了指针掩码功能。其他实用功能(例如代码检查或沙盒强制执行)可能会在软件或未来需要 Zjpm 的硬件扩展程序中实现。一个类似的示例功能是 ARM MTE,它依赖于 TBI。
草稿的更改
Zjpm 目前仍处于提案草稿阶段,但我们希望它能获得 RVA23 批准,以便 HWASan 能够正式支持它。本文档将随规范中的任何重大变更而更新。