代码之家  ›  专栏  ›  技术社区  ›  Dan

试图了解ARM二进制映像中的加载内存地址(lma)和二进制文件偏移量

  •  2
  • Dan  · 技术社区  · 6 年前

    我在一家 臂皮层M4 ( STM32 F4XXXX )我想知道二进制文件到底有多精确( *.elf *.bin )在内存中构建和闪存,特别是与内存位置有关的。具体来说,我不明白的是 LMA 从实际二进制文件偏移量获取“translated”。让我举例说明一下:

    我有一个 *精灵 其(相关)部分如下的文件:(从 objdump -h )

    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
    

    根据该文件,vma和lma是 0x8000000 0x8010000 ,这是非常好的,因为它们在链接器脚本文件中是以这种方式定义的。此外,根据该报告,这些路段的偏移量为 0x10000 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 
    

    根据上面提供的信息,据我所知,生成的二进制文件应该具有 引导加载程序 部分位于 0x800 . 我知道这不是它的实际工作方式,因为文件会变得非常大,所以 bootloader 放在文件的开头,所以地址 0x0 (检查两个内存块是否相同,即使它们位于不同的地址):

    xxd -s 0x00000 -l 16 my_file.bin
    00000000: b007 c0de b007 c0de b007 c0de b007 c0de  ................
    

    据我所知,当所提到的二进制文件闪存到内存中时, 引导加载程序 将在地址 0x0 ,考虑到所讨论的MCU跳到地址,什么是完美的? 0x4 (从 0x0 )当它开始工作时,我在这里检查过(第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 ?如果是,那么在链接器文件中定义内存扇区的目的是什么?

    这是因为 0x0 属于闪存,当MCU启动时,所有闪存都复制到 RAM 在地址 0x800 ?如果是这样,将会 引导加载程序 从闪存和所有其他代码中执行 皇家音乐学院 ?

    考虑到上述问题,如果我没有理解任何内容,那么 线性矩阵不等式 以及 File offset ?

    2 回复  |  直到 6 年前
        1
  •  1
  •   Vovanium    6 年前

    不,bootloader将位于08000000,如elf文件中所定义。

    图像将在该地址的闪存中烧录,并直接从该地址执行(不复制到其他地方)。

    有一些未记录的行为,即在生成二进制图像时跳过实际数据之前的统一区域。作为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.  */
    

    最低部分(.bootloader)lma是.elf中的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. 当闪烁图像时,给闪光器的地址与参数相同,因此图像被放置在正确的位置(在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
    

    (注意:这不适用于十六进制图像(如IntelHex或S-Record),因为这样的格式显式定义了加载地址,并且像现在这样使用)。

        2
  •  2
  •   old_timer    6 年前

    文档非常清楚STM32的应用程序代码的地址空间在何处,即0x0800000(竞争供应商类似于0x1000000,等等)。当以特定模式启动时,0x0800000映射到地址0x00000000,这在调试器中很容易看到(在两个空格中)。

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

    因为Cortex-M是一个向量表机器,当逻辑读取地址0x0000004(在正常启动模式下映射到0x080000004)时,它会看到0x080XXXXX,然后从0x00000000内存空间中取出,从而避免了任何限制。

    当您使用boot0/boot1带管脚时,您可以使0x00000000映射到烧坏的bootloader所在的其他地方。当然,这个引导加载程序可以很容易地读取0x0800000,并且通过分支很容易地模拟重置,或者它可以更改逻辑并实际重置(如果您要求,尽管我不知道这个引导加载程序是否支持运行程序)。谁知道我们是否在那里工作,我们不一定说。很有可能它总是引导到引导加载程序中,然后根据条带更改映射。

    类似于MMU,但解码地址和给地址加别名要简单得多。如果boot0==0和address[31:16]=0x0000,那么address[31:16]=0x0800,内存系统会在不同的地址对其进行解码,就像用C编写一样简单,如果不容易,在HDL中也很容易。

    这在微控制器和其他微控制器中并不少见,但由于微控制器通常是从闪存/ROM启动的,但在某些体系结构中,相同的启动空间也是RTO可能希望操作的向量或异常表,有时您会看到RAM可以交换到该空间中,因此CPU可以在一个控制寄存器在启动时在flash上“看到”矢量表的地方被改变。或者您将flash上的代码转移到RAM中的某个地方,以用于非重置向量,然后RTOS或任何其他关心此操作的应用程序可以对这些异常或中断实际运行的代码进行运行时更改。

    ARM对代码可以在哪里执行、数据可以在哪里生存、您可能希望在哪里启动外围地址空间以及ARM为核心内的资源保留了什么地址空间都施加了地址空间规则。因此,有时您会看到RAM在较低的地址有一个别名,这意味着如果您想在RAM中运行一个程序,您需要使用较低的地址来执行,但可以使用其中一个地址来复制代码。

    这取决于芯片设计人员如何简单或复杂地制作。对于ST来说,它非常简单,然后在包上有一个或多个引导管脚,至少可以让您在应用程序和片上引导加载程序之间进行选择,到目前为止,我所看到的所有STM32应用程序闪存空间都被视为生存在0x0800000,对于其中一种引导模式,它被映射/别名为0x00000000。当暴露了两个引导插脚时,最多可以存在四种可能的引导条件,其中一种是别名为0x00000000到0x0800000的应用程序。

    至于如何将比特放入闪存,这一点因工具而异。像GNU这样的工具链当然会构建一个.bin文件,其中文件的第一个字节是ELF的第一个字节,我们希望它是0x0800000(如果是这样构建的,如果是为0x0200000构建的,它仍然是第一个字节,并且该代码可能无法工作)。有一些工具,你当然可以自己写,知道可以加载一个.bin文件在所需的位置0x0800000,或者你可以让你的太写地址0x00000000在正确的模式下为一个不是太大的程序,并让它仍然在正确的位置执行重置。同样,也可以编写工具来解析.elf文件、intel hex、motorola srecord和其他文件,并根据这些二进制文件中的信息,将数据加载到所需的地址空间中,前提是一切都没有错误。

    你可能试图将其过度复杂化,这并不是什么神奇的事情,工具需要做理智的事情,理智的事情是从编译器中取出二进制文件,把它放在我们想要的芯片中。当然,我们要负责链接器脚本和引导程序代码/向量表,但是如果我们做的对,如果设计正确的工具将把位放在芯片中的正确位置,如果芯片设计正确,那么它将引导并运行。

    引导加载程序是否将实际置于0x0?如果是的话,是什么? 在链接器文件中定义内存扇区的目的是什么?

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

    这是因为0x0属于闪存吗?当MCU启动时, 所有闪存都复制到地址为0x8000000的RAM中?如果是这样,将会 从闪存和所有其它代码执行引导加载程序 从RAM?

    芯片中的逻辑设计为获取处理器在其地址总线上的地址,并在应用程序闪存上有多个地址地,如果它的一个16kbyte闪存只有一个从0x0000到0xffff的地址,则应用程序闪存不在0x0800000。例如,当您访问0x08001234时,它实际上向闪存控制器发送了0x1234。如果知道它应该处理这个请求,那么ller和或flash控制器就会把顶部切掉。0x00000000、0x0800000是地址空间的处理器视图,实际情况是对高位进行解码,并将请求路由到它所属的任何人,最终处理程序将查看低位以确定要寻址的内容。

    就像你送一封信一样,信上有名字,街道地址,城市州邮政编码。一旦它以正确的状态到达正确的邮局,那么街道地址就是对邮政人员最重要的。一旦找到合适的房子,通常第一个名字就是最重要的,剩下的就可以忽略了。这里没有区别。地址的一部分(通常可以)变得不关心,因为负责检查该地址的逻辑将请求指向正确的一方。

    考虑到上述问题,如果我不理解 任何事,LMA和文件之间的关系/区别是什么? 抵消?

    ELF文件格式是通用的,对微控制器来说太过简单了,但是却得到了很好的支持,并且很容易使用。在加载内存地址中,程序员希望代码相对于处理器的世界视图存活。从readelf的角度来看,文件中的偏移量就是elf文件中该信息的偏移量,并且它就在工具放置它的任何位置,它没有其他有趣的关系。或者至少不需要。objcopy将把数据从文件中提取出来,对于-o二进制文件,将其放入一种内存映像文件中,其中被复制出来的最低地址是该文件中的偏移量0,大小由所有可加载块的总地址空间决定(除非使用更多的命令行参数)。正如您所暗示的,但是如果您考虑到它,并且有一个链接器脚本错误,如果您甚至有一条位于0x0800000的单指令和一个位于0x20000000的单字节的.data,但没有执行at>操作,那么您的文件尽管只有三个相关字节,但其长度将为0x20000001-0x0800000字节。(在-o二进制之后)在调试链接器脚本之前,最好不要将objcopy放在make文件中。假设有一个目标,其中flash是0x00000000,内存是0xE0000000,相当大的.bin文件,直到你得到链接器脚本。