メモリデバッガ Dr. Memoryとコード操作ライブラリDynamoRIO
Dr. MemoryはValgrindのmemcheckのような、リークや未初期化メモリ参照を検出することのできるメモリデバッガで、Valgrindと違ってWindowsにも対応している。ただし、64bitアプリケーションとBSD/MacOSのサポートが無い。LGPL2でコードは公開されている。ビルドにはCMakeを採用している。コピーライトはVMwareとgoogleになっている。
Valgrindと同様に、動的バイナリ変換をおこなうインフラ(DynamoRIO)をバックエンドに持っている。このようなアプローチはLLVMのAddressSanitizerのようなコンパイル時のinstrumentationに比べて、既存のコンパイルされたバイナリを直接instrumentできるというメリットが有るが、それなりの速度低下がある。
drmemoryはValgrindより高速と主張している。Valgrind(のバックエンドであるVEX)は完全なVMであり、プログラム全体をIRに変換してからmemcheckのような"tools"でIRに変換を掛けてinstrumentationを実現している(LLVMのAddressSanitizerやThreadSanitizerはコード生成前のIRに変換を掛けることでinstrumentationを実現しているので、どちらかというとValgrindに近い)。これと比較してDynamoRIOは端的に言えば"アプリケーションパッチマネージャ"でしかなく、個々のinstrumentationはAPIに対してhookを掛ける形で実現する。
DynamoRIOやDr. Memoryに関してはそれなりの数の論文が既に公開されている。(例えば: http://dynamorio.org/docs/overview.html )
使用方法はすこぶる単純で、アーカイブを展開し引数としてプログラムを与えれば良い。ログファイルはdrmemoryのアーカイブ中に一緒に含まれるログディレクトリに書き出される。
幾つかのhookを成功させるために、プログラムには多少の下ごしらえが必要になる。たとえば、MinGWではC++ランタイムを静的リンクする。
VisualStudioでビルドしたnmoshに対して仕掛けると、ちゃんとValgrind同様未初期化変数アクセスを検出する。Dr. MemoryはDWARF以外にdbghelpを使ってVisualStudioで作成されたバイナリのシンボル情報もロードすることができる。
(ドキュメントによると古いMinGWはDWARFでなくstubsを使うので明示的にDWARFを生成させる必要があるとしている。)
Error #1: UNINITIALIZED READ: reading register eax # 0 GC_push_all_eager [c:\repos\mosh\extlibs\gc\mark.c:1525] # 1 GC_push_current_stack [c:\repos\mosh\extlibs\gc\mark_rts.c:663] # 2 GC_with_callee_saves_pushed [c:\repos\mosh\extlibs\gc\mach_dep.c:305] # 3 GC_try_to_collect_inner [c:\repos\mosh\extlibs\gc\alloc.c:461] # 4 GC_init [c:\repos\mosh\extlibs\gc\gc_misc.c:1078] # 5 GC_generic_malloc_inner [c:\repos\mosh\extlibs\gc\malloc.c:116] # 6 GC_generic_malloc [c:\repos\mosh\extlibs\gc\malloc.c:174] # 7 GC_core_malloc [c:\repos\mosh\extlibs\gc\malloc.c:263] # 8 GC_malloc [c:\repos\mosh\extlibs\gc\thread_local_alloc.c:176] # 9 scheme::EqHashTable::EqHashTable [c:\repos\mosh\src\eqhashtable.cpp:51] #10 `dynamic initializer for 'nongenerativeRtds'' [c:\repos\mosh\src\utilityprocedures.cpp:1059] #11 MSVCR100.dll!initterm Note: @0:00:06.168 in thread 6256 Note: instruction: cmp %eax %ebx : ERRORS FOUND: 0 unique, 0 total unaddressable access(es) 16 unique, 10866 total uninitialized access(es) 0 unique, 0 total invalid heap argument(s) 0 unique, 0 total warning(s) 0 unique, 0 total, 0 byte(s) of leak(s) 0 unique, 0 total, 0 byte(s) of possible leak(s) ERRORS IGNORED: 416 still-reachable allocation(s) (re-run with "-show_reachable" for details) Details: C:\prog\DrMemory-Windows-1.4.6-2/drmemory/logs/DrMemory-nmosh.exe.5920.000/results.txt
GC_push_all_eagerで未初期化メモリアクセスが起こるのはBoehmGCではよくあること。
同様にMinGWビルドに対しても仕掛けてみる:
: ERRORS FOUND: 2219 unique, 4739317 total unaddressable access(es) 5 unique, 12796 total uninitialized access(es) 0 unique, 0 total invalid heap argument(s) 0 unique, 0 total warning(s) 1 unique, 1 total, 4 byte(s) of leak(s) 0 unique, 0 total, 0 byte(s) of possible leak(s) ERRORS IGNORED: 398 still-reachable allocation(s) (re-run with "-show_reachable" for details) Details: C:\prog\DrMemory-Windows-1.4.6-2/drmemory/logs/DrMemory-nmosh.exe.5580.000/results.txt
... エラーの数が全然違う。。! おそらく何らかの理由でMinGWでコンパイルした方はGCがマップしたメモリがDr Memory側に認識されていないように見える。