Windows システムファイルのビルド日時が表示されない謎を探る
EXE ファイルや DLL ファイルにはビルド日時が埋め込まれており、dumpbin コマンドで表示できます。しかし、Windows のシステムファイルの多くは、ビルド日時が表示されません。OS のファイルなので特別扱いされているのでしょうか。調査します。
動作確認環境
- Windows 11 Home 21H2
- Visual Studio Community 2019
WORD のビルド日時を確認する
実行可能ファイル(ここでは EXE, DLL, SYS などを含む)のビルド日時は、Visual Studio 付属の dumpbin コマンドで確認できます。
WORD のビルド日時を見てみましょう。
C:\> dumpbin /headers "C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE"
Microsoft (R) COFF/PE Dumper Version 14.28.29913.0
......
FILE HEADER VALUES
8664 machine (x64)
8 number of sections
622186C6 time date stamp Fri Mar 4 12:25:58 2022
......
「622186C6 time date stamp Fri Mar 4 12:25:58 2022」の行がビルド日時の情報です。
最初の「622186C6」は、1970 年 1 月 1 日 00:00 からの経過秒数を 16 進数で示しています。
それを解釈したのが後ろの「Fri Mar 4 12:25:58 2022」で、日本式で書くと「2022/03/04(金) 12:25:58」になります。
「ビルド日時」は開発者が実行可能ファイルを作った日時であり、エクスプローラーに表示される「作成日時」「更新日時」「アクセス日時」とは異なります。前者は実行可能ファイルそのものに埋め込まれている情報、後者はファイルシステムが保持している情報です。
ちなみに、経過秒数から年月日時分秒への変換は、次のプログラムで行えます。
#include <stdio.h>
#include <time.h>
int main()
{
__time64_t t;
char buf[32];
errno_t err;
t = 0x622186C6; // ここに経過秒数をセット
err = ctime_s(buf, sizeof(buf), &t);
if (err)
{
printf("err=%d\n", err);
return 1;
}
printf("%s\n", buf); // 「Fri Mar 4 12:25:58 2022」などと表示される
return 0;
}
Windows のビルド日時が表示されない
ところがある日、Windows のシステムファイルのビルド日時が表示されないことに気が付きました。
手元の Windows 11 の kernel32.dll を見てみます。
C:\> dumpbin /headers c:\windows\system32\kernel32.dll
Microsoft (R) COFF/PE Dumper Version 14.28.29913.0
......
FILE HEADER VALUES
8664 machine (x64)
7 number of sections
7B65E245 time date stamp
......
「time date stamp」の行に日時が表示されていません。「7B65E245」を解釈すると、未来の「2035/08/09(木) 21:17:09」になってしまいます。
ほかにも、ddrawex.dll を見てみます。
C:\> dumpbin /headers c:\windows\system32\ddrawex.dll
Microsoft (R) COFF/PE Dumper Version 14.28.29913.0
......
FILE HEADER VALUES
8664 machine (x64)
7 number of sections
61528B38 time date stamp
......
「61528B38」を解釈すると「2021/09/28(火) 12:25:44」となり、適切な値のはずですが、それでも日時が表示されません。
Windows 10 の最初のバージョンのころは表示されていたはず、と思いながらいろいろなバイナリを比較したところ、日時が表示されないバイナリのヘッダにのみ「repro」という型が定義されていることがわかりました。
C:\> dumpbin /headers c:\windows\system32\kernel32.dll
Microsoft (R) COFF/PE Dumper Version 14.28.29913.0
......
FILE HEADER VALUES
8664 machine (x64)
7 number of sections
7B65E245 time date stamp
......
7B65E245 repro 24 00091DEC 91DEC 62 43 09 DC B8......
......
「repro」とは何でしょうか。
repro とは何か
Web で repro を検索したところ、マイクロソフトによる PE フォーマット(Portable Executable format)の解説に行き着きました [1]。
IMAGE_DEBUG_TYPE_REPRO
PE determinism or reproducibility.
The presence of an entry of type IMAGE_DEBUG_TYPE_REPRO indicates the
PE file is built in a way to achieve determinism or reproducibility.
If the input does not change, the output PE file is guaranteed to be
bit-for-bit identical no matter when or where the PE is produced.
Various date/time stamp fields in the PE file are filled with part or
all the bits from a calculated hash value that uses PE file content as
input, and therefore no longer represent the actual date and time when
a PE file or related specific data within the PE is produced.
The raw data of this debug entry may be empty, or may contain a
calculated hash value preceded by a four-byte value that represents
the hash value length.
雰囲気で訳します。
・ IMAGE_DEBUG_TYPE_REPRO 型のエントリーの存在は、
実行可能ファイルが決定論的な(再現性のある)方法でビルドされたことを示す。
・ ソースが同じであれば、いつどこでビルドしても同一のバイナリになることが保証される。
・ 実行可能ファイル内のタイムスタンプのフィールドには、日時ではなく、
実行可能ファイルの内容から生成されたハッシュ値がセットされる。
「repro」とは「reproducibility」の略でした。
ころころ変わる現在時刻の代わりに、入力によって一意に決まる値、具体的には実行可能ファイルのハッシュ値を「ビルド日時」の領域に埋め込み、決定論的な・再現性のあるバイナリを生成する、ということのようです。
dumpbin コマンドは、「この実行可能ファイルは repro なのでビルド日時を解釈しても意味がない」と判断し、年月日時分秒を表示しなかったと考えられます。
自分でも repro したい
自分で作ったプログラムを「repro」にするにはどうすればいいのでしょうか。
Web を検索しても方法が見つからなかったのですが、ビルド日時を埋め込んでいるであろうリンカー(link.exe)に含まれている文字列を洗い出したところ、「/Brepro」という「/?」では表示されないオプションがあることに気が付きました。
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64>
wsl strings --encoding=l link.exe | wsl grep -i repro
......
/Brepro
......
コンパイラ(cl.exe)にも「Brepro」という文字列が含まれていました。
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64>
wsl strings --encoding=l cl.exe | wsl grep -i brepro
......
Brepro
......
試したところ、これが repro ファイルを生成するオプションのようでした。
「Brepro」というのは「binary reproducibility」の略だと思われます。
では、次の簡単なプログラムを用意します。
#include <stdio.h>
int main()
{
printf("Hello\n");
return 0;
}
普通にビルドします。
C:\tmp> cl test.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.28.29913 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
test.c
Microsoft (R) Incremental Linker Version 14.28.29913.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
test.obj
生成された実行可能ファイルのビルド日時を見てみます。
C:\tmp> dumpbin /headers test.exe
Microsoft (R) COFF/PE Dumper Version 14.28.29913.0
......
6231FB6F time date stamp Wed Mar 16 23:59:59 2022
......
普通に現在日時が埋め込まれています。
次に、リンカーに /Brepro オプションを指定してビルドします。
C:\tmp> cl test.c /link /Brepro
Microsoft(R) C/C++ Optimizing Compiler Version 19.28.29913 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
test.c
Microsoft (R) Incremental Linker Version 14.28.29913.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
/Brepro
test.obj
ビルド日時を見てみます 。
C:\tmp> dumpbin /headers test.exe
Microsoft (R) COFF/PE Dumper Version 14.28.29913.0
......
75305D3A time date stamp
........
75305D3A repro 24 0001E9C8 1D1C8 3B 4C C7 7B 2D ........
......
repro になりました。ビルド日時が表示されておらず、また、repro の定義が埋め込まれています。期待通りです。
リンカーではなくコンパイラに /Brepro オプションを指定してビルドしてみます。
C:\tmp> cl /Brepro test.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.28.29913 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
test.c
Microsoft (R) Incremental Linker Version 14.28.29913.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:test.exe
/Brepro
test.obj
/Brepro オプションがリンカーに受け渡されています。
ビルド日時を見てみます。
C:\tmp> dumpbin /headers test.exe
Microsoft (R) COFF/PE Dumper Version 14.28.29913.0
......
75305D3A time date stamp
......
75305D3A repro 24 0001E9C8 1D1C8 3B 4C C7 7B 2D ......
......
repro になりました。
前回のビルド時と「time date stamp」の値が同じになっている点も注目です。
おわりに
Windows のシステムファイルのビルド日時が表示されないのは、repro としてビルドされているためでした。
また、自分のプログラムでも /Brepro オプションでビルドすれば repro になることがわかりました。
ただし、/Brepro を付ければ常に決定論的な・再現性のあるバイナリが生成されるのかというと、そうではありません。前述の test.c では同じバイナリが生成されましたが、複雑なプログラムではどうなるかわかりませんし、デバッグ情報を付けて(/Zi オプションを付けて)ビルドすると「ビルド日時」以外の場所に毎回異なる情報が埋め込まれてしまいます。同一ソース同一バイナリの実現については、別途調査が必要です。
追記
その後、WinDbg で /Brepro 付きでビルドしたモジュールの詳細情報を表示すると、「Image was built with /Brepro flag.」というメッセージが表示されることに気が付きました。cl /? や link /? では表示されないオプションですが、使っても問題なさそうです。
0:001> lmvm test
Browse full module list
start end module name
00040000 0005e000 test (deferred)
Image path: C:\tmp\test.exe
Image name: test.exe
Browse all global symbols functions data
Image was built with /Brepro flag.
Timestamp: D98EBA20 (This is a reproducible build file hash, not a timestamp)
CheckSum: 00000000
ImageSize: 0001E000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:
参考文献
[1] Microsoft. PE Format. 2021. https://docs.microsoft.com/en-us/windows/win32/debug/pe-format