【ARM 嵌入式 编译系列 7.4 -- GCC 链接脚本中 ASSERT 函数】

发布于:2025-06-24 ⋅ 阅读:(20) ⋅ 点赞:(0)

Overview

ASSERT() 是 GNU ld(linker)脚本中的一个非常有用的宏,用于在链接时进行条件检查。

它的语法如下:

ASSERT(expression, "error message")

如果 expression 的值为 false(即为 0),链接器会中断链接,并显示你定义的错误信息。

例子:

ASSERT(INST_LOAD_SIZE <= 0xe00, "inst load overflow")

这个语句的含义是:

  • INST_LOAD_SIZE 是一个在 linker script 中定义的符号,表示一段区域的大小,通常是 INST(指令段或代码段)加载部分的字节数。

  • 0xe00 是十六进制,等于十进制的 3584(3.5 KB)。

  • 如果 INST_LOAD_SIZE > 0xe00,说明代码段过大,链接器将报错,并输出 "inst load overflow"

使用场景

这种断言常用于嵌入式系统或资源受限系统(如 bootloader、裸机程序、microcontroller 上的应用)中,用来保证:

  • 某段代码不会超过特定区域(比如 Flash 起始地址+偏移)。

  • 某些段的对齐或边界满足要求。

  • 多个段之间不会互相覆盖。

示例说明

设想你有如下 linker script 段落:

INST_LOAD_START = ORIGIN(FLASH);
.inst_load :
{
    *(.text)       /* 所有 .text 段 */
    *(.text.*)     /* 所有 .text.子段 */
    *(.rodata)     /* 只读数据 */
    *(.rodata.*)
} > FLASH

INST_LOAD_END = .;
INST_LOAD_SIZE = INST_LOAD_END - INST_LOAD_START;

ASSERT(INST_LOAD_SIZE <= 0xe00, "inst load overflow")

这里做了几件事:

  1. 定义了一个段 .inst_load,把所有的代码和只读数据放到 FLASH 区域。

  2. 使用两个符号 INST_LOAD_STARTINST_LOAD_END 定位这个段的起止地址。

  3. 计算出大小 INST_LOAD_SIZE

  4. ASSERT 保证其大小不超过 0xe00

如果超出怎么办?

如果你的 .text + .rodata 实际大小超过了 0xe00,链接器报错:

ld: inst load overflow

你就需要:

  • 优化代码(裁剪函数、启用 LTO、精简库)。

  • 改变 layout,扩大 Flash 映射区域。

  • 或者容忍更大的大小,放宽 ASSERT 条件。

小贴士

  • 在复杂项目中,多用 ASSERT() 是很好的防御性编程习惯,防止 silent overflow。

  • 可以通过 nmobjdump -h 检查生成 elf 中段大小,辅助你验证实际使用情况。

  • 如果你想打印具体使用的大小,可以写:

    ASSERT(INST_LOAD_SIZE <= 0xe00, "inst load overflow: size too big")
    

    但如果你想更详细地打印值,则需要辅助脚本工具,如 objdump + awk 等。

地址信息打印

在 GNU ldlinker script 中,ASSERT() 报错时无法直接像 C 语言那样拼接字符串或打印数值(比如 INST_LOAD_SIZE 的值)。但有几种技巧可以在错误信息中间接打印 INST_LOAD_SIZE 的数值,以辅助定位问题。

方法一:使用 ASSERT + DEFINED 机制手动制造错误

INST_LOAD_START = ORIGIN(FLASH);
.inst_load :
{
    *(.text .text.*)
    *(.rodata .rodata.*)
} > FLASH

INST_LOAD_END = .;
INST_LOAD_SIZE = INST_LOAD_END - INST_LOAD_START;

ASSERT(INST_LOAD_SIZE <= 0xe00, "inst load overflow")

/* 打印 INST_LOAD_SIZE 的数值(变相输出) */
inst_load_size_debug = INST_LOAD_SIZE;

这个 inst_load_size_debug 会在最终 ELF 文件中作为一个符号出现,可以使用如下命令在出错后查看:

nm -n your_program.elf | grep inst_load_size_debug

方法二:创建多个 ASSERT 分段定位大小(伪“打印”)

这种方法不优雅,但实用:将大小范围分成几段,分别报不同错,从而间接推测数值范围

ASSERT(INST_LOAD_SIZE <= 0x1000, "INST_LOAD_SIZE > 0x1000")
ASSERT(INST_LOAD_SIZE <= 0x0e00, "INST_LOAD_SIZE > 0x0e00")
ASSERT(INST_LOAD_SIZE <= 0x0c00, "INST_LOAD_SIZE > 0x0c00")
ASSERT(INST_LOAD_SIZE <= 0x0a00, "INST_LOAD_SIZE > 0x0a00")
  • 如果只有最后一个断言失败:INST_LOAD_SIZE > 0xa00,表示大小在 0xa00~0xc00 之间。

方法三:使用 --defsym 和脚本外部配合打印

在编译后脚本中增加:

echo "INST_LOAD_SIZE: $(nm -S your_program.elf | grep inst_load_size_debug)"

或者使用 objdump:

arm-none-eabi-objdump -t your_program.elf | grep inst_load_size_debug

方法四:编译失败后使用 size 工具查看段大小

size your_program.elf

会打印 .text.rodata 的大小,总计是否超过限制。


总结

由于 ASSERT() 的限制,不能直接在错误信息中打印数值,但可以通过:

  • 将数值保存为符号供 nm/objdump 查看

  • 用多个 ASSERT 模拟区间“打印”

  • 编译失败后用 size/nm 等工具配合查看


网站公告

今日签到

点亮在社区的每一天
去签到