【ARM 嵌入式 编译系列 7.4 -- GCC 链接脚本中 ASSERT 函数】
文章目录
- Overview
- 使用场景
- 示例说明
- 如果超出怎么办?
- 小贴士
- 地址信息打印
- 方法一:使用 `ASSERT` + `DEFINED` 机制手动制造错误
- 方法二:创建多个 ASSERT 分段定位大小(伪“打印”)
- 方法三:使用 `--defsym` 和脚本外部配合打印
- 方法四:编译失败后使用 `size` 工具查看段大小
- 总结
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.*)
} > FLASHINST_LOAD_END = .;
INST_LOAD_SIZE = INST_LOAD_END - INST_LOAD_START;ASSERT(INST_LOAD_SIZE <= 0xe00, "inst load overflow")
这里做了几件事:
-
定义了一个段
.inst_load
,把所有的代码和只读数据放到FLASH
区域。 -
使用两个符号
INST_LOAD_START
和INST_LOAD_END
定位这个段的起止地址。 -
计算出大小
INST_LOAD_SIZE
。 -
用
ASSERT
保证其大小不超过0xe00
。
如果超出怎么办?
如果你的 .text + .rodata
实际大小超过了 0xe00
,链接器报错:
ld: inst load overflow
你就需要:
-
优化代码(裁剪函数、启用 LTO、精简库)。
-
改变 layout,扩大 Flash 映射区域。
-
或者容忍更大的大小,放宽 ASSERT 条件。
小贴士
-
在复杂项目中,多用
ASSERT()
是很好的防御性编程习惯,防止 silent overflow。 -
可以通过
nm
或objdump -h
检查生成 elf 中段大小,辅助你验证实际使用情况。 -
如果你想打印具体使用的大小,可以写:
ASSERT(INST_LOAD_SIZE <= 0xe00, "inst load overflow: size too big")
但如果你想更详细地打印值,则需要辅助脚本工具,如
objdump + awk
等。
地址信息打印
在 GNU ld
的 linker script
中,ASSERT()
报错时无法直接像 C 语言那样拼接字符串或打印数值(比如 INST_LOAD_SIZE
的值)。但有几种技巧可以在错误信息中间接打印 INST_LOAD_SIZE
的数值,以辅助定位问题。
方法一:使用 ASSERT
+ DEFINED
机制手动制造错误
INST_LOAD_START = ORIGIN(FLASH);
.inst_load :
{*(.text .text.*)*(.rodata .rodata.*)
} > FLASHINST_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
等工具配合查看