操作系统中的文件是一种抽象的机制,提供了一种在磁盘上保存信息而且方便以后读取的方法。在Windows操作系统中,一个用户可以最直接体会到的文件的形式就是以.exe、.dll等为扩展名的可执行文件。伴随着Windows操作系统的不断进步,其可执行文件的格式也发生了巨大变化。这期间主要有4个过程:DOS中出现的最简单的以.com为扩展名的可执行文件和以.exe为扩展名的MZ格式(MZ是MZ格式的主要作者Mark Zbikowski的名字的缩写)的可执行文件,Win 3.x下出现的NE(New Executable:分段可执行文件)格式的.exe和.dll文件,Win 3.x和Win9x所专有的LE(Linear Executable:线性可执行文件,专用于VxD文件),Win9x和Win NT/2000/XP下的32位的可执行文件PE(Portable Executable:可移动的可执行文件)。这里面com、MZ和NE属于Win16,PE属于Win32,LE可以兼容Win16和Win32。
在一个操作系统中,可执行的代码最终被装入内存执行之前是以文件的方式存放在磁盘中的,也就是以可执行文件的方式。下面是Microsoft Windows操作系统中的可执行文件的概述。
1.com格式
Windows下最简单的可执行文件就是DOS下的以.com为扩展名的com文件。com文件是旧有的只有64kb内存的cp/m机器的产物。com格式文件最大64K,com文件内含16位程序的二进制代码映像,没有重定位信息。
com文件包含程序的二进制代码的一个绝对映像。也就是说,为了运行程序准确的处理器指令和内存中的数据,MS-DOS通过直接把该映像从文件拷贝到内存而加载com程序;它不作任何改变。
为加载一个com程序,MS-DOS首先试图分配内存,因为com程序必须位于一个64K的段中,所以com文件的大小不能超过65,024(64K减去用于PSP的256字节和用于一个起始堆栈的至少256字节)。如果MS-DOS不能为程序、一个PSP、一个起始堆栈分配足够内存,则分配尝试失败。否则,MS-DOS分配尽可能多的内存(直至所有保留内存)。即使com程序本身不能大于64K。在试图运行另一个程序或分配另外的内存之前,大部分com程序释放任何不需要的内存。
分配内存后,MS-DOS在该内存的头256字节建立一个PSP(Program Segment Prefix,程序段前缀),PSP结构如下:
偏移
|
大小(Byte)
|
说 明
|
0000h
|
02
|
中断20H
|
0002h
|
02
|
以节计算的内存大小(利用这个可看出是否感染引导型病毒)
|
0004h
|
01
|
保留
|
0005h
|
05
|
至DOS的长调用
|
000Ah
|
02
|
INT 22H 入口 IP
|
000Ch
|
02
|
INT 22H 入口 CS
|
000Eh
|
02
|
INT 23H 入口 IP
|
0010h
|
02
|
INT 23H 入口 CS
|
0012h
|
02
|
INT 24H 入口 IP
|
0014h
|
02
|
INT 24H 入口 CS
|
0016h
|
02
|
父进程的PSP段值(可测知是否被跟踪)
|
0018h
|
14
|
存放20个SOFT号
|
002Ch
|
02
|
环境块段地址(从中可获知执行的程序名)
|
002Eh
|
04
|
存放用户栈地址指针
|
0032h
|
1E
|
保留
|
0050h
|
03
|
DOS调用(INT 21H / RETF)
|
0053h
|
02
|
保留
|
0055h
|
07
|
扩展的FCB头
|
005Ch
|
10
|
格式化的FCB1
|
006Ch
|
10
|
格式化的FCB2
|
007Ch
|
04
|
保留
|
0080h
|
80
|
命令行参数长度
|
0081h
|
127
|
命令行参数
|
如果PSP中的第一个FCB含有一个有效驱动器标识符,则置AL为00h,否则为0FFh。MS-DOS还置AH为00h或0FFh,这依赖于第二个FCB是否含有一个有效驱动器标识符。 mov ax,type(MAN) ;取结构所占字节数 .exit ;可设置返回码 end 2.MZ格式 com发展下去就是MZ格式的可执行文件,这是DOS所能了解的具有重定位功能的可执行文件格式。MZ可执行文件内含16位代码,在这些代码之前加了一个文件头,文件头中包括各种说明数据,如第一句可执行代码执行指令时所需要的文件入口点、堆栈的位置、重定位表等,操作系统根据文件头重的信息将代码部分装入内存,让后根据重定位表修正代码,最后在设置好堆栈后从文件头中指定的入口开始执行。所以DOS可以把程序放在任何它想要的地方。下图是MZ格式的可执行文件的简单结构示意图:
4.LE格式在Windows3.x的时代,从DOS启动Windows,Windows在把机器转到保护模式之前需要在实模式下做一些初始化。实模式的16位代码必须和32位代码一起放在可执行文件中。旧的DOS下的可执行文件和NE格式的可执行文件无法满足这个要求,于是从Win 3.x起到Win 9x,产生了一种新型的可执行文件格式LE,它只适用于工作于系统底层的、同时包含16位代码和32位代码的VxD驱动程序。OS/2 2.x也使用LE格式。
一般而言,保护模式下的可执行文件会在不同的Sections中放置程序代码和数据,利用可执行文件头中的各种属性标志来告诉可执行文件加载器在面对这些Sections时的各种细节动作。但是VxD却将程序代码和数据混杂在段之中,通过不同段前的标识来表明该段在运行时的特性。程序代码和数据之所以能够混杂在一起,而仍然能够有效运作,是因为VxD所使用的平坦模式的代码和平坦模式下的数据选择器有相同的基地址与限制因素。因此不论使用上述哪一个寄存器缓存器都可以取用程序代码或数据。
下图是LE格式的可执行文件的结构示意图:
在LE文件中,代码和数据被存放在几类运行属性不同的段中。下面是LE文件之中一些可用的段类:
LCODE:页面锁定的代码和数据段。这种段被锁定在内存里。换句话说,这段永远不会被放到硬盘上去,所以你一定要谨慎的使用这种段类以免浪费宝贵的内存。那些每时每刻都必须放在内存中的代码和数据应该放在这个段里。尤其是那些硬件中断处理程序。
PCODE:可调页代码段。VMM可以对这种段实行调页处理,在这种段里的代码不必时刻放在内存里,当VMM需要物理内存的时候,它就会把这段放到硬盘上去。
PDATA:可调页数据段。
ICODE:仅用于的初始化段。这种段里的代码仅仅用来进行VxD的初始化。当初始化完成后,VMM就把这段从内存中释放。
DBOCODE:仅用于调试的代码数据段。当你要调试VxD程序时,就要用到这种段里的代码和数据,例如,它包含要调试的消息的处理代码。
SCODE:静态代码和数据段。这种段时刻存在于内存中,即使VxD已经卸载,这种段对某些动态的VxD程序很有用,这些VxD程序需要在某一Windows进程里不停的加载/卸载而又要纪录上次的环境和状态。
RCODE:实模式初始化代码数据段。这种段包含实模式初始化需要的16位代码和数据。
16ICODE:保护模式初始化数据段。这是一个16位的段,它包含VxD要从保护模式拷贝到V86模式的代码。例如,如果你要把一些V86的代码拷贝到一个虚拟机上时,你想拷贝的代码就要放在这里。如果你把它放在其他的段里,编译程序就会产生错误的代码,例如,它会产生32位代码而不是16位代码。
MCODE:锁定的消息字串。这种段包含了由VMM消息宏帮助编译的消息字串,这有助于你构造你的驱程的国际版本。 5.PE格式16位的程序是不健壮的,因此为了保证Windows的健壮性,Microsoft在Microsoft Windows NT 3.1及其以后的操作系统版本,也就是所有的基于Win32的操作系统中推出了一种新的可执行文件的格式,也就是PE文件格式。PE的意思就是Portable Executable(可移植的可执行文件),内含32位程序代码和数据,是UNIX Common Object File Format(COFF)的演化。PE格式比其它格式优越的关键点在于它有依字母次序排列的Exports,以及一个可以直接将程序影像映像成虚拟内存的内存文件映射功能。与此同时,Microsoft也推出了新的obj文件和lib文件的格式。PE文件结构的总体层次分布图如下:
从PE开始,可执行文件的结构开始有了正式的文档化。Microsoft在Visual C++ 6的联机文档(即《MSDN Library Visual Studio 6.0》)中给出了以C语言描述的PE结构,这给开发者提供了很大的方便。同时还在VC的Include目录里的WINNT.h文件中提供PE的各结构的定义,提供了PE文件中使用的原始数据结构。 |