如何在已剥离的ARM R5 ELF文件中获取关键符号地址且不占用RAM
问题描述
使用gcc 11.2.0和ld 2.37.20210721编译面向小内存ARM R5 CPU的程序,需求如下:
- 程序被剥离(stripped)后,仍能获取某个关键符号的地址
- 禁止在RAM中存储该地址或常量,避免内存浪费
- 剥离后的ELF文件需保留可标识该符号地址的信息,理想状态是类似
.comment段:readelf不报告其Addr,且无W/X权限 - 原本希望在链接脚本中使用
ADDR(symbol)语法,但该功能不可用
尝试过的方案及问题
用__attribute__指定自定义段,结合链接脚本设置NOLOAD并将段放置在闲置地址空间(如不可用的处理器寄存器/代码地址空间),但objdump显示段内容丢失;若移除NOLOAD,内容虽保留但会占用RAM。
示例程序
__attribute__((unused)) unsigned int GENERAL_FAULT = 0; volatile unsigned int critical = 5; __attribute__((section(".criticaladdr"),unused)) const void *critical_addr = (void *)&critical; void r5_main(void) { while (1) { if (critical != 5) critical = 5; } }
未添加链接脚本时的段信息
.criticaladdr段为可写且占用RAM,objdump -s -j .criticaladdr能显示正确地址,段信息如下:
[ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00000000 010000 0000c0 00 AX 0 0 4 [ 2] .note.gnu.bu[...] NOTE 000000c0 0100c0 000024 00 A 0 0 4 [ 3] .data PROGBITS 000000e4 0100e4 000004 00 WA 0 0 4 [ 4] .criticaladdr PROGBITS 000000e8 0100e8 000004 00 WA 0 0 4 [...] [ 9] .comment PROGBITS 00000000 01011b 000012 01 MS 0 0 1
添加链接脚本后的问题
添加以下链接脚本后,.criticaladdr段仍为可写,但objdump显示内容为0x0:
… MEMORY { … OCM_RAM_ATF (r) : ORIGIN = 0xFFFD0000, LENGTH = 0x20000 } … SECTIONS { … .criticaladdr (NOLOAD) : { KEEP(*(".criticaladdr")) } > OCM_RAM_ATF }
解决方案
方法1:只读非加载段+链接脚本属性配置
核心是创建只读、非加载、不分配内存的段,让链接器将符号地址写入段的文件内容,但不映射到RAM。
- 修改代码,给自定义段添加只读属性:
__attribute__((section(".criticaladdr"), unused, const)) const void *critical_addr = (void *)&critical;
添加const让gcc将该变量放入只读段,避免默认的可写属性。
- 修改链接脚本,定义段为非加载、无内存分配:
SECTIONS { … .criticaladdr (NOLOAD) : { KEEP(*(".criticaladdr")) } :none }
:none指定段不分配到任何内存区域,readelf将不会显示Addr字段KEEP()确保链接器不会因无代码引用而优化掉该段
编译链接后,即使stripped,用objdump -s -j .criticaladdr仍能读取符号地址,且该段不占用RAM。
方法2:自定义NOTE段
利用ELF的NOTE段特性,该段本身不占用内存,stripped后仍会保留:
- 编写汇编代码创建NOTE段并写入符号地址:
.section .note.criticaladdr, "a" .long 4,4,0 .string "CRITICAL_ADDR" .long critical
- 在链接脚本中保留该段:
SECTIONS { … KEEP(*(.note.criticaladdr)) }
stripped后,可通过readelf -n查看NOTE段内容,从中提取critical的地址,且该段不占用RAM。
方法3:链接脚本直接生成地址段
利用链接器指令直接在链接脚本中生成包含符号地址的段:
SECTIONS { … .criticaladdr (NOLOAD) : { LONG(critical) } :none }
链接器会直接将critical的地址写入.criticaladdr段的文件内容中,该段为非加载状态,不占用RAM,stripped后可通过objdump -s读取地址。
关键注意事项
- 必须使用
KEEP()指令,防止链接器优化掉目标段 - 段属性需设置为只读(R),避免W权限;通过
NOLOAD或:none确保不分配内存 - 用
readelf -S检查段属性,确认Addr字段为0,权限仅含R(无W/X)
内容的提问来源于stack exchange,提问作者Seth Robertson
相关产品推荐
相关产品推荐

