链接LLVM导致gcov失效。

5
--coverage传递给gcc,同时又链接LLVM,会导致链接器报错:undefined reference to `__gcov_exit'。我建立了一个全新的项目来尝试解决这个问题。您可以在github上查看源代码,并在Travis-CI上检查编译器输出
这是覆盖率和非覆盖率构建之间的区别。
-DCMAKE_CXX_FLAGS="--coverage"

这是LLVM构建和非LLVM构建之间的区别。
target_link_libraries(Test
        PUBLIC
        LLVMCore
)

LLVM 作业成功。覆盖率作业成功。LLVM + 覆盖率作业出现了以下错误导致失败。
undefined reference to `__gcov_exit'

这里的最终目标是什么?使用llvm构建工具(编译器、链接器等)还是仅将测试可执行文件与libLLVM链接?它预期要使用的工具集是什么?还有环境是什么?我假设travis(顺便说一下,我没有使用过它)仅用于演示/复制目的(尽管从我的PoV来看,它引入了额外的复杂性水平)。 - CristiFati
@CristiFati 我正在编写一个编译器。我在Travis上进行了覆盖率构建。当我开始处理LLVM IR生成(一个月前)时,覆盖率构建出现了问题。我在测试存储库中重现了这个问题。现在我想知道如何同时链接LLVM和运行测试覆盖率。 - Indiana Kernick
我会尝试在travis之外重现它。我看到你正在使用g++7作为编译器。这是你的意图吗?所以,只有当LINK_WITH_LLVM被设置时才会出现错误? - CristiFati
@CristiFati 是的,cmake .. -DCMAKE_CXX_FLAGS="--coverage" -DLINK_WITH_LLVM=YES - Indiana Kernick
1个回答

6

注意:

起初,我认为这将是一个微不足道的修复(与-fprofile-arcs-ftest-coverage-lgcov标志有关)如[Man7]: GCC(1)-coverage选项)所述,但它不是。
我无法在我的Ubuntu 16 pc064VM上复制问题(尽管Travis非常好,但出于调试目的,它有点慢(特别是当由于匆忙而在编辑时添加或遗漏一个额外字符:)),而且它不提供与本地机器相同的访问级别),因为环境:

  • CMake 3.5.1

  • GCC 5.4.0

  • LLVM 3.8.0

Travis Docker映像中的内容差别很大。我必须提到的是,我既没有尝试从源代码构建软件包(那可能会引起很多麻烦),也没有尝试从下载它们的存储库中下载它们(甚至没有检查这些存储库是否是公开的)。无论如何,该VM几乎无法使用,因为:

这个问题让我苦不堪言,最终我在Travis上构建了48次才解决了问题,而这只是因为我没有注意到显而易见的问题。

问题所在

针对每个配置项,我将粘贴由的构建生成的编译和链接指令(来自[Travis CI]: Kerndog73/gcov_error - Build #24)。

  1. LLVM:

    /usr/bin/g++-7  -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/usr/lib/llvm-7/include  -std=gnu++1z -o CMakeFiles/Test.dir/main.cpp.o -c /home/travis/build/Kerndog73/gcov_error/main.cpp
    /usr/bin/g++-7    -rdynamic CMakeFiles/Test.dir/main.cpp.o  -o Test  -L/usr/lib/gcc/x86_64-linux-gnu/4.8 /usr/lib/llvm-7/lib/libLLVMCore.a /usr/lib/llvm-7/lib/libLLVMBinaryFormat.a /usr/lib/llvm-7/lib/libLLVMSupport.a -lz -lrt -ldl -ltinfo -lpthread -lm /usr/lib/llvm-7/lib/libLLVMDemangle.a
    
  2. Coverage:

    /usr/bin/g++-7  -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/usr/lib/llvm-7/include  --coverage   -std=gnu++1z -o CMakeFiles/Test.dir/main.cpp.o -c /home/travis/build/Kerndog73/gcov_error/main.cpp
    /usr/bin/g++-7  --coverage  -rdynamic CMakeFiles/Test.dir/main.cpp.o  -o Test
    
  3. LLVM + Coverage:

    /usr/bin/g++-7  -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/usr/lib/llvm-7/include  --coverage   -std=gnu++1z -o CMakeFiles/Test.dir/main.cpp.o -c /home/travis/build/Kerndog73/gcov_error/main.cpp
    /usr/bin/g++-7  --coverage  -rdynamic CMakeFiles/Test.dir/main.cpp.o  -o Test  -L/usr/lib/gcc/x86_64-linux-gnu/4.8 /usr/lib/llvm-7/lib/libLLVMCore.a /usr/lib/llvm-7/lib/libLLVMBinaryFormat.a /usr/lib/llvm-7/lib/libLLVMSupport.a -lz -lrt -ldl -ltinfo -lpthread -lm /usr/lib/llvm-7/lib/libLLVMDemangle.a
    
经过追踪,我发现在包含LLVM时,链接器传递了-L/usr/lib/gcc/x86_64-linux-gnu/4.8。在Docker上安装了GCC 4.8,因此编译时显然使用了GCC 7.4,而链接时使用了GCC 4.8,这是一种未定义行为。
我还贴出了稍有变化(带有-v)的第三个命令(所有用-l*指定的库都没有完整路径(例如-lgcov,-lgcc_s,-lgcc),版本错误,因为它们将从错误的目录中获取)。
/usr/lib/gcc/x86_64-linux-gnu/7/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/7/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper -plugin-opt=-fresolution=/tmp/ccyDh97q.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o Test /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.8 -L/usr/lib/gcc/x86_64-linux-gnu/7 -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. CMakeFiles/Test.dir/main.cpp.o /usr/lib/llvm-7/lib/libLLVMCore.a /usr/lib/llvm-7/lib/libLLVMBinaryFormat.a /usr/lib/llvm-7/lib/libLLVMSupport.a -lz -lrt -ldl -ltinfo -lpthread /usr/lib/llvm-7/lib/libLLVMDemangle.a -lstdc++ -lm -lgcov -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.0

.travis.ymlafter_script 部分放置的一些测试命令,产生了以下输出:

$ _GCOV_LIB7=/usr/lib/gcc/x86_64-linux-gnu/7/libgcov.a
$ (nm -S ${_GCOV_LIB7} 2>/dev/null | grep __gcov_exit) || echo ${_GCOV_LIB7}
0000000000001e40 000000000000008b T __gcov_exit
$ _GCOV_LIB48=/usr/lib/gcc/x86_64-linux-gnu/4.8/libgcov.a
$ (nm -S ${_GCOV_LIB48} 2>/dev/null | grep __gcov_exit) || echo ${_GCOV_LIB48}
/usr/lib/gcc/x86_64-linux-gnu/4.8/libgcov.a
所以,在这两个版本之间修改了LibGCC (libgcov.a)(添加了__gcov_exit符号),G++知道它有这个符号,但是Ld没有提供它(因为使用了错误的lib),因此出现了错误。

现在,为什么LLVM会添加这个库搜索路径,我不知道(可能是因为它是使用GCC 4.8构建的 - 或者类似于它的东西),但我可以想到以下几点:

  1. LLVM不应该与GCC 7一起使用(尽管我在快速浏览[LLVM]: Getting Started with the LLVM System时没有找到任何此类限制)

  2. LLVM中存在一个bug(考虑到我遇到的另一个问题,我倾向于认为这是最可能的原因)

对于这两种情况,我都找到了解决方法:

  1. Use an older G++ (v4.8 - installed by default on the Docker)

    • export CXX=g++ (might even not be necessary)

    • G++ (not CMake this time) doesn't know about cxx_std_17, so the easiest way is to remove the afferent target_compile_features (the drawback is that code won't be C++17 "compliant")

  2. Since I don't know how to "undo" passing the (faulty) library search path, the workaround that I came up with is to specify the correct one before it.

    CMakeLists.txt:

    cmake_minimum_required(VERSION 3.2)
    project(gcov_test)
    
    find_package(LLVM 7.0.0 REQUIRED CONFIG)
    message(STATUS "Found LLVM: ${LLVM_DIR} ${LLVM_PACKAGE_VERSION}")
    
    add_executable(Test
        "main.cpp"
    )
    
    target_compile_features(Test
        PUBLIC cxx_std_17
    )
    
    target_include_directories(Test
        PUBLIC ${LLVM_INCLUDE_DIRS}
    )
    
    target_compile_definitions(Test
        PUBLIC ${LLVM_DEFINITIONS}
    )
    
    if(LINK_WITH_LLVM)
        # @TODO - cfati: Everything in this block is to avoid hardcoding default libgcc path (/usr/lib/gcc/x86_64-linux-gnu/7)
        set(_COLLECT_LTO_WRAPPER_TEXT "COLLECT_LTO_WRAPPER=")
        execute_process(
            COMMAND bash -c "$0 -v 2>&1 | grep ${_COLLECT_LTO_WRAPPER_TEXT} | sed s/${_COLLECT_LTO_WRAPPER_TEXT}//" "${CMAKE_CXX_COMPILER}"
            OUTPUT_VARIABLE _GAINARIE_COLLECT_TMP_VAR
        )
        get_filename_component(_GAINARIE_GCC_DEFAULT_PATH ${_GAINARIE_COLLECT_TMP_VAR} DIRECTORY)
        set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${_GAINARIE_GCC_DEFAULT_PATH}")
        #set (GCCOPT "${GCCOPT} -L${_GAINARIE_GCC_DEFAULT_PATH}")
        # @TODO END
        target_link_libraries(Test
            PUBLIC LLVMCore
        )
    endif()
    

    Note: I tried to come up with something more elegant (I'm sure that it is), but I couldn't (tried using CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES, target_link_libraries, link_directories and a few more) at this point.


哇!感谢您在这个上花费了那么多自己的空闲时间!真的非常感激。我会看看能否将您的解决方案应用到我的实际项目中。 - Indiana Kernick
不客气!希望有所帮助。我能够在两种情况下成功构建(并运行虚拟的*.exe*)。 - CristiFati
通过覆盖率报告的构建已经通过。你赢得了100点声望! - Indiana Kernick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接