ARM64_Lab

System32 に x86 と x64 が混ざっていた PE ヘッダー調査

この記事の見出し
  1. ヘッダーだけを読むことにした
  2. フォルダー別に数えた結果
  3. SysWOW64 にも逆向きの混在があった
  4. ARM64X を拾えなかった失敗
  5. どう使い分けるか

System32 の DLL 4324本を読んだら、ARM64 が4155本、x86 が122本、x64 が46本、ARM32 が1本だった(Machine だけを見る割り切りを忘れそうになる)。

ARM64 版 Windows の C:\Windows\System32 なので、ほとんど ARM64 だろうとは思っていた。けれど、System32 という名前から受ける印象よりも中身は雑多に見える。EXE でも ARM64 696本に対して x64 が4本、x86 が2本あり、DLL では x86 の数が122本まで増える(名前のきれいさほど、中身はきれいに分かれていない)。

実際に見て一番引っかかったのは、ARM32 が1本だけ残っていたことだ。該当ファイルは C:\Windows\System32\DriverStore\FileRepository\prnms006.inf_arm64_8bcb2c96990be129\SendToOneNoteFilter.dll だった。ARM64 でも x86 でもなく ARM32 という結果が、プリンタードライバー配下に1本だけ出ている。

計測日は2026-06-23。2026年6月23日の夜に、手元の Surface Pro 11th Edition、Windows 11 Pro 10.0.26200 ARM64 で測った。数字はファイル名ではなく PE ヘッダーの Machine フィールドだけで分類している。短い調査のつもりだったが、フォルダー名と実体のずれを見始めると、思ったより引っかかる点が多かった。

ヘッダーだけを読むことにした

やったことは単純で、C:\Windows\System32C:\Windows\SysWOW64 の EXE / DLL を開き、PE ヘッダーの Machine を読むだけにした。先頭2バイトが MZ0x3C に PE ヘッダーへのオフセットが入っている。そこから PE\0\0 を確認し、4バイト進めた位置の2バイトが Machine になる。

def read_machine(path):
    with open(path, "rb") as f:
        mz = f.read(2)
        if mz != b"MZ":
            return None
        f.seek(0x3C)
        pe_offset = int.from_bytes(f.read(4), "little")
        f.seek(pe_offset)
        if f.read(4) != b"PE\0\0":
            return None
        machine = int.from_bytes(f.read(2), "little")
        return {
            0xAA64: "ARM64",
            0x8664: "x64",
            0x014C: "x86",
            0x01C4: "ARM32",
            0xA641: "ARM64EC",
            0xA64E: "ARM64X",
        }.get(machine, hex(machine))

この方法だと、ファイル名やフォルダー名に引きずられない。System32 にあるから ARM64、SysWOW64 にあるから x86、とは決め打ちしない。実際にそうしなかったから、System32 配下の x86 と x64 が見えてきた(フォルダー名を信じていたら、この部分は丸ごと見落としていたはず)。

一方で、後で書くように ARM64X の判定には足りていなかった可能性がある。COFF ヘッダーの Machine だけを見る、という割り切りが今回の測定条件だ。

フォルダー別に数えた結果

走査した対象は System32 の EXE 702本、System32 の DLL 4324本、SysWOW64 の EXE 330本(ARM64X を拾うには読み取りが浅かった)。SysArm32 の EXE はフォルダー自体が存在しなかったので、件数は取れていない。

容量比率は bytes_by_machine から計算した。System32 の DLL は本数だと ARM64 が96.1%、容量だと93.2%になる(なぜ一部だけ逆向きに混ざるのか、後で引っかかった)。x86 は122本あるが、容量では5.7%だったので、数だけで見るよりは少し軽い存在感になる。

対象 count ARM64 x64 x86 ARM32 容量メモ
System32 *.exe 702本 696本 / 99.1% / 636494216 bytes 4本 / 0.6% / 764264 bytes 2本 / 0.3% / 250704 bytes 0本 ARM64 が99.8%相当
System32 *.dll 4324本 4155本 / 96.1% / 4685434672 bytes 46本 / 1.1% / 52961288 bytes 122本 / 2.8% / 286007728 bytes 1本 / 0.0% / 77312 bytes x86 が5.7%相当
SysWOW64 *.exe 330本 2本 / 0.6% / 1119856 bytes 0本 328本 / 99.4% / 51695656 bytes 0本 x86 が97.9%相当
SysArm32 *.exe なし 0本 0本 0本 0本 フォルダーなし

System32 の EXE で x64 だったのは、C:\Windows\System32\dns-sd.exeC:\Windows\System32\dpnsvr.exeC:\Windows\System32\fixmapix64.exeC:\Windows\System32\wbem\WmiPrvSEx64.exe の4本。x86 は C:\Windows\System32\DriverStore\FileRepository\devicestelemetryservicedriver.inf_arm64_1ed8bdd395254b72\AppLauncher.exeC:\Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe の2本だった。

DLL 側の x64 サンプルには C:\Windows\System32\ccmcore.dllC:\Windows\System32\ccmperf.dllC:\Windows\System32\CcmUsrCse.dllC:\Windows\System32\dfshim.dllC:\Windows\System32\directml_x64.dll がある。x86 サンプルは C:\Windows\System32\AuthFWSnapin.dllC:\Windows\System32\AuthFWWizFwk.dllC:\Windows\System32\enterpriseetw.dllC:\Windows\System32\ETWCoreUIComponentsResources.dllC:\Windows\System32\Microsoft-Windows-AppModelExecEvents.dll だった。

SysWOW64 にも逆向きの混在があった

SysWOW64 は名前どおり x86 の置き場として見れば、328本が x86 という結果は自然だった。目を引いたのは、ARM64 が2本だけ混ざっていた点。該当したのは C:\Windows\SysWOW64\edit.exeC:\Windows\SysWOW64\sudo.exe。SysWOW64 の EXE 330本中2本なので、本数では0.6%にすぎないが、混在している事実は残る(小さい例外ほど、決め打ちの足をすくう)。

容量で見ると、SysWOW64 の x86 は51695656 bytes、ARM64 は1119856 bytes(名前と実体のずれが思ったより多かった)。比率にすると x86 が97.9%、ARM64 が2.1%になる。2本だけでも sudo.exeedit.exe のような目立つ名前が入っているので、フォルダー名だけでアーキテクチャを決めると間違える。

System32 でも同じだった。powershell_ise.exe は System32 の下にいるが x86 として出ている。WmiPrvSEx64.exe は名前にも x64 が入っていて、Machine も x64 だった。名前とヘッダーが一致する場合もあるが、常にそうだとは扱えない、というだけの話。

ARM64X を拾えなかった失敗

この点は測定の失敗として残しておく。判定表には ARM64EC の 0xA641 と ARM64X の 0xA64E を入れている。ところが結果はどちらも0本になり、あまりにきれいすぎて後から不安になった。

最初は、手元の Windows 11 Pro 10.0.26200 には ARM64X がほとんど無いのかもしれない、という見立て(次はヘッダーの奥まで見直したい)。けれど、ARM64X は System32 の一部 DLL で使われていると聞いていたので、0本はきれいすぎる(プリンタードライバー配下の一本が妙に残った)。気づくまで少し時間がかかったが、COFF ヘッダーの Machine だけ見ても ARM64X は ARM64 として見える可能性があり、このあたりで「読んでいる場所が浅いのではないか」と疑い始めた。つまり、今回の関数は「PE ファイルの表面上の Machine を読む」だけで、ARM64X の追加構造までは見ていない(ここを後から掘ってみる余地は残った)。

ここを詰めるなら、別のヘッダーやロード構成、あるいは ARM64X 特有のメタデータまで追う必要がある。今回はそこまで掘っていない。だから verdict は「動いた」ではなく「条件つき」にした。x86、x64、ARM32 の混在を見る用途では動いたが、ARM64X の検出器としては足りていない(例外の数より場所のほうが気になった)。

どう使い分けるか

この結果を見てから、Windows on ARM の調査ではフォルダー名を信用しすぎないことにした。System32 は ARM64 だけの箱ではない(フォルダー名を信用しすぎる癖が出ていた)。SysWOW64 も x86 だけの箱ではない(フォルダー名を信用しすぎる癖が出ていた)。だいたいは名前どおりでも、例外が数本から数百本ある。

個人的には、トラブルシュートで「これはネイティブかエミュレーションか」を見るとき、where.exe の場所だけでは終わらせないほうがいいと感じた。PE ヘッダーを2バイト読むだけなら軽く、今回のように全件を舐めてみるだけでも、フォルダー名からは見えない混在が浮いてくる(Machine だけを見る割り切りを忘れそうになる)。

一番おもしろかったのは、System32 の ARM32 DLL 1本だった。互換性のために残っているのか、ドライバー配下の都合なのか、この時点では分からない。次に測るなら ARM64X を正しく拾う方法を足して、同じ702本と4324本をもう一度数え直したい(ARM64X を拾うには読み取りが浅かった)。

a
arm64lab — 個人運営

Surface Pro 11th Edition(Snapdragon X Elite)を2025年5月から常用機にしている個人の記録です。ARM64 版 Windows で詰まったところと、その場で測った値をそのまま書き残しています。特定の企業・団体とは関係がなく、いかなる組織を代表する見解でもありません。