MinGW-w64下objcopy -L本地化符号失效,如何正确处理?
我有两组代码文件:
foo 模块文件
foo_var.c
int global_var = 1;
foo_print.c
#include <stdio.h> extern int global_var; void print_foo(void) { printf("print_foo = %d\n", global_var); }
我尝试将它们编译链接为foo.o并本地化global_var符号:
gcc -c foo_var.c foo_print.c ld -r -o foo.o foo_print.o foo_var.o objcopy -L global_var foo.o
bar 模块文件
bar_var.c
int global_var = 2;
bar_print.c
#include <stdio.h> extern int global_var; void print_bar(void) { printf("print_bar = %d\n", global_var); }
同样编译链接为bar.o并本地化global_var符号:
gcc -c bar_var.c bar_print.c ld -r -o bar.o bar_print.o bar_var.o objcopy -L global_var bar.o
主程序文件 main.c
void print_foo(void); void print_bar(void); int main(int argc, char const *argv[]) { print_foo(); print_bar(); return 0; }
将所有文件编译链接:
gcc -c main.c gcc main.o foo.o bar.o
运行生成的a.exe时,得到结果:
print_foo = 1 print_bar = 1
但预期结果应为:
print_foo = 1 print_bar = 2
使用的 GCC 版本为 12.2.0(x86_64-win32-seh-rev2, Built by MinGW-W64 project),请问在 MinGW-w64 中如何正确本地化符号?
MinGW-w64 采用 PE/COFF 格式,和 Linux 下的 ELF 符号机制不同,objcopy -L(局部化符号)无法按预期工作——PE 会默认导出所有全局符号,链接器遇到同名符号时会直接选用第一个找到的实现,导致两个模块共用同一个global_var。
要实现符号的模块本地化,推荐使用编译时符号可见性控制,具体步骤如下:
1. 修改模块代码,标记需导出的函数
我们只需要对外暴露print_foo和print_bar,global_var仅作为模块内部变量使用。因此在这两个函数前添加__attribute__((visibility("default"))),显式标记它们为可导出符号:
修改后的foo_print.c:
#include <stdio.h> extern int global_var; // 标记该函数为可导出 void __attribute__((visibility("default"))) print_foo(void) { printf("print_foo = %d\n", global_var); }
修改后的bar_print.c:
#include <stdio.h> extern int global_var; // 标记该函数为可导出 void __attribute__((visibility("default"))) print_bar(void) { printf("print_bar = %d\n", global_var); }
2. 编译模块时启用符号隐藏
使用-fvisibility=hidden编译选项,让编译器默认隐藏所有未显式标记为visibility("default")的全局符号,这样global_var就会被限制在当前模块内部可见。
编译 foo 模块:
# 编译单个源文件,启用符号隐藏 gcc -c -fvisibility=hidden -o foo_var.o foo_var.c gcc -c -fvisibility=hidden -o foo_print.o foo_print.c # 合并为 foo.o ld -r -o foo.o foo_print.o foo_var.o
编译 bar 模块:
gcc -c -fvisibility=hidden -o bar_var.o bar_var.c gcc -c -fvisibility=hidden -o bar_print.o bar_print.c ld -r -o bar.o bar_print.o bar_var.o
3. 编译并链接主程序
gcc -c main.c gcc main.o foo.o bar.o -o a.exe
4. 运行验证
执行a.exe会得到预期结果:
print_foo = 1 print_bar = 2
其他可选方案(需修改代码)
如果不想使用编译选项,也可以通过重命名符号避免冲突:
- 执行
objcopy --rename-symbol global_var=foo_global_var foo.o,将 foo 模块的global_var重命名为foo_global_var; - 修改
foo_print.c中的extern int global_var;为extern int foo_global_var;; - 对 bar 模块执行同样操作,将
global_var重命名为bar_global_var并修改对应代码; - 重新编译链接即可。不过这种方法需要修改代码,灵活性不如符号可见性控制。
内容的提问来源于stack exchange,提问作者Liarsenic

