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

如何解决如何在objcopy生成的二进制图像文件中获取入口点地址?

我出于自己的教育目的而构建了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文件,它对我来说似乎是“合法二进制”。 我在文件的开头发现了一些可读的字符串(例如attempt to calculate the remainder with a divisor of zero)-看起来它们与处理我在% 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加载。

您所使用的工具链应该等效。

  1. 您可以使用了解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

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

  1. 是的,您可以使用_start()指令/编译指示,强制编译器将link_section函数放入特定的链接器节,如here所述:

程序:

#[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部分。

我希望我不会偏离那个方向...

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-