【问题标题】:How are memory addresses placed in an binary files?内存地址如何放置在二进制文件中?
【发布时间】:2019-03-26 00:56:59
【问题描述】:

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

基本上,地址是如何以及何时放入这些部分的,以及如何在操作系统和嵌入式系统中的 ROM 或 RAM 中加载地址。

【问题讨论】:

  • 在多进程环境中,每个进程都有自己的虚拟内存。链接器通常会确定程序映像应该加载到哪个地址,如果其他程序在同一地址中也没关系,因为它们根本看不到彼此的内存(虚拟内存地址与信息实际所在的地址不同)在物理内存中)。除此之外,大多数寻址都是使用相对地址(指定相对于指令所在位置的内存偏移量)而不是绝对内存地址完成的,因此即使您随机播放代码,代码也能正常工作。
  • 二进制文件中是否嵌入了地址?
  • 当然。但正如我所说,它们通常是相对地址而不是绝对地址,因此解码可能有点困难。
  • 在嵌入式系统中,定位器用于指定正在构建的最终图像的地址。为什么需要这样做?
  • 我不确定你的意思。

标签: c assembly memory embedded microcontroller


【解决方案1】:

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

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

所以用一个简单的链接器脚本编写一个简单的程序

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

“二进制”文件恰好是人类可读的:

S00F00006E6F746D61696E2E737265631F
S3150000100002D9A0E3010000EB0000A0E3000000EF1E
S30D000010100000A0E31EFF2FE122
S70500001000EA

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

作为一个加载到内存中的基于操作系统的程序,我们不必玩太多的内存游戏,我们可以假设一个平坦的全内存(读/写),所以如果有 .data、.bss 等,它可以都装在里面。

对于真正的操作系统,希望二进制文件包含附加信息,可能是程序的大小。因此,您可以搜索各种常见的文件格式,看看这是如何完成的,要么是我非常需要的一个简单的预先设置,要么是单独定义的一对多部分。是的,“二进制”不仅仅是操作码和数据,我假设你明白这一点。

我使用的工具链默认输出 elf 格式的文件,但 objcopy 可用于创建许多不同的格式,其中一种是原始内存映像(不包含任何地址/位置信息),其余大部分/大部分包含机器代码和数据以及调试器/反汇编器的标签或该数据块希望在内存空间中的地址等。

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

现在我的简单程序

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

这样链接

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 在如何加载可用工具和/或制作自己的工具方面都不同。 Ihex 和 srec 在舞会程序员中很受欢迎,您曾说过处理器旁边有一个单独的 rom 和/或通孔 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 的路线。但它仍然取决于获取“二进制”并将其编程到 MCU 上的 ROM(/FLASH)中的工具。

现在我作弊来演示上面的大文件问题,相反,当它不是一个仅 ram 的系统时,你必须做更多的工作。如果您觉得需要 .data 或希望将 .bss 归零,那么您需要编写或使用更复杂的链接器脚本来帮助您确定位置和边界。并且该链接器脚本与使用链接器生成的信息来执行这些任务的引导程序相结合。基本上 .data 的副本需要保存在非易失性内存 (ROM/FLASH) 中,但它不能在运行时保存在那里 .data 是读/写的,因此理想情况下/通常您使用链接器脚本语言/魔术来声明 .数据读/写空间是blah,闪存空间在这个地址和这个大小是boo,所以引导程序可以从那个地址的闪存复制那个数量的数据到ram。对于 .bss,链接器脚本会生成我们保存到闪存中的变量,这些变量告诉引导程序从这个地址到这个地址将 ram 归零。

所以操作系统定义了内存空间,如果你想让程序工作,链接描述文件就会匹配。系统设计人员或芯片设计人员确定嵌入的地址空间,并且链接描述文件与之匹配。引导程序与该构建和目标的链接描述文件结合。

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

工具链基础...

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
}

我的引导程序、主程序和链接器脚本

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>

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

编译器为 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 及其大小的信息。

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

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

我制作了这个链接器脚本,以便您可以看到 .data 和 .bss 的移动。链接器已将所有 .text 填充到 0x1000 地址空间中,并修补了对 notmain() 的调用以及如何到达 xyz。它还在 0x2000 地址空间中为 xyz 变量分配/定义了空间。

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

【讨论】:

  • 感谢您。感谢您抽出时间如此彻底地回答。链接器绝对是最令人困惑的部分。还有一个问题。我得到操作系统指定加载规则,但在嵌入式系统上,芯片制造商会说“代码将从 ROM 中的 0x00004000 运行”吗?那么,我基本上需要一个链接器脚本来将 .text 放在那里?他们会为 .data 指定一个位置吗?
  • 是的。处理器有其在处理器内核中的引导和地址规则,但是芯片供应商添加的内存总线无论是他们自己的处理器还是购买的像 arm 一样的处理器,他们都可以将其映射到其他地方。例如 stm32 系列,应用程序闪存位于 0x08000000 以进行正常启动,该启动被镜像到 0x00000000,其中 arm 处理器需要向量表,您可以将链接描述文件设置为基于 0x00000000 或理想情况下基于 0x08000000,因为镜像的窗口不一定是用户闪存的全部。
  • 但是是的,您是绝对正确的,微控制器芯片供应商最终决定或实现了从 rom 到 ram 到所有外围设备的内存映射,并且他们在某处记录了该信息,因此如果您的代码需要该信息(编译C 程序需要获取 ram 中的变量)然后您需要使用与该硬件匹配的映射来链接程序。
  • arm 不制造芯片,他们制造 IP。例如,一个 Cortex-M 芯片供应商可以制作与另一个 Cortex-M 芯片供应商不同的内存映射,即使使用相同内核的相同版本也是如此。在 arm cortex-m 的情况下,处理器对地址空间有一些规则,但这仅在该总线内,芯片供应商可以选择制作任何他们想要的外部总线。但通常你会看到跨供应商的 Cortex-M 芯片有很多相似之处,但有时也会有差异,所以不要假设核心完全占主导地位。
猜你喜欢
  • 1970-01-01
  • 2021-06-27
  • 2020-07-02
  • 2014-05-28
  • 1970-01-01
  • 2022-07-19
  • 1970-01-01
  • 1970-01-01
  • 2014-03-17
相关资源
最近更新 更多