关于库的深入思考[探讨原创]
时间:2006-08-27
来源:互联网
我们都知道库对系统的重要.没了它,系统几乎无法运转,包括LFS整个过程至少是对工具链调整来调整去的过程是以对库的倚赖为核心的.这其中又以动态库为精华.
那先来说简单的静态库.它简单到只是ar打包的目标文件的集合罢了,于是,它的作用也就和目标文件没什么区别了,链接进目标文件,ok,使命完成,至于程序以后的事包括运行则和这个静态库没有关系了.其实我觉的最有说服力的就是例子了,那我们就举最简单的例子.
cat >say.c<<eof
#include "stdio.h"
void say()
{
printf("Say!");
}
eof
cat >test.c <<eof
#include "stdio.h"
void say();
main(){
say();
}
eof
gcc -c say.c
ar -r say.a say.o
gcc test.c say.a -o test
ldd test
输出结果让我们看不到任何跟say.a这个我们自己写的静态库的关系.说明程序运行时已经不需要这个静态库了,它已经被ld链接进最终的程序了.
那么动态库,我们继续
gcc -fPIC -shared say.c -o say.so
gcc test.c say.so -o test
ldd test
如果不出意外的话,会出现say.so => not found.这时的./test是不能运行的.但至少说明程序运行时是需要这个库的.那为什么找不到这个库呢?那就让我们看看系统是怎样寻找这些库的吧.
首先是ld-linux.so.2这个不能不说,它太重要了,以至于也决定了后面的搜索方式.
先是程序内部决定的.
strings test
还好我们这个test程序不大,不用过滤输出,好,你看见什么,/lib/ld-linux.so.2,say.so,libc.so.6,对,用到的库!
但我们发现不同,有的有路径,有的没有,先不管没有路径的怎么寻找,有路径的肯定是能找到了,那好,我们让say.so也有了路径.
gcc test.c ./say.so -o test2
strings test2
我们发现原来的输出中原来的say.so已经变成了./say.so.运行一下./test2,可以运行了!好,找到库了,这里用的相对路径,无疑,我们将say.so移动到非当前文件夹.那test就又不能运行了.这样无疑是把我们用到的库硬编码进了程序里.我不喜欢硬编码,太死板.那不硬编码系统怎么找到我们需要的文件呢.
在程序没有把库地址硬编码经进去的前提下,系统会寻找LD_LIBRARY_PATH环境变量中的地址.
LD_LIBRARY_PATH=./ ./test2
如我们所愿,程序正常运行.
如果系统在这一步也没发现我们需要的库呢.
/etc/ld.so.cache这个由ldconfig生成的文件,记载着在/etc/ld.so.conf文件中指明的所有库路径加上/lib,/usr/lib里的所有库的信息.
其实以上这句话只是在大多数情况下是正确的,是否是这个文件由ld-linux.so.2决定.如过你的LFS中的第一遍工具链/tools还在的话,
strings /tools/lib/ld-linux.so.2|grep etc
输出很可能是/tools/etc/ld.so.cache.那么它用的哪个文件我们就清楚了吧.
可这个路径前面的/tools到底和什么有关呢?首先我们可能会想到与ld-linux所在的位置有关.还好我们有3套glib,感谢LFS,现在我们拿第二遍的工具链下手.假设我们的LFS在/lfsroot
strings /lfsroot/lib/ld-linux.so.2
很奇怪的是输出竟然是/etc/ld.so.cache!那这到底和什么有关呢,没错就是我们编译时候的--prefix有关.
现在再看这个/etc/ld.so.conf,和/lib,/usr/lib这些默认ldconfig路径.也都要加上个这个prefix了.
strings /tools/sbin/ldconfig|grep etc
strings /tools/sbin/ldconfig|grep /lib
验证一下吧.
那要是ld.so.cache里也没有记载这个库的地址怎么办呢.
最后在默认路径里找.这个路径一般是/lib,/usr/lib,但也不全是.
strings /tools/lib/ld-linux.so.2|grep /lib
还是要加个prefix.
现在我们反过来思考,不用程序中硬编码的/lib/ld-linux.so.2做动态加载器了.这也可以?!是的!虽然不一定成功.
LD_TRACE_LOADED_OBJECTS=y /tools/lib/ld-linux.so.2 /bin/test
LD_TRACE_LOADED_OBJECTS=y /lib/ld-linux.so.2 /bin/test
LD_TRACE_LOADED_OBJECTS=y /lfsroot/lib/ld-linux.so.2 /bin/test
试着比较结果吧.
不出意外的话第一个是在/tools/lib中搜索的库,二三个都是在/lib中的库.原因我想上面已经说清楚了.
下面以第二个为例说明问题:
LD_LIBRARY_PATH=./ /tools/lib/ld-linux.so.2 ./test
/tools/sbin/ldconfig ./;/tools/lib/ld-linux.so.2 ./test
cp ./say.so /tools/lib/;/tools/lib/ld-linux.so.2 ./test
三种方法应该都会出现我们想要的结果,这里说明/tools/lib/ld-linux.so.2在这里的含义,是用/tools/lib/ld-linux.so.2这个做动态装载器.不信把这个去掉,后两种方法一定不行.因为以./test自己硬编码进去的/lib/ld-linux.so.2来说,是不会去管/tools/etc/ld.so.cache,和/tools/lib下的库的.
为了说明顺序,我们做如下很危险的实验:
ldconfig /lfsroot/lib;
ldconfig -p
会出现很多内容,但不要试着过滤,因为这时的系统应该很多程序不能运行了.先踏下心来观察.你会发现很多库出现两次/lfsroot/lib,和/lib而且/lfsroot/lib在前,说明ldconfig先处理参数给出的地址,最后是默认地址.但顺序也不一定,应该还和编译glibc时我们的参数--enable-kernel有关(我根据种种表现猜测).
加上export LD_LIBRARY_PATH=/lib 环境变量在前面,不能运行的程序又能运行了,说明LD_LIBRARY_PATH变量的优先级优于ld.so.cache
unset LD_LIBRARY_PATH
echo >/etc/ld.so.cache
ldconfig -p
应该什么都不出现,可大部分程序能运行.说明ld-linux.so.2决定的默认路径起了作用(注意,这里的ldconfig的默认路径没有作用)
ldconfig
恢复系统正常.
如果你原意,可以chroot /lfsroot后,再做类似的操作看有什么不同.
懂了原理我们就来应用一下.
拿./test2为例.
我们把它的库给换了!!!
cat >saa.c <<eof
#include "stdio.h"
say(){
printf("I can do something here!!!");
}
eof
gcc -fPIC -shared saa.c -o saa.so
sed "s#\./say\.so#./saa.so#" test >test3
./test3
看看结果吧!
很令人惊奇是么,如果是setuid程序的话...其实这个也很难,因为这种程序我们一般是无法写的(给自己搞破坏不算).这也就明白了为什么长久以来对setuid程序的权限始终如此重视----因为太危险了.
惊奇过后你可能会想,对于未硬编码库地址的程序,我们直接把LD_LIBRARY_PATH改了不也行么?!指向我们的地址,用我们的库,然后...根本不用改什么文件了,要什么写权限了.
呵呵,要真那么容易我们可爱的Linux不也太脆弱了,这恐怕就玩大了,也是你我都不原意见到的.所以,ld-linux.so.2早以作出限止,setsid程序,LD_LIBRARY_PATH变量不起作用.不过文件中的还是有作用的.
最后,说一下ld,和ld-linux.so.2的区别,一个编译时用,一个运行时用,ld负责在它的搜索路径里找到要求的库,并查看是否有提供了需要的符号(如函数等),如果有,记录相关信息到程序中,由ld-linux.so.2在执行时查找到该库并,并根据相关信息进行需要符号的重定位等工作.注意这两者的搜索库的方式是不同的.
LC_ALL=C ld --verbose|grep search -i
显示了它默认的查找地址.我们可以做个实验.一般它会有个类似i686-pc-linux-gnu/lib的路径,同时是不在ld-linux.so.2的搜索路径里的.其余的是我们编译是--with-lib-path和LIB_PATH变量指定的.
mv ./say.so XXXX/i686-pc-linux-gnu/lib/libsay.so
gcc -o test4 -lsay test.c
ldd ./test4
结果肯定是libsay.so找不到的.
好了,写到这吧,想起什么再加,有什么不对的地方也望大家多多提出来^^
------------------Skymoon作品,转贴请注明出处作者.
作者: Skymoon 发布时间: 2006-08-27
在此感谢大家对我更是对咱坛子的支持。谢谢。^^
作者: Skymoon 发布时间: 2006-08-28

作者: d00m3d 发布时间: 2006-08-30
我可不敢和youbest版主比,呵呵。
文笔不好,是不是挺晦涩的啊,只是想把我的经验贡献出来,让大大批评指正,共同进步^^
再次谢谢版主,没人理的滋味不爽啊。。。
作者: Skymoon 发布时间: 2006-08-30
不是不理,是一下子没看懂。要仔细再看一下再发表意见。。
作者: 晨想 发布时间: 2006-08-30
作者: 终极幻想
谢谢兄弟的心得分享!
不是不理,是一下子没看懂。要仔细再看一下再发表意见。。 |
作者: Skymoon 发布时间: 2006-08-30
作者: 晨想 发布时间: 2006-08-30
几点心得:
=========================
用 .so 文件的话,只要替换了 .so 文件,也就可以改变原来程序的运行。
$ ls 1 2
1:
say.c say.o say.so
2:
say.c say.o say.so
$ LD_LIBRARY_PATH=1 ./test1
Say1!
$ LD_LIBRARY_PATH=2 ./test1
Say2!
至于 ldconfig 的使用,我没测试,实在不想重启系统。不过原理是看明白了。
===========================
.so文件 搜索顺序:
LD_LIBRARY_PATH变量
ld.so.cache (由 ldconfig 生成)
binutils 编译时候指定的 --with-lib-path 参数
还有,如果要让 ldconfig 搜索到相应的 so 文件,文件名必须要是 libXXXX.so 这样的格式才行。
===========================
静态/动态 编译:(有点跑题)
$ ls
say.c test.c
$ gcc -c say.c
$ ar -r say.a say.o
ar: creating say.a
$ ls
say.a say.c say.o test.c
$ gcc -static test.c say.a -o test #静态编译,需要.a 文件,不能用 .so
$ ls -l test
-rwxr-xr-x 1 william users 1966387 2006-08-29 18:54 test
$ ./test
Say1!
$ gcc -fPIC -shared say.c -o libsay.so #生成库文件,需要 -fPIC
$ gcc test.c libsay.so -o test_shared #动态编译,生成 test_shared
$ ./test_shared
./test_shared: error while loading shared libraries: libsay.so: cannot open shared object file: No such file or directory
$ ldd test_shared
linux-gate.so.1 => (0xffffe000)
libsay.so => not found
libc.so.6 => /lib/libc.so.6 (0xb7e60000)
/lib/ld-linux.so.2 (0xb7f9d000)
$ LD_LIBRARY_PATH=. ./test_shared
Say1!
$ LD_LIBRARY_PATH=. ldd test_shared
linux-gate.so.1 => (0xffffe000)
libsay.so => ./libsay.so (0xb7f3a000)
libc.so.6 => /lib/libc.so.6 (0xb7dfe000)
/lib/ld-linux.so.2 (0xb7f3d000)
作者: 晨想 发布时间: 2006-08-30
作者: youbest 发布时间: 2006-08-30
作者: enac 发布时间: 2006-08-31
作者: 晨想 发布时间: 2006-08-31
至于分段,幻想版主已经给出了很好的整理(谢谢),我也说不好怎么分,其实写的时候还是很认真的,希望能写的清晰易懂些,可能是在语文功底欠佳,这篇文章早想写了,直到最近才动笔(应该说是动键盘,呵呵),因为马上就又要开学了,估计半年内没太多时间常来了。也算是最后献给大家的一份礼物吧,想尽力做好,希望大家喜欢。我有机会就会来看看大家的,呵呵^^
作者: Skymoon 发布时间: 2006-09-01
幻想老大,你后面说的“configure 的 --lib-path 参数”应该就是configure中的--with-lib-path指定的路径吧?
按照LFS Book在第二遍binutils时说的“--with-lib-path=/tools/lib,这个选项指示 configure 脚本在 Binutils 编译过程中将传递给连接器的库搜索路径设为 /tools/lib ,以防止连接器搜索宿主系统的库目录”,这个选项只是控制ld的库搜索路径,而根据Skymoon说的ld和ld-linux.so.2的区别,那么这个路径就跟ldconfig,ld-linux.so.2搜索库的路径应该没关系啊,你认为了?
作者: augustusqing 发布时间: 2006-09-08
现在才勉强有点顺序感了,我的理解是
say.c源程序调用say.so共享库到test可执行文件,到test的执行,把这个过程分为三步走
第一步: gcc -c say.c --> say.o 编译
第二步: gcc test.c say.o ./say.so -o test --> test 链接
第三步: ./test 把test载入,获得test内存映象
第一步,没话说,gcc是主角
第二步,ld是主角,ld是binutils中安装的工具,他只要搜到了say.so,就能生成test,而gcc test.c ./say.so -o test中的./路径信息,跟ld的LIB_PATH,configure的--with-lib-path(应该就是幻想版主的--lib-path),还有gcc中的-lsay(必须以libsay.so的文件名,存在于ld的默认搜索路径下,就是ld –verbose|grep SEARCH中列出的目录下),-L/aaa/bbb(libsay.so存在/aaa/bbb),这里5个路径信息(欢迎补充),都是告诉ld如何搜索say.so(libsay.so)。至于生成的可执行文件test用哪个ld-linux.so.2由gcc的spec规定,存在于test程序头的PT_INTERP类型的段(segment)中,与ld无关。ld找say.so的顺序我认为无关紧要,因为ld的工作是它找到相应库和库中的函数名后,在可执行文件里标记这些符号,而整个事情最关键的在于程序载入时ld-linux.so.2找到的say.so.
第三步,主角是ld-linux.so.2,test要载入内存来运行,需由这个ld-linux.so.2载入必须的共享库,而它搜索库的路径跟上面ld的搜索路径相关的地方就是gcc test.c ./say.so -o test中say.so前面的./会硬编码进test中的动态节(.dynamic section)的DT_NEEDED类型入口中,然而当ld在其他路径信息找到了say.so,却不会把路径信息硬编码进DT_NEED入口。然后就是ld-linux.so.2搜索库的顺序
1 DT_NEED入口中包含的路径
2 DT_RPATH入口给出的路径(存在的话)
3 环境变量LD_LIBRARY_PATH路径(setuid类的程序排除)
4 LD_RUNPATH入口给出的路径(存在的话)
5. 库高速缓存文件ld.so.conf中给出的路径
6. /lib,/usr/lib
补充:2 test中的DT_RPATH由什么传递进来,我不清楚。
3 对于setuid类程序,忽略;链接时有—library-path PATH选项时会被override
4 同2,这两处路径,当有链接选项--ignore-rpath LIST时会把LIST中的RPATH和RUNPATH信息忽略掉。
5中ld-linux.so.2找的ld.so.conf则在生成glibc库时的--prefix/etc目录下, ld-linux.so.2其实是找搜索ld.so.cache,由ldconfig生成。
6 当链接时加入了 -z nodeflib 选项,此处路径信息被忽略
第二步中ld找到了say.so,而ld-linux.so.2却找不到时,仅能得到test,却不能生成进程映象。
好想和各位再探讨探讨!
作者: augustusqing 发布时间: 2006-09-09
作者: augustusqing
很不错!学到不少东西,谢谢Skymoon了
幻想老大,你后面说的“configure 的 --lib-path 参数”应该就是configure中的--with-lib-path指定的路径吧? 按照LFS Book在第二遍binutils时说的“--with-lib-path=/tools/lib,这个选项指示 configure 脚本在 Binutils 编译过程中将传递给连接器的库搜索路径设为 /tools/lib ,以防止连接器搜索宿主系统的库目录”,这个选项只是控制ld的库搜索路径,而根据Skymoon说的ld和ld-linux.so.2的区别,那么这个路径就跟ldconfig,ld-linux.so.2搜索库的路径应该没关系啊,你认为了? |
应该是没关系的,,ldconfig 是 glibc 的部件。。
作者: 晨想 发布时间: 2006-09-09
还把Makefile变量LIB_PATH理解成环境变量,好差劲
汗颜。。。
作者: augustusqing 发布时间: 2006-09-10
作者: augustusqing
哈哈,昨天我对ld搜索库的路径和顺序的理解很多错误啊
还把Makefile变量LIB_PATH理解成环境变量,好差劲 汗颜。。。 |
悄悄告诉你一下,我按你的思路去理解楼主的文章,恰好就是在你对于LIB_PATH的说法让我抓了一夜的狂~~~~晕~~~
作者: dyfduck 发布时间: 2006-11-16
作者: dyfduck
悄悄告诉你一下,我按你的思路去理解楼主的文章,恰好就是在你对于LIB_PATH的说法让我抓了一夜的狂~~~~晕~~~
|
作者: augustusqing 发布时间: 2006-11-17
作者: augustusqing
对Skymoon兄的文章我学习了一天多,确实够得上深入思考啊
现在才勉强有点顺序感了,我的理解是 say.c源程序调用say.so共享库到test可执行文件,到test的执行,把这个过程分为三步走 第一步: gcc -c say.c --> say.o 编译 第二步: gcc test.c say.o ./say.so -o test --> test 链接 第三步: ./test 把test载入,获得test内存映象 第一步,没话说,gcc是主角 第二步,ld是主角,ld是binutils中安装的工具,他只要搜到了say.so,就能生成test,而gcc test.c ./say.so -o test中的./路径信息,跟ld的LIB_PATH,configure的--with-lib-path(应该就是幻想版主的--lib-path),还有gcc中的-lsay(必须以libsay.so的文件名,存在于ld的默认搜索路径下,就是ld –verbose|grep SEARCH中列出的目录下),-L/aaa/bbb(libsay.so存在/aaa/bbb),这里5个路径信息(欢迎补充),都是告诉ld如何搜索say.so(libsay.so)。至于生成的可执行文件test用哪个ld-linux.so.2由gcc的spec规定,存在于test程序头的PT_INTERP类型的段(segment)中,与ld无关。ld找say.so的顺序我认为无关紧要,因为ld的工作是它找到相应库和库中的函数名后,在可执行文件里标记这些符号,而整个事情最关键的在于程序载入时ld-linux.so.2找到的say.so. |
gcc test.c say.so -o test
是吧?
作者: dec1985 发布时间: 2007-04-19
作者: augustusqing 发布时间: 2007-05-31
在程序没有把库地址硬编码经进去的前提下,系统会寻找LD_LIBRARY_PATH环境变量中的地址.
LD_LIBRARY_PATH=./ ./test2
如我们所愿,程序正常运行.
如果系统在这一步也没发现我们需要的库呢.
这里面的命令LD_LIBRARY_PATH=./ ./test2
应该是LD_LIBRARY_PATH=./ ./test吧?不知道对不对?
作者: 相风乌 发布时间: 2007-11-09
作者: tlze 发布时间: 2008-01-27
libc-so.6的搜索路径在编译GLibc的时候可以通过
echo "slibdir=/tools/mylib">>configparms
设置。
在Multilib的时候,这一点至关重要哦
作者: 地球发动机 发布时间: 2008-02-29
其特点是包含PLT和GOT
procedure linkage table
global offset table
调用函数实际调用的是plt里的地址
访问全局变量实际访问的则是got里的地址
这些地址在未运行不需要确定,运行时可以实时解析出来,所以可以被加载到进程地址空间的任何地方,因此有position independent之名
作者: zhllg 发布时间: 2008-03-01
^O&
作者: hjk857 发布时间: 2009-06-09
作者: superyongzhe 发布时间: 2010-06-01
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28