缘起
前阵子,朋友遇到一个 .net
程序启动不起来的问题。根据之前的经验,一般是依赖的动态库加载失败导致的。或者找不到(依赖的动态库没有放到相应的目录下,一般放到应用程序所在目录即可),或者不匹配(64
位的程序加载 32
位的动态库,或者 32
位的程序加载 64
位的动态库)。整个排查过程并不复杂,本文不打算介绍整个排查过程,而是想介绍一些 .net
程序的基本常识(比如,以 Any CPU
编译出来的程序,是 32
位的还是 64
位的?),还会介绍几个我认为不错的查看工具。
在介绍查看方法之前,先介绍一些基本常识。
Any CPU
做过 .net
开发的小伙伴一定接触过 Any CPU
,新建一个 c#
测试工程,默认的编译选项就是这个。
目标平台(G)
和 首选 32 位(P)
两个选项共同决定了传递给 csc.exe
的 /platform
选项的值。
在目标平台(G)
是 Any CPU
的情况下,如果勾选了 首选 32 位(P)
,那么 /platform
的值是 anycpu32bitpreferred
,如果未勾选,那么 /platform
的值是 anycpu
。
说明:
首选 32 位(P)
选项在dll
工程中不允许修改。虽然编译的时候不能改,但是我们可以手动修改编译后的文件。:)
/platform
选项对生成的模块的影响以及在运行时的影响,参考下表:
/platform 开关 | 生成的托管模块 | x86 Windows | x64 Windows | ARM Windows RT |
---|---|---|---|---|
anycpu(默认) | PE32 / 任意 CPU 架构 | 作为 32 位应用程序运行 | 作为 64 位应用程序运行 | 作为 32 位应用程序运行 |
anycpu32bitpreferred | PE32 / 任意 CPU 架构 | 作为 32 位应用程序运行 | 作为 WoW64 位应用程序运行 | 作为 32 位应用程序运行 |
x86 | PE32 / X86 | 作为 32位应用程序运行 | 作为 WoW64 位应用程序运行 | 不运行 |
x64 | PE32+ / X64 | 不运行 | 作为 64 位应用程序运行 | 不运行 |
ARM | PE32 / ARM | 不运行 | 不运行 | 作为 32 位应用程序运行 |
说明:以上表格摘录自 《CLR via c#》(第4版)第一章
PE 头相关字段
一般,一个标准的 PE
文件由四大部分组成: DOS
头,PE
头,节表,节内容。这里只关心 PE
头中相关字段。
32
位 PE
头定义如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
其中,Signature
的内容是 PE\0\0
,非常好认。
FileHeader
对应的结构体定义如下:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine
,对于32
位程序,这个值一般是0x14c
,对于64
位程序一般是0x8664
。但对于.net
程序,不能以此字段作为判断依据。
OptionalHeader
对应的结构体定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER32 {
WORD Magic;
// ... 省略无关字段
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 一共16项
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic
如果为010B
,表示这是一个PE32
文件,如果为020B
表示这是一个PE32+
文件,也就是64
位的PE
文件。与
FileHeader.Machine
一样,对于.net
程序,不能以此字段作为判断依据。DataDirectory
中一共有16
项。其中,最后一项是保留项,第14
项(索引从0
开始)指向了CLR
的结构。
这个结构是 IMAGE_COR20_HEADER
,定义如下:
typedef struct IMAGE_COR20_HEADER
{
// Header versioning
DWORD cb;
WORD MajorRuntimeVersion;
WORD MinorRuntimeVersion;
// Symbol table and startup information
IMAGE_DATA_DIRECTORY MetaData;
DWORD Flags; // 这个字段的意义,参考 ReplacesCorHdrNumericDefines
// The main program if it is an EXE (not used if a DLL?)
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, EntryPointToken represents a managed entrypoint.
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, EntryPointRVA represents an RVA to a native entrypoint
// (depricated for DLLs, use modules constructors intead).
union {
DWORD EntryPointToken;
DWORD EntryPointRVA;
};
// This is the blob of managed resources. Fetched using code:AssemblyNative.GetResource and
// code:PEFile.GetResource and accessible from managed code from
// System.Assembly.GetManifestResourceStream. The meta data has a table that maps names to offsets into
// this blob, so logically the blob is a set of resources.
IMAGE_DATA_DIRECTORY Resources;
// IL assemblies can be signed with a public-private key to validate who created it. The signature goes
// here if this feature is used.
IMAGE_DATA_DIRECTORY StrongNameSignature;
IMAGE_DATA_DIRECTORY CodeManagerTable; // Depricated, not used
// Used for manged codee that has unmaanaged code inside it (or exports methods as unmanaged entry points)
IMAGE_DATA_DIRECTORY VTableFixups;
IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
// null for ordinary IL images. NGEN images it points at a code:CORCOMPILE_HEADER structure
IMAGE_DATA_DIRECTORY ManagedNativeHeader;
} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;
其中,Flags
的值可以参考如下枚举:
typedef enum ReplacesCorHdrNumericDefines
{
// COM+ Header entry point flags.
COMIMAGE_FLAGS_ILONLY =0x00000001,
COMIMAGE_FLAGS_32BITREQUIRED =0x00000002,
COMIMAGE_FLAGS_IL_LIBRARY =0x00000004,
COMIMAGE_FLAGS_STRONGNAMESIGNED =0x00000008,
COMIMAGE_FLAGS_NATIVE_ENTRYPOINT =0x00000010,
COMIMAGE_FLAGS_TRACKDEBUGDATA =0x00010000,
COMIMAGE_FLAGS_32BITPREFERRED =0x00020000,
// 省略一些无关的内容
} ReplacesCorHdrNumericDefines;
说明:以上定义可以在
CorHdr.h
中找到。
了解了以上知识,就可以手动查看 PE
文件来进行判断了。但是手动判断既容易错,又麻烦,还得时不时得翻看一下 PE
文件格式,很不方便。除了通过手动查看 PE
文件来查看,还可以通过工具来查看。本文简单介绍几个常用工具及其查看方法。
查看工具
CorFlags.exe
除了查看,
CorFlags.exe
也可以修改对应的标记位。具体用法可以直接在命令行中输入CorFlags.exe
进行查看。
dumpbin
dumpbin
可以查看很多信息,对于.net
程序,可以使用dumpbin /clrheader
选项查看clr
头信息。如下图:上图是两个以不同编译选项生成的程序的对比效果,我第一次查看
dumpbin
的显示结果没看懂,对比后才明白。
cff explorer
带图形界面的
PE
工具,不仅可以查看,也可以修改,很方便。
除了这几个工具,还有很多其它工具也可以查看,就不一一列举了。