内存地址如何放置在二进制文件中?

How are memory addresses placed in an binary files?

我无法理解如何将 elf 文件中的部分加载到内存中以及如何选择地址?嵌入式系统通常会为代码分配特定的地址,但它放在哪里呢?

基本上,如何以及何时将地址放入节中,以及如何将其加载到 OS 以及嵌入式系统中的 ROM 或 RAM 中。

特定的操作系统有一组特定的规则,或者可能有多组规则,用于加载兼容程序的位置。为该平台制作的包含默认 linker 脚本(想想 gcc hello.c -o hello)的工具链符合这些规则。

例如,我决定为具有 MMU 的平台创建一个操作系统。因为它有一个 MMU,所以我可以创建操作系统,使每个程序都能看到相同的(虚拟)地址 space。所以我可以决定,对于我操作系统上的应用程序,内存 space 从 0x00000000 开始,但入口点必须是 0x00001000。支持的二进制文件格式可以说是摩托罗拉 s-record。

所以使用简单的 linker 脚本编写一个简单的程序

MEMORY
{
    ram : ORIGIN = 0x1000, LENGTH = 0x10000
}
SECTIONS
{
    .text : { *(.text*) } > ram
}

我的简单程序的反汇编

00001000 <_start>:
    1000:   e3a0d902    mov sp, #32768  ; 0x8000
    1004:   eb000001    bl  1010 <main>
    1008:   e3a00000    mov r0, #0
    100c:   ef000000    svc 0x00000000

00001010 <main>:
    1010:   e3a00000    mov r0, #0
    1014:   e12fff1e    bx  lr

并且 "binary" 文件恰好是人类可读的:

S00F00006E6F746D61696E2E737265631F
S3150000100002D9A0E3010000EB0000A0E3000000EF1E
S30D000010100000A0E31EFF2FE122
S70500001000EA

你可能会注意到也可能不会注意到地址确实在描述事情去向的二进制文件中。

作为加载到 ram 中的基于操作系统的程序,我们不必玩太多内存游戏,我们可以假设一个平坦的所有 ram (read/write) 所以如果有 .data、.bss等等都可以装进去

对于一个真正的操作系统,希望二进制文件包含额外的信息,比如程序的大小。因此,您可以 google 围绕各种常见文件格式,看看这是如何完成的,要么是我需要这么多的简单前期,要么是单独定义的一对多部分。是的,"binary" 不仅仅是操作码和数据,我想你明白这一点。

我使用的工具链默认输出 elf 格式的文件,但 objcopy 可用于创建许多不同的格式,其中一种是原始内存图像(不包含任何 address/location 信息)[=其余的 112=] 包含机器代码和数据以及 debugger/disassembler 的标签或该数据块希望在内存中存在的地址 space,等等

现在,当您说嵌入式并使用 ROM 和 RAM 一词时,我假设您指的是像微控制器这样的裸机,但即使您指的是引导 x86 或全尺寸 ARM 或任何适用的东西。在 MCU 的情况下,芯片设计者可能根据处理器的规则或他们自己的选择确定了内存的规则 space。就像操作系统会规定规则一样。我们有点作弊,因为我们今天使用的许多工具(基于 gnu)并不是真正为裸机设计的,而是因为通用编译器是通用编译器,更重要的是工具链适合这种可移植性,我们可以使用这样的工具。理想情况下使用交叉编译器意味着输出机器代码不一定意味着 运行 在生成该输出机器代码的计算机上。重要的主要区别是我们想要控制 linking 和库,不要 link 在基于主机操作系统的库中,让我们控制或者对于这个工具链有一个默认的 linker针对我们的 MCU 的脚本。假设我有一个基于 ARM7TDMI 的 MCU,芯片设计者说我需要这样的二进制文件,这样 ROM 从地址 0x00000000 开始并且具有一定的大小,而 RAM 从 0x40000000 开始并且具有一定的大小。作为 ARM7,处理器通过获取地址 0x00000000 处的指令开始执行,芯片设计者已将 0x00000000 映射到 ROM。

所以现在我的简单程序

unsigned int xyz;
int notmain ( void )
{
    xyz=5;
    return(0);
}

link像这样

MEMORY
{
    bob : ORIGIN = 0x00000000, LENGTH = 0x1000
    ted : ORIGIN = 0x40000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > bob
    .bss : { *(.bss*) } > ted
}

对此进行反汇编

Disassembly of section .text:

00000000 <_start>:
   0:   e3a0d101    mov sp, #1073741824 ; 0x40000000
   4:   e38dda01    orr sp, sp, #4096   ; 0x1000
   8:   eb000000    bl  10 <notmain>
   c:   eafffffe    b   c <_start+0xc>

00000010 <notmain>:
  10:   e3a02005    mov r2, #5
  14:   e59f3008    ldr r3, [pc, #8]    ; 24 <notmain+0x14>
  18:   e3a00000    mov r0, #0
  1c:   e5832000    str r2, [r3]
  20:   e12fff1e    bx  lr
  24:   40000000    andmi   r0, r0, r0

Disassembly of section .bss:

40000000 <xyz>:
40000000:   00000000    andeq   r0, r0, r0

这将是一个完全有效的程序,没有做太多有趣的事情,但仍然是一个完全有效的程序。

首先也是最重要的是,如果您省略了 _start,工具链会发出警告,但仍然可以正常运行。 (嗯,其实那个时候没警告,有意思)

arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy --srec-forceS3 notmain.elf -O srec notmain.srec
arm-none-eabi-objcopy notmain.elf -O binary notmain.bin

现在您遇到了加载问题。每个 MCU 在如何加载它方面都是不同的,有哪些可用工具 and/or 你可以制作自己的工具。 Ihex 和 srec 很受舞会程序员的欢迎,你说处理器旁边有一个单独的 ROM and/or 通孔 mcu 会插入舞会程序员。原始二进制图像也可以工作,但很快就会变大,一秒钟后就会显示出来。如上所述,有 .bss 但没有 .data 所以

ls -al notmain.bin
-rwxr-xr-x 1 user user 40 Oct 21 22:05 notmain.bin

40 个字节。但是,如果我出于演示目的这样做,即使它无法正常工作:

unsigned int xyz=5;
int notmain ( void )
{
    return(0);
}

MEMORY
{
    bob : ORIGIN = 0x00000000, LENGTH = 0x1000
    ted : ORIGIN = 0x40000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > bob
    .bss : { *(.bss*) } > ted
    .data : { *(.data*) } > ted
}

给予

Disassembly of section .text:

00000000 <notmain-0x10>:
   0:   e3a0d101    mov sp, #1073741824 ; 0x40000000
   4:   e38dda01    orr sp, sp, #4096   ; 0x1000
   8:   eb000000    bl  10 <notmain>
   c:   eafffffe    b   c <notmain-0x4>

00000010 <notmain>:
  10:   e3a00000    mov r0, #0
  14:   e12fff1e    bx  lr

Disassembly of section .data:

40000000 <xyz>:
40000000:   00000005    andeq   r0, r0, r5

-rwxr-xr-x  1 user user 1073741828 Oct 21 22:08 notmain.bin

哎哟! 0x40000004 字节,这是预期的,我要求一个内存映像,我在一个地址(机器代码)定义了东西,在另一个地址(0x40000000)定义了几个字节,所以原始内存映像必须是整个范围。

hexdump notmain.bin 
0000000 d101 e3a0 da01 e38d 0000 eb00 fffe eaff
0000010 0000 e3a0 ff1e e12f 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0000
*
40000000 0005 0000                              
40000004

相反,我们可以使用工具链生成的 elf 文件或者 ihex 或 srecord。

S00F00006E6F746D61696E2E737265631F
S3150000000001D1A0E301DA8DE3000000EBFEFFFFEA79
S30D000000100000A0E31EFF2FE132
S3094000000005000000B1
S70500000000FA

我需要的所有信息,但不是这么几个字节的大文件。

这不是一个硬性规定,但今天移动数据更容易(比从一台计算机到另一台装有舞会程序员的软盘)。特别是如果你有一个捆绑的 IDE 供应商可能使用工具链默认格式,但即使不支持 elf 和其他类似格式,你也不必走原始二进制或 ihex 或 srec 的路线.但它仍然取决于将 "binary" 写入 MCU 上的 ROM(/FLASH) 的工具。

现在我作弊来演示上面的大文件问题,而不是当它不是一个只有 ram 的系统时你必须做更多的工作。如果您觉得需要 .data 或希望将 .bss 置零,那么您需要编写或使用更复杂的 linker 脚本来帮助您确定位置和边界。那个 linker 脚本与 bootstrap 结合使用 linker 生成的信息来执行这些任务。基本上 .data 的副本需要保存在非易失性内存中 (ROM/FLASH) 但它不能在 运行 时间存在。数据是 read/write 所以 ideally/typically 你使用linker 脚本 language/magic 声明 .data read/write space 是废话,而 flash space 在这个地址和这个大小是 boo 所以bootstrap 可以从该地址的闪存复制该数量的数据到 ram。对于 .bss,linker 脚本生成我们保存到闪存中的变量,这些变量告诉 bootstrap 从该地址到该地址的 ram 归零。

所以操作系统定义内存space,linker脚本匹配,如果你想让程序工作。系统设计者或芯片设计者确定地址 space 用于嵌入的东西并且 linker 脚本与之匹配。 bootstrap 与该构建和目标的 linkerscript 结合。

编辑 --------------

工具链基础...

mov sp,#0x40000000
orr sp,sp,#0x1000
bl notmain
b .


unsigned int xyz;
int notmain ( void )
{
    xyz=5;
    return(0);
}

MEMORY
{
    bob : ORIGIN = 0x1000, LENGTH = 0x1000
    ted : ORIGIN = 0x2000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > bob
    .bss : { *(.bss*) } > ted
}

我的 bootstrap,主程序和 linker 脚本

arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -save-temps -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy --srec-forceS3 notmain.elf -O srec notmain.srec
arm-none-eabi-objcopy notmain.elf -O binary notmain.bin

有些人会争辩说编译不再生成程序集,这有时是真的。仍然是明智的做法,你会经常发现它,就像在这种情况下......

bootstrap 制作了一个我们可以拆解的对象。

00000000 <.text>:
   0:   e3a0d101    mov sp, #1073741824 ; 0x40000000
   4:   e38dda01    orr sp, sp, #4096   ; 0x1000
   8:   ebfffffe    bl  0 <notmain>
   c:   eafffffe    b   c <.text+0xc>

它不是 "linked" 所以这个反汇编器使用的地址是从零开始的,你可以看到对 notmain 的调用是不完整的,还没有 linked.

编译器为 C 代码生成汇编

    .cpu arm7tdmi
    .fpu softvfp
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "notmain.c"
    .text
    .align  2
    .global notmain
    .type   notmain, %function
notmain:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    mov r2, #5
    ldr r3, .L2
    mov r0, #0
    str r2, [r3]
    bx  lr
.L3:
    .align  2
.L2:
    .word   xyz
    .size   notmain, .-notmain
    .comm   xyz,4,4
    .ident  "GCC: (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)"

组装成一个我们也可以拆解的对象。

Disassembly of section .text:

00000000 <notmain>:
   0:   e3a02005    mov r2, #5
   4:   e59f3008    ldr r3, [pc, #8]    ; 14 <notmain+0x14>
   8:   e3a00000    mov r0, #0
   c:   e5832000    str r2, [r3]
  10:   e12fff1e    bx  lr
  14:   00000000    andeq   r0, r0, r0

现在未显示,但该对象还包含全局变量 xyz 及其大小的信息。

link她的工作可能是您困惑的一部分。它 link 将对象组合在一起,这样结果将是理智的或将在最终目的地(裸机或操作系统)上工作。

Disassembly of section .text:

00001000 <notmain-0x10>:
    1000:   e3a0d101    mov sp, #1073741824 ; 0x40000000
    1004:   e38dda01    orr sp, sp, #4096   ; 0x1000
    1008:   eb000000    bl  1010 <notmain>
    100c:   eafffffe    b   100c <notmain-0x4>

00001010 <notmain>:
    1010:   e3a02005    mov r2, #5
    1014:   e59f3008    ldr r3, [pc, #8]    ; 1024 <notmain+0x14>
    1018:   e3a00000    mov r0, #0
    101c:   e5832000    str r2, [r3]
    1020:   e12fff1e    bx  lr
    1024:   00002000    andeq   r2, r0, r0

Disassembly of section .bss:

00002000 <xyz>:
    2000:   00000000    andeq   r0, r0, r0

我制作了这个 linker 脚本,这样您就可以看到 .data 和 .bss 都在移动。 linker 已将所有 .text 填入 0x1000 地址 space 并修补了对 notmain() 的调用以及如何到达 xyz。它还具有 allocated/defined 0x2000 地址中的 xyz 变量 space space。

然后是你的下一个问题或困惑。这在很大程度上取决于加载系统的工具,无论是操作系统将程序加载到内存中运行,还是对 MCU 的闪存进行编程或对其他嵌入式系统的 ram 进行编程(如例如你可能不知道的鼠标,其中一些固件是从操作系统下载的,而不是全部烧录到闪存 /lib/firmware 或其他位置)。