【发布时间】:2020-12-08 15:22:22
【问题描述】:
我正在为自己的教育目的构建 Risk-V CPU 的模拟器。我有小型 POC 工作,想构建示例程序并在模拟器上对其进行测试。
我正在尝试在 Rust 中构建示例程序,并且似乎取得了一些不错的进展,但是当我必须将已编译的程序加载到我的模拟器的内存并将 CPU 执行转移到该程序时,我陷入了困境。
测试程序:
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {
for i in 0..1000 {
unsafe {
let r = i as *mut u32;
// This can panic because (500 - i) can be 0
*r = 20000 % (500 - i);
}
}
}
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
构建:
$ cargo build --target riscv32i-unknown-none-elf --release
从精灵目标生成二值图像:
riscv32-unknown-linux-gnu-objcopy -g -O binary \
target/riscv32i-unknown-none-elf/release/sample1 \
target/riscv32i-unknown-none-elf/release/sample1.bin
到目前为止,这工作正常,并生成了大小为 5156 字节的二进制文件。
我检查了 .bin 文件,它在我看来是“合法的二进制文件”。
我在文件的开头发现了一些可读的字符串(如attempt to calculate the remainder with a divisor of zero)——看起来它们与处理恐慌的代码有关,如果我在做% 0,可能会发生这种情况。
在文件末尾,我发现了一些看起来像 riskv32i 指令的东西(很容易注意到它们,因为最低有效位是11)。
文件的其余部分用零填充。
我卡住的地方我想不通:
- 我应该在哪个偏移量处将此 bin 映像文件加载到我的虚拟 CPU 的内存中?我不认为在 0x0 地址加载它是可以的,因为图像的开头有有用的信息,我认为程序从地址 0x0 读取它并不酷。
- 程序加载后,我需要将CPU执行转移到我程序的入口点(
_start)。如何找出哪个地址是入口点,以便在启动 CPU 周期之前将此地址放入pc寄存器?它显然不在图像的开头(那里有人类可读的字符串)。 - 有没有办法使这个入口点地址稳定,所以我编写的所有程序都将具有相同的入口点地址,这样我就不必对我编译的每个程序进行调整?
当我使用objcopy 时,我可能走错路了。如果是这种情况,请告诉我将 ELF 文件加载到自制 CPU 模拟器中的适当方法是什么。
更新:链接器参数,(由RUSTFLAGS="-Z print-link-args" cargo build --target riscv32i-unknown-none-elf --release --verbose 提供):
rust-lld \
-flavor \
gnu \
-L \
/home/kris/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib \
/mnt/c/src/ws/cpu/sample1/target/riscv32i-unknown-none-elf/release/deps/sample1-4813691a581d1819.sample1.251h7tq6-cgu.0.rcgu.o \
/mnt/c/src/ws/cpu/sample1/target/riscv32i-unknown-none-elf/release/deps/sample1-4813691a581d1819.sample1.251h7tq6-cgu.1.rcgu.o -o \
/mnt/c/src/ws/cpu/sample1/target/riscv32i-unknown-none-elf/release/deps/sample1-4813691a581d1819 \
--gc-sections \
-L \
/mnt/c/src/ws/cpu/sample1/target/riscv32i-unknown-none-elf/release/deps \
-L \
/mnt/c/src/ws/cpu/sample1/target/release/deps \
-L \
/home/kris/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib \
-Bstatic \
/home/kris/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib/librustc_std_workspace_core-6d1cf467df9db3bb.rlib \
/home/kris/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib/libcore-a1a0b4993598bfe4.rlib \
/home/kris/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib/libcompiler_builtins-a229bbbccd019775.rlib \
-Bdynamic
我知道程序中缺少一些重要的东西,比如初始化堆栈指针寄存器。我打算在弄清楚加载逻辑后处理这个问题
【问题讨论】:
-
关于(3),我不这么认为,因为一开始有一个静态内存,其中包含您的程序使用的所有静态数据(正如您提到的 - 字符串文字,以及作为一些额外的)
-
您能否用链接描述文件的确切内容来扩充您的问题?
-
只是一些想法:我会尝试使用
objdump从 ELF 文件中获取信息。纯二进制文件没有任何元数据。 -- 如果你有链接描述文件,你可以尝试复制和编辑它以获得一个固定的入口地址。 -- 为了更深入的检查,您可以比较 ELF 和二进制文件的反汇编。 -
弗兰特,我在他的问题中添加了链接器参数,我希望这就是你所要求的。它由 rustc 编译器自动生成。
-
忙碌的蜜蜂,我用
objdump -d和objdump -f做了一些实验。它告诉我start address 0x000111d8并且与 objdump 反汇编_start函数位置相匹配。问题是这个地址不在我的由objcopy(大约5kb)生成的二进制图像之外。objcopy文档说“内存转储将从复制到输出文件的最低部分的加载地址开始。” - 我怎样才能知道“复制到输出文件的最低部分”的地址是什么?我认为这会告诉我在哪个偏移量处加载我的二进制文件。
标签: rust embedded riscv objcopy