kubo39's blog

ただの雑記です。

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版)

またまた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: 上記バインディングは使わずに動的リンク・ロードで独自実装

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

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

LDCのlibFuzzerを試す

LLVMはlibFuzzerというFuzzingの仕組みがあって、LDCからその機能を使うことができる。

こういうコードで試してみる。LDCは1.19.0。

import ldc.libfuzzer;
import std.string;

mixin DefineTestOneInput!fuzz_target;

int fuzz_target(in ubyte[] data)
{
    if (data == "FU".representation)
        assert(false);
    return 0;
}

コンパイルオプションで -fsanitize=fuzzer をつけることで試せるらしい。 実行してみる。

$ ldc2 -g -fsanitize=fuzzer fuzz.d 
$ ./fuzz                 
WARNING: Failed to find function "__sanitizer_acquire_crash_state".                                           
WARNING: Failed to find function "__sanitizer_print_stack_trace".                 
WARNING: Failed to find function "__sanitizer_set_death_callback".                       
INFO: Seed: 1969889841                                       
INFO: Loaded 1 modules   (25 inline 8-bit counters): 25 [0x56393139edb0, 0x56393139edc9),
INFO: Loaded 1 PC tables (25 PCs): 25 [0x56393139edd0,0x56393139ef60),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes                      
INFO: A corpus is not provided, starting from an empty corpus                     
#2      INITED cov: 7 ft: 7 corp: 1/1b exec/s: 0 rss: 24Mb                        
        NEW_FUNC[1/1]: 0x5639313194b1                                                      
#5      NEW    cov: 9 ft: 9 corp: 2/3b lim: 4 exec/s: 0 rss: 24Mb L: 2/2 MS: 3 ChangeByte-CopyPart-InsertByte-
#4194304        pulse  cov: 9 ft: 9 corp: 2/3b lim: 4096 exec/s: 2097152 rss: 24Mb
#8388608        pulse  cov: 9 ft: 9 corp: 2/3b lim: 4096 exec/s: 1677721 rss: 24Mb                                                                                         
core.exception.AssertError@fuzz.d(10): Assertion failure
----------------                        
??:? [0x563931355425]                                              
??:? [0x563931374daa]                                                             
??:? [0x56393135bbdd]                                             
??:? [0x56393135533c]                                      
fuzz.d:10 [0x5639313192f1]                                                               
libfuzzer.di:66 [0x56393131918a]                                                                              
FuzzerLoop.cpp:553 [0x563931337486]                                                        
FuzzerLoop.cpp:469 [0x56393133d437]                          
FuzzerLoop.cpp:696 [0x56393133f62b]                       
FuzzerLoop.cpp:831 [0x5639313407aa]  
FuzzerDriver.cpp:825 [0x56393132ad79]                                                                                                  
FuzzerMain.cpp:19 [0x563931318ef2]                                                
??:? __libc_start_main [0x7f6dc7dccb96]                                           
??:? [0x563931318fa9]                                                             
uncaught exception                                                                
core.exception.AssertError@/home/kubo39/dlang/ldc-1.19.0/bin/../import/ldc/libfuzzer.di(73): Assertion failure                                                             
----------------
??:? [0x563931355425]
??:? [0x563931374daa]
??:? [0x56393135bbdd]
??:? [0x56393135533c]
libfuzzer.di:73 [0x563931319204]
FuzzerLoop.cpp:553 [0x563931337486]
FuzzerLoop.cpp:469 [0x56393133d437]
FuzzerLoop.cpp:696 [0x56393133f62b]
FuzzerLoop.cpp:831 [0x5639313407aa]
FuzzerDriver.cpp:825 [0x56393132ad79]
FuzzerMain.cpp:19 [0x563931318ef2]
??:? __libc_start_main [0x7f6dc7dccb96]                                                                                                                                    
??:? [0x563931318fa9]                                   
==5215== ERROR: libFuzzer: deadly signal
NOTE: libFuzzer has rudimentary signal handlers.                   
      Combine libFuzzer with AddressSanitizer or similar for better crash reports.
SUMMARY: libFuzzer: deadly signal                                 
MS: 3 ChangeBit-InsertByte-ChangeBinInt-; base unit: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
0x46,0x55,                                                                               
FU                                                                                                            
artifact_prefix='./'; Test unit written to ./crash-510e70573bca51a824e345d5cff7448f740203fb
Base64: RlU=

なんだか出力の結果が怪しいが、とりあえずlibFuzzer runtime上で動いているようにみえる。

opamでnumのインストールに失敗してcoqが入らなかったのでocamlfind removeで解決した

opamでcoqをインストールしようとすると以下のようなエラーになった。

$ opam --version
2.0.5
$ opam list
# Packages matching: installed
# Name              # Installed # Synopsis
base-bigarray       base
base-threads        base
base-unix           base
camlp5              7.10        Preprocessor-pretty-printer of OCaml
conf-findutils      1           Virtual package relying on findutils
conf-m4             1           Virtual package relying on m4
ocaml               4.07.0      The OCaml compiler (virtual package)
ocaml-base-compiler 4.07.0      Official release 4.07.0
ocaml-config        1           OCaml Switch Configuration
ocamlfind           1.8.1       A library manager for OCaml
$ opam install coq                                                                                                                                              
The following actions will be performed:                                                                                                                                    
  ∗ install num 1.3     [required by coq]                                                                                                                                   
  ∗ install coq 8.10.1*                                                                                                                                                     
===== ∗ 2 =====                                                                                                                                                             
Do you want to continue? [Y/n] y                                                                                                                                            
                                                                                                                                                                            
<><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><>                                                                                            
[coq.8.10.1] found in cache                                                                                                                                                 
[num.1.3] found in cache                                                                                                                                                    
                                                                                                                                                                            
<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>                                                                                            
[ERROR] The installation of num failed at "make install".                                                                                                                   
                                                                                                                                                                            
#=== ERROR while installing num.1.3 ===========================================#                                                                                            
# context     2.0.5 | linux/x86_64 | base-bigarray.base base-threads.base base-unix.base ocaml-base-compiler.4.07.0 | https://opam.ocaml.org/#662359aa                      
# path        ~/.opam/4.07.0/.opam-switch/build/num.1.3                                                                                                                     
# command     ~/.opam/opam-init/hooks/sandbox.sh install make install                                                                                                       
# exit-code   2                                                                                                                                                             
# env-file    ~/.opam/log/num-16298-0a5907.env                                                                                                                              
# output-file ~/.opam/log/num-16298-0a5907.out                                                                                                                              
### output ###                                                                                                                                                              
# [...]                                                                                                                                                                     
# make[1]: ディレクトリ '/home/kubo39/.opam/4.07.0/.opam-switch/build/num.1.3/src' から出ます                                                                               
# make -C toplevel install                                                                                                                                                  
# make[1]: ディレクトリ '/home/kubo39/.opam/4.07.0/.opam-switch/build/num.1.3/toplevel' に入ります                                                                          
# sed -e 's/%%VERSION%%/1.3/g' META.in > META                                                                                                                               
# ocamlfind install num-top META num_top.cma num_top.cmi num_top_printers.cmi                                                                                               
# ocamlfind: Package num-top is already installed                                                                                                                           
#  - (file /home/kubo39/.opam/4.07.0/lib/num-top/META already exists)                                                                                                       
# Makefile:27: recipe for target 'install' failed                                                                                                                           
# make[1]: *** [install] Error 2                                                                                                                                            
# make[1]: ディレクトリ '/home/kubo39/.opam/4.07.0/.opam-switch/build/num.1.3/toplevel' から出ます                                                                          
# Makefile:14: recipe for target 'install' failed                                                                                                                           
# make: *** [install] Error 2                                                                                                                                               
                                                                                                                                                                            
                                                                                                                                                                            
                                                                                                                                                                            
<><> Error report <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>                                                                                            
┌─ The following actions failed                                                                                                                                             
│ ∗ install num 1.3                                                                                                                                                         
└─                                                
╶─ No changes have been performed

これはocamlfind内のnumが邪魔をしており、opamのnumが入らないからのようだ。 以下のように ocamlfind remove すればインストールできるようになった。

$ ocamlfind remove num num-top
Removed /home/kubo39/.opam/4.07.0/lib/num/META
Removed /home/kubo39/.opam/4.07.0/lib/num
Removed /home/kubo39/.opam/4.07.0/lib/num-top/META
Removed /home/kubo39/.opam/4.07.0/lib/num-top
$ opam install coq          
The following actions will be performed: 
  ∗ install num 1.3     [required by coq]                                       
  ∗ install coq 8.10.1*    
===== ∗ 2 =====                 
Do you want to continue? [Y/n] y
                                                                                
<><> Gathering sources ><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
[coq.8.10.1] found in cache
[num.1.3] found in cache                                                        
                                                                                                                                                      
<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
∗ installed num.1.3                                                  
∗ installed coq.8.10.1
Done.