尝试了解ARM二进制映像中的装载存储器地址(LMA)和二进制文件偏移量

时间:2019-02-07 16:50:32

标签: memory linker arm embedded microcontroller

我正在使用 ARM Cortex M4 STM32F4xxxx ),并且正在尝试了解二进制文件(*.elf和{{1 }})内置并刷新到内存中,特别是关于内存位置。具体而言,我不了解的是*.bin是如何从实际的二进制文件偏移量中“翻译”的。让我用一个例子来解释:

我有一个LMA文件,其(相关)部分如下:(从*.elf获得)

objdump -h

根据该文件,VMA和LMA是my_file.elf: file format elf32-littlearm Sections: Idx Name Size VMA LMA File off Algn 0 .text 000001c4 08010000 08010000 00020000 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .bootloader 00004000 08000000 08000000 00010000 2**0 CONTENTS, ALLOC, LOAD, DATA 0x8000000,这很好,因为它们是在链接描述文件中定义的。此外,根据该报告,这些部分的偏移量分别为0x80100000x10000。接下来,我执行以下命令来转储与0x20000相对应的内存:

.bootloader

现在,创建要闪存到内存中的二进制文件:

xxd -s 0x10000 -l 16 my_file.elf
00010000: b007 c0de b007 c0de b007 c0de b007 c0de  ................ 

根据上面提供的信息,据我所知,生成的二进制文件应在arm-none-eabi-objcopy -O binary --gap-fill 0xFF -S my_file.elf my_file.bin 处有.bootloader节。我知道这不是它的实际工作方式,因为文件会变得非常大,所以0x8000000放在文件的开头,所以地址为bootloader(检查两个内存块相同,即使的地址不同):

0x0

据我了解,当将提到的二进制文件闪存到内存中时,xxd -s 0x00000 -l 16 my_file.bin 00000000: b007 c0de b007 c0de b007 c0de b007 c0de ................ 将位于地址bootloader,考虑到所涉及的MCU跳至地址0x0(从0x4获得SP后)开始工作,正如我在此处检查的那样(第26页):https://www.st.com/content/ccc/resource/technical/document/application_note/76/f9/c8/10/8a/33/4b/f0/DM00115714.pdf/files/DM00115714.pdf/jcr:content/translations/en.DM00115714.pdf

最后,我的问题是:

0x0实际放置在bootloader处吗?如果是这样,在链接器文件中定义存储扇区的目的是什么?

这是因为0x0属于闪存,并且在MCU启动时,所有闪存都复制到了地址0x0的{​​{1}}中吗?如果是这样,RAM是否将从闪存中执行,其余所有代码是否从0x8000000中执行?

考虑到上述问题,如果我什么都不懂,bootloaderRAM之间的关系/区别是什么?

2 个答案:

答案 0 :(得分:2)

文档非常清楚地说明了stm32的应用程序代码的地址空间在哪里,即0x08000000(竞争的供应商就像0x01000000,依此类推)。而且,以某种模式启动时,可以通过调试器(在两个空格中都可以很容易地看到)将0x08000000映射到地址0x00000000。

映射到0x08000000的0x00000000地址空间小于0x08000000的潜在地址空间,具体取决于芯片。因此,构建并使用0x08000000而不是0x00000000是明智的,但是对于小型程序,您可以选择其中一个。

因为cortex-m是向量表机器,当逻辑读取地址0x00000004时,该地址在正常引导模式下映射到0x08000004,因此它看到0x080xxxxx,然后退出0x00000000内存空间,从而避免了任何限制。

当您使用boot0 / boot1带状引脚时,可以改为使0x00000000映射到Bootloader中被烧毁的其他位置。该引导加载程序当然可以轻松读取0x08000000并通过分支轻松模拟重置,也可以更改逻辑并实际重置(如果您要求这样做,尽管我不知道该引导加载程序是否真正支持运行程序)。谁知道我们是否在那里做过工作,我们不一定会说。总是有可能总是引导到引导加载程序,然后根据皮带改变映射。

类似于mmu,但解码地址和别名简单得多。如果boot0 == 0且address [31:16] = 0x0000,则address [31:16] = 0x0800且存储系统将其解码到不同的地址,就像用C编写一样容易,在HDL中就这么简单如果不是那么容易。

这在微控制器以及其他微控制器中并不罕见,但是由于微控制器通常是从flash / rom引导,但是某些架构上的相同引导空间也是rtos有时可能想要操纵的向量表或异常表您会看到ram可以交换到该空间中,因此,在更改控制寄存器后,cpu会“看到”一些ram,在引导时它会在闪存上“看到”向量表。或您将代码放在flash分支上的ram中用于非复位向量的位置上,然后rtos或其他任何愿意执行此操作的应用程序都可以对这些异常或中断实际运行的代码进行运行时更改。

ARM强制使用地址空间规则,以说明代码可以在何处执行和数据可以存在,以及您可能要在何处启动外围设备地址空间,以及ARM为内核内的资源保留的地址空间。因此有时您会看到ram在较低的地址处有一个别名,这意味着如果您想在ram中运行程序,则希望使用较低的地址来执行程序,但可以使用其中一个地址来复制代码。

由芯片设计人员来决定如何简单或复杂。对于ST来说,它非常简单,然后在包装上有一个或多个引导引脚,至少可以让您在应用程序和片内引导加载程序之间进行选择,到目前为止,我所见过的所有stm32s都被认为位于应用程序闪存空间为0x08000000,并且对于其中一种引导模式,将其映射/别名为0x00000000。如果暴露了两个启动引脚,则最多可以存在四种可能的启动条件,其中一种是将0x00000000别名为0x08000000的应用程序。

关于如何将位放入闪存中,这因工具而异。像gnu这样的工具链肯定会建立一个.bin文件,其中文件的第一个字节是我们希望从elf开始的第一个字节,位于0x08000000(如果以这种方式构建,则如果您是为0x02000000构建的,它仍将是第一个字节,并且该代码可能无法正常工作)。有一些工具,您当然可以编写自己的工具,即会知道可以将.bin文件加载到0x08000000的所需位置,或者对于不太大且具有以下要求的程序,也可以在正确的模式下对地址0x00000000进行写操作:它仍然落在正确的位置以在重置时执行。同样,您也可以编写工具来解析.elf文件,intel hex,motorola srecord和其他文件,并基于那些二进制文件中的信息,假设所有内容都没有错误,则将数据加载到所需的地址空间中。

您可能会尝试使其过于复杂,这没有任何魔术,工具需要做明智的事情,而明智的事情是从编译器中获取二进制文件并将其放入所需的芯片中。当然,我们负责链接描述文件以及此类代码和引导程序代码/向量表,但是如果我们这样做正确,则如果正确设计工具,则会将这些位放置在芯片中的正确位置,并且如果芯片被正确设计为记录下来,它将启动并运行。

  

引导加载程序实际上会放置在0x0吗?如果是这样,那是什么   定义链接器文件中存储区的目的?

理想情况下,您希望应用程序或引导加载程序在调用时位于处理器地址空间中的地址0x08000000处。在某些引导模式下(boot0 / boot1),该地址也被别名为0x00000000,因此您可以在两个位置同时看到该向量表。如果您未处于正确的启动模式,则只有0x08000000会显示您的代码。

  

这是因为0x0属于闪存,并且在MCU启动时,   所有闪存都复制到地址0x8000000的RAM中?如果是这样,   引导加载程序可以从闪存和所有其他代码中执行   来自RAM?

芯片中的逻辑旨在获取处理器放入其地址总线上的地址,并在应用程序闪存上拥有多个地址,如果该应用程序闪存只有一个16KB的闪存,则该应用程序闪存的地址不是0x08000000当您访问0x08001234时,从0x0000到0xFFFF的地址实际上会将0x1234发送到闪存控制器,或者如果闪存控制器知道应该处理该请求,则将其砍掉。 0x00000000、0x08000000是地址空间的处理器视图,实际是对高位进行解码,并将请求路由到其所属的任何人,最终处理程序最终查看低位以确定正在处理的内容。

就像您递送信件时一样,它具有名字和姓氏,街道地址,城市州邮政编码。一旦以正确的状态到达正确的邮局,那么街道地址便成为邮政人员的重要事项。一旦到达正确的房子,通常所有的名字都只有名字,其余的可以忽略。没什么区别。地址的部分(通常)变得无关紧要,因为负责检查该地址的负责逻辑将请求定向到正确的一方。

  

如果我不理解,请考虑上述问题   什么,LMA和文件之间的关系/差异是什么   抵消了吗?

elf文件格式是通用的,对于微控制器的工作而言是一种过大的杀伤力,但是得到了很好的支持,并且易于使用,为什么不这样。装载存储器地址是我们程序员希望代码相对于处理器的世界生存的地方。从readelf的角度来看,文件中的偏移量是elf文件中该信息的偏移量,它只是该工具所处的位置,没有其他有趣的关系。或至少不需要。 Objcopy会将数据从文件中提取出来,并使用-O二进制文件将其放入某种内存映像文件中,其中要复制的最低地址为该文件中的偏移量0,其大小由所有文件的总地址空间确定可加载的块(除非您使用更多的命令行参数)。正如您所暗示的那样,但是如果您考虑它并遇到一个链接描述文件错误,则即使在0x08000000处只有一条指令,在0x20000000处只有一个.data字节,但没有执行AT>指令,则尽管有三个相关字节的长度为0x20000001-0x08000000字节。 (在-O二进制文件之后),因此,在调试链接器脚本之前,请勿将objcopy放入make文件中。假设有一个目标,其中flash为0x00000000,内存为0xE0000000,这是很大的.bin文件,直到您整理了链接描述文件为止。

答案 1 :(得分:1)

否,根据elf文件中的定义,引导程序将位于08000000。

图像将在该地址的闪存中刻录并直接从那里执行(不在其他位置复制)。

有一些未记录的行为,即在生成二进制图像时跳过实际数据之前的统一区域。作为BFDlib源状态(https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=bfd/binary.c;h=37f5f9f7363e7349612cdfc8bc579369bbabbc0c;hb=HEAD#l238)中的注释

/* The lowest section LMA sets the virtual address of the start
   of the file.  We use this to set the file position of all the
   sections.  */

.elf中的最低部分(.bootloader)LMA为08000000,因此二进制文件将从该地址开始。
确定图像中的地址时,应考虑该地址并将其添加到文件偏移中。

Sections:
Idx Name              Size      VMA       LMA       File off  Algn
  0 .text             000001c4  08010000  08010000  00020000  2**0
    /*                                    ^^^^^^^^              */
    /* this section will be at offset 10000 in image            */

                      CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .bootloader       00004000  08000000  08000000  00010000  2**0
    /*                                    ^^^^^^^^              */
    /* this is the lowest LMA in your case it will be used      */
    /* as a start of an image, and this section will be placed  */
    /* directly at start of the image                           */
                      CONTENTS, ALLOC, LOAD, DATA

Memory layout:     Bin. image layout:
000000000                    \ skipped
...       ________________   /
080000000 .bootloader         0
...       ________________
080004000   <gap>          4000
...       ________________
080010000 .text           10000
...       ________________
0800101C4                 101C4

该地址在ldscript中定义,因此二进制映像应从固定位置开始。但是,在处理ldscrips和二进制映像时,您应该意识到这种行为。

总结构建和刷新过程:

  1. 链接时,在ldscript中定义了起始地址,在elf中定义了第一部分。
  2. 转换为二进制文件时,起始地址由LMA确定,而二进制映像则从该地址开始。
  3. 在刷新图像时,将与flasher相同的地址作为参数,因此图像放置在正确的位置(在ldscript中定义)。

更新:STM32F4xxx引导过程。

从地址0开始的地址区域对于那些MCU来说是特殊的。可以将其配置为映射其他区域,例如闪存,SRAM或系统ROM。它们是通过引脚BOOTSELx选择的。 从CPU端看来,第二个闪存副本(SRAM或系统ROM)出现在地址0。

CPU启动时,首先从地址0读取初始SP,从地址4读取初始PC。实际上,是从闪存读取数据。 如果代码被链接为从实际闪存位置运行,则初始PC将指向该位置。在这种情况下,执行将从实际的闪存地址开始。

----- Mapped area (mimics contents as flash) ---
       0:          (02001000)         ;
       4:          (0800ABCD) ----.   ; CPU reads PC here
....                              |   ; (it points to flash)
----- FLASH -----                 |
 8000000:           20001000      |   ; initial stack pointer
 8000004:           0800ABCD --.  |   ; address of _start in flash
....                           |  |   
 800ABCD: <_start:> movw ... <-'<-'   ; Code execution starts here

(注意:这不适用于十六进制图像(如intel十六进制或s记录),因为此类格式显式定义了加载地址,并按原样使用)。