【问题标题】:Bare metal RISC-V CPU - how does the processor know which address to start fetching instructions from?裸机 RISC-V CPU - 处理器如何知道从哪个地址开始获取指令?
【发布时间】:2021-12-18 09:27:24
【问题描述】:

我正在设计自己的 RISC-V CPU,并且已经能够实现一些指令代码。

我已经安装了 GCC 编译器的 RV32I 版本,所以我现在可以使用汇编器 riscv32-unknown-elf-as

我正在尝试用一条指令组装一个程序:

# simple.asm
add x5,x6,x7

我用汇编器编译它,然后用这个命令运行 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

我的问题是:

  1. 这是怎么回事?我想我应该有一个简单的单行十六进制,但还有很多事情要做。
  2. 如何指示我的处理器开始读取某个内存地址的指令?看起来 objdump 也不知道指令从哪里开始。

为了清楚起见,我现在将我的处理器视为裸机。我想象我将在处理器中硬编码指令从内存地址 X 开始,数据在内存地址 Y 可用,堆栈在内存地址 Z 可用。这是正确的吗?或者这是错误的方法?

【问题讨论】:

  • 实际的 CPU 本身会有一些硬连线地址,它会在复位/上电时从中获取。通常系统会在该物理地址处设计有 ROM 或闪存。 (并且可能有一个 ELF 程序加载器的代码,它将尊重 ELF 入口点元数据,或者您可以在二进制文件的开头将一个平面二进制文件与正确的代码链接。)
  • @PeterCordes - 谢谢!是否可以将我上面编译的文件原样传递给我的处理器?如果是,我需要进行哪些更改才能让它从正确的内存地址获取指令?
  • objdump -D 反汇编 所有 ELF 部分,而不仅仅是 .text。如您所见,.text 部分中只有一条指令,如果您使用了objdump -d,这就是您所看到的。 (我通常使用objdump -drwC,虽然-w 可能与RISC-V 无关)
  • 是否可以将我在上面编译的文件按原样传递给我的处理器? - 不是您可能想的那样。另请注意,您为输出选择了错误的文件名。 as 生成一个目标文件(通常是.o),而不是可执行文件。您可以将 ld 链接到平面二进制文件中,或者将 objcopy 链接到其中的 .text 部分。 (理论上,您可以将整个 ELF 可执行文件甚至目标文件放入 ROM 中,这样 .text 部分恰好从 CPU 获取的位置开始,但不会查看元数据字节。
  • @PeterCordes - 我明白了,我想我看错了。我以为as 会生成最终的二进制文件,但根据您的说法,我需要使用ld 为我的处理器生成文件或使用objcopy 将相关位复制到正确的地址。对吗?

标签: assembly cpu-architecture elf riscv riscv32


【解决方案1】:

处理器如何知道从哪个地址开始获取指令?

实际的 CPU 本身会有一些硬连线地址,它会在复位/上电时从中获取。通常系统会在该物理地址处设计有 ROM 或闪存。 (该 ROM 可能具有 ELF 程序加载器的早期引导代码,它将尊重 ELF 入口点元数据以从 ROM 设置 ELF 内核映像,或者您可以在开始时将平面二进制文件与正确的代码链接二进制。)

这里发生了什么?我以为我有一个简单的单行十六进制,但还有很多事情要做。

您的 objdump -D 会反汇编所有 ELF 部分,而不仅仅是 .text。如您所见, .text 部分中只有一条指令,如果您使用 objdump -d 这就是您所看到的。 (我通常使用 objdump -drwC,虽然 -w 没有换行可能与 RISC-V 无关,不像 x86 中单个 insn 可能很长。)

是否可以将我上面编译的文件按原样传递给我的处理器?

不是你想的那样。另请注意,您为输出选择了错误的文件名。 as 生成一个目标文件(通常是 .o),而不是可执行文件。您可以将ld 链接到一个平面二进制文件中,或者将objcopy 链接到其中的.text 部分。

(理论上,您可以将整个 ELF 可执行文件甚至目标文件放入 ROM 中,这样.text 部分恰好从 CPU 获取的位置开始,但不会查看元数据字节。所以 ELF 条目 - ELF 可执行文件中的点地址元数据将无关紧要。)

.o 和可执行文件之间的区别:.o 仅具有重定位元数据,供链接器填写实际地址,la 伪指令是绝对的,auipc 是相对的,例如多个 @ 987654333@ 文件,其中一个引用另一个符号。 (否则相对位移可以在组装时计算,而不是留给链接时。)

因此,如果您的代码使用任何标签作为内存地址,则需要链接器在代码中填写这些重定位条目。然后你可以objcopy链接的ELF可执行文件中的一些部分。或者使用链接器脚本设置平面二进制文件的布局。

对于只有add、没有la 或其他任何内容的简单案例,没有重定位条目,因此.o 中的文本部分与链接的可执行文件中的相同。

使用objcopy 也很棘手的是静态数据,例如.data.bss 部分。如果您将just .text 部分复制到平面二进制文件中,则任何地方都不会有数据。 (但在 ROM 中,您需要一个启动函数,将静态初始化程序从 ROM 复制到 RAM 以获取 .data,并将 .bss 空间归零。如果您想编写 asm 源代码以获得正常外观的 @987654344 @ 具有非零值的部分,您希望您的构建脚本计算出要复制的大小,以便您的启动函数可以使用它,而不必手动执行所有这些操作。)

【讨论】:

    【解决方案2】:

    @PeterCordes 的回答让我走上了正确的道路。我终于想出了如何生成我可以使用的原始内存转储文件。

    步骤如下:

    1. 将程序集文件修改为具有.text.data 部分以及_start 标签。我的simple.asm 文件现在如下所示:

      .globl _start
      
      .text
      _start:
        add x5,x6,x7
      
      .data
      L1: .word 27
      
    2. 使用以下命令将.asm 组装成.o 文件:

      riscv32-unknown-elf-as simple.asm -o simple.o
      
    3. 为特定处理器创建链接描述文件。我关注了这个惊人的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
      }
      
      
    4. 使用自动调用riscv32-unknown-elf-ldriscv32-unknown-elf-gcc生成ELF文件:

      riscv32-unknown-elf-gcc -nostdlib -T mycpu.ld -o simple.elf simple.o
      
    5. .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    ┊        │
    └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
    

    其中b3027300add x5,x6,x7 的十六进制值。

    就是这样!非常感谢@PeterCordes 的帮助! :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-07-15
      • 1970-01-01
      • 2020-03-27
      • 1970-01-01
      • 2018-11-22
      • 2023-03-22
      • 1970-01-01
      相关资源
      最近更新 更多