MinGW製QtDLLをclangから呼び出せなかった話

今回は単なる失敗談。

  • clang 3.2
  • Qt 5.5.1

やろうとしたこと

Windows PCにQt Creatorをインストールしたので、サンプルアプリをclangでビルドして実行しようとした。

QtはMinGWでビルドされていたからABIの不一致がちょっと心配だった。
色々いじって警告を出しながらビルドは通った。でも、実行したらmain()関数の1行目でアクセス違反して落ちてしまった。

アクセス違反の症状

だいたいこんな感じだった。

  • Qt側の関数を3回呼んだところでアクセス違反している
  • 直接の原因はポインタがおかしなアドレスを指していること
  • スタック破壊した感じは見えない(スタックの内容、スタックポインタ)

ここまで確認して以前Windows SDKでアプリをビルドした時に、呼び出し規約が一致していないせいでアクセス違反した時と雰囲気が似ていることに気が付いた。*1

アプリ clang
DLL MinGW

コンパイラが違うので呼び出し規約が違う可能性があると思った。

デバッガで確認した内容

確認したのは主に逆アセンブル結果とスタック上の値。
やはり呼び出し規約が違っているようだった。

コンパイラ 第一引数 スタックの巻き戻し
MinGW ECXレジスタ経由 関数から戻った際にスタック調整
clang スタック スタック調整なし

裏取り

で、ここまで調べて「そういえば、Wikipediaの呼び出し規約ページを*2見ていた時に同じような説明見たな」と思いだした。

thiscall

(中略)

On the Microsoft Visual C++ compiler, the this pointer is passed in ECX
and it is the callee that cleans the stack, mirroring the stdcall convention
used in C for this compiler and in Windows API functions.
When functions use a variable number of arguments,
it is the caller that cleans the stack (cf. cdecl).


ん?あれ?VCの話だぞ。
あと、スタックの後片付けは呼ばれる側になってる。

Microsoft社のサイト*3も見てみた。

The __thiscall calling convention is used on member functions
and is the default calling convention used by C++ member functions
that do not use variable arguments. Under __thiscall,
the callee cleans the stack, which is impossible for vararg functions.
Arguments are pushed on the stack from right to left,
with the this pointer being passed via register ECX,
and not on the stack, on the x86 architecture.


内容的には一緒っぽい。


Google先生に「mingw thiscall calling convention」と聞いてみた*4

Mingw switched abis with the release of gcc 4.7 (http://gcc.gnu.org/gcc-4.7/changes.html).
The main change is that now mingw (like msvc) given thiscall calling convention to methods by default.


ということで、MinGWはとあるバージョンからthiscallに変更されたようだ。
ちなみにリンク先*5を見ても

IA-32/x86-64
Windows x86 targets are using the __thiscall calling convention for C++ class-member functions.

とシンプルに書かれているだけで読み取れない・・・

で、ここまで調べてふと「そもそも呼び出し側で引数をpushしていない」ことに気が付いた。
呼び出し側は関数の先頭でドカンとスタックを取ってからmovで個別にデータを入れている。pushしていないのだから、呼び出し先でスタック調整したら呼び出し元でも再調整しないと一致しない。だからthiscall対応済みのMinGWには呼び出し元に"再調整の"コードが入っていた、と。

試したこと

clangのドキュメント*6によるとthiscallというattributeがあるらしい。
ちょうどアプリのビルド時にdllimportで警告になっていたのでdefineでdllimportをthiscallに変更してみた。

が、dllimportはclassに対してつけられているようだけど、clangはメソッドにだけ対応しているらしい。
個別に各メソッドに付けないと効果を発揮しないようだ。

というところでギブアップ。

試していないこと

Qtソースコード改造

デメリットとして、

  • あちこち修正ないとダメ
  • 手を入れた時点でソース開示とか色々ライセンス上の制約を考慮することになる
Qt本体をclangでビルドし直す

世界のどこかには試した人がいるらしい。成功したのかどうか不明。
詳細なやり方は未調査。

余談

正直そこまでしてclang使います?ということで本件は保留中。
MinGWで問題なく使えそうだし。
どうしてもclang使いたい時はC言語でDLL作って呼び出せば良いしね。


あ、そういえばclangの3.7はWindows版バイナリが配布されてるっぽいね。
64ビット版もあるし入れようかなどうしようかな。
その前にMinGWの64ビット版を入れないとダメか。