埋めるための小ネタです。
Rustのドキュメントのusedの記事みたいに .init_array をLDCで定義したんだけど、最適化なしでやってもうまいこと動かんが…?みたいな疑問を持つのではないかとおもいます(もってほしい)。 @used がLDC 1.20.0時点でないから(元記事をちゃんと読んでないことがわかる)
- 環境
LinuxですがELF format使ってるなら再現できるとおもいます。ただOpenBSDはたぶんできない。 試してないけどできるっぽいなこれは。。
$ uname -msrv
Linux 4.15.0-70-generic #79-Ubuntu SMP Tue Nov 12 10:36:11 UTC 2019 x86_64
$ ldc2 --version| head -2
LDC - the LLVM D compiler (1.18.0):
based on DMD v2.088.1 and LLVM 9.0.0
まず普通に書くとこんな感じのコードになると思います。
import ldc.attributes;
@section(".init_array")
static typeof(&before_main!"")[1] init_array = [ &before_main!"init array" ];
extern (C) void before_main(string s)()
{
import std.stdio;
writeln(s);
}
void main() {}
ですがこれだとなにも出力されません。どうして… 上記参照。
これはコンパイラが _d_dso_registry という関数をmain実行前に呼び出すために .init_array を上書きしてしまうためです。
この関数はアーキテクチャごとに別実装ですが、ランタイムとかモジュールまわりの初期化とかいい感じにやってくれる感じです。
これは違ってました 、原因がわからん。 これは一部のOSだけでLinuxとかは起きなかった。_d_dso_registry は .init_array.d_dso_ctor というシンボルでセクションに埋め込まれるしそもそもダブるようにリンカは配置しないようになっている。
じゃあmain前になんかやりたかったらどうするの、というと方法は(まあいくつかあるのですがmainに手を加えないとすると)2つあります。
まずは普通に pragma(crt_constructor) を使う方法です。これが一番素直で推奨される方法かな、と思います。
これも .init_array (OpenBSDではレガシーな.ctor)に配置されます。
pragma(crt_constructor)
extern (C) void crt_constructor()
{
import std.stdio;
writeln("crt_constructor");
}
void main() {}
ちゃんと出力されてるんじゃないかと思います。
$ ldc2 -run crtctor.d
crt_constructor
もうひとつは .init_array のさらに前で .preinit_array というフック可能なセクションがあるので、そこでやる方法です。
import ldc.attributes;
@section(".preinit_array")
static typeof(&before_main!"")[1] preinit_array = [ &before_main!"preinit array" ];
extern (C) void before_main(string s)()
{
import std.stdio;
writeln(s);
}
void main() {}
これでもいい感じに出力がいい感じだと思います。
$ ldc2 -run preinit.d
preinit array
注意点はどちらの場合であってもGCを必要とする処理やモジュール初期化処理を前提とした処理ができない点です。
GCの初期化処理は pragma(crt_constructor) で実装されているので。(pragma(crt_constructor) を使ってフックする場合は順序未規定になる気がするが、それに依存したコードをそもそも書くべきではない)
実行順は .preinit_array → pragma(crt_constructor) → main → … となるので(参考) うまいこと実行順に気をつけて書いてください。
ちなみにLDCでは crt_constructor はGCC拡張の __attribute__((priority(N))) のように優先順序が与えられるようになっています。
pragma(crt_constructor, 2)
extern (C) void crt_constructor_2()
{
import std.stdio;
writeln(__FUNCTION__);
}
pragma(crt_constructor, 1)
extern (C) void crt_constructor_1()
{
import std.stdio;
writeln(__FUNCTION__);
}
void main()
{
import std.stdio;
writeln("main");
}
昇順になっていることが確認できます。
$ ./ctordtor
ctordtor.crt_constructor_1
ctordtor.crt_constructor_2
main
ではでは。