背景
由于RiscV和Rust都是比较新的两个东西,因此两个新的东西结合在一起就会发生很逆天的事情:Rust在Risc-V上不支持UEFI目标,同时Rust社区貌似没有什么issue讨论这个。
由于目前Risc-V上,许多操作系统都是要把DTB编译进内核,或者是把加载地址写死到内核镜像,导致操作系统无法作为一个与开发板无关的二进制文件进行传播。时至今日(23年11月26日),我翻了网上的很多教程,以及开源操作系统,除了Linux、BSD、Haiku以外,很多的操作系统都是“把启动地址、加载地址写死到uImage的,还有的系统是把内核链接地址也跟具体的开发板绑定了的,更多的教学型的系统甚至是使用qemu的-kernel选项,通过”大自然的鬼斧神工“,把内核直接映射到内存的指定位置,我愿称这种方式为”QEMU特别版“。
因为DragonOS定位是想做成一个服务器系统嘛,那么肯定不能像上面的一些系统那样,换一个设备就重新编译一次内核吧。那么内核就不是一个“标准件”了。因此DragonOS需要能够做成通用型的。
翻看了一堆代码,发现Linux在Risc-V上面是使用EFI stub的内核组件去启动的:
opensbi->uboot->grub2->kernel EFI stub->重定位内核到正确的地址
我想把DragonBoot做成“Stand alone+EFI Stub”二选一的东西。也就是,它既能作为单独的bootloader,也能作为一个内核组件,使得整个内核看起来像是个EFI程序,能够被uboot直接引导。
让Rust能为Risc-V编译uefi程序
先说思路:
- 由于EFI是识别程序头部的,因此需要让程序拥有一个EFI header
- UEFI程序的入口有一个规范,因此需要实现对应的入口
- 要能够把ELF程序转换为EFI格式,然后让qemu启动它
这里面涉及到几个技术点:
- 生成的代码必须是位置无关的
- 代码重定位
在这一块,我借鉴了BSD的代码,整了一个PE Header,并且链接到了二进制的首部。同时借用rust的uefi-rs库的部分代码(这个库不支持riscv),手动补一些初始化逻辑,使得能够在rust的EFI程序里面初始化EFI Boot Service。
实现请看代码,大概几百行。
https://github.com/DragonOS-Community/DragonBoot/tree/0ec3a34
尚未完善的坑点
在上面的代码里面,没法使用println宏,会报错空指针。我花了几个小时去调试这个问题,最终发现BUG出在Rust的core库的FormatWriter里面,它调用buf字段(其实在这里就是stdout,但是类型为&’a dyn (Write+’a) )的时候会出现空指针。由于dyn是动态分发的,然后我严重怀疑是这里需要代码重定位才能正常执行。因为我在前面的汇编里面其实是注释掉了重定位的(我没有实现)。因此接下来可能是需要做个重定位的工作,才能让EFI程序正常执行dyn关键字涉及的代码。
转载请注明来源:https://longjin666.cn/?p=1789
欢迎关注我的公众号“灯珑”,让我们一起了解更多的事物~