资讯动态

LD链接文件memory maps(学习笔记)

发布时间:2026/8/13 16:54:18 来源:尧图企业网站定制
Green Hills Build arm关于此书分为以下几部分使用Multi编译器包括如何使用编译器驱动来控制工具链使用高级工具关于工具链的其他元素汇编器链接器库文件以及一些实用的应用程序语言索引记录高级语言C/C的实现情况包括宏定义预编译指令#pragma库头文件以及一些可选项辅助文档包括如何降GNU编译器和GreenHills工具链使用Chapter 1 GreenHills工具链C/C编译器驱动优化编译器asarm汇编器ax库elxr链接器优化的库和头文件应用程序构建器C/C编译器驱动编译器驱动格式从C/C源文件中构建可执行文件处理输入文件生成其他输出类型文件控制驱动信息使用驱动选项文件使用makefilesChapter 2 DevelopingforARMARM 特性结构打包指定一个ARM目标启用调试功能使用你自己的头文件和库文件控制汇编器控制链接器Thumb ModeText和Data的布局自定义GreenHills的运行环境移植Arm项目其他Chapter 8 The elxr Linker链接器特定选项指定程序入口点使用链接器指令文件配置链接器符号定义高级链接功能链接器特定选项Green Hills elxr链接器由驱动程序调用并将ELF对象文件和库组合成一个适合下载到目标上的可执行程序。要执行此任务它1整合所有的输入文件并输出为一个单一的程序包含了所有的重要的项目2给每个obiect分配内存的地址3通过将每个对象的引用替换为对每个对象的地址来解决重定位的问题当链接器组合目标文件时它使用一个节映射来确定将每个目标文件中的哪些节放入每个程序节中。它还读取段映射将程序段分配给内存块或地址。链接器读取内存映射以确定目标上内存块的大小和位置。链接器从链接器指令.ld文件中读取节映射、内存映射和选项参见页面上的“使用链接器指令文件配置链接器”驱动程序识别许多不同的选项并将它们传递给链接器。有关这些驱动程序选项的列表请参见第 258 页上的“链接器选项”。如果您使用驱动程序通过多个步骤编译和链接您的程序则必须为每个步骤传递完全相同的一组选项。尽管您应尽可能使用驱动程序选项但链接器接受一小部分驱动程序无法识别的选项。使用特定于链接器的选项时要小心因为它们不由驱动程序处理并且可能与驱动程序选项冲突。要将选项直接传递给链接器使用 Linker→Additional Linker Options (before start file) 选项-lnk。有关此选项的更多信息请参见第 263 页上的“Additional Linker Options (before start file)”。直接调用链接器是非常必要的必须通过驱动来指定特定的链接器选项。以下表格为链接器特定的选项指定程序入口使用Linker - StartAddressSymboloption-e选项指定程序入口如果你没有设置这个选项链接器会将入口地址自动设置为以下选项按照优先级排列将入口地址设置为 _start 如果存在将入口地址设置为start如果存在将入口地址设置为_main如果存在将入口地址设置为main如果存在将入口地址设置为0如果在程序启动时入口符号从 ROM 复制到 RAM那么入口点就在入口符号的 ROM 版本中仅支持Thumb的架构比如Cortex-M系列默认的Thumb模式入口点是 _start_T。Configuring the Linker with Linker Directives Files本节解释了如何使用链接器指令.ld文件来定义程序段并将这些段分配到内存块。内存块的名称、地址和大小在内存映射中定义。段的名称、属性和位置在段映射中定义。本节还介绍了链接器指令文件的语法介绍了四种链接器指令并讨论了一些高级主题链接指示文件格式 P473通过OPTION指令设置链接器选项通过DEFAULT指令设置Default通过MEMORY指令定义Memory Map通过SECTIONS指令定义Section的Map根据速度或者大小调整Section的分区自定义程序运行时的段当你使用项目向导创建项目时它会为你的板生成各种程序布局的默认链接器指令文件。如果你想自己编写链接器指令文件我们建议你复制其中一个默认文件并进行修改。有关这些默认文件的信息请参见第68页的“使用链接器指令文件”。在从默认区段映射中移除程序区段时要小心因为 Green Hills 运行时环境可能会使用它们参见第 494 页的“自定义运行时环境程序区段”。备注如果您指定了多个链接器指令文件elxr 将按照您指定的顺序处理这些文件。如果您在单独的文件中定义了内存映射和段映射则必须先指定包含内存映射的文件。Linker Directives File Syntax 链接器指令文件语法本节介绍了一般链接器指令的文件语法和表达式。有关特定指令的语法信息请查阅该指令的文档。表达式——包括所有标准的 C 算术运算符及其正常优先级以及具有副作用的运算符。下面的部分说明了在每个指令中你可以使用表达式的地方。注释——以数字符号#或两个斜杠//开头。如果你使用“链接器→预处理链接器指令”选项写注释时必须使用斜杠。点符号 (.) — 表示链接器在布局内存块或段时的当前位置。你可以使用表达式来改变点符号的值。Setting Linker Options with the OPTION Directive 通过OPTION指令设置链接器的选项你可以使用以下语法在链接指令文件中包含第466页“链接器特定选项”中列出的链接器选项OPTION(option…)你不能在 OPTION 指令中使用表达式。备注这个指令是为了向后兼容我们建议你通过驱动程序来指定链接器选项。Setting Defaults with the DEFAULTS Directive 通过DEFAULT指令来设置Defaults的选项默认值是你在链接器指令文件中定义区段和内存映射时可以用来替代绝对值的标识符。使用以下语法来指定默认值DEFAULTS{namevalue…}将多个默认值在不同的行上进行指定DEFAULTS{int_results_reserve0x100定义了int_results_reserve的值为256字节可以用于在链接文件中的定义 int_param_desp0x20int_app_desp0x20}MEMORY{RESULTS_RAM:org0x60000000,lenint_results_reserve}或者用于段大小的计算.results_buffer:{.int_results_reserve;}RESULTS_RAM 你可以在 DEFAULTS 指令中使用表达式这些表达式可以包含之前在指令中设置的默认值 DEFAULTS{foo0x2000// Set bar to 0x4000barfoo0x2000}当你指定内存量时你可以在字节数后加上 M 来表示兆字节加上 K 来表示千字节。你也可以在数字前加 0x 来以十六进制指定数量。这个例子指定了一个 DEFAULTS 指令用来定义两个默认值 DEFAULTS{// Set heap_reserve to 1048576heap_reserve1M// Set stack_reserve to 524288stack_reserve512K}DEFAULT命令与 -C 命令行选项的关系DEFAULTS 中定义的常量是默认值可以通过链接命令行中的 -Cnamevalue 选项覆盖linker-Cint_results_reserve0x200-oapp.elf...此时 int_results_reserve 的实际值变为 0x200覆盖了 DEFAULTS 中定义的 0x100。这种机制使得链接脚本可以保持通用而具体的尺寸或地址由构建配置动态调整。典型使用场景内存区域大小如上例定义各内存区域如保留区、参数区、应用区的固定大小便于统一管理和调整段的对齐或填充在段定义中引用这些常量作为对齐值或填充长度。多项目变体同一个链接脚本用于多个 ECU 变体通过 -C 传入不同的常量值来适应不同内存布局。与 ABS、MAX_SIZE 等配合例如 MAX_SIZE(int_results_reserve) 限制段的最大尺寸。与普通符号的区别方式特点DEFAULTS { name value; }默认值可在命令行被 -C 覆盖适合作为可配置参数name value;直接写在脚本顶层PROVIDE(name value);PROVIDE(name value);提供默认值但允许链接的目标文件中已定义的同名符号覆盖它DEFAULTS 最适合用于构建时可调节的配置参数Defining a Memory Map with the MEMORY Directive 通过MEMORY指令定义Memory布局MEMORY{memory_block:ORIGINorigin_expression,LENGTHlength_expression...}MEMORY 指令中的表达式可以包括在 DEFAULTS 指令中设置的默认值点符号.点符号对应的是上一个内存块结束后可以使用的第一个地址如果你把内存映射传给链接器只有在里面命名并定义的内存块才能在相关的段映射中被引用。Reserved Memory Blocks有时候默认的 Green Hills 链接器指令文件包含保留的内存块。通常这样做是为了避免使用目标设备上预配置软件比如BT和APP已经使用的内存。保留的内存块就像其他内存块一样只是你不应该为它们分配区段。核心原则当一个镜像如 Bootloader占用了某段内存且另一个镜像如 Application的链接脚本可能无意中将段分配到同一地址时就需要在第二个镜像的链接脚本中通过 reserved 声明该区域已被占用从而避免冲突。BT 和 APP 共享同一片 RAM 或 Flash 地址空间且两者是独立链接的镜像。如果 BT 占用了低地址的 32KB RAMAPP 的链接脚本就必须把这 32KB 声明为 reserved否则 APP 的 .data、.bss 等段可能被链接器放到那里导致覆盖。如果 BT 和 APP 的地址范围完全不重叠例如 BT 使用 Flash 0x08000000~0x0801FFFFAPP 使用 0x08020000~0x080FFFFFRAM 也类似则不需要 reserved因为链接器只会把段分配到脚本中定义的内存区域里不会越界。针对RAM数据BT 的 RAM 数据并不会自动“消失”但 APP 的初始化代码会主动覆盖它认为属于自己的内存区域。因此如果 BT 需要在 RAM 中保留数据给 APP 使用如重编程标志、跳转原因、配置参数就必须在 APP 的链接脚本中保留那块 RAM 区域例如用一个专门的段标记为 NOLOAD或者通过 reserved 声明并且 APP 的启动代码不能对该区域进行清零或复制。针对PFlash只要 BT 和 APP 的 Flash 地址范围不重叠且各自链接脚本正确指定了各自的 Flash 区域就不需要 reserved。因为链接器只会把段放进脚本中定义的 MEMORY 区域里不会跑到别人的地盘去。但如果 BT 和 APP 共用同一片 Flash例如原地升级时需要擦写对方区域那就不是 reserved 能解决的问题了需要更复杂的机制如双 Bank、交换指针、运行时动态映射等。reserved 的本质是“防冲突声明”用在接收方APP的链接脚本中告诉链接器“这里有主了别碰”。是否需要它取决于两个镜像是否共享同一物理内存地址空间以及是否有跨镜像的数据传递需求MEMORY{// 10MB of dram starting at 0x80000000dram_rsvd1:ORIGIN0x80000000,LENGTH32K dram_memory:ORIGIN.,dram_rsvd2:ORIGIN.,// 10MB of flash starting at 0xbfc00000LENGTH10M-32K LENGTH0flash_rsvd1:ORIGIN0xbfc00000,LENGTH32K flash_memory:ORIGIN.,LENGTH10M-32K flash_rsvd2:ORIGIN.,LENGTH0}Defining a Section Map with the SECTIONS Directive节映射将每个目标文件中的每个节映射到一个程序节。节映射还将程序节映射到内存块。节映射的格式如下SECTIONS{secname[start_expression][attributes]:[{contents}][memory_block,…|memory_block,…|.|.]...}在这里的指令中可以包含以下内容定义在默认DEFAULT指令中的默认数据定义在MEMORY指令中的内存块点符号.在链接脚本中被称为位置计数器代表链接器正在处理的输出段的当前地址。简单来说它就是链接器在安排各个段时“走到了哪里”的指针。点符号.。在这种情况下点符号表示链接器在布局对象和段时的当前位置。当你编写一个段时. 表示该段内部的当前位置。你可以通过赋值来调整位置.my_section:{.ALIGN(16);// 将当前位置对齐到 16 字节边界*(.my_data)// 将输入段 .my_data 的内容放在这里..0x100;// 跳过 256 字节预留空间LONG(0xDEADBEEF);// 在这里放置一个 4 字节常量}RAM 这里.的初始值是该段在内存中的起始地址由链接器根据前面的段和内存区域分配决定。每放置一个输入段或数据.的值就会自动增加。 dram_memory:ORIGIN.,LENGTH10M-32K 中.表示上一个内存区域的结束地址。因为 dram_rsvd1 的起始地址是0x80000000长度为32K所以它的结束地址是0x80007FFF。此时.的值就是0x80008000于是 dram_memory 的起始地址自动设为0x80008000实现了连续的内存区域定义无需手动计算.表示该段将被放置在当前输出段的末尾即紧跟前一个段之后。这里的.是链接器在安排段时的全局位置计数器。例如.text:{*(.text)}FLASH.data:{*(.data)}..data 段会被放置在.text 段之后仍在 FLASH 区域中。这种写法避免了显式指定内存区域名称但会使段顺序高度耦合 在表达式中的用法.还可以用在链接脚本的各种表达式中例如ADDR(section)返回段的运行地址但.是当前位置。SIZEOF(section)返回段的大小。ABSOLUTE(.)将当前位置转换为绝对地址去除任何相对偏移。 只能在 SECTIONS 块内部对.赋值在 MEMORY 块或脚本顶层不能直接赋值。 赋值.会向前移动位置计数器但不能向后移动不能“撤销”已放置的内容。 对齐操作是最常见的用法.ALIGN(4);确保后续数据从4字节对齐地址开始 总结 点符号.是链接脚本中动态跟踪当前地址的核心工具它让开发者能够精确控制段的排列、对齐和预留空间而无需手动计算每个段的起始地址。在内存区域定义中它简化了连续区域的声明在段定义中它提供了灵活的定位能力。理解.的工作原理是掌握链接脚本的关键一步。传递给链接器的.o文件中定义的elf文件中的符号当链接器接收到目标文件.o 文件时这些文件中包含 ELF 符号Symbols它们描述了程序中的函数、全局变量、静态变量等的名称、地址、大小和属性。链接器的主要工作之一就是处理这些符号具体包括以下内容符号定义Definition目标文件中定义的符号如函数体、全局变量定义链接器会为其分配最终地址。符号引用Reference目标文件中引用但未在本文件内定义的符号如调用外部函数、使用 extern 变量链接器需要在其他目标文件或库中找到它们的定义。符号解析Resolution将每个符号引用与其唯一的定义匹配起来解决跨文件的依赖关系。重定位Relocation根据最终地址调整指令中的地址偏移使程序能正确运行。关键点描述强符号与弱符号强符号如普通函数定义只能出现一次弱符号如attribute((weak))可以被覆盖。COMMON 符号未初始化的全局变量如 int x;在传统 C 中被视为 COMMON 符号允许多个文件定义链接器会合并它们之前讨论过的 --commons 选项。未定义符号如果某个符号在所有目标文件和库中都找不到定义链接器会报错除非使用了 -undefined 选项。与前面章节的联系链接时检查-argcheck、-globalcheck正是基于 ELF 符号中携带的类型信息来检测跨文件的不一致。多重定义符号-multiple和未定义符号-undefined直接关系到链接器如何处理 ELF 符号表中的冲突或缺失。ELF 符号是链接器进行跨文件连接、地址分配和优化决策的基础数据。.c 文件 ↓ 预处理cpp → 得到.i 文件展开宏、处理#include等 ↓ 编译cc1 → 得到.s 文件汇编代码 ↓ 汇编as → 得到.o 文件可重定位的目标文件 在大多数构建系统中这三步通常由编译器驱动程序如 gcc、armcc一次完成中间文件.i 和.s 默认不保留。只有当你使用-E只预处理或-S只编译到汇编时才会生成它们。 链接阶段.a 文件静态库​ 是由 ar 工具归档器将多个.o 文件打包而成的归档文件它本身不是链接的直接输出而是输入。你可以把.a 看作一个.o 的集合。 链接器ld​ 的输入可以是1.单个.o 文件2.多个.o 文件3..a 文件链接器会从库中按需提取需要的.o4.其他目标文件格式如.obj、.so 链接器的输出​ 通常是1.可执行文件如.elf、.out、.exe2.或者共享库.so、.dll 多个.o 文件 ──┐ ├──→ 链接器ld → 输出.elf 文件 多个.a 文件 ──┘ 不需要先制作.a 再链接。.a 的存在只是为了方便分发和管理多个.o 文件链接器可以直接使用它们。你也可以直接将所有.o 文件传递给链接器跳过.a 这一步。 先后关系 “先有.o 再有.elf”是正确的。.o 是可重定位目标文件包含尚未确定最终地址的代码和数据.elf 是可执行与链接格式包含已经分配好绝对地址的代码和数据以及调试信息、符号表等。链接器的工作就是将多个.o以及库中的.o合并成一个.elf。 后处理从.elf 到.hex/.srec 后处理工具如 objcopy​ 从.elf 文件中提取出纯二进制数据并按照特定格式Intel HEX、Motorola S-record、二进制 bin 文件等输出用于烧录到 Flash 中。完整的编译链接过程如下源文件.c/.cpp │ ├─ 预处理-E →.i 文件可选 │ ├─ 编译-S →.s 文件汇编代码可选 │ ├─ 汇编-c →.o 文件目标文件 │ ├─ 归档ar →.a 文件静态库可选 │ ├─ 链接ld →.elf 文件可执行文件含调试信息 │ └─ 格式转换objcopy →.hex/.srec/.bin烧录文件Specifying the Location of a Section 指定Section的位置The following list describes where the linker allocates a section in memory dependingon how you specify start_expression and memory_block:如果指定了start_expredssion链接器会将对应的section放到对应的start_expredssion不管您是否指定了memory block如果仅仅指定了memory_block那么连接器将会把section放到memory_block最开始可用的地址如果section的大小超过memory_block那么链接器会返回错误如果没有指定start_expredssion但是用了 . 符号指定了memory_block那么链接器会将对应的section放到前一个section的后面在同一个memory_block中如果大小不够那么链接器会返回错误如果你没有指定 start_expression 或 memory_block链接器会把这一节直接分配在前一节之后的同一个内存块里。如果这一节放不下链接器会把它分配到第一个有足够剩余空间的内存块中。无论链接器是从 start_expression 还是 memory_block 确定起始地址它都可能会增加地址以满足其内容的对齐要求。例如假设你有以下指令SECTION{.text0x10001:}如果链接器放入该节的任何目标文件的 .text 节需要 4 字节对齐那么最终可执行文件的 .text 节可能位于 0x10004而不是 0x10001。Specifying Multiple Memory Blocks for a Section 为一个section指定多个内存块如果你使用 语法为一个段指定了单个内存块但该段无法放入这个内存块链接器就会报错。如果你指定了多个内存块链接器会把该段放到第一个有足够剩余空间的指定内存块里参见第479页的例子8.4编写 SECTIONS 指令。Splitting a Section Across Multiple Memory Blocks 把一个部分拆分到多个内存块语法的作用.my_section:{...}RAM1,RAM2 链接器会尝试将这个段的内容拆分分别放入 RAM1 和 RAM2 两个内存块中。 如果拆分成功最终的可执行文件中会出现该段的多个版本每个内存块一个版本每个版本只包含该段的一部分内容。 这不同于语法RAM1,RAM2 表示整个段放入第一个能容纳它的内存块不拆分。拆分失败的原因文档列出了链接器可能无法拆分段的常见原因① 段内容中包含表达式除了简单的段包含规则如果段的花括号内有复杂的表达式例如 . ALIGN(4);、. 0x100;、LONG(expr) 等链接器无法确定如何分割这些内容会放弃拆分。只有纯粹的输入段包含规则如 ROM(.data)、*(.text)才支持拆分。② 段被 __ghsbegin、__ghsend 或 __ghssize 符号引用这些符号用于获取段的起始地址、结束地址或大小。它们假设段是连续且单一的。如果段被拆分这些符号的值会变得不明确例如哪个版本的起始地址因此链接器拒绝拆分。③ 来自同一个源文件、同一个输入段的内容不会被拆分链接器不会将一个输入段例如 foo.o 中的 .data拆开成两部分放到不同内存块中。它只会将不同的输入段来自不同文件或同一文件的不同 section分布到不同的内存块中。例如a.o 的 .data 可能放在 RAM1b.o 的 .data 放在 RAM2。拆分失败后的行为如果无法拆分链接器会退化为 的行为将整个段完整地放入所列举的内存块中的第一个能容纳它的块。如果所有块都无法容纳整个段则报错。如果拆分失败影响了内存分配例如导致某个块空间浪费链接器会发出警告并说明原因。如何提高拆分成功率文档建议使用编译选项-individual_function_sections让每个函数成为独立的输入段例如 .text.func1、.text.func2。-individual_data_sections让每个全局变量成为独立的输入段例如 .data.var1、.data.var2。这样链接器就可以将这些细粒度的输入段分别分配到不同的内存块中从而实现更精细的拆分。与 ROM to RAM 复制的关系文档中提到“interactions with other features that assume a single, contiguous memory region, such as ROM to RAM copy”。这是因为如果你的段需要通过启动代码从 Flash 复制到 RAM即 LMA ≠ VMA那么复制过程通常假设段是连续的一个源地址范围到一个目标地址范围。如果段被拆分到多个 RAM 块启动代码就需要处理多个复制操作增加了复杂性。因此链接器可能为了避免这种复杂性而拒绝拆分。概念 说明这个特性在内存碎片化严重或需要将大段分散到多个物理 bank 时非常有用但需要满足上述条件才能生效。Writing Section Inclusion Commands默认行为同名自动匹配。如果在链接脚本中定义了一个输出段如 .text但没有在花括号内写任何内容链接器会自动将所有目标文件中名为 .text 的输入段放入这个输出段中。这就是之前讨论过的隐式收集。如果没有同名的输出段如果某个输入段如 foo.o 中的 .my_special_data在链接脚本中没有对应的输出段链接器会在 section map 的末尾自动创建一个新的输出段并将该输入段放入其中同时发出一个警告告诉你它自动创建了一个段。警告控制-append 和 -no_append-append当链接器遇到没有对应输出段的输入段时静默地在末尾创建新段不发出警告。-no_append当链接器遇到没有对应输出段的输入段时发出警告默认行为。这两个选项用于控制是否允许自动创建段以及是否显示警告。显式段包含命令改变默认行为如果不想依赖默认的“同名自动匹配”可以使用显式语法指定哪些目标文件中的哪些段放入哪个输出段output_section_name:{filename(section_name)}.mydata:{foo.o(.data)}含义将目标文件 foo.o 中的.data 输入段放入名为.mydata 的输出段中。 注意这里的 foo.o(.data)是显式指定只包含 foo.o 中的.data 段其他目标文件中的.data 段不会被自动放入.mydata。.data:{}如果花括号内为空则不包含任何输入段该输出段为空除非有其他显式命令在后面补充其他所有文件中的data数据会放到data的section中用空格分隔多个命令顺序决定链接顺序.mydata : { foo.o(.data) bar.o(.data) }含义将 foo.o 中的 .data 段和 bar.o 中的 .data 段依次放入输出段 .mydata 中。顺序foo.o(.data) 在前所以 foo.o 的内容会先被放置然后是 bar.o 的内容。最终输出段中foo.o 的 .data 数据位于较低地址bar.o 的数据紧随其后。为什么重要当多个目标文件包含同名的输入段时你可以通过调整命令顺序来控制它们在输出段中的布局顺序。这在某些对地址顺序有要求的场景如中断向量表、初始化表中非常关键。从库中指定特定目标文件.mydata : { lib.a(foo.o(.data)) }含义从静态库 lib.a 中选取名为 foo.o 的目标文件并将其中的 .data 段放入输出段 .mydata 中。语法库名(目标文件名(段名))外层括号是库名内层括号是目标文件和段名。为什么需要这种语法链接器默认会从库中按需提取符号但如果你需要精确控制从库中提取哪个目标文件的哪个段而不是依赖符号解析就可以使用这种显式语法。这在需要确保某个特定模块被包含时非常有用类似于 -u 选项但更精细。与默认隐式收集的关系如果在输出段的花括号内没有写任何命令Green Hills 链接器会隐式收集所有同名的输入段如前所述。一旦写了显式命令如 foo.o(.data)隐式收集就不再发生只有你明确列出的输入段会被包含。因此如果你还想包含其他目标文件中的同名段必须也显式列出它们例如用通配符 *(.data) 或逐个列出。不要包含库或模块的完整路径只使用它们的区分大小写的文件名。Using Wildcards in Section Inclusion Commands 在章节包含命令中使用通配符在段包含命令中使用星号“*”代替文件名以指定传递给链接器的所有目标文件// Place .data sections from all object files into .data.data:{*(.data)}按照上述指令会自动将所有object中的data数据放到data的section中 因为程序的section的名称和object的section的名称相同所以不需要在花括号{content}中再重新指定可以按照以下格式使代码更高效和简洁// Place .data sections from all object files into .data.data:如果程序的section和object的section名称不相同那么必须在花括号中{content}中进行指定// Place .data sections from all object files into .mydata.mydata:{*(.data)}也可以在文件名和 section_name 中使用通配符。星号“*”匹配多个字符问号“?”匹配任意单个字符。当你以这种方式使用通配符时请将 section 包含命令用双引号括起来// Place .text sections from all object files matching code_*.o into .mytext.mytext:{code_*.o(.text)}如果一个object文件中包含多个section而这些section的名称不相同但是包含近似的名称如果你的目标文件中有多个部分使用了相似的命名规则你可以在分段包含命令中使用通配符把它们都分配到输出可执行文件中的同一部分// Place all sections matching .text_* into .mytext.mytext:{*(.text_*)}从库中使用通配符.mydata:{lib.a(*(.data))}lib.a(*(.data))从库文件 lib.a 中所有目标文件由*通配符表示的.data 输入段都被放入输出段.mydata。 这里的*是通配符代表库中的所有目标文件.o 文件。相当于逐个列出库中每个目标文件但用通配符简化。 效果lib.a 中所有目标文件的.data 段都被集中到.mydata 中而其他目标文件不在 lib.a 中的.data 段不会被包含进来。 隐式收集.data:这个写法没有花括号也没有任何显式内容。根据 Green Hills 的默认规则链接器会隐式收集所有目标文件中名为.data 的输入段放入输出段.data 中。 注意由于前面已经通过.mydata 显式包含了 lib.a 中的.data 段这些段已经被“拿走”了所以隐式收集的.data 段不会包含​ lib.a 中的.data 段只包含其他目标文件如 foo.o、bar.o 等中尚未被显式指定的.data 段。 上述写法等效于.data:{*(.data)}已经被显式收集的data数据会被放到指定的段中未被显示指定的段则会放到默认的段会自动把已经显示收集的段剔除掉 默认行为按链接顺序放置当使用.mybss:{*(.bss.*)}链接器会按照它在链接过程中遇到这些输入段的自然顺序放置它们。这个顺序取决于 目标文件在命令行中的出现顺序。 每个目标文件内部各段的定义顺序。 这种顺序有时不可控或者不是你想要的。 SORT/SORT_BY_NAME 的作用 使用 SORT 或 SORT_BY_NAME 关键字可以强制链接器在放置之前将匹配的输入段按段名升序排序字典序*(SORT(.bss.*))或*(SORT_BY_NAME(.bss.*))SORT 和 SORT_BY_NAME 在 Green Hills 中是等价的都表示按段名排序。 该关键字必须放在通配符表达式的外层包裹整个匹配模式。 作用将所有目标文件中以.bss.开头的输入段如.bss.var1、.bss.var2 等收集到输出段.mybss 中并且按段名升序排列。 效果无论这些段在目标文件中出现的顺序如何最终在.mybss 中的排列顺序都是.bss.aaa、.bss.bbb、.bss.ccc…… 这种确定性顺序有助于调试和内存布局预测。 确定性布局在多次构建中即使目标文件的链接顺序发生变化排序后的段顺序保持不变有利于比较 map 文件差异。 性能优化某些硬件对连续地址访问有性能优势排序可以减少碎片。 调试便利按名称排序的段在 map 文件中更容易查找。 SORT 只对通配符匹配的多个段生效。如果只有一个段匹配排序没有意义。 排序只影响段在输出段内部的顺序不影响输出段本身的地址。 在 Green Hills 中SORT 和 SORT_BY_NAME 是同义词都按段名排序。GNU LD 中还有 SORT_BY_ALIGNMENT 等其他排序方式但 Green Hills 可能不支持。Using the COMMON Section in Section Inclusion Commands 在部分包含命令中使用 COMMON 部分在传统的 C 语言中未初始化的全局变量例如 int x; 写在所有函数外面没有 static 也没有初始值在目标文件中被放入一个特殊的段叫做 COMMON。链接器在处理 COMMON 符号时允许多个目标文件定义同名的 COMMON 变量并将它们合并成一个之前讨论过的 --commons 选项与此相关。默认情况下链接器会将所有 COMMON 数据合并到输出文件的 .bss 段中与普通的 .bss 数据例如 static int y; 或显式初始化为 0 的变量放在一起。分开存放有时你可能希望将 COMMON 数据与普通的 .bss 数据分开例如将 COMMON 数据放到特定的内存区域如专用的 RAM 块。对 COMMON 数据施加不同的对齐或初始化策略。为了代码清晰或调试方便。要只把 main.o 对象文件中的 COMMON 数据分配到.mybss输入以下内容.mybss:{main.o(COMMON)}

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价