Shared libraries with GCC on Linux

本文详细介绍了如何在Linux环境下构建共享库,并通过使用`gcc`编译器和`ld`链接器解决常见加载问题。包括编译位置独立代码、创建共享库、链接共享库以及解决加载失败的方法,如设置`LD_LIBRARY_PATH`和使用`rpath`等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

foo.h:

1
2
3
4
5
6
#ifndef foo_h__
#define foo_h__
 
extern void foo( void );
 
#endif  // foo_h__

foo.c:

1
2
3
4
5
6
7
#include <stdio.h>
 
 
void foo( void )
{
     puts ( "Hello, I'm a shared library" );
}

main.c:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include "foo.h"
 
int main( void )
{
     puts ( "This is a shared library test..." );
     foo();
     return 0;
}

foo.h defines the interface to our library, a single function, foo(). foo.c contains the implementation of that function, and main.c is a driver program that uses our library.

For the purposes of this example, everything will happen in /home/username/foo

Step 1: Compiling with Position Independent Code

We need to compile our library source code into position-independent code (PIC):1

$ gcc -c -Wall -Werror -fpic foo.c

Step 2: Creating a shared library from an object file

Now we need to actually turn this object file into a shared library. We’ll call it libfoo.so:

gcc -shared -o libfoo.so foo.o

Step 3: Linking with a shared library

As you can see, that was actually pretty easy. We have a shared library. Let’s compile our main.c and link it with libfoo. We’ll call our final program “test.” Note that the -lfoo option is not looking for foo.o, but libfoo.so. GCC assumes that all libraries start with ‘lib’ and end with .so or .a (.so is for shared object or shared libraries, and .a is for archive, or statically linked libraries).

$ gcc -Wall -o test main.c -lfoo
/usr/bin/ld: cannot find -lfoo
collect2: ld returned 1 exit status

Telling GCC where to find the shared library

Uh-oh! The linker doesn’t know where to find libfoo. GCC has a list of places it looks by default, but our directory is not in that list.2 We need to tell GCC where to find libfoo.so. We will do that with the -L option. In this example, we will use the current directory, /home/username/foo:

$ gcc -L/home/username/foo -Wall -o test main.c -lfoo

Step 4: Making the library available at runtime

Good, no errors. Now let’s run our program:

$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

Oh no! The loader can’t find the shared library.3 We didn’t install it in a standard location, so we need to give the loader a little help. We have a couple of options: we can use the environment variable LD_LIBRARY_PATH for this, or rpath. Let’s take a look first at LD_LIBRARY_PATH:

Using LD_LIBRARY_PATH

$ echo $LD_LIBRARY_PATH

There’s nothing in there. Let’s fix that by prepending our working directory to the existing LD_LIBRARY_PATH:

$ LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
./test: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory

What happened? Our directory is in LD_LIBRARY_PATH, but we didn’t export it. In Linux, if you don’t export the changes to an environment variable, they won’t be inherited by the child processes. The loader and our test program didn’t inherit the changes we made. Thankfully, the fix is easy:

$ export LD_LIBRARY_PATH=/home/username/foo:$LD_LIBRARY_PATH
$ ./test
This is a shared library test...
Hello, I'm a shared library

Good, it worked! LD_LIBRARY_PATH is great for quick tests and for systems on which you don’t have admin privileges. As a downside, however, exporting the LD_LIBRARY_PATH variable means it may cause problems with other programs you run that also rely on LD_LIBRARY_PATH if you don’t reset it to its previous state when you’re done.

Using rpath

Now let’s try rpath (first we’ll clear LD_LIBRARY_PATH to ensure it’s rpath that’s finding our library). Rpath, or the run path, is a way of embedding the location of shared libraries in the executable itself, instead of relying on default locations or environment variables. We do this during the linking stage. Notice the lengthy “-Wl,-rpath=/home/username/foo” option. The -Wl portion sends comma-separated options to the linker, so we tell it to send the -rpath option to the linker with our working directory.

$ unset LD_LIBRARY_PATH
$ gcc -L/home/username/foo -Wl,-rpath=/home/username/foo -Wall -o test main.c -lfoo
$ ./test
This is a shared library test...
Hello, I'm a shared library

Excellent, it worked. The rpath method is great because each program gets to list its shared library locations independently, so there are no issues with different programs looking in the wrong paths like there were for LD_LIBRARY_PATH.

rpath vs. LD_LIBRARY_PATH

There are a few downsides to rpath, however. First, it requires that shared libraries be installed in a fixed location so that all users of your program will have access to those libraries in those locations. That means less flexibility in system configuration. Second, if that library refers to a NFS mount or other network drive, you may experience undesirable delays–or worse–on program startup.

Using ldconfig to modify ld.so

What if we want to install our library so everybody on the system can use it? For that, you will need admin privileges. You will need this for two reasons: first, to put the library in a standard location, probably /usr/lib or /usr/local/lib, which normal users don’t have write access to. Second, you will need to modify the ld.so config file and cache. As root, do the following:

$ cp /home/username/foo/libfoo.so /usr/lib
$ chmod 0755 /usr/lib/libfoo.so

Now the file is in a standard location, with correct permissions, readable by everybody. We need to tell the loader it’s available for use, so let’s update the cache:

$ ldconfig

That should create a link to our shared library and update the cache so it’s available for immediate use. Let’s double check:

$ ldconfig -p | grep foo
libfoo.so (libc6) => /usr/lib/libfoo.so

Now our library is installed. Before we test it, we have to clean up a few things:

Clear our LD_LIBRARY_PATH once more, just in case:

$ unset LD_LIBRARY_PATH

Re-link our executable. Notice we don’t need the -L option since our library is stored in a default location and we aren’t using the rpath option:

$ gcc -Wall -o test main.c -lfoo

Let’s make sure we’re using the /usr/lib instance of our library using ldd:

$ ldd test | grep foo
libfoo.so => /usr/lib/libfoo.so (0x00a42000)

Good, now let’s run it:

$ ./test
This is a shared library test...
Hello, I'm a shared library

That about wraps it up. We’ve covered how to build a shared library, how to link with it, and how to resolve the most common loader issues with shared libraries–as well as the positives and negatives of different approaches.


  1. What is position independent code? PIC is code that works no matter where in memory it is placed. Because several different programs can all use one instance of your shared library, the library cannot store things at fixed addresses, since the location of that library in memory will vary from program to program.


  2. GCC first searches for libraries in /usr/local/lib, then in /usr/lib. Following that, it searches for libraries in the directories specified by the -L parameter, in the order specified on the command line.


  3. The default GNU loader, ld.so, looks for libraries in the following order:

    1. It looks in the DT_RPATH section of the executable, unless there is a DT_RUNPATH section.
    2. It looks in LD_LIBRARY_PATH. This is skipped if the executable is setuid/setgid for security reasons.
    3. It looks in the DT_RUNPATH section of the executable unless the setuid/setgid bits are set (for security reasons).
    4. It looks in the cache file /etc/ld/so/cache (disabled with the ‘-z nodeflib’ linker option).
    5. It looks in the default directories /lib then /usr/lib (disabled with the ‘-z nodeflib’ linker option).

<think>我们正在解决一个链接错误:`arm-openwrt-linux-muslgnueabi ld cannot find -lpaho-mqtt3as` 这个错误表明链接器在尝试链接名为`paho-mqtt3as`的库时找不到它。我们将按照步骤来解决这个问题。 步骤1:确认库文件是否存在 首先,我们需要确认目标系统上是否安装了`libpaho-mqtt3as.so`(或相应的静态库`.a`文件)。通常,这个库文件应该存在于链接器搜索的目录中。 步骤2:检查库文件路径 如果已经安装了库,我们需要确定库文件所在的目录,并确保该目录在链接器的搜索路径中。 步骤3:添加库路径 如果库不在标准搜索路径中,我们需要在编译命令中通过`-L`选项指定库的路径。 步骤4:确认库名称 确保库的名称正确。有时库可能有不同的命名约定,例如版本号后缀。 步骤5:检查环境变量 交叉编译时,可能需要设置环境变量(如`LIBRARY_PATH`或`LD_LIBRARY_PATH`)来指定库的路径。 步骤6:重新安装库 如果库未安装,需要安装或交叉编译该库。 现在,让我们详细展开每一步。 ### 1. 确认库文件是否存在 首先,我们需要在交叉编译工具链的库目录中查找`libpaho-mqtt3as.so`(或`libpaho-mqtt3as.a`)。假设你的OpenWrt SDK的根目录是`/path/to/sdk`,那么库文件通常位于`staging_dir/target-.../usr/lib`下。 你可以使用以下命令查找(在主机上): ```bash find /path/to/sdk -name "libpaho-mqtt3as*" ``` 如果找到,记下路径;如果没有,则说明库没有安装。 ### 2. 检查库文件路径 如果库文件存在,确认其所在目录是否在链接器的默认搜索路径中。交叉编译工具链的链接器通常只会搜索工具链自身的库目录和指定的系统库目录。 ### 3. 添加库路径 在编译命令中,使用`-L`选项指定库文件所在的目录。例如: ```bash arm-openwrt-linux-muslgnueabi-gcc -o my_program my_program.o -L/path/to/sdk/staging_dir/target-.../usr/lib -lpaho-mqtt3as ``` ### 4. 确认库名称 有时库文件可能有版本后缀,例如`libpaho-mqtt3as.so.1`。在这种情况下,通常需要创建一个符号链接,或者直接使用带版本号的库名(但链接时通常只需写`-lpaho-mqtt3as`,链接器会自动查找带版本的文件)。 如果库文件是静态库,确保有`libpaho-mqtt3as.a`文件。 ### 5. 检查环境变量 在交叉编译环境中,可以设置以下环境变量: - `LIBRARY_PATH`:在编译期间查找库的路径。 - `LD_LIBRARY_PATH`:在运行时查找动态库的路径(但链接时不需要,所以这里主要关注`LIBRARY_PATH`)。 例如: ```bash export LIBRARY_PATH=/path/to/sdk/staging_dir/target-.../usr/lib:$LIBRARY_PATH ``` 然后重新运行编译命令(不带`-L`选项)。 ### 6. 重新安装库 如果库不存在,需要安装它。对于OpenWrt,可以使用SDK中的包管理工具`opkg`(但注意,这是在目标系统上安装运行时库的方法)。对于交叉编译,我们需要在主机上安装开发包(即头文件和库文件)。 通常,Paho MQTT C库需要被交叉编译并安装到SDK中。可以按照以下步骤: 1. 下载Paho MQTT C库源代码: ```bash git clone https://github.com/eclipse/paho.mqtt.c.git cd paho.mqtt.c ``` 2. 使用OpenWrt SDK的交叉编译工具链进行编译。OpenWrt SDK提供了`cmake`工具链文件,但也可以使用Makefile。这里以CMake为例: 创建一个构建目录并进入: ```bash mkdir build cd build ``` 运行CMake,指定工具链文件(通常位于OpenWrt SDK的`staging_dir`目录下): ```bash cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/sdk/staging_dir/toolchain-.../share/toolchain.cmake \ -DPAHO_WITH_SSL=TRUE # 如果需要SSL支持 ``` 然后编译并安装到SDK的`staging_dir`中: ```bash make make DESTDIR=/path/to/sdk/staging_dir/target-.../ install ``` 这样,库文件和头文件就会被安装到SDK的相应目录中。 3. 重新编译你的程序,确保链接器能够找到库。 如果上述步骤仍然无法解决问题,可能需要检查OpenWrt的包目录中是否有`paho-mqtt`的包,并尝试使用SDK的包管理进行安装(例如,运行`./scripts/feeds install paho-mqtt`,然后重新编译包)。 注意:具体的路径和工具链名称需要根据你的OpenWrt SDK配置来确定。 总结:解决链接器找不到库的问题,核心是确保库文件存在并且链接器能够找到它。通过以上步骤,应该能够解决该问题。 §§相关问题§§ 1. 如何在OpenWrt上交叉编译Paho MQTT C库? 2. 如何设置OpenWrt SDK的环境变量以便于交叉编译? 3. 在链接时,动态库和静态库有什么区别?如何指定使用静态库? 4. 如何将交叉编译好的程序部署到OpenWrt设备上?</think>### 解决 `arm-openwrt-linux-muslgnueabi ld cannot find -lpaho-mqtt3as` 错误 此错误表明链接器在交叉编译环境中无法定位 **Paho MQTT C 客户端库**(`libpaho-mqtt3as`)。以下是系统化的解决步骤: --- #### 步骤 1:确认库是否安装 检查 OpenWrt SDK 中是否已安装 `libpaho-mqtt3as`: ```bash # 在 OpenWrt SDK 目录下搜索 find ./staging_dir -name "libpaho-mqtt3as*" ``` - **若找到文件**(如 `libpaho-mqtt3as.so` 或 `.a`):跳至步骤 3。 - **未找到文件**:需安装库(进入步骤 2)。 --- #### 步骤 2:安装 Paho MQTT 库 ##### 方法 1:通过 OpenWrt Feed 安装 1. 添加软件源: ```bash ./scripts/feeds update ./scripts/feeds install paho-mqtt-c ``` 2. 启用库配置: ```bash make menuconfig ``` > 导航至: > **Libraries** → **paho-mqtt-c** → 勾选 `asynchronous (paho-mqtt3as)` > 保存后编译:`make package/paho-mqtt-c/compile` ##### 方法 2:手动交叉编译 若官方源未提供,手动编译: ```bash git clone https://github.com/eclipse/paho.mqtt.c cd paho.mqtt.c mkdir build && cd build cmake .. \ -DCMAKE_TOOLCHAIN_FILE=/path/to/sdk/toolchain.cmake \ -DPAHO_BUILD_SHARED=ON \ -DPAHO_BUILD_STATIC=ON make make install DESTDIR=/path/to/sdk/staging_dir ``` --- #### 步骤 3:添加库路径到链接器 在编译命令中显式指定库路径: ```bash # 示例:添加 -L 和 -l 参数 arm-openwrt-linux-muslgnueabi-gcc \ -o my_app \ my_app.c \ -L/path/to/sdk/staging_dir/target-<arch>/usr/lib \ # 替换为实际路径 -lpaho-mqtt3as ``` > **关键参数**: > - `-L/path/to/lib`:库文件所在目录。 > - `-lpaho-mqtt3as`:链接动态库(`.so`)或静态库(`.a`)。 --- #### 步骤 4:验证环境变量 确保链接器能发现路径: ```bash # 临时设置链接器搜索路径 export LIBRARY_PATH=/path/to/sdk/staging_dir/target-<arch>/usr/lib:$LIBRARY_PATH # 编译时添加 -Wl,-rpath-link 确保运行时路径 arm-openwrt-linux-muslgnueabi-gcc ... -Wl,-rpath-link=$LIBRARY_PATH ``` --- #### 步骤 5:检查库文件命名 确认文件名与链接器期望一致: ```bash ls /path/to/lib | grep paho-mqtt3as ``` - 若文件名为 `libpaho-mqtt3as.so.1`,需创建符号链接: ```bash ln -s libpaho-mqtt3as.so.1 libpaho-mqtt3as.so ``` --- #### 常见问题排查 1. **架构不匹配**: 确保库的架构(ARMv7/ARMv8)与工具链一致。 2. **静态/动态库混淆**: 若需静态链接,使用 `-lpaho-mqtt3as` 并确认存在 `.a` 文件。 3. **依赖缺失**: Paho MQTT 依赖 OpenSSL,需先安装 `libopenssl`。 --- ### 相关问题 1. 如何为 OpenWrt 交叉编译其他第三方库(如 OpenSSL)? 2. 动态链接与静态链接在嵌入式系统中的优缺点是什么? 3. 如何调试 OpenWrt 编译过程中的 `undefined reference` 错误? 4. 如何将编译好的程序部署到 OpenWrt 设备并确保依赖库存在? [^1]: Paho MQTT C 库官方文档:https://www.eclipse.org/paho/clients/c/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值