GCC 隐藏符号

默认分类 2024-09-14 595 次浏览 次点赞


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

2. 问题结论

那么该文本符号从 T 变成了 t 只能够是在链接阶段了。最后确认了 -fvisibility=hidden

这个选项会将所有符号的默认可见性设置为隐藏,除非显式地声明为 default。这意味着,尽管在编译 .o 文件时符号是全局可见的,但在链接生成 .dylib 文件时符号变成了局部可见。

那为什么要这么做呢?

https://gitlab.com/wireshark/wireshark/-/blob/4aa814ac25a18ea48d4002351ed45d4b245b0c08/CMakeLists.txt#L1070-1104

#
# 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 上。

https://gitlab.com/wireshark/wireshark/-/blob/4aa814ac25a18ea48d4002351ed45d4b245b0c08/include/ws_symbol_export.h#L131-134

  /*
   * 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

最后运行测试程序:

./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)。


本文由 Jay 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处,点赞0

还不快抢沙发

添加新评论