はじめに
プログラムでdllのTargetPlatformを識別する処理を実装する必要が生じたため, ファイルヘッダを読んでそれを識別が可能かどうかを調査しました.
面倒だったので, そこまで詳細な調査は行ってません. 条件選定が不適切かもしれませんが, まぁ大丈夫でしょう.
先駆者が居そうな気しかしませんが, そこは気にせず.
TL;DR
- 基本的にはMachineの値で識別できる
- Machineの値が"0x014C"(I386)である場合は, 加えてCharacteristicsの値で識別する
- x86の場合, 32BIT_MACHINEフラグ("0x0100")が立っている
- AnyCPUの場合, 32BIT_MACHINEフラグ("0x0100")が下りている
前提知識
Minoru Hatada, PE(Portable Executable)ファイルフォーマットの概要, Hatada's Home Page
事前調査で, "PEヘッダを用いると判別が可能である"という知識を得られていたため, これを参考にします.
調査結果
今回は, 以下のdllファイルについて調査を行いました.
- C#(net461) x64 Release Build (DllExport&ILmerge適用済み)
- C#(net35) x86 Release Build (DllExport&ILmerge適用済み)
- C# AnyCPU Release Build
- C(++) x64 Debug Build
- C(++) Win32 Debug Build
- C(++) Arm Release Build
- C(++) Arm64 Release Build
- C#(UWP) Arm Release Build
- C#(UWP) Arm64 Release Build
実際のファイルヘッダを含む調査結果の詳細は, gistの方にまとめてあります. 但し, 調査結果のファイルヘッダについて, 一部箇所は記載を省略しています.
以下に, 種類ごとのファイルヘッダ解析結果(のうち, 判別に必要な情報)をまとめます. なお, 文中の"XXh"(例 : 03h)は, ファイル内の位置(16進表記)を表します.
解析結果にNTヘッダの開始位置も含まれていますが, この開始位置の定義は種類によらず3Chにて行われているため, 記載を省略しています.
C#(net461) x64 Release Build
- NTヘッダは80hより開始している
- ファイルヘッダ内Machine値は"0x8664"であり, これは"AMD64"(= x86_64)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2022"である
- 0x0020 : IMAGE_FILE_LARGE_ADDRESS_AWARE=2GB分以上のアドレスを扱う[2]
C#(net35) x86 Release Build
- NTヘッダは80hより開始している
- ファイルヘッダ内Machine値は"0x014C"であり, これは"I386"(=Intel 386 / x86)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2102"である
- 0x0100 : IMAGE_FILE_32BIT_MACHINE=32bitである[2]
C# AnyCPU Release Build
- NTヘッダは80hより開始している
- ファイルヘッダ内Machine値は"0x014C"であり, これは"I386"(=Intel 386 / x86)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2022"である
- 0x0020 : IMAGE_FILE_LARGE_ADDRESS_AWARE=2GB分以上のアドレスを扱う[2]
C(++) x64 Debug Build
- NTヘッダはF8hより開始している
- ファイルヘッダ内Machine値は"0x8664"であり, これは"AMD64"(= x86_64)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2022"である
- 0x0020 : IMAGE_FILE_LARGE_ADDRESS_AWARE=2GB分以上のアドレスを扱う[2]
C(++) Win32 Debug Build
- NTヘッダはF0hより開始している
- ファイルヘッダ内Machine値は"0x014C"であり, これは"I386"(=Intel 386 / x86)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2102"である
- 0x0100 : IMAGE_FILE_32BIT_MACHINE=32bitである[2]
C(++) Arm Release Build
- NTヘッダは100hより開始している
- ファイルヘッダ内Machine値は"0x1C4"であり, これは"ARMNT"(=ARM Thumb-2 Little-Endian)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2122"である
- 0x0020 : IMAGE_FILE_LARGE_ADDRESS_AWARE=2GB分以上のアドレスを扱う[2]
- 0x0100 : IMAGE_FILE_32BIT_MACHINE=32bitである[2]
C(++) Arm64 Release Build
- NTヘッダは100hより開始している
- ファイルヘッダ内Machine値は"0xAA64"であり, これは"ARM64"(=ARM64 Little-Endian)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2022"である
- 0x0020 : IMAGE_FILE_LARGE_ADDRESS_AWARE=2GB分以上のアドレスを扱う[2]
C#(UWP) Arm Release Build
- NTヘッダは80hより開始している
- ファイルヘッダ内Machine値は"0x1C4"であり, これは"ARMNT"(=ARM Thumb-2 Little-Endian)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2022"である
- 0x0020 : IMAGE_FILE_LARGE_ADDRESS_AWARE=2GB分以上のアドレスを扱う[2]
C#(UWP) Arm64 Release Build
- NTヘッダは100hより開始している
- ファイルヘッダ内Machine値は"0xAA64"であり, これは"ARM64"(=ARM64 Little-Endian)を表している[1]
- ファイルヘッダ内Characteristicsは"0x2022"である
- 0x0020 : IMAGE_FILE_LARGE_ADDRESS_AWARE=2GB分以上のアドレスを扱う[2]
まとめ
基本的にはMachineの値で識別可能だとわかりました. C#とC++の識別はNTヘッダの開始位置差で判別可能なように思えますが, 設定次第でこの差もなくなりそうな気がします.
x86とAnyCPUの識別は, Characteristicsで"32BIT_MACHINE"フラグが立っている(x86)か否(AnyCPU)かで識別が可能だとわかりました.
なお, Characteristicsの"LARGE_ADDRESS_AWARE"については, 32bitプロセスでも4GBまでメモリ空間を持てる場合があり, その場合にこのフラグが立ってしまうと考えられるため, 識別には使用できないものと思われます.