FROM http://bbs.chinaunix.net/thread-2098566-1-1.html
为了保持简洁,本文省去了对部分细节的描述,例如weak型变量,和GOT等重定位类型。 本文假设读者了解ELF文件格式。代码在附件中。如果论坛不支持附件,可以发邮件给我要 1 ELF文件的装载 在ELF文件中,使用section和program两种结构描述文件的内容。通常来说,ELF可重定位文件采用section,ELF可执行文件使用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是符号所在的section的index(详见下文) 符号的名称,是由st_name和符号名表决定。St_name是一个指向符号名表的索引值,通过“符号名表基地址”+st_name就可以得到符号名的地址。 之所以采用这种方法,是为了处理“变长”的符号名。 我们知道,不同符号名的长度也会有很大的不同。例如int I;的符号名只有1个字符。而int mMyLinkTypeofDoubleLoaderProgram却有34个字符。如果采用数组的方式,会浪费大量的空间,malloc出来的动态内存又不适合硬盘存储。 这是一个非常值得学习的技巧――在涉及硬盘存储时,可以采用索引+字符串表的形式存储变长的字符串(或者其它变长信息)。 符号的值(也就是符号在内存中的地址,我们要计算的东西)是由st_value和st_shndx决定的。 在relocatable文件中,st_value是符号相对于某个section起始地址的偏移,这个section是由st_shndex指定的(COMMON类型的section除外,它很少会被用到)。 在executable和shared object文件中,st_value包含一个虚拟地址。这个地址是和ELF文件的预定装载地址联系在一起的。在进行动态链接时,我们需要计算“当前装载地址”与“预定装载地址”之间的差。 4 ELF装载函数的设计 整体流程: 1遍历section header table,获取所有需要装载的节,以及重定位节的信息 2装载所有需要装载的节 3对所有重定位节,执行重定位。 重定位流程: 1遍历某个重定位节,对其中的每一个重定位项,执行以下处理 a求出需要进行重定位的地址(P) b求出重定位的目标地址(S),若该符号无法解析,则置S为0 c1若S不为0,则执行重定位: R_386_32:*P = *P + S R_386_PC32:*P=*P + S - P C2若S为0,表明该符号解析失败,要进行失败处理。 在我的程序中,处理方法为:在REST RELOCATION数组中加入一个新项,待适当的时机再进行重解析。 源代码中有些部分是和我的组件式操作系统设计有关的,在阅读代码时可跳过不看。 通常的动态链接程序,会采用“依赖性”加载的方式;而我采用的是“rest relocation list”的方式,请读者阅读代码时注意,如果对这个技术不感兴趣,也可跳过不看。 ----------------------------------- 邓小超 2007-10-10 [email]dxcnjupt@126.com[/email] QQ23559356 [attach]178535[/attach] [ 本帖最后由 dxcnjupt 于 2007-10-17 11:16 编辑 ] ELF Loader.rar ELF Loader v1.01.rar src code stored in kendyhj9999 mail box dxcnjupt 回复于:2007-10-10 16:21:27 补充一下 jmp 1000h其实是相对跳转,不需要重定位 必须要 mov eax, 1000h jmp eax 或者 jmp 段:1000h 才是绝对跳转 嗯,写文档的时候不想解释的太多,怕搞的太复杂,不容易阅读。 ruoyisiyu 回复于:2007-10-11 17:18:39 刚好需要这方面的资料,深为感谢:em17: :em17: DustBowl 回复于:2007-10-12 21:11:50 看过代码了,是动态加载器的静态链接过程吧。 dxcnjupt 回复于:2007-10-13 19:02:54 动态加载器的静态链接过程:em14: 我的理解是 静态链接:在发布软件之前链接,在发布软件之后,无法修改程序的运行结果(除非打补丁直接修改可执行文件),运行时加载就可以直接使用。 动态链接:在运行软件之前进行链接,在发布软件之后,只要用新的dll覆盖旧的dll,就可以修改程序的运行结果,每次运行时都必须进行一次链接。 按照上面这个理解,我把这个程序定位为动态链接器。 其实从技术上来说,静态链接和动态链接几乎是一样的(因为它们使用的是一样的原理,一样的数据结构,做同一件事情);区别在于程序使用的时机--是在ld这样的链接器里面用来生成ELF可执行程序,还是在linux这样的操作系统中用来装载进程。 不知LS的朋友有何高见??? 或许你认为我没有用PIC位置无关代码,所以不是动态链接? 我觉得PIC只是动态链接的一种辅助手段,它不影响动态链接器的本质。 因为这段程序是用来装载操作系统内核的,我当然不可能把操作系统内核做成PIC。 PIC的好处是重定位项少,加载快。 而我可以用回写的办法来加快内核的装载速度。 回写的算法已经有了,但是目前我的文件系统还没有写好,所以暂时无法实现。 mik 回复于:2007-10-14 11:04:11 顶一下~ 有时间,会研究一下LZ的大作,希望LZ继续发表一些好的技术文章 honckly 回复于:2007-10-14 11:05:55 :mrgreen: 收藏了 多谢lz yuanchuang 回复于:2007-10-14 15:19:28 恩,谢谢,呵呵 cu回帖还要超过10个字啊? dxcnjupt 回复于:2007-10-17 11:24:39 代码已经更新为1.01版 具体更新信息可以看文件夹中的更新说明 MingLin1231 回复于:2007-10-17 13:49:28 好东西,社会需要这些奉献的人 yanyue 回复于:2007-10-17 17:06:17 好文章 ,收藏学习了~ logonly 回复于:2007-10-17 20:50:16 收藏先,有时间好好研究! lanneret_sky 回复于:2007-10-18 12:17:21 努力大家一起sa'd's'da'a'a'a'a'a'a'a'a'a'a'a'a jixu_yang 回复于:2007-10-18 19:40:22 好文,收藏先!!!:lol: xiaozhang353 回复于:2007-10-21 23:49:29 这个一定要顶一下,以后慢慢看 ztg 回复于:2007-10-27 18:01:13 LZ辛苦了,好文章,收藏先,多谢了 embededboy 回复于:2007-11-06 09:30:45 LZ辛苦 我在读代码 andyzn 回复于:2007-11-22 15:19:31 那请问linux里面处理elf格式,特别是load程序时候相关的源代码都是哪些呢 andyzn 回复于:2007-11-22 16:58:46 引用:原帖由 dxcnjupt 于 2007-10-10 16:03 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6407175&ptid=911079] ![]() 为了保持简洁,本文省去了对部分细节的描述,例如weak型变量,和GOT等重定位类型。 本文假设读者了解ELF文件格式。代码在附件中。 如果论坛不支持附件,可以发邮件给我要 1 ELF文件的装载 在ELF文件中,使 ... //这个部分应该是处理BSS段吧, 代码中好象处理的是rodata段 if( Section[2].pSecHeader!= 0) { int size; size = Section[2].pSecHeader->sh_size; Section[2].pSec = temp_Buf; //ro???应该是BSS while( size) { *temp_Buf = 0; temp_Buf++; size--; } } hongmingjian 回复于:2007-11-24 23:10:37 我比较喜欢看FreeBSD的源代码。根据自己的经验,理解这个过程可以按照下面的顺序来读代码: /src/sys/kern/kern_exec.c: do_execve /src/sys/kern/imgact_elf.c: __CONCAT(exec_, __elfN(imgact)) /src/libexec/rtld-elf/i386/rtld_start.S /src/libexec/rtld-elf/rtld.c: _rtld /src/lib/csu/i386-elf/crt1.c: _start 上面的目录都是FreeBSD中源代码的组织。 xiaozhao73 回复于:2007-11-27 06:10:18 为什么呢? 有的section,如:.rel.dyn, rel.plt会在.dynamic section中有相同的项 dxcnjupt 回复于:2007-11-27 09:25:28 多谢andyzn 帮我找到了这个bug 装载.bss和.rodata的代码应该换一下。 我刚开始写的时候是直接用section_text,section_bss做变量名的,后来在升级代码的时候才改成Section[8],这样便于扩展。 结果升级的时候把数组下标弄错了:lol: dxcnjupt 回复于:2007-11-27 09:49:59 另外代码中的 if( kstrcmp( SecName, ".bss") == 0) { Section[3].pSecHeader = SecEntry; } 应该改成 if( kstrcmp( SecName, ".bss") == 0) { Section[3].pSecHeader = SecEntry; Section[3].SecIndex = SN; } 下一次我把这两个bug改好,再加上对section对齐(__attribute__((aligned)))的支持,加强出错处理,再升级一下代码。 注释确实是太少了,下次多加一些。文档里面再配些图。 asdfning 回复于:2007-12-05 13:46:29 确实是个好东西,想跟LZ切磋一下. lvlei517 回复于:2007-12-06 14:50:51 好文章!谢谢搂主了 davycu 回复于:2007-12-07 16:09:29 好文章哈,收藏学习了~ lyh123 回复于:2009-04-22 11:31:39 thanks!! zerocluo 回复于:2009-04-22 17:23:50 :lol: snowpinex 回复于:2009-04-28 16:44:37 谢谢楼主了,最近正在学这方面的东西。 emmoblin 回复于:2009-04-28 17:03:28 不错,只是大概知道了原理,现在更清楚了细节了 |
![]() |
原文链接:http://linux.chinaunix.net/bbs/viewthread.php?tid=911079 转载请注明作者名及原文出处 |
ELF文件的动态链接器 原理 设计和代码
最新推荐文章于 2024-08-27 22:35:52 发布