6

如何判断一个 Dot Net 程序是 32 位还是 64 位?

 3 years ago
source link: https://bianchengnan.gitee.io/articles/how-to-check-a-dot-net-program-is-32-or-64/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前阵子,朋友遇到一个 .net 程序启动不起来的问题。根据之前的经验,一般是依赖的动态库加载失败导致的。或者找不到(依赖的动态库没有放到相应的目录下,一般放到应用程序所在目录即可),或者不匹配(64 位的程序加载 32 位的动态库,或者 32 位的程序加载 64 位的动态库)。整个排查过程并不复杂,本文不打算介绍整个排查过程,而是想介绍一些 .net 程序的基本常识(比如,以 Any CPU 编译出来的程序,是 32 位的还是 64 位的?),还会介绍几个我认为不错的查看工具。

在介绍查看方法之前,先介绍一些基本常识。

Any CPU

做过 .net 开发的小伙伴一定接触过 Any CPU ,新建一个 c# 测试工程,默认的编译选项就是这个。

csharp-compile-option

目标平台(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头中相关字段。

32PE 头定义如下:

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

    view-net-bitness-in-corflags

    除了查看,CorFlags.exe 也可以修改对应的标记位。具体用法可以直接在命令行中输入 CorFlags.exe 进行查看。

  • dumpbin

    dumpbin 可以查看很多信息,对于 .net 程序,可以使用 dumpbin /clrheader 选项查看 clr 头信息。如下图: view-net-bitness-in-dumpbin

    上图是两个以不同编译选项生成的程序的对比效果,我第一次查看 dumpbin 的显示结果没看懂,对比后才明白。

  • cff explorer

    带图形界面的 PE 工具,不仅可以查看,也可以修改,很方便。

    view-net-bitness-in-cff-explorer

除了这几个工具,还有很多其它工具也可以查看,就不一一列举了。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK