Lib库使用学习笔记
Lib库使用学习笔记
技术前沿 2008-03-31 14:21:10 阅读177 评论0 字号:大中小 订阅
Chapter 1. 为什么使用库文件
我们在实际编程工作中肯定会遇到这种情况:有几个项目里有一些函数模块的功能相同,实现代码也相同,也是我们所说的重复代码。比如,很多项目里都有一个用户验证的功能。代码段如下:
//UserLogin.h文件,提供函数声明int IsValidUser(char* username, int namelen);//UserLogin.c文件,实现对用户信息的验证int IsValidUser(char* username, int namelen){int IsValid = 0;/*下面是具体的处理代码,略去*/return IsValid;}
如果每个项目都保存着这两个UserLogin.h和UserLogin.c文件,会有以下几个弊端:
每个项目里都有重复的模块,造成代码重复。
代码的重用性不好,一旦IsValidUser的代码发生了变化,为了保持设计的一致性,我们还要手工修改其他项目里的UserLogin.c文件,既费时又费力,还容易出错。 库文件就是对公共代码的一种组织形式。
为了解决上面两个弊端,就提出了用库文件存放公共代码的解决方案,其要点就是把公共的(也就是可以被多次复用的)目标代码从项目中分离出来,统一存放到库文件中,项目要用到这些代码的时候,在编译或者运行的时候从库文件中取得目标代码即可。库文件又分两种:静态库和动态库。
静态库的原则是“以空间换时间”,增加程序体积,减少运行时间;动态库则是“以时间换空间”,增加了运行时间,但减少了程序本身的体积。在附录的PIC部分,也会做一些说明。
Chapter 3. 静态库的生成和使用
3.1. 制作最简单的静态库文件
编写如下两个文件,放在同一目录中:
mylib.h //静态库头文件void test();mylib.c //静态库实现文件#include <stdio.h>void test(){printf("hello world./n");}
使用下边的命令生成静态库:
gcc -c mylib.car rc libmy.a mylib.o
这样就生成了静态库libmy.a,注意一定要以lib*.a这样的格式命名,否则链接器ld不能识别。 使用命令"file libmy.a"看看它的格式,是一个档案文件。我们可以使用 nm查看它的内部构成:
[root @Benson libtest]# nm libmy.alibmy.o:U printf00000000 T test
这表示静态库有模块libmy.o,在使用的时候,gcc会根据需要将函数名得到模块,然后从静态库中 提取出对应的".o"文件的内容,然后用来链接,就是使用单独的".o"文件一样。
#include "mylib.h"int main(void){test();return 0;}
由于需要调用libmy.a中的test函数,所以在编译时,需通过"-L -l"参数指定链接这个库:
gcc -I./ -o main main.c -L./ -lmy
通过-I和-L参数制定了gcc的头文件和库文件搜索路径为当前目录,也可以根据需要指定为其他目录。
生成执行文件main后,执行命令"file main",可以看到:
[root @Benson libtest]# file mainmain: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5,staticlly linked (uses shared libs), not stripped
4.1. 制作最简单的动态库文件
我们同样使用上边的mylib.h和mylib.c文件,但使用不同的命令生成库文件:
gcc -fpic -shared libmy.so libmy.c
这样就生成了动态库文件libmy.so,-fpic这个选项指定是否使用 PIC,这个选项的使用需要系统平台的支持,一般建议添加,如果不支持,gcc会报错。
生成libmy.so后,使用"file libmy.so"命令,可以看到是一个ELF格式的文件,这说明共享库的 使用需要通过符号解析和重定位加载入内存才能使用。
gcc -I./ -o main main.c -L./ -lmy # 使用动态库生成maingcc main.c ./libmy.so -o main # 使用动态库生成main,不同的地方在于指定了libmy.so的加载路径,这种用法很少用。详细的内容附录ld.sogcc --static -I./ -o main main.c -L./ -lmy # 使用静态库生成main
[root @Benson libtest]# file mainmain: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5,dynamically linked (uses shared libs), not stripped
[root @Benson libtest]# ./main./main: error while loading shared libraries: libmy.so: cannot open shared object file: No such file or directory
没有找到libmy.so这个动态库,我这个库文件就在当前目录下?
根据ld.so的man手册,ld.so共有六种方式查找需要的动态库,这里只介绍常用的三种方式,详细的 内容见附录ld.so
按照环境变量LD_LIBRARY_PATH的内容查找(setuid类的程序排除)
在库高速缓存文件ld.so.conf中给出的路径查找
在/lib,/usr/lib目录下查找
相对应的,可以分别通过设置环境变量LD_LIBRARY_PATH,通过在ld.so.conf文件中添加路径和将 动态库文件复制到/lib、/usr/lib目录下,使动态库可以在加载时被找到。
[root @Benson libtest]# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/`pwd`[root @Benson libtest]# cat `pwd` >> /etc/ld.so.conf ; ldconfig[root @Benson libtest]# cp libmy.so /lib -f
以上三种方式都可以工作。关于ldconfig的使用详解,见 附录ldconfig
此时再执行main,可以正常运行了。
通过ldd命令,可以看到main程序依赖于libmy.so。
[root @Benson libtest]# ldd mainlibtest.so => not foundlibc.so.6 => /lib/tls/libc.so.6 (0x42000000)/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
/* main.c 测试动态库显式调用的程序 */#include <dlfcn.h> //用于动态库管理的系统头文件 #include "libmy.h" //要把函数的头文件包含进来,否则编译时会报错int main(void){/* 声明对应的函数的函数指针 */void (*pTest)(); /* 加载动态库 */void *pdlHandle = dlopen("libtest.so", RTLD_LAZY);/* 错误处理 */if(pdlHandle == NULL ){printf("Failed load library/n");return -1;}char* pszErr = dlerror();if(pszErr != NULL){printf("%s/n", pszErr);return -1;}/* 获取函数的地址 */pTest = dlsym(pdlHandle, "test");pszErr = dlerror();if(pszErr != NULL){printf("%s/n", pszErr);dlclose(pdlHandle);return -1;}/* 实现函数调用 */pTest ();/*程序结束时关闭动态库 */dlclose(pdlHandle);return 0;}
[root @Benson libtest]# gcc -ldl -o main main.c
运行程序,成功。这种方式,不需要在链接阶段指定动态库的位置。在运行阶段,调用要使用的函数
通过ldd命令,可以看到main程序不再依赖于libmy.so,转为 依赖libdl.so
libdl.so.2 => /lib/libdl.so.2 (0x40034000)libc.so.6 => /lib/tls/libc.so.6 (0x42000000)/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
gcc -o main main.c -Wl -Bstatic -lc -Wl -Bdynamic -test
上边这个例子中制定了静态链接libc和动态连接libtest。而实际上gcc选项-Wl表示将后边的参数传给链接器 ,-Bstatic和-Bdynamic是ld的选项。
将需要查找的目录加入/etc/ld.so.conf,然后运行ldconfig更新,更新结果永久有效
ldconfig + 路径,这种方式能直接更新指定的目录,但是结果在下次执行ldconfig 时,将会失效。
ldconfig的选项比较有用的是-v和-n,其他的可参考man手册:
-v 或 --verbose 详细的显示扫描的目录及搜索到的动态连接库,还有它所创建的 链接的名字
-n 使扫描时,不扫描默认目录/lib、/usr/lib和/etc/ld.so.conf包含的路径
创建静态库文件:
ar -rc libtest.a libmy.o libtest.o
选项分为"r" 和 "c"。-r 表示将后边的".o"模块加入库文件中;-c 表示当库文件不存在时创建。 同样,向存在的库文件添加新的模块时,只需要-r 选项即可。
查看静态库中的模块:
[root @Benson libtest]# ar -t libtest.alibmy.olibtest.o
删除静态库中的模块:
ar -d libtest.a libmy.o
此时在查看库的模块,libmy.o就不存在了。
提取静态库中的模块:
[root @Benson libtest]# ls libtest.a[root @Benson libtest]# ar -x libtest.a libmy.o ; lslibtest.a libmy.o
从libtest.a中提取出了libmy.o文件
nm 命令用来查看目标文件中的符号信息,nm命令对每个符号显示如下内容:
每个符号的值。
当指定-S选项时,显示函数符号对应的函数体的大小。
每个符号的类型,如,U表示该符号没有在库中定义;T表示该符号在库中定义。还有其他的内容涉及 到目标文件的格式,这里就不详细介绍。
每个符号的名字。
nm的选项中,"-s"选项用来显示静态库文件的列表信息。其他选项可查看man手册。
PIC(Position Independent Code),位置无关编码。是一种库文件的编码组织方式, 其特点是方便系统装载。是否支持这种格式的动态库,与硬件系统平台有关。
PIC code radically differs from conventional code in the way it calls functions and operates on data variables.
It will access these functions and data through an indirection table, the "Global Offset Table" (GOT), by software convention accessible using the reserved name "_GLOBAL_OFFSET_TABLE_".
The exact mechanism used for this is hardware architecture dependent, but usually a special machine register is reserved for setting up the location of the GOT when entering a function.
The rationale behind this indirect addressing is to generate code that can be independently accessed of the actual load address.
In a true PIC library without relocations in the text segment, only the symbols exported in the "Global Offset Table" need updating at run-time depending on the current load address of the various shared libraries in the address space of the running process.
Likewise, procedure calls to globally defined functions are redirected through the "Procedure Linkage Table" (PLT) residing in the data segment of the core image. Again, this is done to avoid run-time modifications to the text segment.
The linker-editor allocates the Global Offset Table and Procedure Linkage Table when combining PIC object files into an image suitable for mapping into the process address space. It also collects all symbols that may be needed by the run-time link-editor and stores these along with the image's text and data bits. Another reserved symbol, _DYNAMIC is used to indicate the presence of the run-time linker structures. Whenever _DYNAMIC is relocated to 0, there is no need to invoke the run-time link- editor. If this symbol is non-zero, it points at a data structure from which the location of the necessary relocation- and symbol information can be derived. This is most notably used by the start-up module, crt0, crt1S and more recently Scrt1. The _DYNAMIC structure is conventionally located at the start of the data segment of the image to which it pertains.
On most architectures, when you compile source code to object code, you need to specify whether the object code should be position independent or not. There are occasional architectures which don't make the distinction, usually because all object code is position independent by virtue of the Application Binary Interface (ABI), or less often because the load address of the object is fixed at compile time, which implies that shared libraries are not supported by such a platform). If an object is compiled as position independent code (PIC), then the operating system can load the object at any address in preparation for execution. This involves a time overhead, in replacing direct address references with relative addresses at compile time, and a space overhead, in maintaining information to help the runtime loader fill in the unresolved addresses at runtime. Consequently, PIC objects are usually slightly larger and slower at runtime than the equivalent non-PIC object. The advantage of sharing library code on disk and in memory outweigh these problems as soon as the PIC object code in shared libraries is reused.
PIC compilation is exactly what is required for objects which will become part of a shared library. Consequently, libtool builds PIC objects for use in shared libraries and non-PIC objects for use in static libraries. Whenever libtool instructs the compiler to generate a PIC object, it also defines the preprocessor symbol, `PIC', so that assembly code can be aware of whether it will reside in a PIC object or not.
Typically, as libtool is compiling sources, it will generate a `.lo' object, as PIC, and a `.o' object, as non-PIC, and then it will use the appropriate one of the pair when linking executables and libraries of various sorts. On architectures where there is no distinction, the `.lo' file is just a soft link to the `.o' file.
In practice, you can link PIC objects into a static archive for a small overhead in execution and load speed, and often you can similarly link non-PIC objects into shared archives.
When you use position-independent code, relocatable references are generated as an indirection that use data in the shared object's data segment. The text segment code remains read-only, and all relocation updates are applied to corresponding entries within the data segment.
If a shared object is built from code that is not position-independent, the text segment will usually require a large number of relocations to be performed at runtime. Although the runtime linker is equipped to handle this, the system overhead this creates can cause serious performance degradation.
You can identify a shared object that requires relocations against its text segment using tools such as 'readelf -d foo' and inspect the output for any TEXTREL entry. The value of the TEXTREL entry is irrelevant. Its presence in a shared object indicates that text relocations exist.
/lib/ld.so是系统的动态库加载器,属于Glibc编译生成的,与系统平台密切相关。加载目标代码 涉及的内容很多,但是这里主要围绕ld.so的动态库搜索过程,做全面的解释。
可执行文件自身的动态段(.dynamic section)中DT_NEED入口中包含的路径。
首先应该了解到,每个执行文件的数据是按照段来存储的,有代码段,数据段,BSS段等。那么 这里的动态段也是其中的一部分。我们借助readelf命令来查看这部分信息:
[root @Benson libtest]# readelf -d mainTag Type Name/Value0x00000001 (NEEDED) Shared library: [liba.so]0x00000001 (NEEDED) Shared library: [libc.so.6]0x0000000c (INIT) 0x80483400x0000000d (FINI) 0x80484e40x00000004 (HASH) 0x80481280x001000005 (STRTAB) 0x80482400x00000006 (SYMTAB) 0x80481700x0000000a (STRSZ) 172 (bytes)0x0000000b (SYMENT) 16 (bytes)0x00000015 (DEBUG) 0x00x00000003 (PLTGOT) 0x80495fc0x00000002 (PLTRELSZ) 16 (bytes)0x00000014 (PLTREL) REL0x00000017 (JMPREL) 0x80483300x00000011 (REL) 0x80483280x00000012 (RELSZ) 8 (bytes)0x00000013 (RELENT) 8 (bytes)0x6ffffffe (VERNEED) 0x80483080x6fffffff (VERNEEDNUM) 10x6ffffff0 (VERSYM) 0x80482ec0x00000000 (NULL) 0x0
readelf命令,能够查看ELF格式的文件的所有信息,在这里先不详细介绍。对于上边显示的信息 我们注意前两行,类型是NEEDED,后便对应的是内容,这些就是DT_NEED部分的内容。而内容部分的 liba.so和libc.so.6是没有路径信息的。所以ld.so不能根据这里的信息加载liba.so库。
但如果使用如下的命令生成main程序:
[root @Benson libtest]# gcc -o main main.o ./liba.so[root @Benson libtest]# readelf -d main0x00000001 (NEEDED) Shared library: [./liba.so]0x00000001 (NEEDED) Shared library: [libc.so.6]0x0000000c (INIT) 0x80483400x0000000d (FINI) 0x80484e40x00000004 (HASH) 0x80481280x001000005 (STRTAB) 0x80482400x00000006 (SYMTAB) 0x80481700x0000000a (STRSZ) 172 (bytes)0x0000000b (SYMENT) 16 (bytes)0x00000015 (DEBUG) 0x00x00000003 (PLTGOT) 0x80495fc0x00000002 (PLTRELSZ) 16 (bytes)0x00000014 (PLTREL) REL0x00000017 (JMPREL) 0x80483300x00000011 (REL) 0x80483280x00000012 (RELSZ) 8 (bytes)0x00000013 (RELENT) 8 (bytes)0x6ffffffe (VERNEED) 0x80483080x6fffffff (VERNEEDNUM) 10x6ffffff0 (VERSYM) 0x80482ec0x00000000 (NULL) 0x0
此时liba.so就有了路径信息,那么ld.so就会在当前路径下查找liba.so。
可执行文件自身的动态段(.dynamic section)中DT_RPATH入口中包含的路径(存在的话)。 这种方式的详细信息我没有找到,由于不常用,我将这个问题暂时保留。
环境变量LD_LIBRARY_PATH路径(setuid类的程序排除)。
可执行文件自身的动态段(.dynamic section)中LD_RUNPATH入口给出的路径(存在的话)这种方式 的详细信息也暂时没有。但同第二种方式,当有链接选项--ignore-rpath LIST时会把LIST中 的RPATH和RUNPATH信息忽略掉。
库高速缓存文件ld.so.conf中给出的路径。这里补充一点,在做LFS的时候可能会遇到,ld.so使用 那个缓冲文件,可以通过在编译Glibc的时候,使用命令"echo 'slibdir= 路径' >> configpams"来更改。
默认系统路径/lib,/usr/lib。
这部分的内容涉及的知识非常的广,属于链接器和加载器这部分的内容。根据我们的工作需要,目前 了解到这种程度已经足够。顺便再补充一点小技巧:通过设置LD_TRACE_LOADED_OBJECTS=y环境变量,可以 跟踪库的加载过程。
在了解了以上的所有内容后,我们可以根据掌握的知识做一个有趣,可怕的试验,因为这可能会 引起一些安全隐患。
/* 公用的头文件内容 *//* test.h */void test(void);/*************************************************//* liba.c的内容 */#include <stdio.h>void test(void){printf("hello world/n");}/*************************************************//* libb.c的内容 */#include <stdio.h>void test(void){printf("another say hello world/n");}
[root @Benson libtest]# gcc -fpic -shared -o liba.so liba.c[root @Benson libtest]# gcc -fpic -shared -o libb.so libb.c
#include "test.h"int main(void){test();return 0;}
[root @Benson libtest]# gcc -o main main.c -L./ -la[root @Benson libtest]# ./mainhello world
[root @Benson libtest]# sed "s#liba/.so#libb.so#" main > main2[root @Benson libtest]# chmod +x main2[root @Benson libtest]# ./main2another say hello world
Lib库使用学习笔记相关推荐
- 【Python-pywt】 小波变化库—Pywavelets 学习笔记
(转载) [Python ]小波变化库--Pywavelets 学习笔记_nanbei2463776506的博客-CSDN博客 https://blog.csdn.net/nanbei24637765 ...
- 《C++标准库》学习笔记 — STL —流
<C++标准库>学习笔记 - STL -流 一.操控器 1.原理 2.自定义操控器 3.控制输入的宽度 二.自定义 I/O 操作符 1.重载输出操作符 2.输入操作符 三.自定义格式化标志 ...
- 线性代数库 Armadillo 学习笔记
线性代数库 Armadillo 学习笔记 项目环境 Xcode 项目配置 前置代码 矩阵基本操作 全零矩阵 全一矩阵 对角矩阵 打印一个矩阵 修改获取矩阵元素 获取某行某列 获取对角向量 转置矩阵 逆 ...
- 点云库PCL学习笔记 -- 点云滤波Filtering -- 3. StatisticalOutlierRemoval 统计滤波器
点云库PCL学习笔记 -- 点云滤波Filtering -- 3.StatisticalOutlierRemoval 统计滤波器 StatisticalOutlierRemoval 统计滤波器相关简介 ...
- Python第三方库pygame学习笔记(一)
Pygame Python最经典的2D游戏开发第三方库,也支持3D游戏开发 Pygame适合用于游戏逻辑验证.游戏入门及系统演示验证 Pygame是一种游戏开发引擎,基本逻辑具有参考价值 pygame ...
- CImg库快速学习笔记
CImg库快速学习笔记 中山大学,曾坤老师的<计算机视觉>需要. 本文部分翻译自CImg_latest安装包中附带的英文文档的8 Class Documentation部分,以及互联网中的 ...
- STM32F407霸天虎HAL库CubeMX学习笔记——DS18B20
STM32F407霸天虎HAL库CubeMX学习笔记--DS18B20 一.软件准备 二.硬件准备 三.CubeMX配置 四.Keil printf重写 tim.c DS18B20.h DS18B20 ...
- linux添加自己的库,Linux学习笔记——例叙makefile 增加自定义共享库
Linux学习笔记--例说makefile 增加自定义共享库 0.前言 从学习C语言开始就慢慢开始接触makefile,查阅了很多的makefile的资料但总感觉没有真正掌握makefile,如果自己 ...
- 《装载、链接与库》学习笔记
<装载.链接与库>前七章学习笔记 今天偶然翻到前面几章,发现诸多"新鲜点",造成这样的原因有一部分就是读过后没有即时沉淀先来,形成自己的知识体系.我觉得有必要写一下读书 ...
最新文章
- 单目深度估计方法综述
- MyBatis从缓存查找数据的依据
- JAVA基础--final、static区别以及类加载顺序
- java里面有控制器吗_mvc中 控制器部分可以使用Javabean完成吗?为什么?
- aspx是什么语言_什么是网页
- 第一个错误的版本_寻找第一个错误的版本
- 盘阵多路径学习(转)
- C#中引用第三方ocx控件引发的问题以及解决办法
- AutoCAD 2008中文版实用自学手册•机械设计篇
- WEB前端工具推荐丨分享6个热门颜色选择器组件
- 如何在DOS系统下删除隐藏的文件
- astc纹理压缩格式
- 金融货币学笔记(米什金)第一章 为什么研究货币、银行与金融市场
- 031:verbatim 标签
- linux挂移动硬盘命令,linux挂载命令mount及U盘、移动硬盘的挂载
- JAVA 中 Redis与ehcache对比与使用
- w10投影全屏设置_win10如何让投影仪铺满全屏
- 人生最大的危机就是没有危机感
- 考研政治——马克思原理唯物论之意识观
- 如何通过短信转发在iPad和Mac上发送和接收短信