CygwinのClangでTLSを共有ライブラリからimportすると壊れる問題
これ難しいな。
現象
C++を使ったコードをclangの最新版でビルドし、BFD ldでリンクすると壊れたexecutableができる。
Cygwin runtime failure: /cygdrive/f/webkit/build/webkit/bin/jsc.exe: Invalid relocation. Offset 0x2dc64640c at address 0x1004077b0 doesn't fit into 32 bits
00000001004077b0 T __fu0___emutls_v._ZSt15__once_callable
00000001011c49ac T __fu100___emutls_v._ZSt15__once_callable
00000001012dbe70 T __fu101___emutls_v._ZSt15__once_callable
特徴としては、DLL上に存在するTLSが使われているケースで、同じコードはg++では正常にビルド/リンクできるようだ。
EMUTLSを使用しているのは意図通り
Clangが2018年にそうなるようにパッチを当てている。
ELFのTLSに比べて、Windowsが使用しているPEのTLSは表現力が低く、明示的に dllexport されていないシンボルについて完全に等価な機能を提供することができない。この辺はBFD ldのautoimportとEMUTLSで解決している。
... というかAndroidもEMUTLSなの。。?
一応ネイティブTLSの草案はあるけど。。
gccとの差分を確認する
Stack overflowの質問 https://stackoverflow.com/questions/31617034/stdfuture-and-clang-with-stdlib-libstdc にあったサンプルで問題は再現できた。
#include <iostream>
#include <future>
int main()
{
std::future<int> f1 = std::async([](){ return 42; });
f1.wait();
std::cout << "Magic number is: " << f1.get() << std::endl;
}
Cygwinの該当箇所 https://github.com/cygwin/cygwin/blob/8050ef207494e6d227e968cc7e5850153f943320/winsup/cygwin/pseudo-reloc.cc#L346 はautoimportのための自前のリロケーションを行っていて、ここに出力されているリロケーションが間違っているようだ。
デバッグ用のリンカオプション --enable-extra-pep-debug
を使ってデバッグ出力させてみると、
clang++ -Wl,--enable-extra-pep-debug -fno-exceptions -gdwarf-4 check.cpp
clangは32bitのリロケーションを出力しているのに比べ、gccは64bitのリロケーションを出力しているという違いがある。よって、この差が問題を起こしているようだ。
arelent: __emutls_v._ZSt15__once_callable@0x20: add=0
import of 0x0(0x0) sec_addr=0x20 pcrel 32 bit rel. // ★ 32ビット
creating runtime pseudo-reloc entry for __fu1___emutls_v._ZSt15__once_callable (addend=0)
arelent: __emutls_v._ZSt15__once_callable@0: add=0
import of 0x0(0x0) sec_addr=0x0 64 bit rel. // ★ 64ビット
creating runtime pseudo-reloc entry for __fu17___emutls_v._ZSt15__once_callable (addend=0)
ClangのMinGWサポートもちょっと前までバグっていた
この辺に関連しそうなコミットとしては
がある。これはMinGWにしか効いていないが、Cygwinでも適用されるのが妥当に見える。実際、 -emit-llvm
でビットコードを出力させ、 llvm-dis
でLLVM-IRを出力させてみると、
@_ZSt15__once_callable = external dso_local thread_local global i8*, align 8
のように、 dso_local
としてEMUTLS変数が宣言されていて、 この dso_local
を手で消してアセンブルしてみると正常にリンク/実行できる 。よってClang側をデバッグして、なぜこの変数が dso_local
として宣言されてしまうのかを追求すれば良さそうだ。
shouldAssumeDSOLocal
の修正
... ここだけで行けるか。。?(ビルド中)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 2346176a1562..74bd8614a346 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1059,7 +1059,7 @@ static bool shouldAssumeDSOLocal(const CodeGenModule &CGM,
return false;
const llvm::Triple &TT = CGM.getTriple();
- if (TT.isWindowsGNUEnvironment()) {
+ if (TT.isWindowsGNUEnvironment() || TT.isWindowsCygwinEnvironment()) {
// In MinGW, variables without DLLImport can still be automatically
// imported from a DLL by the linker; don't mark variables that
// potentially could come from another DLL as DSO local.
-femulated-tls
が必要
依然明示的に とりあえず修正してみたが、依然 -femulated-tls
が必要。。
clang++ -femulated-tls -fno-exceptions check.cpp
のようにしてコンパイルすることで正常に動作するようになった。 ...まだ -fno-exceptions
が必要なのは多分ABIが間違っているため。