贡献者: addis
事实上,ubuntu 系统中用 apt 安装 lib*-dev 就是在系统的默认搜索路径添加头文件和 lib*.a,lib*.so 文件,以及它依赖的 package 中的这些文件。这些文件在同一系统版本和同一 cpu 架构都是通用的(运气好的话也可能在不同系统中通用)。
在 C/C++ 中,如果一个函数声明时前面使用了 static,那么它将只在当前 translation unit 中可见,也就是无法被的文件在 link 时使用。
.a 文件是 static library, 在编译的时候一起编入可执行文件. 下面举一个例子
// lib1.cpp
#include <iostream>
using namespace std;
int f1() { cout << "In library 1" << endl; }
再编一个主文件
// main.cpp
#include <iostream>
using namespace std;
void f1();
int main()
{
f1();
cout << "In main" << endl;
}
如果将这两个文件用 g++ 正常编译 g++ main.cpp lib1.cpp 执行结果为
In library 1
In main
但现在我们把 lib1.cpp 先编译成 .o 文件
g++ -static -c -o lib1.o lib1.cpp
(其实 -o lib1.o 可以省略)(-static 用于静态编译), 再从 .o 文件生成 .a 文件 .a 文件的命名规则一般是前面加 (lib*.a)
ar rcs lib1.a lib1.o
可以将多个 .o 文件封装到 .a 里面, 在后面添加 lib2.o, lib3.o 等即可。.a 就是 .o 的压缩文件(archive),ar 和 tar 差不多。(其中 rcs 选项中的 r 选项是添加并替换旧文件(如果有同名), c 选项是 create archive, s 选项是 write out an index, 虽然还是不明白什么意思)。若要打印文件内容,用 ar p lib1.a 或者 ar pv lib1.a(verbose)。若要取出所有文件,用 ar x lib1.a [文件1] [文件2]。
再来将 lib1.a 和主程序文件一块编译
g++ main.cpp -o main.x -L./ -l 1
其中 -o 的作用是给生成的可执行文件命名, -L 的作用是声明 .a 所在的目录, -l 是指明所用的 .a 文件, (将 lib*.a 写成 * 即可).
现在就可以运行 main.x 了
./main.x
.a 就是 archive 的 .o 文件。在 link 阶段使用。
g++ 在(静态)link 阶段 .o 或 .a 的顺序是非常重要的,某个 o 文件只能调用在它后面列出的 o 文件,否则会提示找不到 symbol。要让 g++ 忽略这个顺序,可以使用
g++ 进行动态链接,可以用 -l 的另一种形式 -l :libXXX.so。其中 :文件名 直接搜索 文件名,而不是 lib文件名.so或a。
-Wl,--start-group 文件1 文件2 ... -Wl,--end-group,这样 linker 找不到 symbol 时就会在 group 内的文件中反复搜索(编译速度会降低)。
ldd 仅列出直接依赖的库文件(不包含用 dlopen() 函数直接从代码中加载的,因为很有可能会根据用户输入来加载,无法预判)。
lddtree(apt install pax-utils)
stdc++.so.6 即使同名,也不一定是正确的版本,见这里。如果不同路径都有这个文件,似乎会自动加载正确的版本。
1创建动态库:
g++ -shared -fPIC -o lib1.so lib1.cpp
使用方法:
把 cpp 编译成 .o 文件时不需要声明动态链接库和所在目录, -c 选项(不链接)普通编译即可.
g++ -c main.cpp
把 .o 文件链接成可执行文件时, 在最后 (注意必须是在最后) 加上
-Wl,-rpath,<library path> -L<library path> -l <libname1> -l <libname2> 其中 <library path> 可以是多个路径,如 path1:path2:...
其中 -Wl,aaa,bbb 命令是将 aaa bbb 选项传给 linker,剩下的 -L<library path> -l <libname1> -l <libname2> 的用法和上述 .a 中的一样.
g++ -o main.x main.o -l1 -L./ -Wl,-rpath,./
Linux 程序运行时搜索动态链接库的顺序(详见这里以及 man8):
rpath(一般不推荐)
LD_LIBRARY_PATH
runpath
/lib/ 和 /usr/lib/ 中的,以及 /etc/ld.so.conf 中的文件。在 ubuntu 中,该文件实际上 include 了 /etc/ld.so.conf.d 路径中的所有 *.conf 文件。
-Wl,-rpath 设置的变为 runpath 而不是 rpath。二者唯一区别在于动态库搜索路径顺序。一般并不推荐使用 rpath。
rpath,用 -Wl,--disable-new-dtags,-rpath,如果要强制旧编译器使用 runpath,也可以用 -Wl,--enable-new-dtags,-rpath。
-L 和 rpath 里面的路径可以是相对路径,但如果 rpath 用相对路径,那么就是相对于执行程序时的 pwd。如果要相对于执行程序,那么用 $ORIGIN/相对路径
可以用 ldd main.x 查看动态链接库,会发现其中有 lib1.so。如果不用 rpath,也可以在执行可执行文件以前把路径加入到环境变量 LD_LIBRARY_PATH 中。rpath 和 LD_LIBRARY_PATH 可以是相对于可执行文件的相对路径,也可以是绝对路径。也可以用 $ORIGIN 表示可执行文件的路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/your/custom/path/
g++ 中的 -L 选项(make 的时候搜索静态或动态库的路径)可以用环境变量 LIBRARY_PATH 设置,-I 选项(搜索头文件的路径)可以用环境变量 CPATH 设置。
rpath 只能设定当前可执行文件的的路径,如果可执行文件依赖的 .so 文件所需要的 .so 文件不在默认路径,就只能通过修改 LD_LIBRARY_PATH 或者用 patchelf(见下文)对其修改才可以。
LD_LIBRARY_PATH 中不可以有关键的系统 .so 文件(如 libc),否则如果不兼容,很多系统命令像 ls, touch, echo 等都会用不了。
LIBRARY_PATH 的顺序很重要,如果同名 .a 的路径出现在同名 .so 路径的前面,那么就会链接 .a。如果二者的路径相同,那么还是会有限链接 .so。
rpath,设置环境变量 LD_RUN_PATH 也是等效的。链接的程序是 ld,而动态链接库是 ld-linux.so
DT_SONAME entry 会保存本来的文件名给运行时的 linker 确认。用 patchelf --print-soname 文件 可以显示(通常来说,lib*.so.*.*.* 内部的 soname 可能会省略最后若干个版本编号。所以把 so 文件重命名以后,还要用 patchelf --set-soname 新文件名 新文件名。
chrpath(不推荐)或者 patchelf --set-rpath '路径' XXX.so(其实设置的也是 runpath 而不是 rpath)其中如果要设置相对路径如 '$ORIGIN/相对路径' 参考这里。用这种方法,加上 $ORIGIN 功能,似乎可以像 AppImage 或者 snap 一样把任何动态链接的程序打包到同一个文件夹中,包括所有依赖,也就是所谓的绿色软件。
patchelf --print-rpath 文件 可以查看 rpath。返回的多个路径使用冒号隔开,可以在命令后面加 | tr : \\n 把冒号替换为换行。
rpath(不推荐)而不是 runpath,先用 patchelf --remove-rpath 文件 移除所有 runpath 或 rpath,然后 patchelf --force-rpath --set-rpath '路径1:路径2:..' 文件 即可。
patchelf --print-needed 文件 可以显示直接依赖的动态库(不包含依赖的依赖)。这应该和 lddtree 文件 的第一层是一样的。
patchelf --remove-needed lib依赖库.so 文件 可以删除指定的依赖库。
readelf -a 文件 | grep NEEDED。
objdump -T yourlib.so | grep your_symbol 可以查看 so 中的符号
readelf -Ws yourlib.so | grep your_symbol 可以看到一个 so 中的符号是函数还是变量,是 global 还是 local(是否被 export)。
patchelf --add-needed lib依赖库.so 文件 添加指定依赖库。注意多次添加会出现重复的依赖。
patchelf 可以同时指定多个 文件。
patchelf --print-interpreter main.x 可以显示动态链接程序的链接器,一般是 /lib64/ld-linux-x86-64.so.2。也可以用 patchelf --set-interpreter 链接器 文件 指定。
-l 既能匹配 lib***.a 也能匹配 lib***.so,那么 gcc 会默认选 .so(貌似有时候二者都需要)。如果想要静态链接,要么用 -static 选项(禁止链接到任何动态 lib),要么直接指定 .a 的地址和文件名,如 g++ -o main.x f1.o /some/path/lib***.a another/path/lib***.a。
-l *** 不会自动匹配到带版本号的 lib***.so.x.x,一般存在一个名为 lib***.so 的软链指向某个具体的版本。
-l:lib库.so或a。不能有空格。
rpath 是相对路径,那么它相对的是 pwd 而不是可执行文件的位置,定义时可以使用 ${ORIGIN} 来表示可执行文件的位置。
elf文件 的 runpath 或 rpath 可以用 readelf elf文件 -d | grep PATH 或者 objdump -x elf文件 | grep PATH。
readelf -s 链接库.so 可以检查里面的 symbols。函数的类型是 FUNC,输出有两个表,看 .dynsym 就可以,它是 .symtab 中 allocable 的一个子集(详见这里)。如果是 c++ 函数,那么函数名是 mangle 的,用 --demangle 来显示 c++ 中的函数名(会包括 namespace)。
.a 或 .so 文件里面是否有某个函数,用例如 nm -A /usr/lib/x86_64-linux-gnu/libflint.a | grep fmpz_set 类似地也有 nm xxx.so,同样也可以用 --demangle
so 库中没有 debug 信息,那么除了函数名外并不能查看输入输出变量的个数和类型。如果有 debug 信息,可以用 readelf -wi 查看。
-l 的顺序就是搜索 symbol 的顺序,如果多个依赖的 so 中有相同的 symbol,会使用第一个搜到的。如果函数 prototype 不对就会运行出错。注意不会搜索间接依赖库中的 symbol。
linux-vdso.so.1 不存在于文件系统,开机时自动加载到内存。libc.so.6 是 GNU C library,里面有 system call。ld-linux-x86-64.so.2 是 linker,它的路径是专门由可执行文件的 interpreter path 指定的,不归 rpath 那些管。
libc.so,那么一定要用匹配版本的 ld-*.so 并且专门指定器路径。事实上 dpkg -L libc6 中列出的所有库都似乎需要和 libc.so 的版本匹配。常见的有 libm, libpthread, librt 等。
-Wl,--dynamic-linker=/绝对路径/ld-linux.so.X 可以指定 dynamic linker。
其他笔记:
ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\n 可以查看 linker 的默认搜索路径
sudo ldconfig 可以更新动态链接库的搜索
ldconfig -p | grep 库名 可以查看除了 LD_LIBRARY_PATH 之外的所有动态搜索路径中有没有某个 .so。
ldconfig -N -v $(sed 's/:/ /g' <<< $LD_LIBRARY_PATH) 2>&1 | grep 库名 可以包括 LD_LIBRARY_PATH
动态库本身也可以依赖于其他动态库,例如再添加一个程序
// lib0.cpp
#include <iostream>
using namespace std;
void f0()
{
cout << "In library 0" << endl;
}
然后修改 lib1
// lib1.cpp
#include <iostream>
using namespace std;
void f1()
{
cout << "In library 1" << endl;
void f0();
f0();
}
制作库
g++ -c lib0.cpp lib1.cpp // 生成 lib0.o lib1.o
g++ -shared -fPIC -o lib0.so lib0.cpp
g++ -shared -fPIC -o lib1.so lib1.cpp -l0 -L./ -Wl,-rpath,./
编译主程序,使用库,注意只需要链接 lib1
g++ -c main.cpp
g++ -o main.x main.o -l1 -L./ -Wl,-rpath,./
用 ldd main.x 检查所有依赖的动态库,会发现 lib0, lib1 都在。
main.x 依赖 -l test1, -l test2,而它们又分别依赖 -l:libbase.so.1 和 -l:libbase.so.2,而这两个库里面有同名的 symbol,那么 libbase.so.1和2 中的所有同名 symbol 会使用前者中的,而不可能分别调用不同的版本。这之和链接的顺序有关,和 main.x 中调用函数的顺序无关。
conflict()。注意 main.x 并不直接依赖 libbase.so.x 而是间接依赖,无法调用其中的函数。所以使用的 conflict() 会是最先链接的 libtest 中的。如果 main.x 直接依赖所有四个库,那么编译的时候会警告可能发生冲突,运行时会调用最先链接的库中的 conflict()。
main() 无法直接调用 base(),因为不是直接依赖 libbase。链接阶段错误:undefined reference to symbol '_Z4basev', error adding symbols: DSO missing from command line, ld returned 1 exit status。
export LD_LIBRARY_PATH=$PWD
g++ -shared -fPIC -o libbase.so.1 libbase.1.cpp
g++ -shared -fPIC -o libbase.so.2 libbase.2.cpp
g++ -shared -fPIC -o libtest1.so libtest1.cpp -L . -l:libbase.so.1
g++ -shared -fPIC -o libtest2.so libtest2.cpp -L . -l:libbase.so.2
g++ -o main.x main.cpp -L . -l:libtest1.so -l:libtest2.so
void test1(); void test2();
void conflict();
int main() { test1(); test2(); conflict(); }
#include <iostream>
using namespace std;
void base();
void test1() { base(); }
void conflict() { cout << "conflict() in libtest1.cpp" << endl; }
#include <iostream>
using namespace std;
void base();
void test2() { base(); }
void conflict() { cout << "conflict() in libtest2.cpp" << endl; }
#include <iostream>
using namespace std;
void base() { cout << "base() in libbase.1" << endl; }
void conflict() { cout << "conflict() in libbase1" << endl; }
#include <iostream>
using namespace std;
void base() { cout << "base() in libbase.2" << endl; }
void conflict() { cout << "conflict() in libbase2" << endl; }
ldd 命令查看。
dlopen() 加载库文件,dlsym() 加载具体函数,不需要函数声明,但首先要知道函数指针的类型。
#include <iostream>
#include <dlfcn.h> // Required for dlopen, dlsym, and dlclose
#include <cstdlib> // For exit()
typedef int (*processFunc)(double, const char*);
int main() {
// Load the shared library
void* handle = dlopen("./libexample.so", RTLD_LAZY);
if (!handle) {
std::cerr << "Error: " << dlerror() << std::endl;
exit(EXIT_FAILURE);
}
// Reset errors
dlerror();
// Get the function symbol from the library
processFunc process = (processFunc) dlsym(handle, "process");
const char* dlsym_error = dlerror();
if (dlsym_error) {
std::cerr << "Error: " << dlsym_error << std::endl;
dlclose(handle);
exit(EXIT_FAILURE);
}
// Call the function with sample arguments
double num = 42.5;
const char* str = "Hello, world!";
int result = process(num, str);
std::cout << "Result: " << result << std::endl;
// Close the library
dlclose(handle);
return 0;
}
RTLD_LOCAL(默认)RTLD_GLOBAL
libc.so 这样的东西可能会和系统本身的发生冲突,所以这个方法并不总是 work,应该只对相同发行版的相同版本相同 cpu 架构有效。然而静态链接的程序就没有这个问题。
 
 
 
 
 
 
 
 
 
 
 
友情链接: 超理论坛 | ©小时科技 保留一切权利