kubo39's blog

ただの雑記です。

Linux環境でSharedObjectsを使う

とくになにかあるわけじゃなくてSharedObjectsの使い方の備忘録として。まあこういうのドキュメントないので。。

SharedObjectsというのはLinuxでだけ使えるやつで、実行プログラムがロードしている共有ライブラリを取得するために使うものである。 ようは dl_iterate_phdr(3) といえば通じる人には通じる。

以下はプロセスがロードしている共有ライブラリの名前と各セグメントの仮想メモリ上の位置を表示するプログラム。 界隈あるあるとして(??)、relocationを考慮して実際の仮想アドレスを表示するようにしている。

// druntimeのSharedObjectsを使えるのはLinuxだけ
version(linux):

import core.stdc.stdio;

// SharedObjectsを定義している
import core.internal.elf.dl;

// プログラムヘッダの定義とかが欲しいので
import core.sys.linux.elf;

void main()
{
    foreach (object; SharedObjects)
    {
        // std.stdioはここでは使えない
        printf("%s\n", object.name.ptr);

        foreach (ref phdr; object)
        {
            string name = () {
                final switch (phdr.p_type)
                {
                case PT_NULL: return "NULL";
                case PT_LOAD: return "LOAD";
                case PT_DYNAMIC: return "DYNAMIC";
                case PT_INTERP: return "INTERP";
                case PT_NOTE: return "NOTE";
                case PT_SHLIB: return "SHLIB";
                case PT_PHDR: return "PHDR";
                case PT_TLS: return "TLS";
                case PT_NUM: return "NUM";
                case PT_LOOS: return "LOOS";
                case PT_GNU_EH_FRAME: return "GNU_EH_FRAME";
                case PT_GNU_STACK: return "GNU_STACK";
                case PT_GNU_RELRO: return "GNU_RELRO";
                }
                assert(false);
            } ();

            // dl_iterate_phdr(3) を参照
            ulong actualVMA = phdr.p_vaddr + object.info.dlpi_addr;
            printf("    %p: segment %s\n", cast(void*)actualVMA, name.ptr);
        }
    }
}

getFunctionAttributesでlive関数属性をとれるようにした

これ https://github.com/dlang/dmd/pull/11049

単にtraits.dを修正するだけではだめだった、というのはlive関数属性は名前修飾の定義も実装もされていなかったので、typeof内で普通の関数と区別されていなかったのである。

具体的に言うと以下のようになってしまう。

alias tuple(T...) = T;

struct S1
{
    int f1() @live { return 42; }
    int f2() { return 42; }
}

static assert(__traits(getFunctionAttributes, typeof(S1.f1)) == tuple!("@live", "@system"));
static assert(__traits(getFunctionAttributes, typeof(S1.f2)) == tuple!("@live", "@system"));

struct S2
{
    long f2() { return 0; }
    long f1() @live { return 42; }
}

static assert(__traits(getFunctionAttributes, typeof(S2.f1)) == tuple!("@system"));
static assert(__traits(getFunctionAttributes, typeof(S2.f2)) == tuple!("@system"));

という小ネタがおもしろかったというだけの話。

D言語のvulkan bindingであるEruptedDをいれる

以前挑戦してだめだった vulkan 利用、実はIntel HD用のパッケージを入れればいけるのでは?となったので試す。

$ sudo apt install -y mesa-vulkan-drivers

おお、どうやら動いたっぽい。

$ dub run :devices
Performing "debug" build using /home/kubo39/dlang/dmd-2.091.0/linux/bin64/dmd for x86_64.
erupted:devices 2.0.54+v1.2.134: building configuration "application"...
Linking...
Running ./erupted_devices 
Before vkEnumeratePhysicalDevices
After vkEnumeratePhysicalDevices

Found 1 physical device(s)
==========================

Physical device 0: Intel(R) HD Graphics 520 (Skylake GT2)
API Version: 1.1.102
Driver Version: 79699976
Device type: VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU

Queue Family 0
        Queues in Family         : 1
        Queue timestampValidBits : 36
        VK_QUEUE_GRAPHICS_BIT
        VK_QUEUE_COMPUTE_BIT
        VK_QUEUE_TRANSFER_BIT

VK_QUEUE_GRAPHICS_BIT found at queue family index 0

Logical device created
Graphics queue retrieved

Scope exit: draining work and destroying logical device
Scope exit: destroying instance

これからはvulkanや!

emacsのD言語環境 (2020.03.07版)

【2020.04.08追記】 dlsが非推奨になったので以下の内容はすべて古くなってしまいました。なんてこった。。

https://forum.dlang.org/post/usqhsdtwfhrterzpstst@forum.dlang.org


注意: この内容はもう古いです。

またまたemacsD言語環境をいじってLSP仕様にしました。時代はLanguage Server Protocolや!

設定は以下のような感じです。 コーディングスタイル/インデントルールはdmdやdruntimeで使われている書式にあわせています。

(use-package d-mode
  :ensure t
  :hook
  (d-mode . (lambda ()
              (c-set-style "bsd")
              (setq c-basic-offset 4)
              (setq tab-width 4)
              (lsp)))
  :commands d-mode
  )

emacs側に関してはlsp-modeuse-packageの使用を前提としています。 lsp-modeはLSPを使うので必要として(まあeglotでもいいのですが)、use-packageを使うと可読性が高い感じで設定が書けるのでよいです。

D言語のLSPサーバとしてはdlsを使っています。 LSPと通信する必要があるので dub fetch dls して dub run dls:bootstrap してできたバイナリにパスを通してやる必要があります。 好きなとこにおいてもいいのですが、dlsが最新のバイナリに追従しやすいようにdls-latestにsymlinkを作ってくれるので普通にそこにパスを通しています。

ちなみにlsp-register-clientまわりの設定は現時点でlsp-modeが公式にdlsをサポートしてくれていないので書いていますが、サポートされれば要らなくなるはずです。

dls対応のパッチがマージされたので ~/.dub/packages/.bin/dls-latest/dls にdlsを置いている場合は設定を書く必要はなくなりました。

追記(2020.3.9):

いろいろあった結果PATHで設定して通して、って感じになり、通したければユーザに設定してもらうかんじになった。 ドキュメントは https://emacs-lsp.github.io/lsp-mode/lsp-mode.html#lsp-dls

DMDはmain関数(not _Dmain)を実行する前になにをするのか

たとえば自前で pragma(crt_constructor) とか使った場合にどういう順で実行されるんだろう、とか。

こういうコードを用意して、

enum body = "import std.stdio;writeln(__PRETTY_FUNCTION__);";

pragma(crt_constructor)
extern (C) void crt_constructor()
{
    mixin(body);
}

void main()
{
    mixin(body);
}

一応環境

$ dmd --version| head -1
DMD64 D Compiler v2.090.1
$ uname -mrsv
Linux 4.15.0-76-generic #86-Ubuntu SMP Fri Jan 17 17:24:28 UTC 2020 x86_64

コンパイルして .init_array セクションをみてみるとこんな感じ。

$ dmd hoge.d
$ LANG=C readelf -Wx .init_array ./hoge

Hex dump of section '.init_array':
  0x00291dd0 70590400 00000000 70530400 00000000 pY......pS......
  0x00291de0 7c530400 00000000 f0d80600 00000000 |S..............
  0x00291df0 bcfb0600 00000000 54fc0600 00000000 ........T.......

適当にアドレスひっかけて(リトルエンディアンなことに注意)、nmコマンドでシンボルをひいてみる。

一番最初のやつは .init_array の先頭を表すやつ。

$ nm ./hoge| grep 291dd0
0000000000291dd0 t __init_array_start

他のやつは関数ポインタっぽいやつ。frame_dummyはgccが入れてるやつ。

$ nm ./hoge| grep 00045370
0000000000045370 t frame_dummy
$ nm ./hoge| grep 0004537c
000000000004537c t
000000000004537c W crt_constructor
$ nm ./hoge| grep 0006fbbc
000000000006fbbc t
000000000006fbbc W _d_register_conservative_gc
$ nm ./hoge| grep 0006d8f0
000000000006d8f0 t
000000000006d8f0 W _d_register_manual_gc
$ nm ./hoge| grep 0006fc54
000000000006fc54 W _d_register_precise_gc

こうしてみると基本はユーザ定義のやつがGCの初期化処理より先に呼ばれるので、GC使うコードは書けないはず。リンク順に依存してしまうので絶対ではないだろうけど。

そのスタックを食べたのはだれ? ~travisでdubがSEGVし続けたワケ~

あらすじ

ある日のこと、Travis上のプロジェクトがdub使ってるとSEGVで落ちる、というチケットが。さらにスレッドが進み、どうもいろいろなパッケージで同様の問題が起きているとのこと。

LinuxでしかおきないぞとかdubでParallel GC(注:mark&sweep gcのmarkingのフェーズが並列)使ったときしか再現しないっぽいとか再現条件が特定されていく中でParallel GC内のスレッドでスタックオーバーフローが起きているのがどうやら原因だと判明。

といってもこの問題、Parallel GCの内部実装の問題ではない。OSスレッド、それもglibcのpthread実装に関連した問題だった。

この問題はglibcでは静的なTLSブロックがスレッドのスタックのtopに配置されることに起因する。 スレッド実装では最小スタックサイズを保証するために PTHREAD_STACK_MIN よりも小さいサイズが与えられた場合切り上げが行われるのだが、glibcの場合は静的TLSブロックのサイズがこの定数に加算されない仕組みになっている。 (静的TLSブロックサイズを考慮した __pthread_get_minstack という関数があるがこれはglibcバージョン2.15以降で利用可能となる)

Parallel GC内で生成してるスレッドのスタックサイズはアプリケーションに影響を及ぼさないようにするためか0x4000(=16kb)ほどしか設定されてなく、そこそこでかい静的TLSブロックサイズになると簡単にSEGVが起きてしまう状態になっていた。このIssueでは4ページ(0x4000)のスタック領域を確保しているはずが(ガード領域の1ページを除き) 1ページ以上(0x1100)の領域が静的TLSデータに食べられてしまい、利用可能なスタック領域がほんとうにわずかになっていることが確認できる。 おまけにD言語ではグローバルな変数はデフォルトでスレッドローカルストレージに配置されスレッド生成時に暗黙にコピーが走るためバグが顕現しやすいという特性もあったのでさまざまなプロジェクトで影響が出てしまった。

この問題はParallel GCの作者が書いた 静的TLSブロックサイズを考慮する修正パッチ がすでにマージされていてdmd-2.090.1がリリースされているのでひとまず解決した。

影響範囲

Parallel GCを使っている場合 (かつDMD <= 2.090.0) のすべてのバージョンで起きうる。 また自分でスレッド生成を管理している場合なども当然注意が必要。

ワークアラウンド

ランタイムのバグが修正済みであるDMD 2.090.1を使うのがよいのだが、それができない場合

  • プログラム実行時に --DRT-gcopt=parallel:0 を指定する
  • コード内で
// parallel gcが追加されたのは 2.087: https://dlang.org/changelog/2.087.0.html#gc_parallel
// 修正は2.090.1で取り込まれたがパッチバージョンは考慮できないのでとりあえず 2.091を与える
static if (__VERSION__ >= 2087 && __VERSION__ < 2091)
    extern (C) __gshared string[] rt_options = [ "gcopt=parallel:0" ];

という宣言をするとParallel GCを無効にできるので、ひとまずParallel GC起因のものは抑制できる。

自前でスレッドを使いたい場合スタックサイズを余裕をもって大きくしておくか、もしくはスタックサイズとして0を渡すとシステムデフォルトのスタックサイズ(静的TLSブロックが考慮されている)を設定することができる。

おまけ

今回自分は修正パッチのレビューから参加したのでここまでの流れを追うの(特に英語)がしんどかったが、とにかく解決してほっとした。原因究明と修正にあたってくれたGeod24氏とrainers氏に感謝。

ちなみにこの修正、地味に引数に与えたスタックサイズと静的TLSブロックの合計が PTHREAD_STACK_MIN より小さい場合に PTHREAD_STACK_MIN のサイズまでしか切り上げされないというコーナーケースがあるので、追加パッチ を書いた。こちらもマージ済だけど影響範囲は微小と思われるのでリリースは2.091.0になりそうだ。

D言語のOpenSSL事情、というか愚痴

つらい話です、というかただの愚痴です。

D言語のOpenSSLバインディングとしては D-Programming-Deimos/openssl があるのですが、メンテナンスが行き届いているとは言い難い状態です。 それ以外にもいくつか問題があって、

  • 新しめのAPIへの対応が追従できていない
  • サポートしているOpenSSLのバージョンが不明
  • リリーススケジュールが不明
  • LibreSSL対応されていない、する予定があるのかも不明
  • だれがコアのメンテナなのかわからない
  • Cヘッダからある程度自動で生成するってあるけど方法が不明、みんな手動でパッチ書いてる状況
  • スタッティックリンクできない
  • ライブラリのバージョニングが不明
  • Cヘッダ同根するメリットあるのこれ?

なんてこともあり、パッチを書くにしてもどうしたもんかという感じです。

そういう事情もあり、TLSが必要なライブラリにいたっても、

  • vibe-d: ベースはD-Programming-Deimos/openssl を使いつつも足りない箇所はバージョンによる分岐を独自実装
  • dlang-requests: 上記バインディングは使わずに動的リンク・ロードで独自実装

みたいな感じになってしまっています。

だれかイニシアチブとってやってくれると助かるんだけど、まあないだろうな。自前で新しいの書いたほうがいい気さえしてきている。。