I. 1. 问题分析
日常被 GCC 折磨,GCC 链接报错,提示未定义符号。
ld: Undefined symbols:
_ssl_print_string, referenced from:
_tls13_get_quic_secret in packet-tls.c.o
_tls13_get_quic_secret in packet-tls.c.o
排查编译流程确认该符号其最终被链接进入 third_party/wireshark/build/run/libwireshark.dylib
。
而我最终又链接了 libwireshark.dylib。
usr/bin/cc -lwireshark packet-tls.c.o -o tls_dissector.so
并且确认该符号并非 static。
extern void ssl_print_string(const gchar* name, const StringInfo* data);
void ssl_print_string(const gchar* name, const StringInfo* data)
{
ssl_print_data(name, data->data, data->data_len);
}
直接查找二进制文件也能够确认其符号已经被链接进入 dylib。
grep "packet-tls-utils.c.o" -r . --include=*.dylib
Binary file ./third_party/wireshark/build/run/libwireshark.17.0.5.dylib matches
通过 nm 确认符号属性发现其为 t。
$ nm third_party/wireshark/build/run/libwireshark.17.0.5.dylib|grep _ssl_print_string
0000000000ad2108 t _ssl_print_string
符号类型 | 描述 |
---|---|
U | 未定义符号 (undefined) |
A | 绝对符号 (absolute) |
T | 全局可见的文本段符号 (global text section symbol) |
t | 本地文本段符号 (local text section symbol) |
D | 全局可见的数据段符号 (global data section symbol) |
d | 本地数据段符号 (local data section symbol) |
B | 全局可见的 BSS 段符号 (global bss section symbol) |
b | 本地 BSS 段符号 (local bss section symbol) |
C | 公共符号 (common symbol) |
c | 本地公共符号 (local common symbol) |
- | 调试符号表条目 (for debugger symbol table entries) |
S | 全局可见的其他段中的符号 (global symbol in a section other than those above) |
s | 本地其他段中的符号 (local symbol in a section other than those above) |
I | 全局可见的间接符号 (global indirect symbol) |
i | 本地间接符号 (local indirect symbol) |
u | 动态共享库中的小写 u 表示对同一库中另一个模块的私有外部未定义引用 (an undefined reference to a private external in another module in the same library) |
再次查看对应的 .o 确认其为 T。
$ nm third_party/wireshark/./build/epan/dissectors/CMakeFiles/dissectors.dir/packet-tls-utils.c.o |grep _ssl_print_string
0000000000001c00 T _ssl_print_string
II. 2. 问题结论
那么该文本符号从 T 变成了 t 只能够是在链接阶段了。最后确认了 -fvisibility=hidden
。
这个选项会将所有符号的默认可见性设置为隐藏,除非显式地声明为 default。这意味着,尽管在编译 .o 文件时符号是全局可见的,但在链接生成 .dylib 文件时符号变成了局部可见。
那为什么要这么做呢?
#
# Try to have the compiler default to hiding symbols, so that only
# symbols explicitly exported with WS_DLL_PUBLIC will be visible
# outside (shared) libraries; that way, more UN*X builds will catch
# failures to export symbols, rather than having that fail only on
# Windows.
#
# We don't need that with MSVC, as that's the default.
#
if( NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
#
# Try the GCC-and-compatible -fvisibility-hidden first.
#
check_c_compiler_flag(-fvisibility=hidden FVHIDDEN)
if(FVHIDDEN)
set(CMAKE_C_FLAGS "-fvisibility=hidden ${CMAKE_C_FLAGS}")
主要目的是设置编译器默认隐藏符号,确保只有显式导出的符号才会在共享库(shared library)中可见。这可以帮助在更多的 Unix 系统上捕捉到符号导出失败的问题,而不仅仅是在 Windows 上。
/*
* Symbols exported from libraries.
*/
#define WS_DLL_PUBLIC_DEF __attribute__ ((visibility ("default")))
III. 3. 测试用例
下面是一个简单的示例代码,用于测试 -fvisibility=hidden
和 __attribute__((visibility("default")))
的效果。
我们将创建两个文件:一个是库文件 libtest.c
,另一个是测试程序 main.c
。在库文件中,我们将定义两个函数,其中一个函数使用 __attribute__((visibility("default")))
明确导出,而另一个函数不使用任何可见性属性。
3.1. 创建 libtest.c
// libtest.c
#include <stdio.h>
// 这个函数将被默认隐藏,因为我们使用 -fvisibility=hidden 编译
void hidden_function() {
printf("This is a hidden function.\n");
}
// 这个函数将被显式导出,即使我们使用 -fvisibility=hidden 编译
__attribute__((visibility("default"))) void visible_function() {
printf("This is a visible function.\n");
}
3.2. 创建 main.c
// main.c
#include <stdio.h>
// 声明从库中导入的函数
void hidden_function();
void visible_function();
int main() {
// 调用这两个函数
hidden_function();
visible_function();
return 0;
}
3.3. 编译并测试
首先编译库文件 libtest.c
,使用 -fvisibility=hidden
来隐藏默认符号:
gcc -fvisibility=hidden -c libtest.c -o libtest.o
然后创建静态库 libtest.a
:
ar rcs libtest.a libtest.o
接着编译主程序 main.c
并链接静态库:
gcc main.c libtest.a -o main
最后运行测试程序:
./main
3.4. 预期输出
如果一切正常,你应该看到以下输出:
This is a hidden function.
This is a visible function.
在编译和链接过程中,可以使用 nm
命令来查看库中的符号,验证符号的可见性:
nm libtest.o
你应该看到类似以下的输出:
0000000000000000 t hidden_function
000000000000001a T visible_function
其中,hidden_function
是本地可见的(小写 t
),而 visible_function
是全局可见的(大写 T
)。
还不快抢沙发