实用经验 96 可执行文件*.exe(*.dll)剖析

关于可执行文件,维基百科上有这么一段定义:可执行文件在计算机科学上,指一种内容可被电脑解释为程序的电脑文件。通常可执行文件内,含有以二进制编码的微处理器指令,也因此可执行文件有时称为二进制档。这些二进制微处理器指令的编码,于各种微处理器有所不同,故此可执行文件多数要分开不同的微处理版本。一个电脑文件是否为可执行文件,主要由操作系统的传统决定。例如根据特定的命名方法(如扩展名为exe)或文件的元数据信息(例如UNIX系统设置“可运行”权限)。

可执行历史

  • 可执行文件的格式跟操作系统和编译器密切相关,所以不同的系统平台下会有不同的格式,但是这些格式都大同小异。如无论是可执行文件还是DLL和lib。在Windows中他们都统一采用PE-COFF格式存储,Linux则采用ELF格式存储。
  • COFF最早由Unix System V Release 3提出并使用,后来在其基础上指定了PE格式标准。Unix System V Release 3在COFF基础上引入了ELF格式,目前流行的Linux以ELF作为可执行文件格式标准。
  • 现在PC平台流行的可执行文件格式主要是Windows的PE和linux的ELF,他们都是COFF格式的变种。其实不仅仅是可执行文件按照可执行文件格式存储,动态连接库(Windows的DLL和Linux的so)以及静态链接库(Windows的lib和Linux的a)都按照可执行文件格式存储。

你可能想知道为什么要关注可执行文件的格式。答案永远是:操作系统的可执行文件格式和数据结构展现了操作系统内部许多信息。通过理解EXE和DLL的内部情况,你会发现你已经变成你周围一个更优秀的程序员。作为一个名C++开发程序员,有时候了解可执行文件的内部结构是十分必要的。通过了解可执行文件,你可进一步理解执行行文件的运行原理。甚至在某些情况下,对异常的定位也是有一定的帮助的。

我们大概能猜到,可执行文件中的内容至少有编译后的机器指令、数据。是的没错的。但是实际上,除了这些内容,目标文件中还包含比如符号表、调试信息、字符串等。一般可执行文件将这些信息按照不同的属性,以“节”的形式存储,有时也叫“段”。这种分配机制适合于COFF格式、ELF格式,也适合PE文件格式。

在这里插入图片描述

图13-14 COFF文件格式

COFF文件格式如图13-14。COFF文件头部的数据结构:

struct filehdr
{
       unsigned short  f_magic;    /* 魔数 */
       unsigned short  f_nscns;    /* 节个数 */
       long            f_timdat;   /* 文件建立时间 */
       long            f_symptr;   /* 符号表相对文件的偏移量 */
       long            f_nsyms;    /* 符号表条目个数 */
       unsigned short  f_opthdr;   /* 可选头部长度 */
       unsigned short  f_flags;    /* 标志 */
 };

COFF文件头部中魔数表示针对的机器类型,例如 0x014c 相对于 I386 平台,而 0x268 相对于 Motorola 68000系列等。当 COFF 文件为可执行文件时,字段 f_flags 的值为 F_EXEC(0X00002),同时也表示此文件没有未解析的符号,换句话说,也就是重定位在连接时就已经完成。由此也可以看出,原始的 COFF 格式不支持动态连接。为了解决这个问题以及增加一些新的特性,一些操作系统对 COFF 格式进行了扩展。Microsoft 设计了名为 PE(Portable Executable)的文件格式,主要扩展是在COFF文件头部之上增加了一些专用头部。紧接文件头部的是可选头部,COFF 文件格式规范中规定可选头部的长度可以为0。

Linux的ELF文件有三种类型:可重定位文件:也就是通常称的目标文件,后缀为.o。 共享文件:也就是通常称的库文件,后缀为.so。总的来说,可执行文件的格式与上述两种文件的格式之间的区别主要在于观察的角度不同:一种称为连接视图(Linking View),一种称为执行视图(Execution View)。ELF文件的总体布局如图13-15所示。

在这里插入图片描述

图13-15 ELF文件布局图

ELF文件格式最前部是ELF文件头,他包含了描述整个文件的基本属性,比如ELF文件的版本、目标机器型号、程序入口地址等。紧接其后的是ELF文件的各个段。其中ELF文件中与段有关的重要结构就是段表,该表描述的ELF文件的所有段信息,比如每个段的段名、段长度、在文件中的偏移、读写权限等等。

PE可执行文件格式是Windows在继承COFF基础上定义的一种文件标准。PE文件一个非常好的地方就是它的数据结构在磁盘上与在内存中一样。加载一个可执行文件到内存(例如通过调用LoadLibrary函数)主要就是把PE文件中的某个部分映射到地址空间中。因此如果你知道如何在一个PE文件中找到某些内容,你几乎可以确定当文件被加载进内存时可以找到同样的信息。

注意

  • 虽然上文中说道数据结构在磁盘上与在内存中一样,但PE文件并不是作为单一的内存映射文件被映射进内存的这一点非常重要。
  • Windows加载器查看PE文件并确定文件中的哪些部分需要被映射。当映射进内存时,文件中的高偏移相对于内存中的高地址。某项内容在磁盘文件中的偏移可能与它被加载进内存之后的偏移不同,但是将磁盘文件中的偏移转换成内存偏移需要的所有信息都存在。

PE文件布局图如图13-16所示。当PE文件由Windows加载器加载进内存时,它在内存中被称为模块(module)。文件被映射到的内存的起始地址被称为HMODULE。这是需要记住的一点:给你一个HMODULE,你就知道在那个地址处到底有什么样的数据结构,并且你可以根据PE文件的知识找到内存中所有其它的数据结构。这个强大的功能可以被用作其它用途,例如拦截API。

在这里插入图片描述

图13-16 PE文件布局图

内存中的模块代表一个进程所需的可执行文件中的所有代码、数据和资源。PE文件中的其它部分可能会被读取,但并不被映射进内存(例如重定位节)。一些部分可能根本就不被映射,例如放在文件末尾的调试信息。PE文件头中的一个域告诉系统将这个可执行文件映射进内存时需要占用多少内存。不被映射的数据放在文件末尾,位于所有需要被映射的部分之后。

描述PE文件(和COFF文件)的关键位置是WINNT.H文件。在这个头文件中,你能找到几乎所有结构的定义、枚举类型以及使用PE文件或它在内存中的等价结构所需的定义。当然在其他地方有这方面的文档,例如MSDN中有“Microsoft可移植可执行文件和通用目标文件格式文件规范”(2001年十月MSDN的Specifications下)。但是WINNT.H确定了PE文件最终的样子。

我们已经介绍了三种标准的可执行文件格式。现在我们看一下C++代码编译成一个可执行文件是如何存储的。图13-17是一个简单的程序编译成可执行文件后的结构。从图13-16来看,可得出如下结论:
在这里插入图片描述

图13-17 程序与可执行文件

结论

  • C++语言编译后执行语句都编译成机器码,保存到.text段;
  • 已初始化的全局变量和局部静态变量,都保存到.data段中;此外.data中不仅保存全局变量和局部静态变量,各类自动变量和const变量也保存到.data段中。而未初始化的全局变量和局部静态变量默认值都是0,保存到.bss段。.bss段至少为未初始化的全局变量和局部静态变量预留位置而已,他并没有内容,也不在文件中占据空间。
  • 总体而言,程序源代码编译以后主要分成两个段:程序指令和程序数据。代码段属于成员指令,而数据段和.bss段属于程序数据。

现在语言的存储和数据的存储都已经说明白了。也许你还有疑问函数调用栈、new(malloc)出来的变量怎么保存的呢?

他们也是程序正常运行的必要条件。他们的位置位于.bss数据段之后。调用栈对应栈段,new(malloc)对应堆段。考虑到上述讨论,一个整体的可执行文件格式如图13-18所示。

在这里插入图片描述

13-18 可执行文件格式

有了上述的描述。最后讨论一下大家经常定义或声明的变量,都存放到内存的什么位置了:

(1)、函数中声明的变量——栈区

(2)函数中已初始化的static变量——数据区中的.rwdata区

(3)未初始化的全局变量,未初始化的static变量 ——数据区的.bss区

(4)“liuguang” ——数据区的.rodata区

(5)const修饰的变量——可能存放于数据区的.rodata,亦可能放于.rwdata区或.bss区。

const static int a = 0; // .bss区
const static int b = 2; // .rwdata区
const int c = 20; // .rodata区

(6)malloc,new 申请的数据——堆区

请谨记

  • 每个C++可执行程序都包含文件头,代码段,数据段。他们是怎么存储的这是你需要掌握的。
  • 了解可执行文件布局,掌握程序中各部分分别存储在什么位置。可以指导我们编写出更优秀的代码。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值