谷动谷力

标题: 【进阶】详解KEIL的分散加载文件 [打印本页]

作者: 鸣涧    时间: 2022-11-4 22:25
标题: 【进阶】详解KEIL的分散加载文件
【进阶】详解KEIL的分散加载文件使用分散文件指定栈和堆

ARM C 库提供了该函数的多种实现__user_setup_stackheap(),并且可以从分散文件中提供的信息中自动为您选择正确的一种。

要选择两个区域内存模型,请在名为ARM_LIB_HEAP和的分散文件中定义两个特殊的执行区域ARM_LIB_STACK。两个区域都有该EMPTY属性。这会导致库选择__user_setup_stackheap()使用符号值的非默认实现:

Image$$ARM_LIB_STACK$$Base

Image$$ARM_LIB_STACK$$ZI$$Limit

Image$$ARM_LIB_HEAP$$Base

Image$$ARM_LIB_HEAP$$ZI$$Limit
1234567

只能指定一个ARM_LIB_STACKARM_LIB_HEAP区域,并且必须分配一个大小,例如:

ARM_LIB_HEAP 0x20100000 EMPTY 0x100000-0x8000  ; Heap starts at 1MB
                                               ; and grows upwards
ARM_LIB_STACK 0x20200000 EMPTY -0x8000         ; Stack space starts at the end
                                               ; of the 2MB of RAM
                                               ; And grows downwards for 32KB
12345

可以通过定义名为单一执行区域使用组合的栈和堆区域ARM_LIB_STACKHEAP,与EMPTY属性。这会导致__user_setup_stackheap()使用符号Image$$ARM_LIB_STACKHEAP$$BaseImage$$ARM_LIB_STACKHEAP$$ZI$$Limit的值。

注意如果您重新实现__user_setup_stackheap(),这将覆盖所有库里面的实现。

创建root执行区

要将区域指定为分散文件中的根区域,您可以:

以下示例显示了隐式定义的根区域:

LR_1 0x040000          ; load region starts at 0x40000   
{                      ; start of execution region descriptions      
    ER_RO 0x040000     ; load address = execution address
    {
        * (+RO)        ; all RO sections (must include section with
                       ; initial entry point)
    }
    ...                ; rest of scatter-loading description
}
123456789

The following example shows the corresponding scatter-loading description: 下面的例子给出了相应分散加载描述:

LR_1 0x040000              ; load region starts at 0x40000   
{                          ; start of execution region descriptions           
    ER_RO 0x040000         ; load address = execution address
    {
        * (+RO)            ; RO sections other than those in init.o
    }
    ER_INIT 0x080000 FIXED ; load address and execution address of this
                           ; execution region are fixed at 0x80000
    {
        init.o(+RO)        ; all RO sections from init.o
    }
    ...                    ; rest of scatter-loading description
}
12345678910111213

Examples of misusing the FIXED attribute误用 FIXED 属性 例子

The following example shows common cases where the FIXED execution region attribute is misused:

LR1 0x8000
{
    ER_LOW +0 0x1000
    {
        *(+RO)
    }
; At this point the next available Load and Execution address is 0x8000 + size of
; contents of ER_LOW. The maximum size is limited to 0x1000 so the next available Load
; and Execution address is at most 0x9000
    ER_HIGH 0xF0000000 FIXED
    {
        *(+RW+ZI)
    }
; The required execution address and load address is 0xF0000000. The linker inserts
; 0xF0000000 - (0x8000 + size of(ER_LOW)) bytes of padding so that load address matches
; execution address
}

; The other common misuse of FIXED is to give a lower execution address than the next
; available load address.

LR_HIGH 0x100000000
{
    ER_LOW 0x1000 FIXED
    {
        *(+RO)
    }
; The next available load address in LR_HIGH is 0x10000000. The required Execution
; address is 0x1000. Because the next available load address in LR_HIGH must increase
; monotonically the linker cannot give ER_LOW a Load Address lower than 0x10000000
}
12345678910111213141516171819202122232425262728293031
使用 FIXED 属性创建根区域

您可以FIXED在执行区分散文件中使用该属性来创建在固定地址加载和执行的根区。FIXED用于在单个加载区域内创建多个根区域,因此通常是单个 ROM 设备。例如,您可以使用它来将函数或数据块(例如常量表或校验和)放置在 ROM 中的固定地址,以便可以通过指针轻松访问。

例如,如果您指定将一些初始化代码放置在 ROM 的开头并在 ROM 的末尾放置一个校验和,则某些内存内容可能未被使用。使用*或.ANY模块选择器来填充初始化块末尾和数据块开头之间的区域。

为了使您的代码更易于维护和调试,建议您在分散文件中使用最少的布局规范,并将函数和数据的详细布局留给链接器。

您不能指定已部分链接的组件对象。例如,如果您将对象obj1.oobj2.o和部分链接obj3.o在一起以产生obj_all.o,则在生成的对象中会丢弃组件对象名称。因此,您不能按名称引用其中一个对象,例如,obj1.o。您只能引用组合对象obj_all.o

注意在某些情况下,使用FIXED和 单个加载区域是不合适的。指定固定位置的其他方式是:

在特定地址放置函数和数据

通常,编译器从单个源文件生成 RO、RW 和 ZI 节。这些区域包含源文件中的所有代码和数据。要将单个函数或数据项放置在固定地址,您必须使链接器能够将函数或数据与其余输入文件分开处理。

链接器有两种方法可以让您将段放置在特定地址:

要将函数或变量放置在特定地址,必须将其放置在其自己的段中。有几种方法可以做到这一点:

在没有分散加载的情况下将变量放置在特定地址的示例

此示例显示如何修改源代码以将代码和数据放置在特定地址,并且不需要分散文件:1、创建main.c包含以下代码的源文件:

#include <stdio.h>

extern int sqr(int n1);
int gSquared __attribute__((at(0x5000)));  // Place at 0x5000

int main()
{
    gSquared=sqr(3);
    printf("Value squared is: %d\n", gSquared);
}
12345678910

2、创建function.c包含以下代码的源文件:

int sqr(int n1)
{
    return n1*n1;
}
1234

3、编译并链接源:

armcc -c -g function.c
armcc -c -g main.c
armlink --map function.o main.o -o squared.axf
123

--map选项用于生成内存映射文件即.map文件,同样--autoat是默认值

在此示例中,__attribute__((at(0x5000)))指定将全局变量gSquared放置在绝对地址处0x20000gSquared被放置在执行区ER$$.ARM.__AT_0x00005000和加载区中LR$$.ARM.__AT_0x00005000

The memory map shows:

...
  Load Region LR$$.ARM.__AT_0x00005000 (Base: 0x00005000, Size: 0x00000000, Max: 0x00000004, ABSOLUTE)

    Execution Region ER$$.ARM.__AT_0x00005000 (Base: 0x00005000, Size: 0x00000004, Max: 0x00000004, ABSOLUTE, UNINIT)

    Base Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x00005000   0x00000004   Zero   RW           15    .ARM.__AT_0x00005000  main.o

123456789

使用分散加载将变量放置在指定段中的示例

此示例显示如何使用分散文件修改源代码以将代码和数据放置在特定部分中:1、创建main.c包含以下代码的源文件:

#include <stdio.h>

extern int sqr(int n1);
int gSquared __attribute__((section("foo")));  // Place in section foo

int main()
{
    gSquared=sqr(3);
    printf("Value squared is: %d\n", gSquared);
}
12345678910

2、创建function.c包含以下代码的源文件:

int sqr(int n1)
{
    return n1*n1;
}
1234

3、创建scatter.scat包含以下加载区域的分散文件:

LR1 0x0000 0x20000
{
    ER1 0x0 0x2000
    {
        *(+RO)                      ; rest of code and read-only data
    }
    ER2 0x8000 0x2000
    {
        main.o
    }
    ER3 0x10000 0x2000
    {
        function.o
        *(foo)                      ; Place gSquared in ER3
    }
    RAM 0x200000 (0x1FF00-0x2000)   ; RW & ZI data to be placed at 0x200000
    {
        *(+RW, +ZI)
    }
    ARM_LIB_STACK 0x800000 EMPTY -0x10000
    {
    }
    ARM_LIB_HEAP  +0 EMPTY 0x10000
    {
    }
}
1234567891011121314151617181920212223242526

ARM_LIB_STACKARM_LIB_HEAP都需要,因为程序将与半主机库链接。4、编译并链接

armcc -c -g function.c
armcc -c -g main.c
aarmlink --map --scatter=scatter.scat function.o main.o -o squared.axf
123

内存映射显示:

  Load Region LR1 (Base: 0x00000000, Size: 0x00001778, Max: 0x00020000, ABSOLUTE)
...
    Execution Region ER3 (Base: 0x00010000, Size: 0x00000004, Max: 0x00002000, ABSOLUTE)

    Base Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x00010000   0x00000004   Data   RW           15    foo                 main.o
...
12345678

注意

如果*(foo)从分散文件中省略,则该部分将放置在相同类型的区域中。在这个例子中就是RAM区。

使用分散加载将变量放置在特定地址的示例

1、创建main.c包含以下代码的源文件

#include <stdio.h>

extern int sqr(int n1);

// Place at address 0x10000
const int gValue __attribute__((section(".ARM.__at_0x10000"))) = 3;

int main()
{
    int squared;
    squared=sqr(gValue);
    printf("Value squared is: %d\n", squared);
}
12345678910111213

2、创建function.c包含以下代码的源文件:

int sqr(int n1)
{
    return n1*n1;
}
1234

3、创建scatter.scat包含以下加载区域的分散文件:

LR1 0x0
{
    ER1 0x0
    {
        *(+RO)                      ; rest of code and read-only data
    }
    ER2 +0
    {
        function.o
        *(.ARM.__at_0x10000)         ; Place gValue at 0x10000
    }
    RAM 0x200000 (0x1FF00-0x2000)   ; RW & ZI data to be placed at 0x200000
    {
        *(+RW, +ZI)
    }
    ARM_LIB_STACK 0x800000 EMPTY -0x10000
    {
    }
    ARM_LIB_HEAP  +0 EMPTY 0x10000
    {
    }
}
12345678910111213141516171819202122

ARM_LIB_STACKARM_LIB_HEAP都需要,因为程序将与半主机库链接。

4、编译并链接

armcc -c -g function.c
armcc -c -g main.c
armlink --no_autoat --scatter=scatter.scat --map function.o main.o -o squared.axf
123

内存映射显示变量放置ER2在地址处的执行区中0x11000:

...
    Execution Region ER2 (Base: 0x00001598, Size: 0x0000ea6c, Max: 0xffffffff, ABSOLUTE)

    Base Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x00001598   0x0000000c   Code   RO            3    .text               function.o
    0x000015a4   0x0000ea5c   PAD
    0x00010000   0x00000004   Data   RO           15    .ARM.__at_0x10000   main.o...
12345678

在这个例子中,ER1的大小是未知的。因此,gValue可能放在ER1或 中ER2。要确保将gValue其放置在ER2中,您必须包含相应的选择器ER2并与--no_autoat命令行选项链接。如果省略--no_autoat, gValue将被放在一个单独的加载区域LR$$.ARM.__AT_0x00010000,包含执行区域ER$$.ARM.__AT_0x00020000

变量指定段

方式一

int variable __attribute__((section("foo"))) = 10;
1
FLASH 0x24000000 0x4000000
{
    ...                                ; rest of code

    ADDER 0x08000000
    {
        file.o (foo)                  ; select section foo from file.o
    }
}
123456789

方式二

// place variable1 in a section called .ARM.__at_0x00008000
int variable1 __attribute__((at(0x8000))) = 10;

// place variable2 in a section called .ARM.__at_0x8000
int variable2 __attribute__((section(".ARM.__at_0x8000"))) = 10;
12345
ER_FLASH 0x8000 0x2000
{
    *(+RO)
    *(.ARM.__at_0x8000) ;
}
12345

函数地址指定

int sqr(int n1) __attribute__((section(".ARM.__at_0x20000")));

int sqr(int n1)
{
    return n1*n1;
}
123456

注意

使用分散加载显式放置命名部分

以下示例显示如何使用分散加载显式放置命名部分:

LR1 0x0 0x10000
{
    ER1 0x0 0x2000                   ; Root Region, containing init code
    {
        init.o (INIT, +FIRST)        ; place init code at exactly 0x0
        *(+RO)                       ; rest of code and read-only data  
    }
    RAM_RW 0x400000 (0x1FF00-0x2000) ; RW & ZI data to be placed at 0x400000
    {
        *(+RW)
    }
    RAM_ZI +0
    {
        *(+ZI)
    }
    DATABLOCK 0x1FF00 0xFF           ; execution region at 0x1FF00
    {                                ; maximum space available for table is 0xFF
        data.o(+RO-DATA)             ; place RO data between 0x1FF00 and 0x1FFFF
    }
}
1234567891011121314151617181920

在这个例子中,分散加载描述放置:

使用.ANY模块选择器放置未分配的段

链接器尝试将输入节放入特定的执行区。对于无法解析的任何输入部分,并且这些部分的放置不重要,您可以使用.ANY分散文件中的模块选择器。

在大多数情况下,使用单个.ANY选择器等同于使用*模块选择器。但是不同的是,您可以.ANY在多个执行区中指定。

放置未分配段的默认规则

默认情况下,链接器使用以下条件放置未分配的段:

使用多个.ANY选择器时的放置规则

如果分散文件中存在多个.ANY 选择器,则链接器采用最大大小的未分配段并将该段分配给具有足够可用空间的最具体的.ANY执行区。例如,.ANY(.text)被判断为比 .ANY(+RO)更具体。

如果多个执行区具有相同的特性,则该段将分配给具有最多可用剩余空间的执行区。

例如:

.ANY优先段

如果您有多个.ANY带有选择器的部分,您可以给出优先顺序,其中是从零向上的正整数。最高优先级被赋予具有最高整数的选择器。.ANYnum以下示例显示了如何使用:.ANYnum

lr1 0x8000 1024
{
    er1 +0 512
    {
        .ANY1(+RO) ; evenly distributed with er3
    }
    er2 +0 256
    {
        .ANY2(+RO) ; Highest priority, so filled first
    }
    er3 +0 256
    {
        .ANY1(+RO) ; evenly distributed with er1
    }
}
123456789101112131415

控制多个.ANY选择器的输入段的放置

.ANY通过使用不同的放置算法或不同的排序顺序,您可以修改链接器在使用多个选择器时放置未分配输入段的方式。以下命令行选项可用:

first_fit当您想要按顺序填充区域时使用。best_fit当您想最大程度地填充区域时使用。worst_fit当您想要均匀填充区域时使用。使用相同大小的区域和部分worst_fit循环填充区域。当您需要更具确定性的填充模式时,请使用 next_fit。

如果链接器尝试将区域填充到其极限,就像使用first_fit和 一样best_fit,它可能会过度填充该区域。这是因为在将节分配给.ANY选择器之前,链接器生成的内容(例如填充和单板)是未知的。如果发生这种情况,您可能会看到以下错误:

Error: L6220E: Execution region regionname size (size bytes) exceeds limit (limit bytes). 错误:L6220E:执行区regionname大小(size字节)超过限制(limit字节)。

--any_contingency选项可防止链接器将区域填充到最大值。它为链接器生成的内容保留了该区域大小的一部分,并且仅当其他区域没有空间时才填充此应急区域。默认情况下为first_fitbest_fit算法启用它,因为它们最有可能表现出这种行为。

指定允许放置未分配段的最大尺寸

执行区属性使您能够指定armlink可以用未分配的节填充的区域中的最大大小。ANY_SIZE max_size

使用此关键字时请注意以下限制:

当ANY_SIZE存在时,armlink:

ANY_SIZE不需要--any_contingency指定。但是,无论何时--any_contingency指定和ANY_SIZE未指定,armlink 都会尝试调整意外情况。目标是:

如果您--any_contingency在命令行上指定,则对于已ANY_SIZE指定的区域将忽略它。它通常用于未ANY_SIZE指定的区域。

以下示例显示了如何使用ANY_SIZE

LOAD_REGION 0x0 0x3000
{
   ER_1 0x0 ANY_SIZE 0xF00 0x1000
   {
      .ANY
   }
   ER_2 0x0 ANY_SIZE 0xFB0 0x1000
   {
      .ANY
   }
   ER_3 0x0 ANY_SIZE 0x1000 0x1000
   {
      .ANY
   }
}
123456789101112131415

在这个例子中:

ER_1为链接器生成的内容保留了0x100。ER_2为链接器生成的内容保留了0x50。这和--any_contingency的自动应急保留类似。ER_3没有预留空间。因此,100%的区域被填满,没有应急保留。省略ANY_SIZE参数会导致98%的区域被填满,只有2%的应急保留。

使用 __at 在外设寄存器上放置

要将未初始化的变量放置在外设寄存器上,您可以使用 ZI__at部分。假设一个寄存器可用于0x10000000,定义一个__at名为.ARM.__at_0x10000000. 例如:

int foo __attribute__((section(".ARM.__at_0x10000000"), zero_init));
1
ER_PERIPHERAL 0x10000000 UNINIT
{
    *(.ARM.__at_0x10000000)
}
1234

使用自动放置,并假设附近没有其他执行区0x10000000,链接器会自动创建一个UNINIT属性为 at的区域0x10000000。该UNINIT属性创建一个包含未初始化数据或内存映射 I/O 的执行区。

预留一个空区域

可以EMPTY在执行区分散加载描述中使用该属性来为堆栈保留一块空内存。

内存块不构成加载区的一部分,而是在执行时分配使用。因为它是作为虚拟 ZI 区域创建的,所以链接器使用以下符号来访问它:

如果长度为负值,则该地址被视为区域的结束地址。这必须是绝对地址而不是相对地址。

在以下示例中,执行区定义STACK 0x800000 EMPTY -0x10000定义了一个名为的区域STACK,该区域从 address 开始并在 address0x7F0000结束0x800000:

LR_1 0x80000                          ; load region starts at 0x80000   
{
    STACK 0x800000 EMPTY -0x10000     ; region ends at 0x800000 because of the
                                      ; negative length. The start of the region
                                      ; is calculated using the length.
    {
                                      ; Empty region used to place stack
    }
    HEAP +0 EMPTY 0x10000             ; region starts at the end of previous
                                      ; region. End of region calculated using
                                      ; positive length
    {
                                      ; Empty region used to place heap
    }
    .   ..                               ; rest of scatter-loading description...
}
12345678910111213141516

注意

为EMPTY执行区域创建的虚拟ZI区域在运行时不会初始化为零。

如果地址是相对的(+offset)形式并且长度是负的,链接器会产生一个错误。下图显示了该示例的图解表示。

图 9. 为堆栈保留一个区域

在这里插入图片描述

在本例中,链接器生成符号:

Image$$STACK$$ZI$$Base      = 0x7f0000
Image$$STACK$$ZI$$Limit     = 0x800000
Image$$STACK$$ZI$$Length    = 0x10000
Image$$HEAP$$ZI$$Base       = 0x800000
Image$$HEAP$$ZI$$Limit      = 0x810000
Image$$HEAP$$ZI$$Length     = 0x10000
123456

该EMPTY属性仅适用于执行区。链接器生成警告并忽略EMPTY加载区定义中使用的属性。

链接器检查用于该EMPTY区域的地址空间是否与任何其他执行区域不一致。

在分散文件中使用预处理命令

您可以通过 C 预处理器传递分散文件。这允许访问 C 预处理器的所有功能。

使用分散文件中的第一行指定链接器调用以处理文件的预处理器命令。命令的格式如下:

#! preprocessor [
pre_processor_flags
]
123

最典型的命令是#! armcc -E. 这会通过armcc预处理器传递分散文件。

你可以:

例如,分散文件file.scat, 可能包含:

#! armcc -E

#define ADDRESS 0x20000000
#include "include_file_1.h"

lr1 ADDRESS
{
    ...
}
123456789

链接器解析预处理后的分散文件并将指令视为注释。

您还可以将分散文件的预处理与–predefine命令行选项结合使用。对于这个例子:

在分散文件中使用表达式求值以避免填充

使用ALIGN,ALIGNALL或FIXED在分散的文件属性可导致在镜像中的大量填充的。要删除此填充,您可以使用表达式计算来指定加载区和执行区的起始地址。内置函数AlignExpr可用于帮助您指定地址表达式。

避免在分散文件中填充的示例 以下分散文件生成带有填充的图像:

LR1 0x4000
{
    ER1 +0 ALIGN 0x8000
    {
        ...
    }
}
1234567

使用ALIGN关键字ER10x8000加载视图和执行视图中的边界对齐。要在加载视图中对齐,链接器必须插入0x4000填充字节。

以下分散文件生成没有填充的图像:

LR1 0x4000
{
    ER1 AlignExpr(+0, 0x8000)
    {
        ...
    }
}
1234567

使用AlignExpr的结果+00x8000边界对齐。这将创建一个执行区,其加载地址为0x4000但执行地址为0x8000。

参考 : https://developer.arm.com/docume ... using-scatter-files








欢迎光临 谷动谷力 (http://bbs.sunsili.com/) Powered by Discuz! X3.2