3

我正在尝试构建和链接单个图像以加载为针对 aarch64-unknown-none-softfloat 的 OS 内核(即在 QEMU 中)。我使用一个自定义的 linker.ld 文件来设置内核的入口点ENTRY(_reset)并定位图像

. = 0x40080000

其中程序计数器 (PC) 处于复位状态。

它工作正常,直到我将 0x40080000 的页面映射到内核将驻留的高内存并启用虚拟内存转换。为了保证切换后调试信息网格,我将标称图像位置更改为

. = 0xffffff8200000000

并重建。

我发现了访问:

  • 到一些(酒吧外部)静态,和
  • 通过某些核心库函数

是通过从.rodata. 这在映射之前运行时会破坏代码。如果我把它改回来,它会在我映射后运行它时破坏代码。

它生成的代码在 O1 看起来有点像这样(间接通过 PC 相关页面):

adrp  x0, 0x10000 // page offset from PC up to rodata
add   x0, 0x120   // byte offset from page in rodata
ldr   x0, [x0]    // use as address

我需要的是真正跨代码和数据定位独立代码,以便它可以在内存中的两个位置工作,而无需引用任何存储的绝对地址,即使这些地址相对于 PC 可用。

我已经尝试过其他重定位模型,包括 Pic 和 RopiRwpi,但我看不到它生成不同的代码。

谢谢!

编辑:非常感谢临时映射的建议。我见过用的。我更感兴趣的编译器选项将使 -no-dynamic-linker 能够工作,避免生成需要 R_AARCH64_ABS64 重定位的代码,以保证代码和数据将相隔一定距离。

4

2 回答 2

1

根据我的经验,大多数想要生活在高内存中的操作系统内核只是链接到它们的目标高内存地址,内核在启动时做的第一件事就是构建页表以将自己映射到高内存. 这样,操作系统内核设计人员只需要确保入口点的某些块是位置独立的(或者实际上,只需将启动代码放在其真正的低内存位置,方法是将其放置在一个特殊的部分并更改链接器脚本)。

请参阅示例入口点和链接器脚本(不是 Rust 内核/aarch64)。

一个足够复杂的内核可以控制其内存分配器,然后可以考虑释放包含入口点代码的页面,如入口点部分的开始和结束所标识的(实际上扩展它以处理所有 init以同样的方式编码)。

如果你能负担得起在启动时使用额外的内存,一个不错的 rust 选项也可以是为加载程序编写一个单独的 crate,将所有内容与链接描述文件绑定在一起,然后在启动后释放它的内存。

于 2020-06-08T08:34:04.167 回答
-1

您不能在执行过程中对任意代码进行变基并期望它继续运行。从字面上看,在变基点使用的每个地址都将变得无效。即使您摆脱了所有静态引用,您将如何处理动态分配的内存?

您要么必须在高地址映射后加载二进制文件的干净副本,要么必须使用高负载地址编译并在运行任何其他代码之前将自己映射到那里。

如果您觉得您的映射逻辑太长/太复杂而无法在这样的阶段完成,您可以先使用将所有内容映射为 RWX 的琐碎页表,然后您可以切换,调出您的库和其他代码,然后继续构建最终页表。这样的“普通”页表可以在编译时构建,最多可以容纳 4 页内存(每个 TTBR 两个),并且使用它们切换到高地址最多需要 20 条指令。

于 2020-06-08T16:31:42.213 回答