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_string2. 问题结论
那么该文本符号从 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")))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最后运行测试程序:
./main3.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)。
还不快抢沙发