C++ linker error: Undefined reference - when linking package libtorch and shared objects together

C++ linker error: Undefined reference - when linking package libtorch and shared objects together

我是 CMake 的新手,也在尝试了解 linking 的工作原理,或者是什么导致 libtorchOpenNMTTokenizer.so 无法协同工作。前者是带有CMake配置的包,后者是共享库。

如果我删除其中任何一个,二进制文件可以正常工作,但它们不能 link 在同一个项目中。也许 OpenNMTTokenizer.so 缺少 headers,但我不确定如何正确添加它们。

这是错误信息:

/usr/bin/ld: CMakeFiles/example_shared.dir/src/app.cpp.o: in function `main':
app.cpp:(.text+0x292): undefined reference to `onmt::Tokenizer::joiner_marker'
/usr/bin/ld: app.cpp:(.text+0x2a7): undefined reference to `onmt::Tokenizer::Tokenizer(onmt::Tokenizer::Mode, int, std::string const&, std::string const&, std::string const&, int)'
collect2: error: ld returned 1 exit status

该程序是一个简单的 hello world,只是为了隔离问题:

.
│
├── CMakeLists.txt
│
├── src
│   └── app.cpp
├── include
│   └── app.h.in
│   └── app.h
├── build
  
#
#. Project meta
#
cmake_minimum_required(VERSION 3.2 FATAL_ERROR)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
project(hello_world)
configure_file(include/app.h.in include/app.h)

#
# Add libraries
#

# 1. Torch
find_package(Torch REQUIRED PATHS /usr/local/libtorch)
# Required flags 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}")
if (Torch_FOUND)
  list(APPEND LINK_LIBRARIES "${TORCH_LIBRARIES}")
else()
  message(FATAL_ERROR "Cannot find libtorch")
endif()

# 2. OpenNMTTokenizer
find_library(OpenNMTTokenizer lib/libOpenNMTTokenizer.so)
if (OpenNMTTokenizer)
  add_library(OpenNMTTokenizer SHARED IMPORTED lib/libOpenNMTTokenizer.so)
  set_target_properties(OpenNMTTokenizer
    PROPERTIES
    IMPORTED_LOCATION /usr/local/lib/libOpenNMTTokenizer.so
    LINKER_LANGUAGE CXX)
  list(APPEND LINK_LIBRARIES OpenNMTTokenizer "${OpenNMTTokenizer_LIBRARIES}")
else()
  message(FATAL_ERROR "Cannot find OpenNMTTokenizer")
endif()

#
# Add the executable
#
add_executable("${PROJECT_NAME}" src/app.cpp)
set_property(TARGET "${PROJECT_NAME}" PROPERTY CXX_STANDARD 14)
target_link_libraries("${PROJECT_NAME}" "${LINK_LIBRARIES}")

target_include_directories("${PROJECT_NAME}" PUBLIC
  "${CMAKE_SOURCE_DIR}/include"
  "${TORCH_INCLUDE_DIRS}")

set_target_properties("${PROJECT_NAME}"
  PROPERTIES
  ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/../lib"
  LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/../lib"
  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/../bin"
)

这是来源src/app.cpp

#include "app.h"
#include <iostream>
#include <vector>
#include <torch/torch.h>
#include <torch/script.h>
#include <onmt/Tokenizer.h>

using namespace onmt;

int main(){
  torch::Tensor tensor = torch::eye(3);
  std::cout << tensor << std::endl;

  // It doesn't work if torch is linked
  Tokenizer tokenizer(Tokenizer::Mode::Conservative, Tokenizer::Flags::JoinerAnnotate);

  return 0;
}

完成构建输出

/usr/local/bin/cmake -S/home/inez/Projects/cmake_hello_world -B/home/inez/Projects/cmake_hello_world/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/local/bin/cmake -E cmake_progress_start /home/inez/Projects/cmake_hello_world/build/CMakeFiles /home/inez/Projects/cmake_hello_world/build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/inez/Projects/cmake_hello_world/build'
make  -f CMakeFiles/hello_world.dir/build.make CMakeFiles/hello_world.dir/depend
make[2]: Entering directory '/home/inez/Projects/cmake_hello_world/build'
cd /home/inez/Projects/cmake_hello_world/build && /usr/local/bin/cmake -E cmake_depends "Unix Makefiles" /home/inez/Projects/cmake_hello_world /home/inez/Projects/cmake_hello_world /home/inez/Projects/cmake_hello_world/build /home/inez/Projects/cmake_hello_world/build /home/inez/Projects/cmake_hello_world/build/CMakeFiles/hello_world.dir/DependInfo.cmake --color=
Dependencies file "CMakeFiles/hello_world.dir/src/app.cpp.o.d" is newer than depends file "/home/inez/Projects/cmake_hello_world/build/CMakeFiles/hello_world.dir/compiler_depend.internal".
Consolidate compiler generated dependencies of target hello_world
make[2]: Leaving directory '/home/inez/Projects/cmake_hello_world/build'
make  -f CMakeFiles/hello_world.dir/build.make CMakeFiles/hello_world.dir/build
make[2]: Entering directory '/home/inez/Projects/cmake_hello_world/build'
[ 50%] Linking CXX executable ../bin/hello_world
/usr/local/bin/cmake -E cmake_link_script CMakeFiles/hello_world.dir/link.txt --verbose=1
/usr/bin/c++  -D_GLIBCXX_USE_CXX11_ABI=0 -rdynamic CMakeFiles/hello_world.dir/src/app.cpp.o -o ../bin/hello_world  -Wl,-rpath,/usr/local/libtorch/lib:/usr/local/cuda-11.2/lib64/stubs:/usr/local/cuda-11.2/lib64:/usr/local/lib /usr/local/libtorch/lib/libtorch.so /usr/local/libtorch/lib/libc10.so /usr/local/libtorch/lib/libkineto.a /usr/local/cuda-11.2/lib64/stubs/libcuda.so /usr/local/cuda-11.2/lib64/libnvrtc.so -lnvToolsExt /usr/local/cuda-11.2/lib64/libcudart.so /usr/local/libtorch/lib/libc10_cuda.so /usr/local/lib/libOpenNMTTokenizer.so -Wl,--no-as-needed,"/usr/local/libtorch/lib/libtorch_cuda.so" -Wl,--as-needed -Wl,--no-as-needed,"/usr/local/libtorch/lib/libtorch_cuda_cpp.so" -Wl,--as-needed -Wl,--no-as-needed,"/usr/local/libtorch/lib/libtorch_cpu.so" -Wl,--as-needed -lpthread /usr/local/libtorch/lib/libc10_cuda.so /usr/local/libtorch/lib/libc10.so /usr/local/cuda-11.2/lib64/libcufft.so /usr/local/cuda-11.2/lib64/libcurand.so /usr/local/cuda-11.2/lib64/libcublas.so /usr/local/cuda-11.2/lib64/libcudnn.so -Wl,--no-as-needed,"/usr/local/libtorch/lib/libtorch_cuda_cu.so" -Wl,--as-needed -Wl,--no-as-needed,"/usr/local/libtorch/lib/libtorch.so" -Wl,--as-needed -lnvToolsExt /usr/local/cuda-11.2/lib64/libcudart.so 
/usr/bin/ld: CMakeFiles/hello_world.dir/src/app.cpp.o: in function `main':
app.cpp:(.text+0x292): undefined reference to `onmt::Tokenizer::joiner_marker'
/usr/bin/ld: app.cpp:(.text+0x2a7): undefined reference to `onmt::Tokenizer::Tokenizer(onmt::Tokenizer::Mode, int, std::string const&, std::string const&, std::string const&, int)'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/hello_world.dir/build.make:114: ../bin/hello_world] Error 1
make[2]: Leaving directory '/home/inez/Projects/cmake_hello_world/build'
make[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/hello_world.dir/all] Error 2
make[1]: Leaving directory '/home/inez/Projects/cmake_hello_world/build'
make: *** [Makefile:91: all] Error 2

手电筒有 TorchConfig.make

# Finds the Torch library
#
# This will define the following variables:
#
#   TORCH_FOUND        -- True if the system has the Torch library
#   TORCH_INCLUDE_DIRS -- The include directories for torch
#   TORCH_LIBRARIES    -- Libraries to link against
#   TORCH_CXX_FLAGS    -- Additional (required) compiler flags

来自 LINK_LIBRARIES

的链接库
LINK_LIBRARIES=torchtorch_library/usr/local/libtorch/lib/libc10.so/usr/local/libtorch/lib/libkineto.a/usr/local/cuda-11.2/lib64/stubs/libcuda.so/usr/local/cuda-11.2/lib64/libnvrtc.so/usr/lib/x86_64-linux-gnu/libnvToolsExt.so/usr/local/cuda-11.2/lib64/libcudart.so/usr/local/libtorch/lib/libc10_cuda.soOpenNMTTokenizer

查看下面的答案

库被 linked 到不同的标准库。正如 @botje 所建议的那样,_GLIBCXX_USE_CXX11_ABI 的值不匹配。

我用这一行重新编译了OpenNMTTokenizer,然后主工程编译没有报错:

add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0)

来自 libstdc++

If you get linker errors about undefined references to symbols that involve types in the std::__cxx11 namespace or the tag [abi:cxx11] then it probably indicates that you are trying to link together object files that were compiled with different values for the _GLIBCXX_USE_CXX11_ABI macro.

This commonly happens when linking to a third-party library that was compiled with an older version of GCC. If the third-party library cannot be rebuilt with the new ABI then you will need to recompile your code with the old ABI.

Read more at gnu.org

正如评论中所讨论的,事实证明其中一个组件 (OpenNMTTokenizer) 是 WITH CXX11 ABI 编译的,另外两个(Torch 和 app.cpp) 不是。这导致符号名称不匹配。

立即修复是在没有 CXX11 ABI 的情况下重新编译 OpenNMTTokenizer,尽管正确的修复是使用 CXX11 ABI 编译所有内容。这意味着重新编译 Torch。