在嵌入式系统开发中,精确控制函数和数据在内存中的位置是一项关键技能。特别是在以下场景中:
ARM编译器提供了一套完整的解决方案,通过__attribute__属性和链接器scatter-loading机制,开发者可以精确控制代码和数据的物理地址。
最基本的地址定位语法如下:
c复制int sqr(int n1) __attribute__((section(".ARM.__at_0x20000")));
int sqr(int n1)
{
return n1*n1;
}
这个语法有几个关键点:
__attribute__是GCC风格的函数/变量属性声明section(".ARM.__at_<address>")指定了特殊的段名格式对于ZI(零初始化)数据,需要使用特殊的段名前缀:
c复制int variable1 __attribute__((section(".bss.ARM.__at_0x8000")));
注意:地址必须满足对齐要求。对于4字节变量,地址必须是4的倍数;对于8字节数据,地址必须是8的倍数,以此类推。
这是默认的工作模式,链接器会自动处理.ARM.__at_<address>段。其工作流程如下:
链接器首先检查所有已定义的执行区域(Execution Region)
对于每个__at段,链接器寻找满足以下条件的兼容区域:
如果找不到兼容区域,链接器会创建一个新的加载区域和执行区域
关键兼容性规则:
__at段可以放在RO区域(但反过来不行)--max_er_extension调整通过--no_autoat选项可以禁用自动放置,此时开发者需要显式在scatter文件中指定__at段的放置位置。典型配置如下:
scatter复制LR1 0x0
{
ER_RO 0x0 0x2000
{
*(+RO)
}
ER_RO2 0x2000
{
*(.ARM.__at_0x02000)
}
ER2 0x4000
{
*(+RW, +ZI)
}
}
手动模式的优势:
许多Flash设备需要在特定地址写入密钥才能激活某些功能。使用__at段可以优雅地实现这一需求:
c复制// 在0x8000地址处放置Flash密钥
long flash_key __attribute__((section(".ARM.__at_0x8000")));
对应的scatter文件配置:
scatter复制ER_FLASH 0x8000 0x2000
{
*(+RW)
*(.ARM.__at_0x8000) // 密钥
}
当需要基于表达式计算地址时,可以使用指针方式:
c复制#define MY_PREDEFINED_OFFSET 0x100
static int * const my_var_addr = (int *)(0xE0001000 + MY_PREDEFINED_OFFSET);
#define my_variable (*my_var_addr)
这种方法适用于:
在Cortex-M系列MCU中,通常需要将中断向量表固定在Flash起始位置:
c复制__attribute__((section(".ARM.__at_0x0000")))
const void* const vector_table[] = {
(void*)&_estack, // 初始栈指针
(void*)Reset_Handler, // 复位处理函数
// 其他中断向量...
};
对应的scatter文件需要确保该段被优先放置:
scatter复制LR1 0x0
{
ER_VECTORS 0x0 0x400
{
startup.o(.ARM.__at_0x0000, +FIRST)
}
// 其他区域...
}
__at段地址范围不得重叠__at段不能用于位置无关执行(PIE)区域$$Base、$$Limit和$$Length符号__at段不能用于BPABI可执行文件和动态链接库+FIRST和+LAST排序约束对__at段无效问题1:链接错误"section address not aligned"
解决方案:
__attribute__((aligned(n)))明确对齐问题2:__at段未被正确放置
排查步骤:
--autoat(默认)还是--no_autoat--map选项生成内存映射报告进行验证问题3:ZI数据未正确清零
解决方法:
.bss.ARM.__at_<address>段名__at段--map选项:生成详细的内存映射报告fromelf -s查看符号地址address_map.h头文件管理所有固定地址项__attribute__语法:c复制#define AT_ADDR(x, addr) __attribute__((section(".ARM.__at_" #addr))) x
AT_ADDR(int system_flags, 0x20000000);
使用.ANY选择器可以灵活控制未明确分配的段:
scatter复制LR1 0x8000
{
ER1 +0 512
{
.ANY1(+RO) // 与ER3均分
}
ER2 +0 256
{
.ANY2(+RO) // 最高优先级
}
ER3 +0 256
{
.ANY1(+RO) // 与ER1均分
}
}
控制算法选项:
--any_placement=first_fit:顺序填充--any_placement=worst_fit:均匀填充(默认)--any_placement=best_fit:尽量填满单个区域--any_placement=next_fit:不回溯已满区域使用ANY_SIZE指定区域最大可用空间:
scatter复制LOAD_REGION 0x0
{
ER_1 0x0 ANY_SIZE 0xF00 0x1000
{
.ANY // 保留0x100应急空间
}
ER_2 0x0 ANY_SIZE 0x1000 0x1000
{
.ANY // 使用全部空间
}
}
通过--any_sort_order控制未分配段的处理顺序:
descending_size:从大到小(默认)cmdline:按命令行中对象文件的顺序在实际项目中,我通常会采用混合策略:关键功能使用精确的__at定位,非关键部分使用.ANY自动分配,既保证关键需求,又保持灵活性。对于需要严格内存预算的项目,ANY_SIZE配合--any_contingency可以避免意外溢出。