Linking LLVM causes gcov to fail

后端 未结 1 1178
我在风中等你
我在风中等你 2021-01-14 18:02

Passing --coverage to gcc while also linking LLVM causes an undefined reference to `__gcov_exit\' error from the linker. I\'ve set up a fresh proje

相关标签:
1条回答
  • 2021-01-14 18:15

    Notes:

    • Check [cmake 3.13]: CMAKE_<LANG>_FLAGS for details regarding which env var affects which cmake var - as you figured out in your (deleted) answer
    • Only noticed cxx_std_17 from cmake 3.8, so you might want to update your cmake_minimum_required
    • Forked your repo to [GitHub]: CristiFati/gcov_error - An attempt to reproduce the gcov/llvm linker error and did some debugging on it - beware, I might delete it someday
    • Was able to get my head around Travis CI, it's a real gold-mine

    At the beginning I thought that it would be a trivial fix (something related to -fprofile-arcs, -ftest-coverage, -lgcov flags) as noted in [man7]: GCC(1) (--coverage option), but it wasn't.
    I wasn't able to reproduce the problem on my Ubtu 16 x64 VM (although travis is very nice, it's kind of slow for debugging purposes (especially when due to rushing one might forget or add an extra character when editing :) ) and also it doesn't offer the same access level that a local machine does,) because the environment:

    • cmake 3.5.1
    • gcc 5.4.0
    • llvm 3.8.0

    is pretty far away from what's on the travis docker image. I must mention that neither I tried building the packages from sources (that would probably have caused lots of headaches), nor did I try downloading them from the repositories where they are downloaded during CI builds (didn't even check if those repos are public). Anyway the VM was pretty much unusable because:

    • I couldn't reproduce the issue (after some minor changes to adapt to older tools,) everything worked
    • More: I ran into [SO]: Error when using CMake with LLVM - (@CristiFati's answer)

    It was eating me alive, so I ended up building 48 freaking times (on travis) in order to get it working, and that is only because I failed to notice the obvious.

    The problem

    For each of the 3 configurations I'm going to paste the compile and link (g++) commands generated (from your build: [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
      

    After lots of ghosts chasing, I noticed -L/usr/lib/gcc/x86_64-linux-gnu/4.8 being passed to the linker when llvm is included. gcc 4.8 is installed on the docker, so apparently gcc 7.4 was used for compilation, and gcc 4.8 for linking, which is Undefined Behavior.
    I'm also pasting the collect command (closer to the linker) from a slight variation (with -v) of #3. (all the libraries specified with -l* and without the full path (e.g. -lgcov, -lgcc_s, -lgcc) have the wrong version because will be picked up from the wrong directory):

    /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
    

    Some test commands placed in .travis.yml's after_script section, yielded the following output:

    $ _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
    

    So, libgcc (libgcov.a) was modified somewhere between the 2 involved versions (__gcov_exit symbol was added), and g++ knew it has one but ld didn't provide it (because of the wrong lib) hence the error.

    Now, why does llvm add this library search path, I don't know (probably because it was built with gcc 4.8 - or smth close to it), but here's what I can think of:

    1. llvm is not meant to be used with gcc 7 (although I didn't find any such restriction while quickly browsing [LLVM]: Getting Started with the LLVM System)
    2. A bug in llvm (considering the other issue that I ran into, I tend to think this is the winner)

    I found ways for both of them:

    1. Use an older g++ (4.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.

    0 讨论(0)
提交回复
热议问题