【问题标题】:Why does changing the linking order fix some linking errors on one system?为什么更改链接顺序可以修复一个系统上的一些链接错误?
【发布时间】:2018-06-17 14:44:26
【问题描述】:

所以我对 GitLab CI 有这种奇怪的行为。我得到了它的工作,但现在我想知道它为什么工作。

首先,我从 GitLab CI 开始。我在我的机器(Arch Linux)上安装了一个带有 docker 的本地运行程序,这样我就可以在不推动和等待的情况下进行测试。我用 googletest 框架编写了一个测试(只是断言为真)。我在本地触发了脚本,一切正常。所有测试都在本地 docker 镜像中通过。

所以现在,当一切正常时,我推送到存储库,然后一个跑步者接手了这项工作。这在 Ubuntu 16.04 上运行。现在它编译并在执行后引发了分段错误。

我开始在 Ubuntu 系统上调试,过了一会儿我切换了两个库的链接顺序:

发件人:

target_link_libraries(${PROJECT_NAME}_test PRIVATE Threads::Threads -lglog -lpthread
    ${QT_LIBRARIES}
    ${PROJECT_BINARY_DIR}/googletest-build/googlemock/libgmock.a
    ${PROJECT_BINARY_DIR}/googletest-build/googlemock/gtest/libgtest.a
    ${OpenCV_LIBRARIES}
)

收件人:

target_link_libraries(${PROJECT_NAME}_test PRIVATE Threads::Threads -lglog -lpthread
    ${QT_LIBRARIES}
    ${OpenCV_LIBRARIES}
    ${PROJECT_BINARY_DIR}/googletest-build/googlemock/libgmock.a
    ${PROJECT_BINARY_DIR}/googletest-build/googlemock/gtest/libgtest.a
)

我正在使用 CMake 进行构建。

两台电脑都运行相同版本的 docker (17.12.0-ce)。

使用的gcc docker镜像是:sha256:95d81930694ca9e705b71dc141ddd13f466f4989857f74aebaf1d29ba6553775

显然这个问题是相关的: Why does the order in which libraries are linked sometimes cause errors in GCC?

现在我的问题是:当两个系统都运行一个 docker 容器时。为什么在这种情况下更改链接顺序可以解决问题?

【问题讨论】:

    标签: c++ docker cmake ld gitlab-ci-runner


    【解决方案1】:

    依赖关系。文件顺序很重要。

    链接器一次处理一个静态库,并解析丢失的符号并将它们拉入正在创建的可执行文件中。

    因此,如果一个静态库 (*.a) 依赖于另一个静态库,则它必须出现在满足其缺失符号的静态库之前。

    对象文件 (*.o) 被“批发”消费,因此订购它们不那么麻烦。

    【讨论】:

    • 好的,我现在只是想知道为什么这发生在一个 docker 实例中而不是另一个实例中。
    • 除了外部分辨率之外的另一个问题是某些机器/CPU 对寻址有限制,并且链接的顺序会影响例程/数据在内存中的位置,这会使地址修复工作/不工作;这将是一个链接/构建时间错误。此外,由于内存布局以及野指针和其他问题,未定义的程序行为(将指针地址与有符号/无符号算术混合)可能会导致错误。根据 OP,这将是一个运行时错误。
    【解决方案2】:

    CMake 中对此的正确解决方案是不是手动调整顺序,而是正确建模不同目标之间的相互依赖关系。

    这里的顺序依赖的确切性质是依赖于工具链的(在 gcc 上,依赖者必须在链接器命令行的依赖之前出现;MSVC 不在乎;其他工具链可能会选择不同的顺序要求)。 CMake 确保为给定工具链生成正确顺序的唯一方法是在 CMake 中显式建模依赖关系。

    在您的示例中,您已经对依赖项的平面列表进行了建模:

    target_link_libraries(${PROJECT_NAME}_test PRIVATE Threads::Threads -lglog -lpthread
        ${QT_LIBRARIES}
        ${OpenCV_LIBRARIES}
        ${PROJECT_BINARY_DIR}/googletest-build/googlemock/libgmock.a
        ${PROJECT_BINARY_DIR}/googletest-build/googlemock/gtest/libgtest.a
    )
    

    您有一个依赖于一堆库的目标 ${PROJECT_NAME}_test。但这实际上是错误的!实际上,从 gmock 到 gtest 存在您没有告诉 CMake 的依赖关系。您需要显式地对此依赖项建模,以使 CMake 正常工作。由于只能在目标之间指定依赖关系,因此我们需要为 gtest 和 gmock 引入两个额外的目标:

    add_library(gtest INTERFACE)
    target_link_libraries(gtest INTERFACE ${PROJECT_BINARY_DIR}/googletest-build/googlemock/gtest/libgtest.a)
    add_library(gmock INTERFACE)
    target_link_libraries(gmock INTERFACE ${PROJECT_BINARY_DIR}/googletest-build/googlemock/libgmock.a)
    target_link_libraries(gmock INTERFACE gtest)   # now gmock depends on gtest
    
    target_link_libraries(${PROJECT_NAME}_test PRIVATE Threads::Threads -lglog -lpthread
        ${QT_LIBRARIES}
        ${OpenCV_LIBRARIES}
        gtest
        gmock     # order doesn't matter here;
                  # you can even omit gtest completely now
    )
    

    注意这里的target_link_libraries 调用建立了从gmockgtest 的依赖关系。在 CMake 中始终对像这样的静态库之间的直接依赖关系进行建模非常重要,否则您会遇到您所描述的问题,一旦您的构建超过一定的复杂性,这些问题就会迅速蔓延到您的脑海中。

    附带说明,尽量不要在 CMake 文件中硬编码库路径,因为这再次使您的构建不可移植,并且可能在不同的工具链上完全中断。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-23
      • 2011-02-05
      • 1970-01-01
      相关资源
      最近更新 更多