Chinaunix首页 | 论坛 | 博客
  • 博客访问: 192466
  • 博文数量: 73
  • 博客积分: 5000
  • 博客等级: 大校
  • 技术积分: 1160
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-23 15:53
文章分类

全部博文(73)

文章存档

2011年(1)

2009年(72)

我的朋友

分类: LINUX

2009-04-23 17:02:50

elf文件装载和动态链接


1 ELF文件的装载

       ELF文件中,使用sectionprogram两种结构描述文件的内容。通常来说,ELF可重定位文件采用sectionELF可执行文件使用program,可重链接文件则两种都用。

       装载文件,其实是一个很简单的过程,通过section或者program中的type属性判断是否 需要加载,然后通过offset属性找到文件中的数据,将它读取(复制)到相应的内存位置就可以了。 这个位置,可以通过program里面的vaddr 属性确定;对于section来说,则可以自己定义装载的位置。

2 ELF文件的重定位

       动态连接的本质,就是对ELF文件进行重定位和符号解析。

       重定位可以使得ELF文件可以在任意的执行(普通程序在链接时会给定一个固定执行地址);符号解析,使得ELF文件可以引用动态数据(链接时不存在的数据)。

       从流程上来说,我们只需要进行重定位。而符号解析,则是重定位流程的一个分支。

      

先让我们简单的介绍一下重定位的原理。

假设我们写出了一句汇编源代码

jmp dxc

dxc:

……

假设dxc这个标号的地址1000h

那么在编译链接之后,就变成了jmp 1000h

如果我们在运行时移动了程序的位置,1000h就会指向错误的地址(因为我们已经不在那里了)。而当我们使用了外部符号时(例如动态链接库里面的函数),在链接时根本就不知道这个符号在哪里,所以也没有办法生成这个1000h的地址。

       重定位的目的,就是在运行的时候修改这个1000h地址,使其指向正确的地址。(链接时也需要重定位,暂且不提)。

       为了进行重定位,我们需要三个数据。

1是进行重定位的地址,也就是jmp 1000h这条指令中操作数的地址,也就是1000h自己在内存中的地址。(你可以把它想象为C指针自己的储存地址&point

2是需要指向的符号,上例中就是dxc这个标号。通过对这个符号进行解析,就可以得到运行时该标号的正确地址。(你也可以把它想象成C指针point所指向的地址)

3是重定位的类型,例如R_386_32表示绝对地址的重定位;R_386_PC32表示对相对地址的重定位。前者可以直接使用符号的地址,后者则要用符号地址-重定位地址得出相对地址。       其它关于重定位类型,请参考ELF白皮书。

 

              重定位表,是一个由许多重定位表项组成的数组。

下面是ELF里面重定位项的结构

struct elf32_rel {

 Elf32_Addr        r_offset; 

 Elf32_Word      r_info;                          //SYMBOL<<8+TYPE&0xff.

} ;

r_offset是需要进行重定位的地址;

SYMBOL是重定位以后需要指向的符号;

TYPE是重定位的类型。

 

只要我们遍历所有的重定位节,对其中的所有重定位项进行遍历,就可以实现重定位了。

3 ELF文件的符号解析

在上面的算法中,我们提到 “2通过对符号进行解析,就可以得到运行时该符号的正确地址,至于具体要怎么做,就是符号解析的工作了。

所谓的符号解析,实际上就是:通过给定的符号名,找到该符号在内存中的正确地址。

ELF文件中,符号解析是通过符号表符号名表实现的。

 

符号名表,是许多变长字符串的合集,并且以两个’\0’作为结尾。

 

符号表,是由许多符号表项组成的数组。

下面是符号表项的结构,

struct elf32_sym{

 Elf32_Word        st_name;        //index into the symbol string table

 Elf32_Addr        st_value;

 Elf32_Word        st_size;           //size of the symbol. 0 for no size or unkown size

 unsigned char     st_info;          //BIND<<4+TYPE&0x0f     

 unsigned char     st_other;         //0 for reserve

 Elf32_Half   st_shndx;              //relevant section table index, some indicates special meanings

};

St_name是符号的名称的index(详见下文)

St_value是该符号在内存中的地址(详见下文)

st_size是该符号的大小,以字节为单位

BIND说明符号是内部符号还是外部符号

TYPE说明符号的类型,是函数,还是变量,等等

st_other恒为0,保留字节

st_shndx是符号所在的sectionindex(详见下文)

 

符号的名称,是由st_name和符号名表决定。St_name是一个指向符号名表的索引值,通过符号名表基地址”+st_name就可以得到符号名的地址。

之所以采用这种方法,是为了处理变长的符号名。

我们知道,不同符号名的长度也会有很大的不同。例如int I;的符号名只有1个字符。而int mMyLinkTypeofDoubleLoaderProgram却有34个字符。如果采用数组的方式,会浪费大量的空间,malloc出来的动态内存又不适合硬盘存储。

这是一个非常值得学习的技巧??在涉及硬盘存储时,可以采用索引+字符串表的形式存储变长的字符串(或者其它变长信息)。

 

符号的值(也就是符号在内存中的地址,我们要计算的东西)是由st_valuest_shndx决定的。

relocatable文件中,st_value是符号相对于某个section起始地址的偏移,这个section是由st_shndex指定的(COMMON类型的section除外,它很少会被用到)

executableshared object文件中,st_value包含一个虚拟地址。这个地址是和ELF文件的预定装载地址联系在一起的。在进行动态链接时,我们需要计算当前装载地址预定装载地址之间的差。

4            ELF装载函数的设计

整体流程:

1遍历section header table,获取所有需要装载的节,以及重定位节的信息

2装载所有需要装载的节

3对所有重定位节,执行重定位。

 

重定位流程:

1遍历某个重定位节,对其中的每一个重定位项,执行以下处理

       a求出需要进行重定位的地址(P

       b求出重定位的目标地址(S),若该符号无法解析,则置S0

              c1S不为0,则执行重定位:

                     R_386_32*P = *P + S

                     R_386_PC32*P=*P + S - P

              C2S0,表明该符号解析失败,要进行失败处理。

在我的程序中,处理方法为:在REST RELOCATION数组中加入一个新项,待适当的时机再进行重解析。

 

源代码中有些部分是和我的组件式操作系统设计有关的,在阅读代码时可跳过不看。

通常的动态链接程序,会采用依赖性加载的方式;而我采用的是“rest relocation list”的方式,请读者阅读代码时注意,如果对这个技术不感兴趣,也可跳过不看。

 

阅读(447) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~