为何ld、nm、objdump显示libXSAL.so无符号,Ghidra却能识别?
首先咱们拆解下你遇到的矛盾现象:
nm -D和objdump -t看不到符号,但Ghidra能识别函数名,手动查看十六进制也能看到函数名- GCC链接时提示未定义引用,无法直接完成链接
为什么部分工具识别不到符号?
最核心的原因是这个共享库的动态符号表(.dynsym)和静态符号表(.symtab)已被刻意移除或隐藏,但函数名作为字符串常量(比如函数内部的日志打印、调试残留信息)保留在了库的只读数据段(.rodata)中。Ghidra之所以能“识别”函数名,大概率是它在反汇编过程中,把这些残留的字符串和对应的函数代码做了关联(比如函数开头附近有自身名称的字符串),而非真的读取到了合法的符号表。
你可以用readelf -s libXSAL.so验证这个结论——如果输出里完全没有你需要的函数条目,就坐实了符号表被清空的情况。
常见导致符号表消失的操作包括:
- 编译库时使用了
-fvisibility=hidden,且未显式标记需要导出的函数(比如没加__attribute__((visibility("default")))),导致动态符号表中无这些函数条目 - 用
strip --strip-all对库做了彻底的符号剥离,移除了所有符号表但保留了字符串数据 - 使用自定义链接脚本,把所有符号排除在了动态符号表之外
怎么解决链接问题?
因为链接器(ld)依赖动态符号表解析符号,直接链接这个库行不通,咱们可以用以下几种方案绕过限制:
1. 动态加载(推荐,灵活可控)
放弃编译时链接,改用运行时动态加载。步骤如下:
- 在代码中用
dlopen()打开libXSAL.so - 用
dlsym()根据Ghidra给出的函数名(或直接用函数地址)获取函数指针 - 通过指针调用目标函数
示例代码框架:
#include <dlfcn.h> #include "your_header.h" // 你编写的函数签名头文件 int main() { void *lib_handle = dlopen("./libXSAL.so", RTLD_NOW); if (!lib_handle) { fprintf(stderr, "%s\n", dlerror()); return 1; } // 获取函数指针 typedef int (*XSAL_Init_t)(); XSAL_Init_t xsal_init = dlsym(lib_handle, "XSAL_Init"); if (!xsal_init) { fprintf(stderr, "%s\n", dlerror()); dlclose(lib_handle); return 1; } // 调用函数 xsal_init(); dlclose(lib_handle); return 0; }
编译时需链接dl库:gcc your_code.c -o your_program -ldl
2. 创建包装共享库
写一个轻量的共享库,内部用动态加载调用libXSAL.so的函数,再导出这些函数供主程序链接。这样主程序可以像正常链接库一样调用函数,无需修改主代码。
示例包装库代码(xsal_wrap.c):
#include <dlfcn.h> #include "your_header.h" static void *lib_handle; // 程序启动时自动加载原库 __attribute__((constructor)) void xsal_wrap_init() { lib_handle = dlopen("./libXSAL.so", RTLD_NOW); if (!lib_handle) { fprintf(stderr, "Failed to load libXSAL.so: %s\n", dlerror()); abort(); } } // 包装目标函数并导出 int XSAL_Init() { typedef int (*func_t)(); static func_t func = NULL; if (!func) { func = dlsym(lib_handle, "XSAL_Init"); if (!func) { fprintf(stderr, "Failed to find XSAL_Init: %s\n", dlerror()); abort(); } } return func(); } // 其他需要的函数同理编写包装
编译包装库:gcc -shared -fPIC xsal_wrap.c -o libXSAL_wrap.so -ldl
主程序链接包装库:gcc your_code.c -o your_program -L. -lXSAL_wrap
3. 手动生成符号链接脚本(适合固定地址场景)
如果Ghidra能给出每个函数的准确内存地址(注意:共享库加载地址可能受ASLR影响,除非库编译为固定地址),你可以写一个链接脚本,把地址映射为符号供链接器识别。
示例链接脚本(xsal_syms.ld):
XSAL_Init = 0x00001234; // 替换为Ghidra给出的实际地址 XSAL_Shutdown = 0x00005678; // 其他函数同理添加
编译时使用脚本:gcc your_code.c -o your_program libXSAL.so -Wl,-T,xsal_syms.ld
注意:这种方法依赖库的加载地址固定,ASLR可能导致运行时崩溃,仅适合测试或特殊场景。
内容的提问来源于stack exchange,提问作者igk

