寄托天下
查看: 2144|回复: 1
打印 上一主题 下一主题

[技术问答] 静态和动态链接的技术详解(LINUX) zz [复制链接]

Rank: 11Rank: 11Rank: 11Rank: 11

声望
3110
寄托币
48275
注册时间
2003-9-1
精华
44
帖子
1491

荣誉版主 GRE斩浪之魂 Golden Apple

跳转到指定楼层
楼主
发表于 2009-6-20 12:53:35 |只看该作者 |倒序浏览
http://linux.chinaunix.net/bbs/thread-1118727-1-1.html

原创!!!

所谓的静态文件也就是说以.o结尾的文件,这类文件会被ld合并起来形成一个可执行的文件
例如写了一个文件add.c和main.c可以分别的生成add.o和main.o文件,然后ld可以把这两个文件合并起来形成一个真正可执行的文件

代码如下:
//main.c
#include <....>

extern int add (int, int);
int main (int argc, char **argv)
{
  int x = 4;
  return add (x, argc);
}

<><><><><><><><><><><><>
// add.c
int x = 0;
int add (int a, int b);
{
  return (a + b + x);
}

一般来说ld会把相关的静态库都链接起来,一般一个静态库包含数据段,可执行段,ld所负责的主要的问题就是把所有的静态库里面的数据段合并成一个数据段,所有的可执行段组合成一个可执行段,这里面所涉及到的主要的问题就是引用。

一般来说对于在同一个静态库之间的函数引用实际上是引用的偏移地址,例如在同一个静态库中的函数a引用了函数b,那么实际上在call的时候,地址是b相对于a当前的偏移地址,对于a来说,掉用b不论在什么时候的偏移都是样的,因为ld并不会去拆分一个静态库的可执行段,但是对于像a函数调用了另外一个另外一个静态库中的函数b的话,因为在对a所在的文件编译成静态库的时候,因为不知道b将会在什么位置,所以编译器就会在对a所在的文件编译的时候,就会在目标文件中增加一项重定位项,该项中描述了在那里需要进行重定位。例如对于a来说,会有大概这样的一个项:在可执行段的偏移0x7f处,有一个引用了函数b,b是外部的函数。这样当ld在把所有的静态文件进行链接的时候,就会在各个被链接的静态库中寻找关于函数b的描述,任何一个静态文件都有一个关于自身的符号的描述,例如包含了函数b的这个静态文件就一个类似于对b的描述:b在可执行段的偏移为0x43的地方,大小为80b,这样当ld在对a和b所各自在的静态文件进行链接的时候,就会首先的把a和b的可执行段放在一起,这样就确定了函数b的最终的执行地址,和a最终的执行地址,这个时候,就可以根据a中的提供的关于b的重定位信息来修改a所在的静态文件的可执行段,从而让a能正确的引用函数b,

对于静态文件的数据合并实际上在遵循同样的规则。

一般的静态文件的数据
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     8: 00000000    22 FUNC    GLOBAL DEFAULT    1 add


num是符号的标识号码,没有意义。value是指该符号在它所在的段的偏移地址,当该符号没有绑定的时候也是该数值。size用来指定该符号一共描述了多少的数据,例如对于一个整数来说的话,size就是4,type指定该符号的意义,ndx用来指定该符号所出现的段,一般1为text,2为data

Offset     Info    Type            Sym.Value  Sym. Name
0000000d  00000701 R_386_32          00000000   number
对于可执行段中的重定位信息都放在section .rel.text中,上面这个项则指定了在text段中的偏移0x0d中,有一个对符号number的引用,info字段则主要用来描述该符号的名字索引,以及重定位的类型,也就是字段type的数值。

静态库的一个不好的地方就是:如果一个函数需要被不同的可执行文件引用,则当这些可执行文件在运行的时候,内存中会有多分该函数的复制,这对内存的浪费是相当的严重的。于是就引入了PIC,也就是所谓的位置无关代码。所谓的位置无关代码也就是说这些代码不论放在内存什么的位置,他们都可以被执行。这样的技术也就是动态链接库技术。一般来说如果一个动态连接库内的函数引用了该动态库中的函数的话,那么该函数可以正常的执行,但是问题是如果一个在动态库中的函数a引用了另外一个动态库中的函数b,那么a在编译成动态库的时候压根就不知道b的位置,如果在对a所在的库进行编译生成动态库的时候,还是按照静态库的方法来生成重定位信息的话,那么产生的问题就是:因为a所在的段是可执行段,这些可执行段在被加载放进内存的时候一般都会被设置成不可修改,可执行标识,所以就没有机会在对a进行修改了,也就是说哪怕是找到了b,a同样也不能对b进行引用了,因为a所在的段不允许被修改。

看来原先的那种静态库中的重定位的方式对于PIC来说是不合适的。这就要求有新的技术来应付这一个问题。我们都知道call可以引用某一个内存地址中的数据,这就为解决a引用其他动态库中的函数b提供了契机,当对a所在文件进行编译生成动态库的时候,可以生成一段数据段,该段主要用来存储各个函数外部引用的函数的地址。在生成a所在的动态库的时候,a对b的引用,也就是call后的数据则不在是b的绝对地址,而是对生成的那段用来存储外部函数的段的地址。这样当那个段中的地址改变成b的地址的时候,a就可以正确的引用b这个函数了。

这里又引入到另外的一个问题,如何找到b。系统提供了一个专门的解释器,该解释器也就是动态加载器,他的主要的功能就是接受一个被查询的字符,以及把这个找到的结果存储到那里。一般来说当a第一此调用b的时候,实际上call后面的那个地址里面所装的数据就是一个用来解析符号的函数的地址,该函数会找到call所要调用的函数的名字,然后控制会从该函数转向动态加载器,只不过在转向动态加载器之前,这个函数会把那个要找的函数的名字,以及把结果存放在那里推向堆栈,然后调用那个动态加载器,而那个要被修改的地址,就是a的call所引用的那个地址,当动态加载器找到b函数所在的地址的时候,就会把该地址存储到那个地址中去,这样当a再次调用b的时候,call只要把该他所引用的地址中的数据取出来就是了。
0 0

使用道具 举报

Rank: 9Rank: 9Rank: 9

声望
957
寄托币
3838
注册时间
2008-11-23
精华
7
帖子
131

荣誉版主 港澳资深筒子 港澳申请助理 Golden Apple

沙发
发表于 2009-6-20 12:54:52 |只看该作者
终于看到有技术贴咯:D

我来做广告,有本事来封我啊 笨~~~

使用道具 举报

RE: 静态和动态链接的技术详解(LINUX) zz [修改]

问答
Offer
投票
面经
最新
精华
转发
转发该帖子
静态和动态链接的技术详解(LINUX) zz
https://bbs.gter.net/thread-973619-1-1.html
复制链接
发送
回顶部