如何使GCC/LD对BSS与.data段采用指令相对寻址生成PIC
解决.data/.bss段硬编码地址问题,实现全PC相对寻址的位置无关Blob
要让.bss和.data段也使用PC相对寻址,核心思路是通过链接脚本记录各段相对于.text的固定偏移,在代码中基于当前PC计算出这些段的实际加载地址,避免直接使用链接时的绝对地址。以下是具体实现步骤:
1. 修改链接脚本,导出段偏移符号
在链接脚本中定义各段的起始地址和相对于.text起始的偏移量,让代码可以引用这些符号计算实际地址:
SECTIONS { .text : { __text_base = .; /* 记录.text段的链接起始地址 */ *(.text); } .data : { __data_base = .; /* 记录.data段的链接起始地址 */ *(.bss); /* 将BSS包含进镜像,初始化为0 */ *(.data); } = 0 .rodata : { __rodata_base = .; /* 记录.rodata段的链接起始地址 */ *(.rodata); } /* 计算各段相对于.text的偏移(链接时的固定值) */ __data_offset = __data_base - __text_base; __rodata_offset = __rodata_base - __text_base; }
2. 修改代码,用PC相对方式访问可写数据
代码中需要先获取当前PC的实际地址,再结合链接脚本导出的偏移量,计算出.data/.bss的实际加载地址。注意不同CPU架构的PC获取方式不同,以下是多架构兼容的实现:
#include <stdint.h> /* 声明链接脚本中导出的符号 */ extern uintptr_t __text_base, __data_offset, __data_base; /* 链接时的占位变量(实际地址通过计算得到) */ static int some_bss_values_link[8]; static int some_data_values_link[] = {1,2,3,4,5,6,7,8}; const int some_rodata_values[] = {9,10,11,12,13,14,15,16}; /* 获取当前PC的实际地址(架构相关实现) */ static inline uintptr_t get_current_pc(void) { #ifdef __arm__ uintptr_t pc; // ARM的PC寄存器指向当前指令+8,所以需要调整 __asm__ volatile ("mov %0, pc" : "=r"(pc)); return pc - 8; #elif defined(__x86_64__) uintptr_t pc; __asm__ volatile ("lea 0(%%rip), %0" : "=r"(pc)); return pc; #elif defined(__i386__) uintptr_t pc; // 通过call指令将当前PC压栈,再弹出获取 __asm__ volatile ("call 1f; 1: pop %0" : "=r"(pc)); return pc; #endif } /* 计算.data段的实际加载基地址 */ static inline uintptr_t get_data_load_base(void) { // 当前PC的链接地址与__text_base的差值,等于实际PC与TEXT加载基地址的差值 uintptr_t text_load_base = get_current_pc() - ((uintptr_t)get_current_pc - (uintptr_t)&__text_base); // TEXT加载基地址加上data偏移,得到data实际加载基地址 return text_load_base + (uintptr_t)&__data_offset; } /* 定义宏,将占位变量转换为实际加载地址的指针 */ #define some_bss_values ((int *)(get_data_load_base() + ((uintptr_t)&some_bss_values_link - (uintptr_t)&__data_base))) #define some_data_values ((int *)(get_data_load_base() + ((uintptr_t)&some_data_values_link - (uintptr_t)&__data_base))) int some_function(int x) { some_bss_values[x] = some_rodata_values[x]; return some_data_values[x]++; }
3. 调整编译命令
保持原有编译命令不变,确保-fPIC和-nostdlib参数正确:
gcc -Wl,-esome_function -o t.elf -fPIC -nostdlib -T my_linker_script.ld -g -O0 my_code.cpp objcopy -Obinary -j.text -j.data -j.rodata t.elf t.bin
原理说明
- .rodata能自动使用PC相对寻址,是因为
-fPIC下GCC对只读数据默认生成PC相对访问指令,且.rodata不需要写入,无需依赖GOT表。 - .data/.bss默认使用绝对地址,是因为PIC模式下可写数据通常依赖动态链接器维护的GOT表,但嵌入式无动态链接器,因此需要手动通过PC偏移计算实际地址。
- 链接脚本中定义的偏移量是链接时的固定值,Blob加载后各段的相对位置不变,因此通过PC计算出的实际地址始终正确。
内容的提问来源于stack exchange,提问作者Marco Merlini
相关产品推荐
相关产品推荐

