裸机 RISC-V CPU - 处理器如何知道从哪个地址开始获取指令?
Bare metal RISC-V CPU - how does the processor know which address to start fetching instructions from?
我正在设计自己的 RISC-V CPU 并且已经能够实现一些指令代码。
我已经安装了 GCC 编译器的 RV32I 版本,所以我现在可以使用 assembler riscv32-unknown-elf-as
。
我正在尝试 assemble 一个只有一条指令的程序:
# simple.asm
add x5,x6,x7
我用 assembler 编译它,然后用这个命令 运行 objdump:
riscv32-unknown-elf-as simple.asm -o simple
riscv32-unknown-elf-objdump -D simple
这将打印出以下内容:
new: file format elf32-littleriscv
Disassembly of section .text:
00000000 <.text>:
0: 007302b3 add t0,t1,t2
Disassembly of section .riscv.attributes:
00000000 <.riscv.attributes>:
0: 2d41 jal 0x690
2: 0000 unimp
4: 7200 flw fs0,32(a2)
6: 7369 lui t1,0xffffa
8: 01007663 bgeu zero,a6,0x14
c: 00000023 sb zero,0(zero) # 0x0
10: 7205 lui tp,0xfffe1
12: 3376 fld ft6,376(sp)
14: 6932 flw fs2,12(sp)
16: 7032 flw ft0,44(sp)
18: 5f30 lw a2,120(a4)
1a: 326d jal 0xfffff9c4
1c: 3070 fld fa2,224(s0)
1e: 615f 7032 5f30 0x5f307032615f
24: 3266 fld ft4,120(sp)
26: 3070 fld fa2,224(s0)
28: 645f 7032 0030 0x307032645f
我的问题是:
- 这是怎么回事?我以为我会有一个简单的单行十六进制,但还有很多事情要做。
- 如何指示我的处理器开始读取特定内存地址的指令?看起来 objdump 也不知道指令将从哪里开始。
需要说明的是,此时我将我的处理器视为裸机。我想我会在处理器中硬编码指令从内存地址 X 开始,数据在内存地址 Y 可用,堆栈在内存地址 Z 可用。这是正确的吗?或者这是错误的方法?
how does the processor know which address to start fetching instructions from?
实际的 CPU 本身会有一些硬连线地址,它会在重置/开机时从中获取这些地址。通常,系统将在该物理地址处设计有 ROM 或闪存。 (该 ROM 可能具有用于 ELF 程序加载器的早期引导代码,它将尊重 ELF 入口点元数据以从 ROM 设置 ELF 内核映像,或者您可以 link 具有正确代码的平面二进制文件二进制文件的开头。)
What is going on here? I thought I'd have a simple single line of hex, but there's a lot more going on.
您的 objdump -D
不包含 assemble 所有 ELF 部分,而不仅仅是 .text。如您所见,.text 部分中只有一条指令,如果您使用 objdump -d,就会看到这样的指令。 (我通常使用 objdump -drwC
,虽然 -w
没有换行可能与 RISC-V 无关,不像 x86,其中单个 insn 可能很长。)
Would it be possible to pass the file I compiled above as is to my processor?
可能不是您想象的那样。另请注意,您为输出选择了错误的文件名。 as 生成目标文件(通常为 .o),而不是可执行文件。您可以将 link 和 ld
放入平面二进制文件中,或者 link 和 objcopy
中的 .text
部分。
(理论上您可以将整个 ELF 可执行文件甚至目标文件放入 ROM,这样 .text
部分恰好从 CPU 将从中获取的位置开始,但不会查看元数据字节。因此 ELF 可执行文件中的 ELF 入口点地址元数据将无关紧要。)
.o
和可执行文件之间的区别:.o
只是有重定位元数据供 link 用户填写实际地址,绝对用于 la
伪-指令,或 的相关文件,例如多个 .o
文件,其中一个文件引用另一个文件的符号。 (否则可以在assemble时计算相对位移,而不是在link时计算。)
因此,如果您的代码使用任何标签作为内存地址,则需要 linker 在您的代码中填写这些重定位条目。然后,您可以 objcopy
编辑 link 的 ELF 可执行文件中的某些部分。或者使用 linker 脚本来设置平面二进制文件的布局。
对于只有 add
、没有 la
或其他任何东西的简单情况,没有重定位条目,因此 .o
中的文本部分与 link编辑可执行文件。
使用 objcopy
也很棘手的是静态数据,例如.data
和 .bss
部分。如果您将 只是 .text
部分复制到平面二进制文件,您将不会在任何地方拥有数据。 (但是在 ROM 中,您需要一个启动函数,将 .data
的静态初始值设定项从 ROM 复制到 RAM,并将 .bss
space 归零。如果您想编写 asm 源代码要有一个看起来正常的 .data
非零值部分,你会希望你的构建脚本计算出要复制的大小,这样你的启动函数就可以使用它,而不是必须手动完成所有这些。)
@PeterCordes 的回答让我走上了正确的道路。我终于想出了如何生成我可以使用的原始内存转储文件。
步骤如下:
修改了程序集文件以具有 .text
和 .data
部分以及 _start
标签。我的 simple.asm
文件现在如下所示:
.globl _start
.text
_start:
add x5,x6,x7
.data
L1: .word 27
Assemble 使用以下命令将 .asm
复制到 .o
文件:
riscv32-unknown-elf-as simple.asm -o simple.o
为特定处理器创建一个链接描述文件。我遵循了这个令人惊奇的 video,它介绍了从头开始创建链接描述文件的过程。现在,我只需要 .text
和 .data
部分。所以我的链接描述文件(mycpu.ld
)如下所示:
OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv")
ENTRY(_start)
MEMORY
{
DATA (rwx) : ORIGIN = 0x0, LENGTH = 0x80
INST (rx) : ORIGIN = 0x80, LENGTH = 0x80
}
SECTIONS
{
.data :
{
*(.data)
}> DATA
.text :
{
*(.text)
}> INST
}
使用自动调用 riscv32-unknown-elf-ld
:
的 riscv32-unknown-elf-gcc
生成 ELF 文件
riscv32-unknown-elf-gcc -nostdlib -T mycpu.ld -o simple.elf simple.o
从 .elf
文件创建一个原始二进制或十六进制文件,我将使用它来填充内存的内容。
riscv32-unknown-elf-objcopy -O binary simple.elf simple.hex
最终 simple.hex
包含以下内容(使用 hexyl
):
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 1b 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │•0000000┊00000000│
│00000010│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000040│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000050│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000060│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000070│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000080│ b3 02 73 00 ┊ │וs0 ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
其中 b3027300
是 add x5,x6,x7
的十六进制值。
就是这样!非常感谢@PeterCordes 的帮助! :)
我正在设计自己的 RISC-V CPU 并且已经能够实现一些指令代码。
我已经安装了 GCC 编译器的 RV32I 版本,所以我现在可以使用 assembler riscv32-unknown-elf-as
。
我正在尝试 assemble 一个只有一条指令的程序:
# simple.asm
add x5,x6,x7
我用 assembler 编译它,然后用这个命令 运行 objdump:
riscv32-unknown-elf-as simple.asm -o simple
riscv32-unknown-elf-objdump -D simple
这将打印出以下内容:
new: file format elf32-littleriscv
Disassembly of section .text:
00000000 <.text>:
0: 007302b3 add t0,t1,t2
Disassembly of section .riscv.attributes:
00000000 <.riscv.attributes>:
0: 2d41 jal 0x690
2: 0000 unimp
4: 7200 flw fs0,32(a2)
6: 7369 lui t1,0xffffa
8: 01007663 bgeu zero,a6,0x14
c: 00000023 sb zero,0(zero) # 0x0
10: 7205 lui tp,0xfffe1
12: 3376 fld ft6,376(sp)
14: 6932 flw fs2,12(sp)
16: 7032 flw ft0,44(sp)
18: 5f30 lw a2,120(a4)
1a: 326d jal 0xfffff9c4
1c: 3070 fld fa2,224(s0)
1e: 615f 7032 5f30 0x5f307032615f
24: 3266 fld ft4,120(sp)
26: 3070 fld fa2,224(s0)
28: 645f 7032 0030 0x307032645f
我的问题是:
- 这是怎么回事?我以为我会有一个简单的单行十六进制,但还有很多事情要做。
- 如何指示我的处理器开始读取特定内存地址的指令?看起来 objdump 也不知道指令将从哪里开始。
需要说明的是,此时我将我的处理器视为裸机。我想我会在处理器中硬编码指令从内存地址 X 开始,数据在内存地址 Y 可用,堆栈在内存地址 Z 可用。这是正确的吗?或者这是错误的方法?
how does the processor know which address to start fetching instructions from?
实际的 CPU 本身会有一些硬连线地址,它会在重置/开机时从中获取这些地址。通常,系统将在该物理地址处设计有 ROM 或闪存。 (该 ROM 可能具有用于 ELF 程序加载器的早期引导代码,它将尊重 ELF 入口点元数据以从 ROM 设置 ELF 内核映像,或者您可以 link 具有正确代码的平面二进制文件二进制文件的开头。)
What is going on here? I thought I'd have a simple single line of hex, but there's a lot more going on.
您的 objdump -D
不包含 assemble 所有 ELF 部分,而不仅仅是 .text。如您所见,.text 部分中只有一条指令,如果您使用 objdump -d,就会看到这样的指令。 (我通常使用 objdump -drwC
,虽然 -w
没有换行可能与 RISC-V 无关,不像 x86,其中单个 insn 可能很长。)
Would it be possible to pass the file I compiled above as is to my processor?
可能不是您想象的那样。另请注意,您为输出选择了错误的文件名。 as 生成目标文件(通常为 .o),而不是可执行文件。您可以将 link 和 ld
放入平面二进制文件中,或者 link 和 objcopy
中的 .text
部分。
(理论上您可以将整个 ELF 可执行文件甚至目标文件放入 ROM,这样 .text
部分恰好从 CPU 将从中获取的位置开始,但不会查看元数据字节。因此 ELF 可执行文件中的 ELF 入口点地址元数据将无关紧要。)
.o
和可执行文件之间的区别:.o
只是有重定位元数据供 link 用户填写实际地址,绝对用于 la
伪-指令,或 .o
文件,其中一个文件引用另一个文件的符号。 (否则可以在assemble时计算相对位移,而不是在link时计算。)
因此,如果您的代码使用任何标签作为内存地址,则需要 linker 在您的代码中填写这些重定位条目。然后,您可以 objcopy
编辑 link 的 ELF 可执行文件中的某些部分。或者使用 linker 脚本来设置平面二进制文件的布局。
对于只有 add
、没有 la
或其他任何东西的简单情况,没有重定位条目,因此 .o
中的文本部分与 link编辑可执行文件。
使用 objcopy
也很棘手的是静态数据,例如.data
和 .bss
部分。如果您将 只是 .text
部分复制到平面二进制文件,您将不会在任何地方拥有数据。 (但是在 ROM 中,您需要一个启动函数,将 .data
的静态初始值设定项从 ROM 复制到 RAM,并将 .bss
space 归零。如果您想编写 asm 源代码要有一个看起来正常的 .data
非零值部分,你会希望你的构建脚本计算出要复制的大小,这样你的启动函数就可以使用它,而不是必须手动完成所有这些。)
@PeterCordes 的回答让我走上了正确的道路。我终于想出了如何生成我可以使用的原始内存转储文件。
步骤如下:
修改了程序集文件以具有
.text
和.data
部分以及_start
标签。我的simple.asm
文件现在如下所示:.globl _start .text _start: add x5,x6,x7 .data L1: .word 27
Assemble 使用以下命令将
.asm
复制到.o
文件:riscv32-unknown-elf-as simple.asm -o simple.o
为特定处理器创建一个链接描述文件。我遵循了这个令人惊奇的 video,它介绍了从头开始创建链接描述文件的过程。现在,我只需要
.text
和.data
部分。所以我的链接描述文件(mycpu.ld
)如下所示:OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv") ENTRY(_start) MEMORY { DATA (rwx) : ORIGIN = 0x0, LENGTH = 0x80 INST (rx) : ORIGIN = 0x80, LENGTH = 0x80 } SECTIONS { .data : { *(.data) }> DATA .text : { *(.text) }> INST }
使用自动调用
的riscv32-unknown-elf-ld
:riscv32-unknown-elf-gcc
生成 ELF 文件riscv32-unknown-elf-gcc -nostdlib -T mycpu.ld -o simple.elf simple.o
从
.elf
文件创建一个原始二进制或十六进制文件,我将使用它来填充内存的内容。riscv32-unknown-elf-objcopy -O binary simple.elf simple.hex
最终 simple.hex
包含以下内容(使用 hexyl
):
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 1b 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │•0000000┊00000000│
│00000010│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000040│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000050│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000060│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000070│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│00000080│ b3 02 73 00 ┊ │וs0 ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
其中 b3027300
是 add x5,x6,x7
的十六进制值。
就是这样!非常感谢@PeterCordes 的帮助! :)