GCC 8下隐藏共享库filesystem符号,无需指定-lstdc++fs
项目包含三个CMake目标:
- 可执行文件:链接时触发filesystem相关未定义符号错误
- 共享库:核心库,已对原直接使用的filesystem做抽象处理,未在API中暴露filesystem相关内容
- 静态库:尝试将filesystem符号隐藏在私有实现中,但未成功
需求:不想要求GCC 8用户添加-lstdc++fs编译选项,但仍需使用实验性filesystem(非std::filesystem)。链接可执行文件时出现以下错误:
[100%] Linking CXX executable g++-8.3.1/bin/executable-d g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::status(std::filesystem::path const&)' g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::filesystem_error::~filesystem_error()' g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::path::replace_extension(std::filesystem::path const&)' g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::path::replace_filename(std::filesystem::path const&)' g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::path::_M_find_extension() const' g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::path::compare(std::filesystem::path const&) const' g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::filesystem_error::_M_gen_what()' g++-8.3.1/lib/libShared-d.so: undefined reference to `std::filesystem::path::_M_split_cmpts()' g++-8.3.1/lib/libShared-d.so: undefined reference to `typeinfo for std::filesystem::filesystem_error' g++-8.3.1/lib/libShared-d.so: undefined reference to `vtable for std::filesystem::filesystem_error' collect2: error: ld returned 1 exit status
执行objdump -TC libShared-d.so显示:
objdump -TC libShared-d.so libShared-d.so: file format elf64-x86-64 DYNAMIC SYMBOL TABLE: 0000000000000000 D *UND* 0000000000000000 vtable for std::filesystem::filesystem_error
附带疑问:为何这些符号会被导出?是否因为GCC 8的filesystem位于外部库中?
已尝试的方法:
- 对静态库执行
strip --strip-unneeded libStatic-d.a - 为静态库添加以下配置(未同时启用):
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") set_target_properties(${TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS_RELEASE -s) target_link_options(${TARGET_NAME} PRIVATE "-fvisibility=hidden") target_link_options(${TARGET_NAME} PRIVATE "--version-script=${CMAKE_CURRENT_SOURCE_DIR}/export-symbols.version")
- 添加自定义命令:
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND strip --strip-unneeded $<TARGET_FILE:${TARGET_NAME}> )
- 为共享库添加配置:
target_link_options(${TARGET_NAME} PRIVATE "--exclude-libs,ALL") target_compile_options(${TARGET_NAME} PRIVATE "-Wl,--exclude-libs,ALL")
补充信息:
ldd libShared-d.so输出:
ldd libShared-d.so libShared-d.so: linux-vdso.so.1 => (0x00007ffd791b7000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fb088e40000) libicuuc.so.50 => /lib64/libicuuc.so.50 (0x00007fb088ac7000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fb0887bf000) libm.so.6 => /lib64/libm.so.6 (0x00007fb0884bd000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fb0882a7000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb08808b000) libc.so.6 => /lib64/libc.so.6 (0x00007fb087cbd000) /lib64/ld-linux-x86-64.so.2 (0x00007fb0899b5000) libicudata.so.50 => /lib64/libicudata.so.50 (0x00007fb0866ea000)
ldd -d libShared-d.so输出:
ldd -d libShared-d.so libShared-d.so: linux-vdso.so.1 => (0x00007fff1c158000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f28aadf6000) libicuuc.so.50 => /lib64/libicuuc.so.50 (0x00007f28aaa7d000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f28aa861000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f28aa559000) libm.so.6 => /lib64/libm.so.6 (0x00007f28aa257000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f28aa041000) libc.so.6 => /lib64/libc.so.6 (0x00007f28a9c73000) /lib64/ld-linux-x86-64.so.2 (0x00007f28ab96c000) libicudata.so.50 => /lib64/libicudata.so.50 (0x00007f28a86a0000) undefined symbol: _ZNSt10filesystem16filesystem_errorD1Ev (./libShared-d.so) undefined symbol: _ZTINSt10filesystem16filesystem_errorE (./libShared-d.so) undefined symbol: _ZTVNSt10filesystem16filesystem_errorE (./libShared-d.so)
核心原因
GCC 8中std::filesystem的实现位于独立的libstdc++fs库中,并非libstdc++.so的一部分。你的共享库在编译时引用了filesystem符号,但未链接该库,导致这些符号成为未定义的外部引用,进而传递给了可执行文件的链接阶段。而filesystem_error的vtable和typeinfo符号导出,是因为C++异常机制的特性:如果代码中抛出了该异常(即使在私有实现中),编译器会默认导出这些符号,除非你确保异常不会跨越库的边界。
具体解决步骤
让共享库静态链接
libstdc++fs
在共享库的CMake配置中,添加对libstdc++fs的静态链接,这样filesystem的符号会被嵌入到共享库中,不会暴露给可执行文件:# 针对GCC 8及以下版本,链接静态版libstdc++fs if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0") target_link_libraries(${SHARED_TARGET_NAME} PRIVATE -l:libstdc++fs.a) endif()使用
-l:libstdc++fs.a而非-lstdc++fs,确保链接的是静态库,避免依赖传递给可执行文件。彻底隐藏私有符号
确保共享库的所有私有符号(包括filesystem相关)都不被导出:- 为共享库设置可见性预设:
set_target_properties(${SHARED_TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON ) - 使用版本脚本明确指定需要导出的符号(仅保留你的公共API符号):
创建export-symbols.version文件,内容示例:
然后在CMake中添加:{ global: # 列出你的共享库需要导出的公共API函数/类名 public_function1; public_class::public_method; local: *; };target_link_options(${SHARED_TARGET_NAME} PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/export-symbols.version" )
- 为共享库设置可见性预设:
处理filesystem异常
如果你的私有实现中会抛出std::filesystem::filesystem_error,需要在库内部捕获并转换为自定义异常(或错误码),避免该异常跨越库边界。这样编译器就不会导出filesystem_error的vtable和typeinfo符号:// 私有实现中的示例代码 try { // 调用filesystem操作 std::filesystem::status(path); } catch (const std::filesystem::filesystem_error& e) { // 转换为自定义异常或错误码抛出 throw MyLibraryError(e.what()); }调整静态库的链接方式
如果共享库依赖静态库,确保静态库中的filesystem符号被正确合并到共享库中:target_link_libraries(${SHARED_TARGET_NAME} PRIVATE ${STATIC_TARGET_NAME})同时为静态库设置
CXX_VISIBILITY_PRESET hidden,确保其内部符号默认不可见。
验证方法
完成配置后,重新编译共享库,执行以下命令验证符号是否被隐藏:
objdump -TC libShared-d.so | grep filesystem
如果没有输出,说明filesystem相关符号已成功隐藏。链接可执行文件时也不会再出现未定义符号错误。
内容的提问来源于stack exchange,提问作者Klypto

