内存中。在加载之前,内核把段的标记直接传递给 mmap(),段的标记指示该段
在内存中是否可读、可写,可执行。显然,文本段是只读可执行,而数据段是可
读可写。
这种方式是利用了现代操作系统和处理器对内存的保护功能。
著名的
Shellcode( 参考资料 17)的编写技巧则是突破此保护功能的一个实际例子。
2:内核分析出 ELF 文件标记为 PT_INTERP 的段中所对应的动态连接器名称,
并加载动态连接器。现代 LINUX 系统的动态连接器通常是 /lib/ld-linux.so.2,
相关细节在后面有详细描述。
3:内核在新进程的堆栈中设置一些标记-值对,以指示动态连接器的相关操作。
4:内核把控制传递给动态连接器。
5:动态连接器检查程序对外部文件(共享库)的依赖性,并在需要时对其进行
加载。
6:动态连接器对程序的外部引用进行重定位,通俗的讲,就是告诉程序其引用
的外部变量/函数的地址,此地址位于共享库被加载在内存的区间内。动态连接
还有一个延迟(Lazy)定位的特性,即只在"真正"需要引用符号时才重定位,这
对提高程序运行效率有极大帮助。
7:动态连接器执行在 ELF 文件中标记为 .init 的节的代码,进行程序运行的初
始化。在早期系统中,初始化代码对应函数 _init(void)(函数名强制固定),在现
代系统中,则对应形式为
void
__attribute((constructor))
init_function(void)
{
……
}
其中函数名为任意。
8:动态连接器把控制传递给程序,从 ELF 文件头部中定义的程序进入点开始
执行。在 a.out 格式和 ELF 格式中,程序进入点的值是显式存在的,在 COFF
格式中则是由规范隐含定义。
从上面的描述可以看出,加载文件最重要的是完成两件事情:加载程序段和数据
段到内存;进行外部定义符号的重定位。重定位是程序连接中一个重要概念。我
们知道,一个可执行程序通常是由一个含有 main() 的主程序文件、若干目标文
件、若干共享库(Shared Libraries)组成。(注:采用一些特别的技巧,也可编
写没有 main 函数的程序,请参阅 参考资料 2)一个 C 程序可能引用共享库定
义的变量或函数,换句话说就是程序运行时必须知道这些变量/函数的地址。在
静态连接中,程序所有需要使用的外部定义都完全包含在可执行程序中,而动态
连接则只在可执行文件中设置相关外部定义的一些引用信息,真正的重定位是在
程序运行之时。静态连接方式有两个大问题:如果库中变量或函数有任何变化都
必须重新编译连接程序;如果多个程序引用同样的变量/函数,则此变量/函数会
在文件/内存中出现多次,浪费硬盘/内存空间。比较两种连接方式生成的可执行
文件的大小,可以看出有明显的区别。
评论0
最新资源