[VisualStudio][VC++] .obj ファイル群からシンボル名を取り出す

昨日 SQLitex64 DLL とインポートライブラリがどうしても欲しくなり、自前で Visual C++ でビルドすることになった。

SQLite は DLL エクスポート宣言をプロトタイプに書かないポリシーらしく、別途モジュール定義ファイル (DEF) を用意してやらないといけない。以前有志が作成した sqlite3.def というファイルがそのまま CVS に入っていたが、API との同期の問題を考えてか Attic 行きになっている模様。

Check-in [3381] : Automatically compute the sqlite3.def and tclsqlite3.def files when building windows DLLs. This will (hopefully) keep the .def files in perfect synchronization with the DLLs. Ticket #1951. By drh. (diff)

http://www.sqlite.org/cvstrac/chngview?cn=3381

で、DEF はどうなったのかというと mkdll.sh というこれまた Cygwin か MinGW32 な環境で使うことが前提のシェルスクリプトによって DLL と一緒に生成されるようになっていた。

$NM sqlite3.o | grep ' T ' >temp1
echo 'EXPORTS' >sqlite3.def
grep ' _sqlite3_' temp1 | sed 's/^.* _//' >>sqlite3.def

…う、そこで nm ですか。

ま、nm も x64 の COFF に対応したものを使えば、もちろんこのスクリプトのこの部分はそのまま Visual C++ でビルドしたオブジェクトファイルにも適用できるわけだけど、普段 MinGW32 は -mno-cygwinCygwin から使わせてもらっているので、わざわざ MinGW32 の x86_64 版 を別途入れたくもなく、自前でツールセットをビルドしたくもないわけで。

さて、Visual C++ には nm と objdump の機能を兼ね備えたような DUMPBIN というユーティリティが付属していて、これと FINDSTR とかを組み合わせてなんとか DEF を CMD スクリプトだけで生成することができないかといろいろ試してみたところ、一応できたみたいなのでちょっと紹介。

CD "$(IntDir)"
SET TMPFILE=out%RANDOM%
LIB /NOLOGO /LTCG /OUT:%TMPFILE%.lib *.obj
DUMPBIN /LINKERMEMBER %TMPFILE%.lib /OUT:%TMPFILE%.txt
FINDSTR /C:"_sqlite3_" %TMPFILE%.txt > "%TMPFILE%.tmp"
ECHO EXPORTS > "$(IntDir)\$(SolutionName).def"
FOR /F "usebackq delims=_ tokens=1,*" %%I IN ("%TMPFILE%.tmp") DO ECHO %%J >> "$(IntDir)\$(SolutionName).def"
DEL "%TMPFILE%*"

ポイントは一回 LIB でスタティックライブラリを作っていることと CMD 拡張機能の FOR /F を使って強引にシンボル名の切り出しを行っているところ。

DUMPBIN は引数として複数のファイルも受け付けるので別にライブラリにしなくてもいいじゃないかと思うかもしれない。ところが、リリース版ビルドで生成されるオブジェクトファイルは ANONYMOUS OBJECT なので、これを与えても /SYMBOLS オプションでは何も吐いてくれなくなってしまうのだ。一旦ライブラリアンにパブリックなシンボルをかき集めさせて、/LINKERMEMBER を使って LIB ファイルを DUMP することでデバッグ版とリリース版両方に対応できた。

FOR /F については、あまり知られてないけど、これとテンポラリファイルを組み合わせればちょっとした awk スクリプト並の処理ができる。usebackq は () の中で UNIX ライクにバッククオートが使えるようにもなる素敵な拡張だけど、特に別コマンドの出力を処理する必要がなくても指定したほうがいい。usebackq を指定しない素の状態では () の中でダブルクオートが使えないのでスペースを含むファイル名を指定することができないから。(ちなみにバッククオートの中身は && などを使って複文にすることができないみたいだ。)

上記は 32bit 版の DEF を生成するもの。x64 では "_" をシンボル名のプレフィックスとしてつける規則はないみたい*1。あと DUMPBIN の出力をそのまま FINDSTR にパイプしてないのは、Visual C++ のカスタムビルド下ではパイプの扱いがおかしくなるというバグがあるため。

*1:ソース探し中