gpt4 book ai didi

rust - 如何获取 objcopy 生成的二进制镜像文件中的入口点地址?

转载 作者:行者123 更新时间:2023-12-03 11:28:52 25 4
gpt4 key购买 nike

我正在出于自己的教育目的构建 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

从 elf 目标生成二进制图像:

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 文件,它对我来说看起来是“合法的二进制文件”。我在文件的开头发现了一些可读的字符串(例如尝试计算除数为零的余数) - 看起来它们与处理 panic 的代码有关,如果我这样做,可能会发生这种情况%0。在文件末尾,我发现了一些看起来像riskv32i指令的东西(很容易注意到它们,因为最低有效位是11)。文件的其余部分用零填充。

我陷入困境的地方是我无法弄清楚:

  1. 我应该在哪个偏移量处将此 bin 镜像文件加载到虚拟 CPU 的内存中?我认为在 0x0 地址加载它是不行的,因为图像的开头有有用的信息,而且我认为程序从地址 0x0 读取它并不酷。
  2. 加载程序后,我需要将 CPU 执行转移到程序的入口点 (_start)。如何找出哪个地址是入口点,以便在启动 CPU 周期之前将该地址放入 pc 寄存器中?它显然不在图像的开头(那里有人类可读的字符串)。
  3. 有没有办法让这个入口点地址稳定,这样我编写的所有程序都将具有相同的入口点地址,这样我就不必对我编译的每个程序进行调整?

当我使用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

我知道程序中缺少一些重要的东西,例如初始化堆栈指针寄存器。我打算在弄清楚加载逻辑后处理这个问题

最佳答案

免责声明:我对 Rust 不熟悉,但你的问题更与 ELF 文件格式和可以理解它的工具有关 - 我的两分钱。

  1. 您应该加载二进制文件的偏移量可能应该由 rust-ldd 正在使用的链接器设置来指导。

例如,这个documentation描述了一个文件 memory.x ,定义了链接器使用的内存映射:

MEMORY
{
RAM : ORIGIN = 0x80000000, LENGTH = 16K
FLASH : ORIGIN = 0x20000000, LENGTH = 16M
}

REGION_ALIAS("REGION_TEXT", FLASH);
REGION_ALIAS("REGION_RODATA", FLASH);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);

在此示例中,生成的二进制文件可能应在偏移量 0x20000000 处加载。

应该有与您正在使用的工具链等效的工具。

  • 您可以使用理解 ELF 文件格式的工具找到 _start
  • 例如,为 Aarch64 编译的一个可执行文件上的 aarch64-none-elf-nm 将显示:

    aarch64-none-elf-nm h5-example.elf
    0000000042000078 t $d
    0000000042000000 t $x
    0000000042000080 t $x
    00000000420001dc t $x
    00000000420001f4 t $x
    0000000042000230 B __bss_end__
    0000000042000230 B __bss_start__
    0000000042000080 T c_entry
    000000004200022c D __copy_table_end__
    0000000042000220 D __copy_table_start__
    0000000042000230 D __data_end__
    0000000042000230 D __data_start__
    0000000042000230 ? __end__
    0000000042000230 B __etext
    0000000042000218 T __exidx_end
    0000000042000218 T __exidx_start
    0000000042000230 d __fini_array_end
    0000000042000230 d __fini_array_start
    0000000046000230 ? __HeapLimit
    0000000004000000 A __HEAP_SIZE
    0000000042000230 d __init_array_end
    0000000042000230 d __init_array_start
    00000000420001f4 T main
    0000000042000000 A __RAM_BASE
    000000000e000000 A __RAM_SIZE
    0000000042000000 T Reset_Handler
    0000000000000000 A __ROM_BASE
    0000000000000000 A __ROM_SIZE
    000000004c000000 ? __StackLimit
    0000000004000000 A __STACK_SIZE
    0000000050000000 ? __StackTop
    00000000420001dc t system_read_CurrentEL
    0000000042000230 B __zero_table_end__
    0000000042000230 B __zero_table_start__

    就我而言,执行的第一条指令将位于Reset_Handler。我可以使用以下命令检索引用它的行:

    aarch64-none-elf-nm h5-example-02.elf | grep ' Reset_Handler$'
    0000000042000000 T Reset_Handler

    及其十六进制的确切地址,使用:

    aarch64-none-elf-nm h5-example-02.elf | grep ' Reset_Handler$' | cut -d ' ' -f1
    0000000042000000

    RESET_HANDLER=$(aarch64-none-elf-nm h5-example-02.elf | grep ' Reset_Handler$' | cut -d ' ' -f1)
    echo ${RESET_HANDLER}

    当然会显示:

    0000000042000000

    现在起始地址已知,在您的 DIY 模拟器中使用它有多种选择。我想到的两个是:

    a) 将地址作为参数传递给您的模拟器,即:

    my-emulator 0000000042000000my-emulator -s 0000000042000000

    b) 由于您掌握了模拟器将加载的图像的格式,因此您可以系统地将起始地址添加到 objcopy 生成的二进制文件中:这样,您将读取二进制文件的前 4 或 8 个字节首先文件,获取起始地址,然后读取剩余字节。

    一个简单的方法是使用 xxdcat:

    echo 0000000042000000 | xxd -r -p > final-image.bin
    cat sample1.bin >> final-image.bin

    使用包含“ABCD”的示例文件,我们将得到:

    printf "ABCD" > sample1.bin
    hexdump -C sample1.bin
    00000000 41 42 43 44 |ABCD|
    00000004

    echo 0000000042000000 | xxd -r -p > final-image.bin
    hexdump -C final-image.bin

    00000000 00 00 00 00 42 00 00 00 |....B...|
    00000008

    cat sample1.bin >> final-image.bin
    hexdump -C final-image.bin
    00000000 00 00 00 00 42 00 00 00 41 42 43 44 |....B...ABCD|
    0000000c

    您当然可以定义一个更复杂的 header ,可能包含一些其他重要符号,或者向您的模拟器添加更多命令行选项 - 基本原理保持不变。

  • 是的,您可以强制编译器将 _start() 函数放入特定的链接器部分,如 here 中所述。 ,使用 link_section 指令/编译指示:
  • 程序:

    #[no_mangle]
    pub unsafe extern "C" fn Reset() -> ! {
    let _x = 42;

    // can't return so we go into an infinite loop here
    loop {}
    }

    // The reset vector, a pointer into the reset handler
    #[link_section = ".vector_table.reset_vector"]
    #[no_mangle]
    pub static RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset;

    链接脚本:

    /* Memory layout of the LM3S6965 microcontroller */
    /* 1K = 1 KiBi = 1024 bytes */
    MEMORY
    {
    FLASH : ORIGIN = 0x00000000, LENGTH = 256K
    RAM : ORIGIN = 0x20000000, LENGTH = 64K
    }

    /* The entry point is the reset handler */
    ENTRY(Reset);

    EXTERN(RESET_VECTOR);

    SECTIONS
    {
    .vector_table ORIGIN(FLASH) :
    {
    /* First entry: initial Stack Pointer value */
    LONG(ORIGIN(RAM) + LENGTH(RAM));

    /* Second entry: reset vector */
    KEEP(*(.vector_table.reset_vector));
    } > FLASH

    .text :
    {
    *(.text .text.*);
    } > FLASH

    /DISCARD/ :
    {
    *(.ARM.exidx .ARM.exidx.*);
    }
    }

    这样,_start() 函数的代码将始终放在 .vector_table 部分的开头,该部分被定义为第一个FLASH区域。

    因此,_start() 的地址始终为 0x00000000,或者您决定重置地址位于 CPU 中的任何地址:您只需修改FLASH区域的起始地址。

    该示例与 Arm Cortex-M MCU 相关,您可以将 .vector_table 部分替换为您自己的 .startup 部分。

    我希望我没有偏离正轨......

    关于rust - 如何获取 objcopy 生成的二进制镜像文件中的入口点地址?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63485595/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com