如何判斷一個(gè) Dot Net 程序是 32 位還是 64 位?
緣起
前陣子,朋友遇到一個(gè) .net 程序啟動(dòng)不起來的問題。根據(jù)之前的經(jīng)驗(yàn),一般是依賴的動(dòng)態(tài)庫加載失敗導(dǎo)致的?;蛘哒也坏剑ㄒ蕾嚨膭?dòng)態(tài)庫沒有放到相應(yīng)的目錄下,一般放到應(yīng)用程序所在目錄即可),或者不匹配(64 位的程序加載 32 位的動(dòng)態(tài)庫,或者 32 位的程序加載 64 位的動(dòng)態(tài)庫)。整個(gè)排查過程并不復(fù)雜,本文不打算介紹整個(gè)排查過程,而是想介紹一些 .net 程序的基本常識(shí)(比如,以 Any CPU 編譯出來的程序,是 32 位的還是 64 位的?),還會(huì)介紹幾個(gè)我認(rèn)為不錯(cuò)的查看工具。
在介紹查看方法之前,先介紹一些基本常識(shí)。
Any CPU
做過 .net 開發(fā)的小伙伴一定接觸過 Any CPU ,新建一個(gè) c# 測試工程,默認(rèn)的編譯選項(xiàng)就是這個(gè)。

目標(biāo)平臺(tái)(G) 和 首選 32 位(P) 兩個(gè)選項(xiàng)共同決定了傳遞給 csc.exe 的 /platform 選項(xiàng)的值。
在目標(biāo)平臺(tái)(G) 是 Any CPU的情況下,如果勾選了 首選 32 位(P),那么 /platform 的值是 anycpu32bitpreferred,如果未勾選,那么 /platform 的值是 anycpu。
說明:
首選 32 位(P)選項(xiàng)在dll工程中不允許修改。雖然編譯的時(shí)候不能改,但是我們可以手動(dòng)修改編譯后的文件。:)
/platform 選項(xiàng)對(duì)生成的模塊的影響以及在運(yùn)行時(shí)的影響,參考下表:
| /platform 開關(guān) | 生成的托管模塊 | x86 Windows | x64 Windows | ARM Windows RT |
|---|---|---|---|---|
| anycpu(默認(rèn)) | PE32 / 任意 CPU 架構(gòu) | 作為 32 位應(yīng)用程序運(yùn)行 | 作為 64 位應(yīng)用程序運(yùn)行 | 作為 32 位應(yīng)用程序運(yùn)行 |
| anycpu32bitpreferred | PE32 / 任意 CPU 架構(gòu) | 作為 32 位應(yīng)用程序運(yùn)行 | 作為 WoW64 位應(yīng)用程序運(yùn)行 | 作為 32 位應(yīng)用程序運(yùn)行 |
| x86 | PE32 / X86 | 作為 32位應(yīng)用程序運(yùn)行 | 作為 WoW64 位應(yīng)用程序運(yùn)行 | 不運(yùn)行 |
| x64 | PE32+ / X64 | 不運(yùn)行 | 作為 64 位應(yīng)用程序運(yùn)行 | 不運(yùn)行 |
| ARM | PE32 / ARM | 不運(yùn)行 | 不運(yùn)行 | 作為 32 位應(yīng)用程序運(yùn)行 |
說明:以上表格摘錄自 《CLR via c#》(第4版)第一章
PE 頭相關(guān)字段
一般,一個(gè)標(biāo)準(zhǔn)的 PE 文件由四大部分組成: DOS 頭,PE 頭,節(jié)表,節(jié)內(nèi)容。這里只關(guān)心 PE頭中相關(guān)字段。
32 位 PE 頭定義如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
其中,Signature 的內(nèi)容是 PE\0\0,非常好認(rèn)。
FileHeader 對(duì)應(yīng)的結(jié)構(gòu)體定義如下:
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,對(duì)于32位程序,這個(gè)值一般是0x14c,對(duì)于64位程序一般是0x8664。但對(duì)于.net程序,不能以此字段作為判斷依據(jù)。
OptionalHeader 對(duì)應(yīng)的結(jié)構(gòu)體定義如下:
typedef struct _IMAGE_OPTIONAL_HEADER32 {
WORD Magic;
// ... 省略無關(guān)字段
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 一共16項(xiàng)
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic如果為010B,表示這是一個(gè)PE32文件,如果為020B表示這是一個(gè)PE32+文件,也就是64位的PE文件。與
FileHeader.Machine一樣,對(duì)于.net程序,不能以此字段作為判斷依據(jù)。DataDirectory中一共有16項(xiàng)。其中,最后一項(xiàng)是保留項(xiàng),第14項(xiàng)(索引從0開始)指向了CLR的結(jié)構(gòu)。
這個(gè)結(jié)構(gòu)是 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; // 這個(gè)字段的意義,參考 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,
// 省略一些無關(guān)的內(nèi)容
} ReplacesCorHdrNumericDefines;
說明:以上定義可以在
CorHdr.h中找到。
了解了以上知識(shí),就可以手動(dòng)查看 PE 文件來進(jìn)行判斷了。但是手動(dòng)判斷既容易錯(cuò),又麻煩,還得時(shí)不時(shí)得翻看一下 PE 文件格式,很不方便。除了通過手動(dòng)查看 PE 文件來查看,還可以通過工具來查看。本文簡單介紹幾個(gè)常用工具及其查看方法。
查看工具
CorFlags.exe
view-net-bitness-in-corflags 除了查看,
CorFlags.exe也可以修改對(duì)應(yīng)的標(biāo)記位。具體用法可以直接在命令行中輸入CorFlags.exe進(jìn)行查看。dumpbindumpbin可以查看很多信息,對(duì)于.net程序,可以使用dumpbin /clrheader選項(xiàng)查看clr頭信息。如下圖:
上圖是兩個(gè)以不同編譯選項(xiàng)生成的程序的對(duì)比效果,我第一次查看
dumpbin的顯示結(jié)果沒看懂,對(duì)比后才明白。cff explorer帶圖形界面的
PE工具,不僅可以查看,也可以修改,很方便。
view-net-bitness-in-cff-explorer
除了這幾個(gè)工具,還有很多其它工具也可以查看,就不一一列舉了。
參考資料
《CLR via c#》(第4版) https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only https://www.cnblogs.com/seacryfly/articles/CorFlags.html https://ntcore.com/files/dotnetformat.htm CorHdr.h
