程序员自我修养阅读笔记——Windows PE/COFF

Posted grayondream

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员自我修养阅读笔记——Windows PE/COFF相关的知识,希望对你有一定的参考价值。

  本章主要将windowsPE格式和ELF格式的区别。

1 Windows PE/COFF

  PE是windows引入的一种可执行文件格式,该格式和Linux系统的ELF文件格式同源,都是由COFF格式发展而来。在Windows上可执行文件格式为PE格式,而目标文件的格式为COFF,二者略微不同但是大体上相似。同时32bit的windows和64bit的windows的目标二进制文件格式基本相同,只不过64bit中将原PE中的32bit的字段修改为64bit。
  与ELF文件格式类似,PE采用段管理数据与代码,不同的是采用的段名不同,比如windows上的.code,.data(改名字根据编译器的不同而不同)。另外,vc中可以通过预编译处理命令指定目标的变量希望存放的段。

2 PE的前身——COFF

  为了描述COFF文件格式,下面使用下面简单的程序测试生成的段。

int add(int a, int b)
    return a + b;


const char *file = "main.o";
int glob_a = 15;
int glob_b;
//test
int main()
    static char static_a = 16;
    static char static_b;
    long long a = 3;
    long long b;
    add(a, b);

  将上面的程序通过下面的命令编译后能够得到一个obj文件。windows上可以通过dumpbin /SUMMARY main.obj命令查看简要的短信息和dumpbin /ALL main.obj查看所有的详细信息。

# /Za 参数是为了减少编译器行为对结果的干扰
cl /c /Za main.c

  下面是通过Summary查看的段的内容:

          38 .chks64
          14 .data
          68 .debug$S
          18 .drectve
           C .pdata
          41 .text$mn
           8 .xdata

  下图为COFF文件的基本格式,其基本格式和ELF类似。
映像文件头
  首先是映像文件头,该头描述了当前文件的相关内容,相应的结构定义于winnt.h文件中的_IMAGE_FILE_HADER,能够看到该头包含了下面吉祥内容:

  • 当前机器类型;
  • 段的数量;
  • 文件创建的时间;
  • 指向符号表的指针;
  • 符号表中符号的数量;
  • Header的大小,该字段仅仅在PE文件中使用。
  • 标志位。

  上面通过dumpbin命令获取的header内容如下,机器类型8664可以在winnt.h文件中搜索IMAGE_FILE_MACHINE_前缀,就能找到对应的机器类型的表示,我这里查到是Amd64;包含7个段符合上面通过SUMMARY参数查看的段的数量;19个符号,后续我们再看下是哪19个符号,如果仅仅看程序的话貌似我们并未定义这么多的符号。

FILE HEADER VALUES
            8664 machine (x64)
               7 number of sections
        61ED5A40 time date stamp Sun Jan 23 21:38:08 2022
             27F file pointer to symbol table
              19 number of symbols
               0 size of optional header
               0 characteristics

段表
  段表是一个数组,数组的每一个元素都是描述一个段的IMAGE_SECTION_HEADER的结构体(该结构体的定义同样能够在winnt.h中找到),该段描述的基本内容包括:

  • Name:段名;
  • Misc:该字段是一个union,要么是物理地址,要么是该段被加载到内存的大小;
  • VirtualAdderss:段被加载到内存后的虚拟地址;
  • SizeofRawData:段在文件中的大小往往和Mics中的```VirtualSize``大小不同,比如内存对齐等原因;
  • Characteristics:标志位,描述段的读写权限,对齐方式等。
  • 其他字段的含义如其名称所描述。

链接指令
  使用dumpbin命令查看详细内容能够看到如下段描述,首先是和上面段表项中描述的一致每个字段都有相应的内容,最后的Linker Directives是传递给连接器的命令,这里的/DEFAULTLIB:LIBCMT表示当前段依赖于libcmt.lib这个库。

SECTION HEADER #1
.drectve name
       0 physical address
       0 virtual address
      18 size of raw data
     12C file pointer to raw data (0000012C to 00000143)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
  100A00 flags
         Info
         Remove
         1 byte align

RAW DATA #1
  00000000: 20 20 20 2F 44 45 46 41 55 4C 54 4C 49 42 3A 22     /DEFAULTLIB:"
  00000010: 4C 49 42 43 4D 54 22 20                          LIBCMT" 

   Linker Directives
   -----------------
   /DEFAULTLIB:LIBCMT

调试信息
  输出的内容可能包含类似下面的段,以debug开头表示调试信息,后面的内容描述具体的调试类型:

  • .debug$S:表示包含符号相关的调试信息;
  • .debug$P:表示包含预编译头文件相关的调试信息;
  • .debug$T:表示包含类型相关的调试信息。
SECTION HEADER #2
.debug$S name
       0 physical address
       0 virtual address
      68 size of raw data
     144 file pointer to raw data (00000144 to 000001AB)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42100040 flags
         Initialized Data
         Discardable
         1 byte align
         Read Only

符号表
  一个完整的符号表如下:

  • 第一列为符号的标号,16进制计数;
  • 第二列为符号占用的大小;
  • 第三列为符号所在的位置,SECT接数字表示当前符号所载的段的序号;
  • 第四列为符号的类型,分为notypenotype()分别表示变量、其他符号和函数;
  • 第五列表示符号的可见性,static表示局部可见,external表示外部可见;
  • 第六列为符号的名称,如果为段名的符号如.drectve后面会继续跟段的一些基本属性如段长度、重定位数、行号数、校验和。
COFF SYMBOL TABLE
000 010469A5 ABS    notype       Static       | @comp.id
001 80000190 ABS    notype       Static       | @feat.00
002 00000000 SECT1  notype       Static       | .drectve
    Section length   18, #relocs    0, #linenums    0, checksum        0
004 00000000 SECT2  notype       Static       | .debug$S
    Section length   68, #relocs    0, #linenums    0, checksum        0
006 00000000 SECT3  notype       Static       | .data
    Section length   14, #relocs    1, #linenums    0, checksum 57950DAC
008 00000000 SECT3  notype       External     | file
009 00000008 SECT3  notype       Static       | $SG4309
00A 00000010 SECT3  notype       External     | glob_a
00B 00000004 UNDEF  notype       External     | glob_b
00C 00000000 SECT4  notype       Static       | .text$mn
    Section length   41, #relocs    1, #linenums    0, checksum FEED5EA9
00E 00000000 SECT4  notype ()    External     | add
00F 00000020 SECT4  notype ()    External     | main
010 00000020 SECT4  notype       Label        | $LN3
011 00000000 SECT5  notype       Static       | .xdata
    Section length    8, #relocs    0, #linenums    0, checksum 37887F31
013 00000000 SECT5  notype       Static       | $unwind$main
014 00000000 SECT6  notype       Static       | .pdata
    Section length    C, #relocs    3, #linenums    0, checksum 35DC62C8
016 00000000 SECT6  notype       Static       | $pdata$main
017 00000000 SECT7  notype       Static       | .chks64
    Section length   38, #relocs    0, #linenums    0, checksum        0

String Table Size = 0x1D bytes

3 PE文件格式

  PE文件格式是COFF文件格式的扩展,基本结构和COFF类似但是添加了额外的一些描述,下面是winnt.h中32bit和64bit的文件头的描述。能够看到其中除了COFF的IMAGE_FILE_HEADER还额外包含了SignatureOptinalHeader描述,前者基本固定标识PE文件,后面的对于DLL文件和可执行文件是必须的。另外对比IMAGE_OPTIONAL_HEADER64IMAGE_OPTIONAL_HEADER32就能够发现只有部分字段由DWORD改成了ULONGLONG基本相同(IMAGE_OPTIONAL_HEADER32多了一个BaseOfData)。

typedef struct _IMAGE_NT_HEADERS64 
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
 IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS 
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
 IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

  关于PE按照书里面的节奏不会解释所有的字段,只会关注静态链接需要的字段。Windows系统装载PE可执行文件时,需要很快找到一些装载所需要的数据结构,比如导入表,导出表,资源,重定位表等。这些常用的数据的位置和长度都被保存在了数据目录DataDirectory成员中。这个成员是IMAGE_DATA_DIRECTORY结构的一个数组。而每个_IMAGE_DATA_DIRECTORY结构体就是一个地址+大小的模式。

typedef struct _IMAGE_OPTIONAL_HEADER 
    //省略...
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
 IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_DATA_DIRECTORY 
    DWORD   VirtualAddress;
    DWORD   Size;
 IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

  而这16个表每个表所表示的内容是固定的,索引所对应的表的含义如下所示。

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

以上是关于程序员自我修养阅读笔记——Windows PE/COFF的主要内容,如果未能解决你的问题,请参考以下文章

程序员自我修养阅读笔记——Widnows下的动态链接

程序员自我修养阅读笔记——Widnows下的动态链接

程序员自我修养阅读笔记——静态编译

程序员自我修养阅读笔记——目标文件里有什么

《程序员自我修养》阅读笔记-动态链接

程序员自我修养阅读笔记——运行库