加载共享库时出错:无法在外部硬件上打开共享对象文件 ::

Error loading shared libraries: cannot open shared object file :: on external hardware

我目前正在开发一个 C++ 应用程序,它将引用多个 *.so 库,每个库包含用于不同机器的代码 - 用 C 编写。 我还有一个包含自定义命名空间实用程序代码的共享对象,它(顾名思义)包含对应用程序有用的基本实用程序(用 C++ 编写,就像应用程序的其余部分一样)。

目前,utilities.so 是应用程序引用的唯一(自定义)库。 应用程序编译和链接都很好,但是在目标硬件上执行它会显示以下错误:bin/updater_v4test: error while loading shared libraries: ../../../bin/device_modules/utilities.so.1.0.0: cannot open shared object file: No such file or directory

在所述应用程序上调用 LDD 时,显示以下输出:../../../bin/device_modules/utilities.so.1.0.0 => not found

我写了一个脚本(在 make 退出时没有错误)收集所有 SO 并将它们推送到目标硬件上的 /lib/ 目录;这意味着在每次成功构建应用程序(无论是部分还是全部)时,更新的文件都会被推送到目标硬件上的正确目录。

为了模仿我使用过的其他库(例如 zlib),我试图创建一个到库文件的符号链接,但没有成功(utilities.so.1.0.0 是符号链接, utilities.so 是实际的 SO)。 utilities.so.1.0.0 --> utilities.so

此刻我不知所措,我不能浪费更多的时间来尝试自己解决这个问题。


以下是实用程序 Makefile 的摘录:

include ../../common/user.mk

LIB_DIR=../../../libs/lib/powerpc-linux-gnu
CFLAGS=-Wall -ggdb -I../../libs/include -I../../common -lpthread -lddc -std=c++0x -I../../libs/include/zlib
LDFLAGS=../../libs/lib/powerpc-linux-gnu/libcurl.so.4 ../../libs/include/minizip/.libs/libminizip.so.1.0.0 \
-L ../../libs/lib \
-L ../../libs/lib/powerpc-linux-gnu \
-Wl,-rpath-link,../../libs/lib/powerpc-linux-gnu \
-lrt -lddc -lpthread -shared
BIN_DIR=../bin
CANONICAL_BIN_DIR := $(shell readlink -f $(BIN_DIR))
CANONICAL_CUR_DIR := $(shell readlink -f "./")
COMP_OBJECTS := $(wildcard *.cpp)
OBJECTS := $(patsubst %.cpp,%.o,$(wildcard *.cpp))

all: objects utilities.so
    -cp --parents utilities.so $(GLOBAL_BIN_LIB)
    -ln -s utilities.so utilities.so.1.0.0
    -mv utilities.so.1.0.0 $(GLOBAL_BIN_LIB)
#   -cp --parents *.h $(GLOBAL_HEADER_DIR)
    -rm -f utilities.so
    -rm -f *.o
    for header in $(wildcard *.h); do \
        echo $$header; \
        ln -s $(CANONICAL_CUR_DIR)/$$header $(GLOBAL_HEADER_DIR)/$$header; \
    done;
    @printf "########## BUILT $^ ##########\n\n"

utilities.so: $(OBJECTS)
    ${CXX} $^ -o $@ ${LDFLAGS}

objects: $(COMP_OBJECTS)
    ${CXX} -c $^ ${CFLAGS}

现在是应用程序 Makefile 的摘录。 在这里,我在链接器标志中添加了对库的引用,头文件都包含在一个目录中。

include ../../../common/user.mk

LIB_DIR=../../../libs/lib/powerpc-linux-gnu
CFLAGS=-Wall -ggdb -I../../../libs/include -I${COMMON_DIR} -lpthread -lddc -std=c++0x -I../../../libs/include/zlib -I${HEADER_DIR}
LDFLAGS=../../../libs/lib/powerpc-linux-gnu/libcurl.so.4 ../../../libs/include/minizip/.libs/libminizip.so.1.0.0 ../../../bin/device_modules/utilities.so.1.0.0 \
    -L ../../../libs/lib \
    -L ../../../libs/lib/powerpc-linux-gnu \
    -Wl,-rpath-link,../../../libs/lib/powerpc-linux-gnu \
    -lrt -lddc -lpthread -L ${SO_DIR}
BIN_DIR=../bin
CANONICAL_BIN_DIR := $(shell readlink -f $(BIN_DIR))
CANONICAL_CUR_DIR := $(shell readlink -f "./")

all: test_update.bin
    -cp --parents test_update.bin $(BIN_DIR)
    -ln -s $(CANONICAL_BIN_DIR)/test_update.bin $(GLOBAL_BIN_APP)/test_update.bin
    -rm -f *.bin
    @printf "########## BUILT $^ ##########\n\n"

test_update.bin: main.o updaterdelegate.o commonfunctions.o tinyxml.o
    ${CXX} $^ -o $@ ${LDFLAGS}

####################
#  Required Files  #
####################

main.o: Main.cpp
    ${CXX} -c $^ -o $@ ${CFLAGS}

updaterdelegate.o: UpdaterDelegate.cpp
    ${CXX} -c $^ -o $@ ${CFLAGS}

commonfunctions.o: $(shell python -c "import os.path; print os.path.relpath('${IMPL_CMN_FUNC}'.replace('\"', ''), '${CANONICAL_CUR_DIR}'.replace('\"', ''))")
    ${CXX} -c $^ -o $@ ${CFLAGS}

tinyxml.o: $(shell python -c "import os.path; print os.path.relpath('${IMPL_TXML}'.replace('\"', ''), '${CANONICAL_CUR_DIR}'.replace('\"', ''))")
    ${CXX} -c $^ -o $@ ${CFLAGS}

####################
# /Required Files  #
####################

只是为了 gits 和 shiggles,我将添加 utilities.so 和应用程序的构建输出。

utilities.so:

(干净省略)

=============== CLEAN COMPLETE... BUILDING... ===============


make: Entering directory `~/_workspace/upv4/common/utils'
powerpc-linux-gnu-g++  -c ArgumentHandling.cpp Extensions.cpp Logging.cpp -Wall -ggdb -I../../libs/include -I../../common -lpthread -lddc -std=c++0x -I../../libs/include/zlib
ArgumentHandling.cpp: In member function ‘void Utilities::ArgumentHandler::freeMemory()’:
ArgumentHandling.cpp:136: warning: deleting ‘void*’ is undefined
powerpc-linux-gnu-g++  ArgumentHandling.o Extensions.o Logging.o -o utilities.so ../../libs/lib/powerpc-linux-gnu/libcurl.so.4 ../../libs/include/minizip/.libs/libminizip.so.1.0.0 -L ../../libs/lib -L ../../libs/lib/powerpc-linux-gnu -Wl,-rpath-link,../../libs/lib/powerpc-linux-gnu -lrt -lddc -lpthread -shared
cp --parents utilities.so """~/_workspace/upv4""/bin/device_modules"
ln -s utilities.so utilities.so.1.0.0
mv utilities.so.1.0.0 """~/_workspace/upv4""/bin/device_modules"
rm -f utilities.so
rm -f *.o
for header in ArgumentHandling.h Enumerations.h Extensions.h Logging.h; do \
        echo $header; \
        ln -s ~/_workspace/upv4/common/utils/$header """"~/_workspace/upv4""/bin/device_modules"/headers"/$header; \
    done;
ArgumentHandling.h
Enumerations.h
Extensions.h
Logging.h
########## BUILT objects utilities.so ##########

make: Leaving directory `~/_workspace/upv4/common/utils'



=============== BUILD COMPLETE... PARSING... ===============


========== Warnings ==========

Total: 1

ArgumentHandling.cpp:136: warning: deleting ‘void*’ is undefined


========== Errors ==========

Total: 0

test_update.bin:

(干净省略)

=============== CLEAN COMPLETE... BUILDING... ===============


make: Entering directory `~/_workspace/_workspace/upv4/test/app/src'
powerpc-linux-gnu-g++  -c Main.cpp -o main.o -Wall -ggdb -I../../../libs/include -I"""~/_workspace/_workspace/upv4""/common" -lpthread -lddc -std=c++0x -I../../../libs/include/zlib -I"""""~/_workspace/_workspace/upv4""/bin/device_modules"/headers""
powerpc-linux-gnu-g++  -c UpdaterDelegate.cpp -o updaterdelegate.o -Wall -ggdb -I../../../libs/include -I"""~/_workspace/_workspace/upv4""/common" -lpthread -lddc -std=c++0x -I../../../libs/include/zlib -I"""""~/_workspace/_workspace/upv4""/bin/device_modules"/headers""
powerpc-linux-gnu-g++  -c ../../../common/commonFunctions.cpp -o commonfunctions.o -Wall -ggdb -I../../../libs/include -I"""~/_workspace/_workspace/upv4""/common" -lpthread -lddc -std=c++0x -I../../../libs/include/zlib -I"""""~/_workspace/_workspace/upv4""/bin/device_modules"/headers""
powerpc-linux-gnu-g++  -c ../../../common/xmlreader/tinyxml2.cpp -o tinyxml.o -Wall -ggdb -I../../../libs/include -I"""~/_workspace/_workspace/upv4""/common" -lpthread -lddc -std=c++0x -I../../../libs/include/zlib -I"""""~/_workspace/_workspace/upv4""/bin/device_modules"/headers""
powerpc-linux-gnu-g++  main.o updaterdelegate.o commonfunctions.o tinyxml.o -o test_update.bin ../../../libs/lib/powerpc-linux-gnu/libcurl.so.4 ../../../libs/include/minizip/.libs/libminizip.so.1.0.0 ../../../bin/device_modules/utilities.so.1.0.0 -L ../../../libs/lib -L ../../../libs/lib/powerpc-linux-gnu -Wl,-rpath-link,../../../libs/lib/powerpc-linux-gnu -lrt -lddc -lpthread -L """"~/_workspace/_workspace/upv4""/bin/device_modules""
cp --parents test_update.bin ../bin
ln -s ~/_workspace/_workspace/upv4/test/app/bin/test_update.bin """~/_workspace/_workspace/upv4""/bin"/test_update.bin
ln: failed to create symbolic link `~/_workspace/_workspace/upv4/bin/test_update.bin': File exists
make: [all] Error 1 (ignored)
rm -f *.bin
########## BUILT test_update.bin ##########

make: Leaving directory `~/_workspace/_workspace/upv4/test/app/src'



=============== BUILD COMPLETE... PARSING... ===============


========== Warnings ==========

Total: 0




========== Errors ==========

Total: 0

构建脚本是自定义的,我自己构建它是为了在不需要时摆脱整个 make 输出,它会解析出所有错误和警告。 由于这个问题,我重新打开了 make 输出。

我是不是在 compilation/linking 库中遗漏了什么,或者是在链接应用程序的过程中出了什么问题? 我倾向于说它是在库的 compilation/linking 期间——尽管我不知道到底出了什么问题,因为一切都在编译和链接中。 为什么应用程序在它不可能拥有的路径中寻找 .so 文件?

我还确保通过 $PATH 变量找到了 .so 文件,因此应用程序应该能够找到它们。

系统通常在一组固定的目录中搜索共享对象。您可以通过定义环境变量 LD_LIBRARY_PATH 并添加安装共享库的目录来解决这个问题。

或者您可以将这些库添加到一些标准库目录并执行ldconfig -a 以更新共享库的缓存。

有关详细信息,请参阅 ldconfig(8)

编辑

加载共享对象有两种机制。

  • 第一个是正常的库加载机制,它指定在启动 executable 时必须加载的内容,并暗示 linking 你的 executable 与共享对象。这就是您在 Makefile 中所做的。您指定共享 executables,并且 ld.so.xxx 共享对象(当您动态 linking 它时 linked 到您的应用程序)加载并遵循所有未解析的标识符在虚拟地址 space 中为它们找到一个位置。 ld.so.xxx 对象使用 /etc/ld.so.cache 文件,它只是一个散列 table,其中包含可以通过这种方式加载的目录和共享 executable。该文件由所谓的 soname 索引(这对于允许同一库的不同版本在同一系统中共存很有用)并且通常映射到在中列出的目录列表中找到的最后一个版本化共享文件 /etc/ld.so.conf(这是加速库加载过程的静态信息,在每次系统启动时生成)。如果在 link 时发现共享(此机制未被 ld 使用,而只是在程序启动时加载库)有一个 soname,那么 soname/etc/ld.so.cache 中搜索以找到必须加载的最终文件。这个缓存是在系统每次启动时建立的,所以你不必处理它,但前提是你不想重新启动并且你安装了一个新的库供系统使用。重要的是要注意共享对象必须被赋予 sonames 才能工作,并且文件中的目录列表 /etc/ld.so.conf 是唯一用于搜索文件的目录。这意味着要使标准库可用,您需要将其放在其中一个目录中(或将目录添加到 /etc/ld.so.conf 中的列表),然后执行 ldconfig -a 重建缓存并让它包含对 soname.

    下文件的引用

    它添加到搜索列表的另一种方法是将列表以 PATH 变量的形式放置。变量是 LD_LIBRARY_PATH 因此,如果您在 ${HOME}/libs 中有共享对象,您可以将此行添加到您的 .profile:

    export LD_LIBRARY_PATH=${HOME}/libs
    

    setenv LD_LIBRARY_PATH ${HOME}/libs
    

    这允许您的库位于标准目录之外,但请三思,因为这是一种效率低得多的文件加载方式(因为它涉及处理目录列表以查找最终的共享对象,虽然以前的方法是直接的,但您要求的文件与 ld.so.xxx 要求的 soname 相匹配,只需一次搜索,只需一步)

  • 第二种是使用库函数 dlopen(3),它允许您加载共享对象并在调用内部任何内容之前进行一些内务处理。 dl 库允许您在共享 executable 中搜索符号,然后决定将其解释为数据还是跳转目标。 dlopen() 只是打开并加载到虚拟地址 space 一个共享对象。它解决了依赖关系(如果需要)并且是更灵活的方式(但它也是不透明的)来加载未知代码以供执行。这是插件通常的工作方式。您决定在配置文件中或动态加载什么,然后加载它。该程序不必预先知道您正在处理的符号table,您可以自由地在您正在加载的模块中实现您想要的任何内容。

所有这些方法都适用于 ELF 二进制文件,因此您有很大的自由度,但也有很多复杂性。

更多...

正如我从你的汇编中看到的:

  • 您在 仅编译 命令中包含 -l,仅当您 link 执行 table 时才需要这些库, 不要把库放在编译阶段。
  • 对于要由 linker 搜索和选择的库,它必须命名为 lib<name>.so(末尾没有版本信息)所以这意味着您通常会为标准库找到三个名称(让我以数学库 -lm 为例):

    /usr/lib/libm.so.3.2.8 # 这是包含库内容的 ELF 文件。 /usr/lib/libm.so.3 -> libm.so.3.2.8 # 这是用来创建库的 soname。 /usr/lib/libm.so -> libm.so.3 # 这是 ld(1) 程序在使用 -lm 时搜索的实际文件。

必须创建那些 link,因为系统通常不会。这是共享库安装过程的一部分。 soname link 允许您拥有不同版本的库,并检测将在 运行 时使用其中的一个(所有版本都必须兼容,以便您可以互换,当您制作不兼容的修改,那么你必须改变soname,这样系统就不会在加载时混淆)

知道 ld(1) 程序仅在调用 lib<name>.so 时才选择一个库,没有版本信息,这一点非常重要。事实上,编译器首先搜索 lib<name>.so,然后搜索 lib<name>.a,然后它会报错。

在任何将使用[=110=中的目录的-l选项之前,放置-L位置来搜索库是非常重要的]ing参数。

如果您不打算使用 LD_LIBRARY_PATH 机制,您只需 运行 ldconfig -a 并将库安装在系统目录中。 (由于显而易见的原因,此机制不适用于 root 帐户 :))

期待这个添加的评论能给这个过程带来一些启发。