dumpbin /headers が想定外の値を返した話

「dumpbin /headers ファイル名」が想定外のビット数を報告してきて戸惑った話です。

動作確認環境

  • Windows 11 Home 21H2
  • Visual Studio Community 2019

System32 と SysWOW64

64bit 版 Windows では、64bit 版のシステムファイルが「C:\Windows\System32」内に、32bit 版のシステムファイルが 「C:\Windows\SysWOW64」に格納されています。

C:\Windows
    +-- System32  ★ 名前がややこしいが、この中は 64bit 版
    |    +-- kernel32.dll
    |    +-- user32.dll
    |    +......
    |
    +-- SysWOW64  ★ 名前がややこしいが、この中は 32bit 版
         +-- kernel32.dll
         +-- user32.dll
         +......

しかし、各ディレクトリにある「kernel32.dll」のビット数を以下のように dumpbin コマンドで確認したところ、どちらも「x86(= 32bit)」であると報告してきました。

★ System32 ディレクトリで dumpbin
C:\Windows\System32> dumpbin /headers kernel32.dll
......
         14C machine (x86)  ★ System32 内なのに 32bit?
......

★ SysWOW64 ディレクトリで dumpbin
C:\Windows\SysWOW64> dumpbin /headers kernel32.dll
......
         14C machine (x86)  ★ SysWOW64 内なので 32bit
......

うちの OS って 32bit 版だったかなと思いましたが、[設定]-[システム]-[バージョン情報]には「64 ビットオペレーティングシステム、x64 ベースプロセッサ」とあります。

64bit 版の dumpbin を使う

調べた結果、64bit 版の dumpbin を使えば正しいビット数が表示されることがわかりました。

★ System32 ディレクトリで dumpbin
C:\Windows\System32> dumpbin /headers kernel32.dll
......
        8664 machine (x64)  ★ System32 内なので 64bit
......

★ SysWOW64 ディレクトリで dumpbin
C:\Windows\SysWOW64> dumpbin /headers kernel32.dll
......
         14C machine (x86)  ★ SysWOW64 内なので 32bit
......

Visual Studio には x64 用のコマンドプロンプトと x86 用のコマンドプロンプトがあり、dumpbin.exe にも 64bit 版と 32bit 版があります。事象発生時に使っていたのはたまたま 32bit 版でした。

しかし、32bit 版の dumpbin がモジュールの 32bit/64bit を識別できないわけではありません。

ファイルシステムリダイレクション

64bit 版の Windows は、互換性保持のため、32bit の実行ファイルに対して「ファイルシステムリダイレクション」機能を提供しています。この機能により「%windir%\System32」は自動的に「%windir%\SysWOW64」に読み替えられます(%windir% は通常 C:\Windows)。

次のプログラムを 32bit でコンパイルし、指定したファイルと実際に開いたファイルの違いを調べてみましょう。

#include <stdio.h>
#include <windows.h>

int main(int argc, char *argv[])
{
    // 32bit の .exe としてビルドすること。
    // エラーチェック省略。

    // ファイルを開く
    HANDLE hFile = CreateFile(argv[1],
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    // 実際に開いたファイルのパス名を表示する
    char pcFinalPath[MAX_PATH];
    GetFinalPathNameByHandleA(  // ★ この関数で実ファイル名を取得
        hFile,
        pcFinalPath,
        sizeof(pcFinalPath),
        FILE_NAME_NORMALIZED);
    printf("実ファイル名: %s\n", pcFinalPath);

    CloseHandle(hFile);

    return 0;
}

いろいろなパターンで「System32」内のファイルを指定してみましたが、実際に開かれるのは常に「SysWOW64」内のファイルでした。ファイルシステムリダイレクションが機能しています。

c:\> fsr.exe c:\windows\system32\kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\> cd windows

c:\Windows> fsr.exe system32\kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\Windows> cd system32

c:\Windows\System32> fsr.exe kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\Windows\System32> fsr.exe .\kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\Windows\System32> fsr.exe ..\system32\kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\Windows\System32> cd drivers

c:\Windows\System32\drivers> fsr.exe ..\kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

ファイルシステムリダイレクションの無効化

ファイルシステムリダイレクションの機能は、邪魔になることがあります。ちょうど dumpbin コマンドがそうですが、指定したファイルの情報を得たいのに勝手にリダイレクトされると困ってしまいます。

そのため、ファイルシステムリダイレクションを無効化する機能も提供されています。具体的には、Wow64DisableWow64FsRedirection 関数を呼び出します。

当該関数を使った次のプログラムを 32bit でコンパイルし、指定したファイルと実際に開いたファイルの違いを調べてみましょう。

#include <stdio.h>
#include <windows.h>

int main(int argc, char *argv[])
{
    // 32bit の .exe としてビルドすること。
    // エラーチェック省略。

    // ファイルシステムリダイレクションを無効化
    PVOID pFsrPrev = NULL;
    Wow64DisableWow64FsRedirection(&pFsrPrev); // ★ この関数で無効化

    // ファイルを開く
    HANDLE hFile = CreateFile(argv[1],
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    // ファイルシステムリダイレクションを元の状態に戻す
    Wow64RevertWow64FsRedirection(pFsrPrev);

    // 実際に開いたファイルのパス名を表示する
    char pcFinalPath[MAX_PATH];
    GetFinalPathNameByHandleA(
        hFile,
        pcFinalPath,
        sizeof(pcFinalPath),
        FILE_NAME_NORMALIZED);
    printf("実ファイル名: %s\n", pcFinalPath);

    CloseHandle(hFile);

    return 0;
}

基本的には指定通りのファイルが開かれるものの、「System32」をカレントディレクトリにしている場合は「SysWOW64」内のファイルが開かれました。ややこしい動作です。

c:\> fsr.exe c:\windows\system32\kernel32.dll
実ファイル名: \\?\C:\Windows\System32\kernel32.dll  ★ 指定通り

c:\> cd windows

c:\Windows> fsr.exe system32\kernel32.dll
実ファイル名: \\?\C:\Windows\System32\kernel32.dll  ★ 指定通り

c:\Windows> cd system32  ★ System32 から実行すると結果が変わる

c:\Windows\System32> fsr.exe kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\Windows\System32> fsr.exe .\kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\Windows\System32> fsr.exe ..\system32\kernel32.dll
実ファイル名: \\?\C:\Windows\SysWOW64\kernel32.dll  ★ SysWOW64 にリダイレクト

c:\Windows\System32> cd drivers

c:\Windows\System32\drivers> fsr.exe ..\kernel32.dll
実ファイル名: \\?\C:\Windows\System32\kernel32.dll  ★ 指定通り

32bit 版の dumpbin が想定外の値を返した理由

32bit 版の「dumpbin.exe」は(正確にはその実体である 32bit 版の「link.exe」は)、ファイルシステムリダイレクションを無効化してファイルを開いています。そのため、ひとつ上のテストプログラムと同様、基本的には指定通りのファイルが開かれるものの、「System32」をカレントディレクトリにしている場合は「SysWOW64」内のファイルが開かれます。

c:\> dumpbin /headers c:\windows\system32\kernel32.dll
        ......
        8664 machine (x64)  ★指定通り
        ......

c:\> cd windows

c:\Windows> dumpbin /headers system32\kernel32.dll
        ......
        8664 machine (x64)  ★ 指定通り
        ......

c:\Windows> cd system32  ★System32 から実行すると結果が変わる

c:\Windows\System32> dumpbin /headers kernel32.dll
        ......
         14C machine (x86)  ★ SysWOW64 にリダイレクト
        ......

c:\Windows\System32> dumpbin /headers .\kernel32.dll
        ......
         14C machine (x86)  ★ SysWOW64 にリダイレクト
        ......

c:\Windows\System32> dumpbin /headers ..\system32\kernel32.dll
        ......
         14C machine (x86)  ★ SysWOW64 にリダイレクト
        ......

c:\Windows\System32> cd drivers

c:\Windows\System32\drivers> dumpbin /headers ..\kernel32.dll
        ......
        8664 machine (x64)  ★ 指定通り
        ......

この仕様 (?) が「dumpbin /headers ファイル名」が想定外のビット数を報告してきた原因でした。したがって、32bit 版の dumpbin コマンドであっても、「System32」ディレクトリ以外から「kernel32.dll」を指定すれば期待通りのビット数が表示されます。いや、こんなのわかんないでしょ。