DSP学习(8)—— linker.cmd文件解析

DSP学习(8)—— linker.cmd文件解析



前言

写工程的时候遇到报内存不够的错误,出现在linker.cmd的内存分配section,找到一篇英文文章详细说明了linker.cmd文件。记录下来以便日后查看。

英文原文链接:https://software-dl.ti.com/ccs/esd/documents/sdto_cgt_Linker-Command-File-Primer.html


介绍

本文介绍通常出现在大多数 TI 链接器命令文件linker.cmd中的代码。

问题陈述

您已经有一个linker.cmd,它适用于你开始的开发系统(套件,启动板,EVM等)。假设你需要更改linker.cmd,以对生产系统上的内存配置进行修改。本文通过解释您现在拥有的linker.cmd来帮助你做到这一点。

文章概述

Basic部分适合所有人。Basic部分中描述的代码出现在每个linker.cmd中。在Beyond Basic部分,选择性阅读,这里可以找到出现在某些(但不是全部)linker.cmd中的代码说明。

基本

Linker.cmd文件可能显示的任何内容:options, object file names, library names(选项、对象文件名、库名称。)Linker.cmd文件中可以创建全局符号,但本文没有描述这些。本部分重点介绍 MEMORY 指令,尤其是 SECTIONS 指令。这些指令出现在每个linker.cmd中。

MEMORY指令

MEMORY 指令的用途是为内存范围指定名称。这些内存范围名称在 SECTIONS 指令中使用。以下是来自典型MSP430系统的MEMORY指令的一部分...

MEMORY
{
    SFR                     : origin = 0x0000, length = 0x0010
    PERIPHERALS_8BIT        : origin = 0x0010, length = 0x00F0
    PERIPHERALS_16BIT       : origin = 0x0100, length = 0x0100
    RAM                     : origin = 0x1C00, length = 0x0FFE
    INFOA                   : origin = 0x1980, length = 0x0080
    INFOB                   : origin = 0x1900, length = 0x0080
    INFOC                   : origin = 0x1880, length = 0x0080
    INFOD                   : origin = 0x1800, length = 0x0080
    FLASH                   : origin = 0x8000, length = 0x7F80
    INT00                   : origin = 0xFF80, length = 0x0002
    /* ... and so on */
}

例如:以 RAM 开头的行定义了一个名为 RAM 的内存范围。它从地址0x1C00开始,具有0xFFE长度。

SECTIONS 指令

SECTIONS 指令同时执行两项操作。

  • 它从输入部分形成输出部分
  • 它将这些输出部分分配给内存

下面是显示 SECTIONS 指令如何工作的图形方式。

在这里插入图片描述

词汇表

描述 SECTIONS 指令需要了解这些术语。

  • Object file 对象文件 - 对象文件是输入部分的集合。对象文件可以直接呈现给链接器(通过命令行或在命令文件中),也可以来自库。
  • Input Section 输入节 - 一个对象文件中的一个节。输入部分可以初始化也可以不初始化。它可以包含代码或数据。
  • Output Section 输出节 - 一个或多个输入节的集合。输出部分是根据 SECTIONS 指令形成的,但极少数例外情况在本文中未作说明。
  • Memory Range 内存范围 - 在 MEMORY 指令中指定的系统内存范围

Section 命名约定

从理论上讲,仅凭名称,无法了解有关输入部分内容的任何信息。尽管如此,具有这些名称的输入部分通常具有以下内容:

名字初始 化笔记
.text是的可执行代码
.bss全局变量
.cinit是的初始化全局变量的表
.data (EABI)是/否初始化来自汇编程序 / 更改为未被链接器初始化
.data (COFF ABI)是的初始化的数据
.stack系统堆栈
.heap / .sysmemmalloc 堆
.const是的初始化的全局变量
.switch是的某些开关语句的跳转表jump
.init_array 或 .pinit是的启动时调用的C++构造函数表
.cio用于 stdio 函数的缓冲区

节名称通常以“.”开头,但这不是必需的。

您可能会看到这些名称的变体,例如 .ebss 或 .fardata。这些部分与此表中描述的部分非常接近。

语法说明

本文这一部分中的所有示例都出现在 SECTIONS 指令中。

SECTIONS
{
   /* all examples appear here */
}

表单输出部分

    output_section_name         /* Name the output section        */
    {
       file1.obj(.text)         /* List the input sections        */
       file2.obj(.text)
       file3.obj(.text)
    } > FLASH                   /* Allocate to FLASH memory range */

这将创建一个名为 output_section_name 的输出部分。它由 3 个输入部分组成:来自 file1.obj 的 .text、来自 file2.obj 的 .text 和来自 file3.obj 的 .text。它被分配给闪存范围。

显然,此语法不能很好地扩展到具有许多此类对象文件的系统。

    output_section_name         /* Name the output section           */
    {
        /* Shortcut syntax for all input sections named .text        */
        *(.text)
    } > FLASH                   /* Allocate to FLASH memory range    */

这与前面的示例执行相同的操作,但有一个区别。前面的示例仅使用了 3 个输入节,并且为对象文件名和输入节名显式指定了这些输入节。此示例使用名为 .text 的所有输入节。确切地说,它使用所有名为 .text 的输入部分,这些部分不属于任何其他输出部分。

因为即使这样也不够短,所以此示例中的快捷方式建立在上一个示例的基础上。

选择文本
    .text > FLASH

此示例与上一个示例只有一个区别:输出部分的名称已从 output_section_name 更改为 .text。请注意输出节名称和输入节名称如何完全相同:.text。尽管有这种相似性,但重要的是不要忽视输入部分和包含它们的输出部分之间的区别。

下面是另一个快捷方式示例:

选择文本
    .text : {} > FLASH

此示例与上一个示例没有什么不同。之所以显示它,是因为该语法模式在许多linker.cmd中很常见。

您可以将这些快捷方式混合在一起。例如:

选择文本
    output_section_name
    {
        first.obj(.text)        /* This code must be first */
        *(.text)
    } > FLASH

这将创建一个名为 output_section_name 的输出部分。第一个输入部分是 first.obj 中的 .text 部分。其余输入部分是来自所有其他对象文件的所有 .text 部分。它被分配给闪存范围。

将输出部分分配到内存

上述示例中的此语法...

选择文本
    ... > FLASH

将输出部分分配到内存。闪存范围用于此特定情况。

您可能还会看到...

选择文本
    ... > 0x20000000

对硬编码地址的分配始终在分配到命名内存范围之前完成。您的linker.cmd可能会利用这种排序差异。

另一种值得关注的技术...

选择文本
#define BASE 0x20000000

/* many lines later */

... > BASE

这看起来与对命名内存范围的分配相同,但它实际上是对硬编码地址的分配。

勉强超越基础

从这一点开始,文章变得不那么全面,更具选择性。查看每个部分附带的示例。如果linker.cmd中出现此类代码,则该部分将对其进行描述。否则,您可以忽略它。

除非另有说明或说明,否则所有示例都发生在 SECTIONS 指令中。

添加和更改

如果您在linker.cmd中看到本文中未介绍的代码,请发布到 E2E 论坛。论坛回复通常会导致对本文进行添加或更改。

内存范围中的第一个输出部分

假设您看到类似于 ...

选择文本
#define BASE 0x00200000

MEMORY
{
FLASH : origin = BASE, length = 0x0001FFD4

}

SECTIONS
{
.intvecs > BASE /* only section allocated to BASE */
.text > FLASH
.const > FLASH

}

此代码的净效应是 .intvecs 是闪存范围中的第一个输出部分。其余的输出部分也位于FLASH中,但可以按任何顺序分配。

#define BASE 是使用链接器的类似 C 的预处理器功能的一个示例。它用于建立闪存范围的开始。它还用于将 .intvecs 分配给该特定地址。对特定地址的分配始终在分配到命名内存范围之前完成。

分配到多个内存范围

考虑这个例子...

选择文本
    .text > FLASH0 | FLASH1

这意味着 .text 输出部分被分配到内存范围 FLASH0 或 FLASH1。首先尝试 FLASH0。如果它不能包含所有 .text,则尝试 FLASH1。注意:.text 不会拆分。整个输出部分放置在 FLASH0 或 FLASH1 中。

将输出部分拆分到多个内存范围

考虑这个例子...

选择文本
    .text : >> RAMM0 | RAML0 | RAML1

这意味着 .text 将跨这些内存范围进行拆分。请注意语法。如果所有 .text 都不适合 RAMM0,则将其拆分,其余部分进入剩余的内存范围。拆分发生在输入截面边界上。输入部分永远不会拆分。这意味着任何函数,数组,结构等都不能在中间拆分。内存范围按该顺序使用。>>

内存页

内存页仅在 C28xx 链linker.cmd中使用。

内存页在内存指令中指定,如下所示...

选择文本
MEMORY
{
    PAGE 0 :
        RAMM0   : origin = ...
        RAML0L1 : origin = ...
PAGE <span class="hljs-number">1</span> :
    RAMM1   : origin = ...
    RAML2   : origin = ...

}

每一页内存都是完全独立的。在页面之间,您可以重复使用内存范围名称和内存地址。下面的例子是完全合法的,但是一个非常糟糕的主意...

选择文本
/* DO NOT DO THIS!!! */
MEMORY
{
    PAGE 0 :
        MEM_RANGE : origin = 0x100, length = 0x100
    PAGE 1 :
        MEM_RANGE : origin = 0x100, length = 0x100
}

C28xx 器件是从 20 世纪 80 年代开始的一长串 C2xxx 器件的后裔。这些早期的设备具有用于代码和数据的单独内存总线。这些总线连接到物理上独立的内存块。因此,PAGE 0 上的特定地址可能具有与 PAGE 1 上的相同地址不同的内容。从理论上讲,在C28xx设备上可以实现存储器总线的这种相同的单独连接,尽管它是一种很少(接近从未使用过)的功能。如果有丝毫疑问,请查看特定于设备的文档。

尽管几乎所有 C28xx 设备的所有内存总线都连接到所有内存,但这种使用内存页的传统仍然存在。如果linker.cmd使用 PAGE 0 和 PAGE 1,则最好继续以这种方式使用它。请注意上一个示例所说明的陷阱。

语法提示

在任何地方都可以写MEMORY_RANGE_NAME也可以写MEMORY_RANGE_NAME PAGE 0。从表面上看,这是因为可以在多个页面上使用相同的内存范围名称。因为这是糟糕的编程实践,所以编写页码实际上是为了保持所有内容清晰且易于维护。如果将内存范围名称和不存在的页面组合在一起,链接器将告诉您。

使输出部分无效

如果你看到这样的东西...

选择文本
   .reset : > RESET, PAGE = 0, TYPE = DSECT    /* not used */

语法使此输出部分成为虚拟部分。虚拟部分不占用内存中的空间,并且不存在于输出文件中。最终效果是,所有名为 .reset 的输入部分都被静默地丢弃了。在这种特定情况下,这没关系。但并非所有情况都行。假设另一节中的代码调用 .reset 中的函数,或者另一节使用 .reset 中的数据。链接器以静默方式吞噬那些对 .reset 的引用。您可能更喜欢诊断。TYPE = DSECT

有关 DSECT 和其他类似特殊部分的详细信息,请参阅文章链接器特殊部分类型

请参阅 ROM 代码或数据

此代码...

选择文本
FPUmathTables : > FPUTABLES, PAGE = 0, TYPE = NOLOAD

是指向系统中已存在的部分。该部分通常以ROM或闪存形式提供。该语法赋予特殊的 noload 属性。noload 部分确实会占用内存中的空间,但它不存在于输出文件中。在实践中,noload部分内部没有对外部任何内容的引用。但是 noload 部分以外的其他部分可以引用 noload 部分内的代码和数据。在这种特定情况下,必须在ROM中提供一些浮点单位表,其他代码使用这些表。TYPE = NOLOAD

有关NOLOAD和其他类似特殊部分的更多信息,请参阅文章链接器特殊部分类型

在一个地址加载,从另一个地址运行

请考虑以下示例:

选择文本
 .TI.ramfuncs : LOAD = FLASHD,
                RUN = RAML0,
                LOAD_START(_RamfuncsLoadStart),
                LOAD_END(_RamfuncsLoadEnd),
                RUN_START(_RamfuncsRunStart)

这将创建一个名为 的输出部分。它由所有输入部分组成,也命名为 。它有两种不同的分配。它被分配给FLASHD用于加载,RAML0用于运行。此输出部分放置在输出文件中,以便当加载程序时(可能通过将其编程到闪存中来实现),它位于FLASHD内存范围内。在系统执行期间的某个时候,在 中出现任何内容之前。使用TI.ramfuncs,应用程序将其从FLASHD复制到RAML0。请注意,此复制不会自动完成。此处未讨论的显式步骤必须在应用程序代码中执行。调用 中函数的任何其他部分。TI.ramfuncs 就像 .TI.ramfuncs已经在RAML0中。LOAD_START等运算符建立用于实现复制的符号。符号_RamfuncsLoadStart的值是起始加载地址。同样,_RamfuncsLoadEnd具有结束加载地址,_RamfuncsRunStart具有起始运行地址。

从库中分配单个输入节

请考虑以下示例:

选择文本
   IQmathTables3 : > IQTABLES3
   {
       IQmath.lib<IQNasinTable.obj> (IQmathTablesRam)
   }

这形成了一个名为 IQmathTables3 的输出部分。它包含一个名为 IQmathTablesRam 的输入部分。该输入部分来自对象文件IQNasinTable.obj,它是IQmath.lib库的成员。此输出部分分配给 IQTABLES3 内存范围。

变体可用于分配库中的所有部分。

选择文本
    sinetext : > DDR2
    {
         --library=Sinewave_lib.lib(.text)
    }

这将形成一个名为正弦文本的输出部分。它包含链接器从库Sinewave_lib.lib中使用的文件中的所有 .text 输入部分。它被分配给 DDR2 内存范围。请注意,链接器不会从 Sinewave_lib.lib 中引入所有文件。它只引入满足其他对象模块的开放引用所需的文件。这些其他模块的示例包括主应用程序代码中的文件,以及已包含的其他库中的对象文件。语法告诉链接器此文件不在当前目录中,并在库搜索路径中收集的目录中查找它。在前面的示例中不需要语法,因为尖括号的使用与 具有相同的效果。--library=--library=<>--library=

将库中的输入部分分配到不同的加载和运行地址

此示例将前两个示例的技术组合在一起。

选择文本
    ipcConst
    {
       driverlib.lib<ipc.obj>(.const)
    } LOAD = FLASH5, RUN = RAMLS0,
    LOAD_START(constLoadStart),
    LOAD_SIZE(constLoadSize),
    RUN_START(constRunStart), ALIGN(8)

这将形成一个名为 ipcConst 的输出部分。它包含一个名为 .const 的输入部分。该输入部分来自对象文件 ipc.obj,它是库 driverlib.lib 的成员。它有两种不同的分配。它被分配给 FLASH5 用于加载,RAMLS0 用于运行。此输出部分放置在输出文件中,以便当加载程序时(可能通过将其编程到闪存中来实现),它位于FLASH5内存范围内。在系统执行期间的某个时候,在使用 ipcConst 中的任何内容之前,应用程序会将其从 FLASH5 复制到 RAMLS0。请注意,此复制不会自动完成。此处未讨论的显式步骤必须在应用程序代码中执行。引用 ipcConst 中数据的任何其他部分都表现得好像 ipcConst 始终位于 RAMLS0 中。LOAD_START、LOAD_SIZE等运算符建立用于实现复制的符号。有关这些运算符的详细信息,请在 CPU 系列的汇编语言工具手册中搜索标题为“地址和维度运算符”的子章节。ALIGN(8) 表示运行地址与 8 的倍数对齐。有关详细信息,请在同一汇编语言工具手册中搜索标题为“指定加载地址和运行地址”的子章节。

将输出部分组合在一起

假设您需要一些输出部分按顺序彼此相邻。你可能会写...

选择文本
    /* This does NOT work */
    output_section_1 > RAM
    output_section_2 > RAM
    output_section_3 > RAM

链接器将所有这些输出部分放在 RAM 内存范围内。但它可以按任何顺序放置它们,但其他输出部分可以介于它们之间。使用 GROUP 指令按特定顺序将输出部分一起分配。这是一个实际的例子...

选择文本
    GROUP : > CTOMRAM
    {
        PUTBUFFER
        PUTWRITEIDX
        GETREADIDX
    }

输出部分是PUTBUFFER,PUTWRITEIDX和GETREADIDX。它们以完全相同的顺序作为一个组分配给存储器范围CTOMRAM。请注意,各个输出部分没有任何内存分配规范。

这些输出部分名称全部采用大写字母。linker.cmd中的不成文约定是,只有像CTOMRAM这样的内存范围名称才用全大写字母书写。但是,你需要为违反该公约的行为做好准备。

内存属性

内存指令可能包含如下行...

选择文本
MEMORY
{
    ...
    FLASH1 (RX) : origin = 0x00204000, length = 0x1C000
    FLASH2 (RX) : origin = 0x00260000, length = 0x1FFD0
    CSM_RSVD_Z2 : origin = 0x0027FFD0, length = 0x000C
    CSM_ECSL_Z2 : origin = 0x0027FFDC, length = 0x0024
    C0 (RWX)    : origin = 0x20000000, length = 0x2000
    ...
}

请注意两行和另一行上的。该语法指定这些内存范围的属性。每个字母表示一个内存属性属性。(RX)(RWX)

  • R:可以读取
  • W:可以写
  • X:可以包含可执行代码
  • I:可以初始化

默认情况下,内存范围具有所有四个属性。

这些属性的记录目的是在 SECTIONS 指令中支持按内存属性进行节分配。这里没有这方面的例子,因为 TI 提供的linker.cmd都没有使用此功能。在这种情况下,此语法仅用作记录该内存范围内通常包含哪些类型的部分的方法。

  • 4
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
f2833x_boot_rom_lnk.cmd是一个命令文件,用于配置F2833x系列微控制器的引导 (boot) ROM。 引导 (boot) ROM是芯片上预留的一段内部程序,用于在设备上电时执行的初始化操作。它的作用是引导设备启动,并且加载并运行用户定义的程序代码。 该命令文件的作用是为引导ROM的链接器 (linker) 提供配置信息,以便正确地将用户定义的程序代码与引导ROM进行连接。在连接时,该命令文件需要提供一些重要的参数,例如使用的引导ROM存放的起始地址、设备的FLASH存储器大小等。 通过配置该命令文件开发人员可以根据自己的需求定制引导ROM的链接设置,以确保用户定义的程序能够正确地与引导ROM进行连接。这样,在设备上电时,引导ROM会按照预定义的方式执行初始化操作,并加载并运行用户的程序代码。 在开发过程中,我们可以通过修改f2833x_boot_rom_lnk.cmd文件来改变引导ROM的链接设置,以满足不同的应用需求。例如,我们可以配置引导ROM存放在不同的起始地址,或者修改FLASH存储器的分区等。这样,我们就能够更好地调整设备的初始化过程,适应不同的应用场景。 总之,f2833x_boot_rom_lnk.cmd文件的作用是为F2833x系列微控制器的引导ROM提供链接设置,并确保用户定义的程序能够正确地与引导ROM进行连接,在设备启动时实现自定义的初始化过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值