20200707-01动态库UNIX使用说明

Unit 系统编程手册-(41-42) 共享库基础

一、静态库 Vs 共享库 优缺点

静态库 共享库
可靠,已经包含运行所需的全部库,与系统无关 运行之前需要确保相关共享库已经存在
加载速度更快 需要运行之前依次检索、加载所需的共享库,以及符号的重新定位
每次静态库改动,相关引用该库程序都需要重新编译 运行时动态加载,所以不需要重新编译
浪费磁盘,每被引用一次就会生成一次副本 运行是加载,所以不需要
浪费内存,每次运行都会在内存生成一次副本 只会在内存中生成一次副本
库更加简单
编译时必须使用位置独立的代码,带来额外性能开销

二、静态库简单应用

2.1 创建、维护

使用 ar 命令进行

#创建
cc -g -c mod1.c mod2.c mod3.c
ar r libdemo.a mod1.o mod2.o mod3.o
#打印
ar tv libdemo.a
#删除
ar d libdemo.a mod3.o
2.2 链接

标准库:

/usr/lib 、/lib 、/usr/local/lib

#库当前目录
cc -g -o prog prog.o libdemo.a
#库位于标准库 使用 -l 去掉 .a
cc -g -o prog prog.o -ldemo
#指定路径 -L 文件路径 -l 库名称
cc -g -o prog prog.o -Lmydir -ldemo

三、共享库使用

3.1 创建
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
gcc -g -shared -o libfoo.so mod1.o mod2.o mod3.o
#或则
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c -shared -o libfoo.so

-fPIC 指定编译器生成位置独立的代码,因为链接的时候无法直到共享库位于内存的何处

#方式一: 是否使用 -fPIC 选项
nm mod1.o | grep _GLOBAL_OFFSET_TABLE_
readelf -s mod1.o | grep _GLOBAL_OFFSET_TABLE_
#方式二: 若输出信息,则说明至少有一个目标模块没有使用 -fPIC 选项
objump --all-headers libfoo.so | grep TEXTREL
readelf -d libfoo.so | grep TEXTREL
3.2 共享库 soname

目的: 提供一层间接,使得程序能够运行时使用与链接时使用的库不同但兼容的共享库

# 1 创建
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
# 2 创建 soname: 将 libfoo.so 的 soname 设为 libbar.so
gcc -g -shared -Wl,-soname,libbar.so -o libfoo.so mod1.o mod2.o mod3.o
# 3 检查 soname: 确定一个既有共享库 soname,以下命令二选一 
objump -p libfoo.so | grep SONAME
readelf -d libfoo.so | grep SONAME
# 4 soname 嵌入程序: 编译器检测到 soname 就将 soname 顶替 libfoo.so 嵌入程序中
gcc -g -Wall -o prog prog.c libfoo.so
# 5 创建 soname: 创建符号链接
ln -s libfoo.so libbar.so
3.3 共享库小工具
  1. ldd命令:显示一个程序所需共享库

  2. objump / readelf 命令:获取各类信息(包括反汇编二进制码),从可执行文件、库,显示 ELF 节头部信息

  3. nm 命令:列出目标库或可执行程序中定义的一组符号 nm -A /usr/lib/lib*.so 2> /dev/null | grep 'crypt$'

3.4 命名规则

真实库命名:libname.so.major_id.minor_id(次要版本号.补丁号) 如libdemo.1.0.1

soname 命名: libname.so.major_id 如 libdemo.so.1 -> libdemo.so.1.0.2

连接器命名:libname.so -> libdemo.so.1(指向 soname 常见,指向真实库也可)

3.5 安装共享库
方式一:(不推荐)LD_LIBRARY_PATH 所有用户都可以使用
方式二:标准库中
方式三:使用 ldconfig

ldconfig :解决库位置分散带来的加载缓慢和 soname 链接符号因为库迭代而失效

1 ldconfig 会依次检索 /etc/ld.so.conf中指定目录、/lib、/usr/lib 生成 /etc/ld.so.cache (ldconfig -p 打印)

2 检查每个库主要版本的最新次要版本(最大)找出嵌入的 soname,在同一目录创建相对符号链接(命名符合规范) -N 防止缓存重建 -X 阻止 soname 符号链接创建

方式四:目标文件中指定库检索 -rpath

1 强制指定

gcc -g -Wall -Wl,rpath,/home/mtk/pdir -o prog prog.c libdemo.so

DT_RPATH (优先级高于 LD_LIBRARY_PATH) / DT_RUNPATH (低于LD):

默认情况下,链接器会创建 DT_RPATH 标签,--enable-new-dtags 强制使用 DT_RUNPATH 标签

gcc -g -Wall -o prog prog.c -Wl,--enable-new-dtags -Wl,-rpath,/home/mtk/pdir/d1 -L/home/mtk/pdir/d1 -lx1
#这样是可执行文件中将包含两种标签,为了兼容老式动态链接器工作

2 不强制指定:使用 $ORIGIN(被解释成包含应用程序的目录),允许程序在任意目录中运行应用程序

gcc -Wl,-rpath,'$ORIGIN'/lib ...

3.6 运行时符号链接顺序

若主程序定义了与库相同的全局符号,将会覆盖库

gcc -g -shared -Wl,-Bsymbolic -o libfoo.so foo.o

-Bsymbolic 能够确保库自身调用与主程序相同的全局符号时,正确调用自身的符号


四、共享库高级特性

1 动态加载库

  1. dlopen() 将共享库加载进调用进程的虚拟空间并增加该库的打开引用次数

  2. dlerror() 错误信息

  3. dlsym() handle 指向库及所依赖树的库中检索 symbol 的符号(函数或变量)

  4. dlclose() 关闭共享库

  5. dladdr() 获取与加载的符号相关的消息

2 支持回调(库调用主程序符号)

gcc -Wl,--export-dynamic main.c

使用 gcc -rdynamic / gcc -Wl -E / -Wl,–export-dynamic 含义一样

3 符号可见性

1 static 符号的可见性局限於单个源代码文件中

2 void __attribute__((visibility("hidden"))) fun(void) {}对所有共享库不可见

4 链接器版本脚本

4.1 控制全局可见的符号
gcc -g -c -fPIC -Wall vis_com.c 
gcc -g -shared -o vis.so vis_com.o -Wl,--version-script,vis.map

#vis.map 内容
VER_1 {
	global:
		vis_f1;
		vis_f2;
	local:
		*;
};

#打印可见符号
readelf --syms --use-dynamic vis.so | grep vis_
4.2 符号版本化

也就是一个共享库提供同一个函数的多个版本

//cat sv_lib_v2.c
#include <stdio.h>
__asm__(".symver xyz_old,xyz@VER_1");
__asm__(".symver xyz_new,xyz@@VER_2"); //在.symver 指令中只能有一个 @@ 标记

//sv_v2.map
VER_1 {
    global: xyz;
    local: *;
};
VER_2 {
    global: pqr;
}VER_1; //VER_2 依赖 VER_1
4.3 初始化和终止函数
  1. 老式 _init() 和 _fini(),需要指定 gcc -nostartfiles 也可以通过 -Wl,-init / -Wl,-fini 指定函数

  2. 使用 gcc ,函数名根据需要替换

void __attribute__((constructor)) some_name_load(void) 
{    
}
void __attribute__((destructor)) some_name_unload(void) 
{
}
4.4 预加载共享库: LD_PRELOAD

LD_PRELOAD=libalt.so ./prog

这样的好处就是,本来 prog 会加载指定库的符号,使用预加载之后,预加载内存在的符号会覆盖原先的符号被主程序调用

4.5 监控动态链接器: LD_DEBUG

LD_DEBUG=libs date 根据关键字从动态链接器获得跟踪信息,以便清楚所检索的库

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章