当前位置:首页 » 专业资讯
开发技术指南» 文章正文
    引言: 王瑞川 (jeppeterone@163.com) 2003 年 10 月动态链接,一个经常被人提起的话题。
 

 

 ·基础linux备份与恢复    »显示摘要«
    摘要:unix系统为每个文件都记录这三个不同的时间,第一个是mtime,即修改时间。无论何时, 只要文件内容被改变,mtime的值就会被相应修改。第二个是atime,即访问时间。只要文 件被访问(比如运行或读取),它就会被修改。第三个是ctime,即变更时间。当文件的属性 发生变化(比如改变权限或者所有关系)时,ctime的值就会被改变。管理员用ctime来查 找黑客。备份会改变atime,tar,cpio,dd都会这样做,dump通过原始设备来读取文件系统,因......
 ·before main() 分析[转]    »显示摘要«
    摘要:本文分析了在main()之前的elf程序流程,试图让您更清楚的把握程序的流程的脉络走向。从而更深入的了解elf。不正确之处,还请斧正。★ 综述elf的可执行文件与共享库在结构上非常类似,它们具有一张程序段表,用来描述这些段如何映射到进程空间.对于可执行文件来说,段的加载位置是固定的,程序段表中如实反映了段的加载地址.对于共享库来说,段的加载位置是浮动的,位置无关的,程序段表反映的是以0作为基准地址的相对加载地址.尽管共享库的连接是不充分的,为了便于测试动态......


Intel平台下Linux中 ELF文件动态链接的加载、解析及实例分析(一): 加载

转载自:ibm developerworks 中国网站

【相关文章:OPENSSL命令行签发证书

【扩展阅读:重新装了一次lfs

动态链接,一个经常被人提起的话题。但在这方面很少有文章来阐明这个重要的软件运行机制,只有一些关于动态链接库编程的文章。本系列文章就是要从动态链接库源代码的层次来探讨这个问题。 【扩展信息:Linux中的shell命令

王瑞川 (jeppeterone@163.com) 2003 年 10 月

当然从文章的题目就可以看出,intel平台下的linux elf文件的动态链接。一则是因为这一方面的资料查找比较方便,二则也是这个讨论的意思比其它的动态链接要更为重要(毕竟现在是intel的天下)。当然,有了这么一个例子,其它的平台下的elf文件的动态链接也就大同小异。你可以在阅读完了本文之后"举一隅,而反三隅"了。

由于这是一个系列的文章,我计划分三部分来写,第一部分主要分析加载,涉及dl_open这个函数的内容,但由于这个函数所包含的内容实在太多。这里主要是它的_dl_map_object与_dl_init这两个部分,因为这里是把动态链接文件通过在elf文件中的得到信息映射到内存空间中,而_dl_init中是一个特殊的初始化。这是对面向对象的函数实现的。

第二部分我将分析函数解析与卸载,这里要讲的内容会比较多,但每一个内容都不会多。首先是在前一篇中没有说完的dl_open中的涉及的_dl_map_object_deps与_dl_relocate_object两个函数内容,因为这些都与函数解析的内容直接相关,所以安排在这里。而下面的函数解析过程_dl_runtime_resolve是在程序运行中的动态解析过程。这里从本质上来讲没有太多的代码,但它的精巧程度却是最多的(正是我这三篇文章的核心之处)。最后是一个dl_close的实现。这里是一个结尾的工作,顺带一下是_dl_signal_cerror,与 _dl_catch_error的错误例外处理。

第三部将给出injectso实例分析与应用,会介绍一个应用了动态链接的实例,并可以在日后的程序调试过程中使用的injectso 实例,它不仅可以让我们对前面所说的动态链接原理有一个更感性的认识,而且就这个实例而言,还可以在以后的代码开发过程中来作为一种动态打补丁的工具,甚至有可能,我会在以后的文章中会用这个工具来介绍新的技术。

一、历史问题

关于动态链接,可以说由来已久。如果追溯,最早的思想就在五十年代就有了,那时就想把一些公用的代码放在内存中的一个地方上,在别的地址用call便是了。到后来又发展到了 loading overlays(就是把在程序运行生命期不同的代码在不同的时间段被加入内存),这是在六十年代的事。但这只能算是"滥觞"时期。接近于我们现在所说的动态链接是在unix操作系统之后,因为从unix的设计结构而言,本身就是分成模块来实现一个复杂的功能的操作系统。但这些还不是现代意义上的动态链接,原因是现代意义上的动态链接要符合两个特点:

1、动态的加载,就是当这个运行的模块在需要的时候才被映射入运行模块的虚拟内存空间中,如一个模块在运行中要用到mylib.so中的myget函数,而在没有调用mylib.so这个模块中的其它函数之前,是不会把这个模块加载到你的程序中(也就是内存映射),这些内容在内核中实现,用的是页面异常机制(我可能在另一篇文章中提到这个问题)。

2、动态的解析,就是当要调用的函数被调用的时候,才会去把这个函数在虚拟内存空间的起始地址解析出来,再写到专门在调用模块中的储存地址内,如前面所说的你已经调用了myget,所以mylib.so模块肯定已经被映射到了程序虚拟内存之中,而如果你再调用mylib.so中的myput函数,那它的函数地址就在调用的时候才会被解析出来。

(注:这里用的程序就是一般所说的进程process,而模块既可能是你的程序的二进制代码,也可能是被你的程序所依赖的别的共享链接文件-------同样elf格式。)

在这两点中很有点像现在的操作系统中对内存的操作,也就是只有当要用到一个内存空间中的时候才会进行虚拟空间映射,而不是过早的把所有的空间映射好,而只有当要从这个内存空间读的时候才分配物理空间。这有点像第一条。而只有当对这个内存空间进行写的时候产生一个cow(copy on write)。这就有点像第二条。

这样的好处就是充分避免不必要的开销。因为任何一个程序在运行的时候,大部分情况下,不可能用到所有的调用函数。

这样的思想方法提出与实现都是在八十年代的sun公司的sunos的系统上。

关于这一段历史,请你参见资料[1]。

elf二进制格式文件与现代的动态链接思想大致是在同一时段形成的,它的来源是at&t公司的最早的unix中的a.out二进行文件格式。bell labs的工作人员为了使这种在unix的早期主要的文件格式适应当时新的软件与操作系统的要求(如aix,sunos,hp-ux这样的unix变种,对更广泛的应用程序的扩展要求,对面向对象的支持等等),就发明了elf文件格式。

我在这里并不详细讨论elf文件的具体细节,这本来就可以写一篇很长的文章,你可以参看资料[2]来得到关于它的abi (application binary interface的规范)。但在elf文件所采用的那种分层的管理方式却不仅在动态链接中起着重要的作用,而且这一思想可以说是我们计算机中的最古老,也是最经典的思想。

对每个elf文件,都有一个elf header,在这里的每个header有两个数据成员,就是

elf32_off e_phoff;

elf32_off e_shoff;

它们分别代表了program header 与section header 在elf文件中的偏移量。program header 是总纲,而section header 则是第一个小目。

elf32_addr sh_addr;

elf32_off sh_offset;

sh_addr这个section 在内存中的映射地址(对动态链接库而言,这是一个相对量,它与整个elf文件被加载的l_addr形成绝对地址)。sh_offset是这个 section header在文件中的偏移量。

用一图来表示就是这样的,它就是用elf header 来管理了整个elf文件:

举个例子,如果要从一个elf动态链接库文件中,根据已知的函数名称,找到相应的函数起始地址,那么过程是这样的。

先从前面的elf 的ehdr中找到文件的偏移e_phoff处,在这其中找到为pt_dynamic 的d_tag的phdr,从这个地址开始处找到dt_dynamic的节,最后从其中找到这样一个elf32_sym结构,它的st_name所指的字符串与给定的名称相符,就用st_value便是了。

这种的管理模式,可以说很复杂,有时会看起来是繁琐。如找一个function 的起始地址就要从 elf header >> program header >> symbol section >> function address 这样的四个步骤。但这里的根本的原因是我们的计算机是线性寻址的,并且冯*诺依曼提出的计算机体系结构相关,所以在前面说这是一个古老的思想。但同样也是由于这样的一个elf文件结构,很有利于elf文件的扩充。我们可以设想,如果有一天,我们的elf文件为了某种原因,对它进行加密。这时如果要在elf 文件中保存密钥,这时候可以在elf文件中开辟一个专门的section encrypt ,这个section 的type 就是st_encrypt,那不就是可以了吗?这一点就可以看出elf文件格式设计者当初的苦心了(现在这个真的有这么一个节了)。

二、代码举例

讲了这么多,还没有真正讲到在intel 32平台下linux动态链接库的加载与调用。在一般的情况下,我们所编写的程序是由编译器与ld.so这个动态链接库来完成的。而如果要显式的调用某一个动态链接库中的程序,则下面是一个例子。

#include

#include

main()

{

void *libc;

void (*printf_call)();

char* error_text;

if(libc=dlopen("/lib/libc.so.5",rtld_lazy))

{

printf_call=dlsym(libc,"printf");

(*printf_call)("hello, worldn");

dlclose(libc);

return 0;

}

error_text= dlerror();

printf(error_test);

return -2;

}


...   下一页
 ·gcc 4.0 的新特性    »显示摘要«
    摘要:本文侧重介绍了 gcc 4.0 内部结构相对于 3.4.x 版本的一些全新变化。本文侧重介绍了 gcc 4.0 内部结构相对于 3.4.x 版本的一些全新变化。gcc(gnu compiler collection) 是 gnu(gnus not unix) 计划提供的编译器家族,它能够支持 c, c++, objective-c, fortran, java 和 ada 等等程序设计语言前端,同时能够运行在 x86, x86-64, ia-64, pow......
» 本期热门文章:

©2000-2007 All Rights Reserved. 最佳浏览:1024X768 MSIE