昔の最近の出来事(2011.04)

2011/04/30

昼過ぎ起床。

もそもそとコーディング。あまりにも遅すぎるのでやり方を変えてみる。 std.regexを使う訳ですが、tokenをランダムアクセスしたい場合に、Rengeで tokenを返すsplitter()とかは使いにくいです。 また、ランダムアクセス可能なように Rangeをstring配列に変換しようとしたの ですが、lengthプロパティもwalkLength()も使えないRangeのようで、tokenの数を得るのに 一度スキャンして長さを求め、配列要素に代入するのにもう一度スキャンし直す必要がありました。

積みっぱになっていた「まもるクンは呪われてしまった 冥界活劇ワイド版」を開封。 アーケード版がオリジナルのようですが、実機稼動は一度も見た事ありません。 そんな訳でやってみたり。ワイド版はアーケードとは少しゲームの仕様が違う模様。 何気に1080pで見易かったり。それはアーケードモードをやってみて、「画面狭!」 って感じるくらいに。呪い弾の使い方がイマイチ要領を得なかったのですが、使い方 が判ってくると面白くなってくるように思います。とは言え、最近は2D STGもあまり やって無いのと寄る年波には勝てないのとで、コンティニュー押しで進めるのが 精一杯です(^^;

2011/04/29

起きたら午後もいい時間。寝すぎ。

もそもそとコーディング。pathのデータパースに使う正規表現群を 一通り入れてみたのですが、やっぱりマッチングが遅すぎます。

正規表現の性能についてWebを眺めていたら、 鬼車 なる正規表現ライブラリの存在を知ったり。更にリンクを辿っていて、 「メールアドレスの正規表現」 というページに辿り着いたり。えー?!こんな事になっちゃうんだっけ?(^^; という感想。

2011/04/28

早くも無く遅くも無く。

もそもそとコーディング。先日の件は正規表現を少し整理したり、事前加工で マッチの種類を減らしたりしたらマシになった気も。

2011/04/27

早くも無く遅くも無く。

PSN障害。不正アクセスで情報漏えいの可能性があるとも。まぁ、大した情報は入ってない ので、それほど大事でも無い気が。再開のメドはまだ立っていない模様。

もそもそとコーディング。std.regexを弄っているのですが、何故かマッチの性能が 極端に下がる場合があったり。イメージ的なザックリした例ですが、 「((num num) (num num))+」という感じで、二つの数を1ペアとして、そのペア×2個が 1つ以上並んでいる場合のマッチだと問題無い速度でマッチできるのですが、 これが「((num num) (num num) (num num))+」という感じでペア×3個になると途端に ハングしたかと思うくらいの速度になってしまいました。 うーむ、どうしたもんか。

2011/04/26

遅めに帰着。

もそもそとコーディング。うーむ面倒臭い。

2011/04/25

遅めに帰着。

もそもそとコーディング。

2011/04/24

AM中に起床。

積みっぱになっていたファミ通WAVE休刊号のDVDをやっと観たり。 最後まで自由な感じでした。

そういやPSNが4/21頃から障害で停止しているもよう。PSNが止まると 週トロとか見れないので結構不便かも。

もそもそとコーディング。std.regexの 正規表現エンジンの格納に、 「auto r = regex("pattern") ;」のようにauto変数として代入する例があります。 この例での r を連想配列にしたい場合、auto変数にしたのではダメなので、 「Regex[string] r; r["hoge"]=regex("pattern");」みたいにすれば良いと思ったのですが、 これだと「Error: struct std.regex.Regex(E) if (is(E == Unqual!(E))) is used as a type」なるエラー。 意味がよくわからないのですが、テクニカル過ぎて基本的な用法がダメになっている 予感がしたりも。

2011/04/23

昼過ぎ起床。雨が激しすぎる。

新しいgdcをビルドしてみる事にしたり。tdmのバイナリを置いてくれている のですが、/usrの下に置かれるのが個人的に具合が悪いので。ただ、configureオプションを 参考にさせてもらうことにしたり。
で、r550:3cf208768d86 はエラー無くビルドできたのですがインストールして使ってみたところ、 リンクで_tlsstart, _tlsendが見つからなくてリンクできず。Webに置かれているバイナリと同じ r546:efb1b1ed90d8 をビルドしてみたところ、これもエラー無くビルドできたのですが、インストール して使ってみると_tls_indexが見つからなくてリンクできず。これはr548で修正が入っている ようだったので、それを手でちょこっと直してひとまず使えるようになりました。
で、手持ちのコードをコンパイルしてみる訳ですが、ファイルエクスプローラ から起動するとずっこける問題が発動。この辺は何も変わっていないようです。 自前対処をあててなんとなくOKな感じ。

r550で_tlsstart,_tlsendのリンクできなくなった方に、何かしらの対処が入った事を期待したのですが なんとなくデグってただけっぽい。動的配列の内容が壊れる場合があるのは特に変わりなく。

シネ通でやってたレゴランドの話。スターウォーズのシーンなどをレゴで再現した事が紹介されて いました。そのイベントに旧三部作でレイア姫役だったキャリー・フィッシャーが招待されて いたのですが、それを見たバナナマン設楽の「え?この人がレイア姫? ジャバザハットじゃなくて?」 に思わず吹いたり(^^;

2011/04/22

遅めに帰着。

もそもそとコーディング。SVGのフォーマット。パスのデータの並びは 判ったものの、ルールが大らか過ぎてなんだかパースが面倒臭いなぁと 思ったりも。例えば同じ命令が続いている場合、後に続く命令では命令文を 省いて良いという仕様。「L 100 100 L 200 200」 と 「L 100 100 200 200」 は同じという感じなのですが、後者の場合だと一つ前のコマンドを覚えておく 必要があります。また、命令と次に続く値の間には空白があっても無くても良い (「L 100 200」と「L100 200」は同じ)とかは、トークン分離を少し面倒臭くして いるようにも感じます。パターンが大らかだと、間違い(文法エラー)を検出するのも難しく なるような気がするので良い事は無いと思うし、大してデータの圧縮にもなっていない ように思うのですが、何故こんな感じになっているのかは知る由も無く。

2011/04/21

早くも無く遅くも無く。

もそもそとコーディング。std.regexpが非奨励になっていたので、std.regexを 使ってみたり。トークン分離の方法で、以前はstringの配列を返すRegExpクラスの メソッドがあったのですが、最近はRangeなるものとして返されるっぽい。 このRangeはスタックというかリストというか、そういうイメージに近いものらしい。 そういや以前いじったErlangには tupleとlistがありましたが、このときのlistもしかり今回のRangeもしかり、 ランダムアクセス可能な配列以外に、listやRangeのようにシーケンシャルアクセスに 限定するようなデータを用意するメリットってあるのかしら?

2011/04/20

早くも無く遅くも無く。

std.xmlを使う練習。以前は特殊な構文っぽくて イマイチ意味が判らなかったのですが、タグをハッシュキーとした関数呼び出しの リストを作っているんだと判ったり。
XMLとしてアトリビュートやエレメントを取り出す事はできそうなのですが、 例えばstyleアトリビュートは style="color:#000000;fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" の様に「長っ」てな感じの上に、更にXMLとは違う感じのトークン分離を 行わなくてはならないようです。あと、pathのデータ並びがどういう風に なっているのかがイマイチよく判らなかったり。タグやパラメータの 解説が無いかと探してみると、 SVG 1.1仕様(第2版) 日本語訳 というのが見つかったり。パスなどについても一通りの説明がされているようですが、 パッと見た感じ仕様が大きすぎて読むのに挫けそうかも(^^; また、全ての図形を cairoの描画プリミティブに1:1に置き換えられる訳では無さそうです。

2011/04/19

早くも無く遅くも無く。

もそもそと調査。割と簡単に再現プログラムを書けないか?と思って少し 試してみたのですが、うまく再現できなかったり。

そういや、SVGをパースするのに std.xmlを使うのはどうかしら?と いうのは以前思った訳ですが、 なんだかバグバグしていたのと、まるで違う言語のようなコードでそのまま フェードアウトしてました。ひとまず描画プリミティブさえ取り出せれば良い気が するので、少し実験してみたり。でもまだ何も掴めず。

2011/04/18

気持ち遅めに帰着。

もそもそと配列が壊れる件を調査。何に反応しそうかを見てみるだけで終了。 いつの間にか中身がすり替わったりするようなのでどうしたもんか。

2011/04/17

昼頃起床。

cairoを使用するコードをgdc 2052対応する為にcairoをMinGWでビルドしたり。 cairoのconfigureで使用する pkg-configが入っていないのを入れようとしたら、 pkg-configは glibを使っていたりしてまずはそこから入れなくてはならないとか、 なかなか面倒臭かったです。 ひとまずOKそう。ついでにlibrsvgもなんとかならないか?と思ってビルドしようと したのですが、librsvgが依存しているlibxml2 のconfigureが通らないという 感じになってて、あまり調べずに終了。

調査。動的配列の内容が壊れているようなのですが、その前にTLSを調べたり。 問題ありそうなところをshared修飾してみたのですが状況は変わらず。 一応単スレッドアプリなのと、GCをdisableにすると配列の内容が壊れたりは しないようなので、GC起因の問題のような気が。

なんとなく壊れている配列が限定されてきたりも。でも、どのタイミングで 壊れたのかが良くわからず。少なくとも、配列の範囲チェックに引っかかる事 無くSegfaultでずっこけているようなのと、GCをdisableにすると大丈夫な事から、 開放してはならない領域を間違って開放しているんじゃないか?という推測です。

bitbucketのgdcページ では、MinGWに関連した変更がいくつか入っている模様。その中にtlsに関連したものも あるようなので、もしかすると色々直っているかも知れません。

2011/04/16

地震の揺れでAM中に起床。

そういや以前 クラス内で 動的配列を確保した時、デストラクト時に何もしないと確保された動的配列の方は 開放されないという話がありました。gdcの2.052対応版だとその辺どうなっているん だろう?と思い確認してみたり。結果、


そんな訳で、多少手放しでも良い感じではあるものの、大きなメモリブロックを動的配列 で扱うのであれば、ある程度意識して自力でメモリ開放しなくてはならない事には 変わりないようです。

もそもそと手持ちコードの gdc 2052対応。概ね問題無さそうなのですが、一つだけ 実行ファイルがずっこけるのがあったり。動的メモリ割り当てした 配列の内容が壊れているように思うのですが原因はまだ掴めず。

gdc の r545:71f10f204790 というバージョンでMinGWのパッチがマージされた模様。 でも、druntime/rt/monitor_.dの /+ +/コメント外しとかが入っていないような 気が。これで大丈夫なんだろうか?

2011/04/15

遅めに帰着。

もそもそと手持ちコードをgdc 2052対応したり。

2011/04/14

早くも無く遅くも無く。

もそもそと調査。 __gshared 修飾されている __blkcache_offsetを threadクラス内のtlsとして 保持するように書き換えてみたり。で、デバッガで値を確認してみたりした のですが、tlsとして持つようにしたblkcache_offsetと __gshared修飾された 元の__blkcache_offsetは同じ値になっていて、あれぇ?やっぱりオリジナルの コードで良いのかなぁ?と思ったり。でも、何故かずっこけなくなりました(^^;;; 問題はgdb上で実行するとやっぱりずっこけるという謎の挙動を示すようになった 点でしょうか。もう何が何やら。これで少し使ってみることにします。

2011/04/13

少し早めに帰着。

昨日のコード。Cygwinのシェルから起動した場合は問題無かったのですが、 ファイルエクスプローラからダブルクリックで起動した場合はエラー窓が 開いて起動できなかったり。デバッガから起動したら上手く起動したり するもんなので、色々こねくり回していたのですが、どうやら 件のコードと同じ現象が発生している感じだったり。 うーむ、しばらく大丈夫かなと思ったのですが、昨日の今日でもう踏んでしまいましたか。

ずっこけ所はfullcollect時のrt_processGCMarks()内で再現プログラムと 同じ箇所。GCをdisableにすれば動き続けられるのは変わらず。

そういえば、あるスレッドのTLSな領域を、別のスレッドから見る事って できるんだっけ?と思ったり。GCのfullcollect内では、collectの発動した スレッドがthread_suspendALL() を実行(これでcollect発動スレッド以外のスレッドが全て止まる)します。 続いてcollectコードを実行した後に、全スレッド分のrt_processGCMarks()を実行します。このとき、 スレッド毎の TLSに配置された __blkcache_storage をcollect発動スレッド から検査する流れになる訳ですが、別のスレッドのTLSな領域を見る事が できないのであれば、そもそもこの動作って成立しないように思った訳です。

2011/04/12

早くもなく遅くも無く。

進展あり。ボンヤリのコードについて、Window基底クラスで 使用しているプロシージャのリストをsharedで修飾して、それに伴うコンパイル エラーを直したところ、GCをenableにしたままでうまく絵が出るように なりました(^^;;;; どうも TLSなグローバル変数になっていたのが 猛烈に具合が悪かったようです。

そもそもTLSとsharedだとどう違うのか?という所を確認してみたり。

$ cat -n tls_test.d
     1  import std.stdio;
     2  import std.stream;
     3  import std.string;
     4  
     5  import core.thread;
     6  
     7  class ThreadEx : Thread {
     8    shared static int shared_No ;
     9    static int        tls_No    ;
    10    
    11    this(){
    12      super( &run ) ;
    13    }
    14    
    15    private  void run() {
    16      writef("enter Thread shared_No=%d, tls_No=%d\n", shared_No, tls_No ) ;
    17      shared_No++ ;
    18      tls_No++ ;
    19      sleep(10_000_000_0) ;  // 10sec
    20      writef("leave Thread\n") ;
    21    }
    22    
    23  }
    24  
    25  void main(string[] args)
    26  {
    27    writef("start\n") ;
    28  
    29    auto t1 = new ThreadEx();
    30    t1.name("Thread1") ;
    31    t1.start();
    32    
    33    auto t2 = new ThreadEx();
    34    t2.name("Thread2") ;
    35    t2.start();
    36  
    37    for( int i=0 ; i<10000000 ; i++ ){}
    38    
    39    Thread[] threadlist = Thread.getAll() ;
    40    for( int i=0 ; i<threadlist.length ; i++ ){
    41      writef("%d : %s\n", i, threadlist[i].name()) ;
    42    }
    43    
    44    writef("end\n") ;
    45  }

$ gdc -g tls_test.d

$ ./a.exe 
start
enter Thread shared_No=0, tls_No=0
enter Thread shared_No=1, tls_No=0
0 : Thread2
1 : Thread1
2 : 
end
leave Thread
leave Thread

sharedで修飾された変数 shared_Noは 各スレッドで共有されている為、 二つ目のスレッドインスタンスでは+1された値になります。 一方 shared修飾しないと、static変数にも関わらずスレッド共用では 無いので、二つのスレッドインスタンスではどちらも0始まりとなってます。 丁度インクリメントしている17行目と18行目のアセンブラコードを 表示してみると、

$ objdump -S a.exe | grep -A12  shared_No++
    shared_No++ ;
  4013c5:       a1 08 80 49 00          mov    0x498008,%eax
  4013ca:       83 c0 01                add    $0x1,%eax
  4013cd:       a3 08 80 49 00          mov    %eax,0x498008
    tls_No++ ;
  4013d2:       c7 04 24 00 c0 47 00    movl   $0x47c000,(%esp)
  4013d9:       e8 c2 33 05 00          call   4547a0 <___emutls_get_address>
  4013de:       8b 00                   mov    (%eax),%eax
  4013e0:       8d 78 01                lea    0x1(%eax),%edi
  4013e3:       c7 04 24 00 c0 47 00    movl   $0x47c000,(%esp)
  4013ea:       e8 b1 33 05 00          call   4547a0 <___emutls_get_address>
  4013ef:       89 38                   mov    %edi,(%eax)
    sleep(10_000_000_0) ;  // 10sec

という感じになってました。変数shared_Noの方は0x498008番地の内容を 単純にインクリメントしてますが、変数tls_Noの方は 関数__emutls_get_address で得たアドレスに対して値をインクリメントする操作を行っている感じになってます。

そんな訳なのですが、TANEの単純なチョンボだったのか?と言うと、 調査用に再現させた件のコードはsharedは 一切関係ありません。こちらについては今のところ原因を特定できず。 ひとまず手持ちコードの 2.052対応を進めてみようかと 思う訳ですが、まだ不具合を踏む可能性は残っているという感じかも。

2011/04/11

早くもなく遅くも無く。

もそもそと調査。dmdのオリジナルコンパイラに -vtlsというオプションがあり、 TLSになっているグローバル変数が表示できます。これでrt/lifetime.dをコンパイル してみると、__blkcache_storageはTLSだという事のようです。で、 __blkcache_offsetは __gsharedが指定されている為、TLSじゃないグローバル変数 になっています。で、やっぱりよく判らないのが、 thread毎のm_tlsというポインタ位置とTLSである__blkcache_storage領域との 距離が、どのスレッドの場合でも毎回同じになるんだっけ?という点。 やっぱりここん所、何か変な気がするんだけどなぁ?.....

2011/04/10

AM中に起床。

もそもそと調査。改めて順に調べてみることにしたり。

各スレッドはそれぞれにm_tlsなるメンバー変数を保持しています。 この変数はあるメモリ領域を指していて、その範囲は_tlsstart〜_tlsend で示されるアドレスの範囲を指しています。ところが、この_tlsstartと_tlsend は単純な変数ではなくて、gcc/emutls.cというソース内の __emutls_get_address()なる関数呼び出しに置き換えられていました。どうりで 表示しようと思っても見られない訳です(^^; で、得られた_tlsstartと_tlsend の範囲は32バイトとなっており、Phobosの中ではスライシングでこのメモリ領域 を参照する(実際に見る事があるのか否かがまだ不明)ようになってました。

もう一つlibphobos/rt/lifetime.dの中に__blkcache_storageというポインタ変数があります。 このポインタの指す領域の中に不当な値が入っていてずっこけるという流れなのですが、 この領域も__emutls_get_address()で得ていました。で、この領域はどうやって phobos内で参照しているかと言うと、先に述べたスレッド毎のm_tlsの先頭アドレス (すなわち_tlsstart)と__blkcache_storageのアドレスとのoffsetを __blkcache_offsetという変数に保持して、毎度m_tls+__blkcache_offseで __blkcache_storage領域を参照しています(以前 、何故__blkcache_storageを直接参照してないのか?をスルーした点にかかっています)。
ただ、この__blkcache_offsetの計算が_Dmainに到達する前に一度実行されるだけ となってるのが謎です。つまり、メインスレッドが起きた時にメインスレッドのm_tlsと __blkcache_storageから__blkcache_offsetを計算するのですが、 それっきりなので、_Dmain以降に起こしたスレッドは そのスレッド用に確保 した m_tls領域の先頭アドレスに__blkcache_offsetを足した場所を参照 してる事になっていて、そこっていつメモリ確保したんだっけ?という状況。 この点が以前__blkcache_storageって本当にTLSに なっているんだろうか? にかかってきます。んじゃ、__blkcache_storageを 直接参照すれば良いのか?というとそういう訳でもなく、__blkcache_offsetの 計算と同様に、__blkcache_storageもメインスレッドの分しか確保していない ように見えます。

という訳で、特に何も解決せず。

散髪予約。予約満杯のようで日も暮れた頃に行ったり。

2011/04/09

昼頃起床。

もそもそと調査。やっぱり全然関係無い場所をアクセスしているように見えるなぁ?

2011/04/08

気持ち早めに帰着。

もそもそと調査。例えば、あるメモリアドレスの内容がいつの間にか変わっていた 場合、コードのどこで そのメモリアドレスを触ったのかを知る方法は無いん だっけ?と思って調べたり。gdbにそういう事を行う方法がありました。

次の様なD言語のコードを例にとってみます。

$ cat -n gdb_memtest.d
     1  import std.stdio;
     2  import std.string;
     3  
     4  void main(string[] args)
     5  {
     6    int[] array ;
     7  
     8    array.length=256 ;
     9  
    10    for( int i=0 ; i<array.length ; i++ ){
    11      array[i]=i ;
    12    }
    13  
    14  }

gdbのコマンド列を順に示してみます。
(gdb) b _Dmain                                                             #いわゆるDのmainにブレークポイント設定
Breakpoint 1 at 0x401361: file gdb_memtest.d, line 6.
(gdb) run
Starting program: /cygdrive/c/cygwin/home/tane/develop/dlang/test/a.exe 
[New Thread 5840.0x1504]

Breakpoint 1, main (args=...) at gdb_memtest.d:6                           #止まった
(gdb) n                                                                    #6行目ステップ実行 
(gdb) n                                                                    #8行目ステップ実行 
(gdb) print/x &array                                                       #変数arrayのアドレス
$1 = 0x22fce4
(gdb) x/16x $1                                                             #arrayをメモリダンプ
0x22fce4:	0x00000100	0x00c95800	0x00425c73	0x0022fdfc #0x22fce4が配列サイズ、0x22fce8が配列実体へのポインタ
0x22fcf4:	0x0022fee4	0x0040dff1	0x0022fee4	0x00000009
0x22fd04:	0x0022fd4c	0x0022fd28	0x0040e00e	0x00000001
0x22fd14:	0x0022fe80	0x00000000	0x0022fee4	0x0040dff1
(gdb) x/16x 0xc95800                                                       #配列実体をメモリダンプ
0xc95800:	0x00000000	0x00000000	0x00000000	0x00000000 #全部0でクリアされている
0xc95810:	0x00000000	0x00000000	0x00000000	0x00000000
0xc95820:	0x00000000	0x00000000	0x00000000	0x00000000
0xc95830:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) watch *(int*)(0xc95810)!=0                                           #watchポイント設定
Hardware watchpoint 2: *(int*)(0xc95810)!=0                                #0xc95810の内容が0で無くなったら止まるハズ
(gdb) c                                                                    #コンティニュー実行
Continuing.
Hardware watchpoint 2: *(int*)(0xc95810)!=0                                #止まった

Old value = 0
New value = 1
0x004013f3 in main (args=...) at gdb_memtest.d:11
(gdb) x/16x 0xc95800                                                       #配列実体をメモリダンプ
0xc95800:	0x00000000	0x00000001	0x00000002	0x00000003
0xc95810:	0x00000004	0x00000000	0x00000000	0x00000000 #0xc95810が0でなくなっている
0xc95820:	0x00000000	0x00000000	0x00000000	0x00000000
0xc95830:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) print i
$2 = 4

という感じ。便利です。ただし、ハードウェア機構を使って実現しているようなので、 あまり沢山のウォッチポイントを同時に設定する事はできないっぽい。

因みにステップ実行 's' の代わりに 'n'を使っています。phobosも-gでコンパイル している為、array.length=256の行は、実際には phobos内の配列メモリの割り当て など大量のコードを実行する必要があるのですが、's'だとそれらを全て見る必要 があります。nを使うと 同じスタックレベルのコードに達するまで 実行を進めるので、見かけ上用事のあるコードだけを見る事ができるという訳です。

報道ステーションで輪番停電が今日で終わりというのを報道してました。コメント の中で「あれ(輪番停電)はなんだったんだ?」というような事を言ってたのですが、 そのコメントはいくらなんでも無いだろうと思ったり。そもそも地震直後は 3100万kWしか供給できないと言われていた訳ですが、例えば今日の最大需要実績は 電力使用状況グラフ によれば3276万kWでしたので、今日のような感覚で電力消費したとしても、 地震直後では余裕で供給不足となっていた訳です。グラフには前年の同時期実績も 表示されてますが、去年と同じノリで 使うと今の時点でも供給能力が足りてないのが判ります。これを踏まえて「なんだったんだ?」 と聞くにつけ、どこを見てればそのコメントが出てくるんだ?と思わざるを得ない訳です。

2011/04/07

早くも無く遅くも無く。

もそもそと調査。色々観察したり試しにいじったりしてるのですが、 イマイチ掴めず。

とかやってると意外と大きな揺れが。余震はまだまだ続くなぁ。

2011/04/06

気持ち早めに帰着。

もそもそと調査。流れがつかめたようなそうでもないような。
失敗の流れは、core.memory.GC.collect() ⇒ thread_processGCMarks() ⇒ rt_processGCMarks() と関数呼び出しして最後のrt_processGCMarks()内でSegfaultするという感じ。 Threadクラスの中にはm_tlsというメンバー変数があり、全てのスレッドに対して rt_processGCMarks(m_tls) という感じでGCMarksなる処理を行っているようです。 m_tlsにはThreadクラスの中で何やらメモリ領域を割り当て(正確には_tlsstart〜_tlsendの範囲をスライシングで 割り当て)ているようです。で、良くわからないのは、各スレッドのm_tlsの メモリアドレス番地を基準に、__blkcache_storageの領域に辿りつくように __blkcache_offsetが計算されているようなのですが、__blkcache_offset がどこで設定されているのかが判りません。一箇所だけ__blkcache_offset に値を設定するコードがあるのですが、「shared static this()」という 初期化の為の特別なコードで、一度だけ実行されているのですが、生成される スレッド毎に計算されているような気配はありません。
今の感じで見ると、__blkcache_offsetに値が入っているがそれは(恐らく) メインスレッドのm_tlsとの距離をセットしているけど、それ以外のスレッド のm_tlsとの距離では無い?ので、アクセスしてはならない領域が 指されてしまっている?気がしたり。lifetime.dの中に

// note this is TLS, so no need to sync.
BlkInfo *__blkcache_storage;

なる文言と宣言があるのですが、これ、本当にTLSになっているんだろうか?

2011/04/05

早くも無く遅くも無く。

調査の続き。追いかけたり、ちょっとパッチを当ててみたりするものの、 うまくゆかず。

2011/04/04

気持ち遅めに帰着。

先日の調査の続き。ひとまずcore/thread.dの内容については特に今のままで問題無いように 思ったり。そんな訳でデバッガを使いながら変数の中身や動きを調べたり。
どうやらrt/lifetime.d内に原因がありそうな予感がしてみたり。まず、Segfaultの直接 の原因はcacheなる変数の値がなんだか変な感じになっている所です。cacheの元になる __blkcache_offseにはどこで値が入っているのだろう?と辿ってみると、 「__blkcache_offset = (cast(void *)&__blkcache_storage) - tls.ptr;」なるコードが ありました。とどのつまり、cache は _blkcache_storage へのポインタと等しいという 事になるようです。何故__blkcache_storageを直接参照してはならないのかが謎ですが、ひとまず そこはスルーします。更に__blkcache_storageにはどこで値が入っているのだろう? と辿ってみると、

@property BlkInfo *__blkcache()
{
    if(!__blkcache_storage)
    {
        // allocate the block cache for the first time
        immutable size = BlkInfo.sizeof * N_CACHE_BLOCKS;
        __blkcache_storage = cast(BlkInfo *)malloc(size);
        memset(__blkcache_storage, 0, size);
    }
    return __blkcache_storage;
}

なるコードがありました。で、このコードのmemset()の所にブレークポイントを設定 してみたのですが何故か通りません。return の所にブレークポイントを設定しなおして みたのですがそれでも通りません。うーむ。
ところで、上記のコードは__blkcache_storageにうっかりゴミが入っていると __blkcache_storageは初期化されないのですが、それで大丈夫なんだっけ?と 思ったりも。でもそうだとすれば__blkcache_storageがクリアされてないので、 たまたま入ってたゴミを有効なアドレスだとみなして踏んだ結果がSegfaultという 流れはありうる話かも。

試しにデバッガから問題と思われる __blkcache_storageの領域をクリアして、 実行してみたり。

Breakpoint 3, rt_processGCMarks (tls=...) at ../../../libphobos/rt/lifetime.d:430
(gdb) l
425	    // might be ready to sweep
426	
427	    debug(PRINTF) printf("processing GC Marks, %x\n", tls.ptr);
428	    int  debug_var1 = __blkcache_offset ;
429	    uint debug_var2 = cast(uint)(tls.ptr) ;
430	    auto cache = *cast(BlkInfo **)(tls.ptr + __blkcache_offset);
431	    if(cache)
432	    {
433	        debug(PRINTF) foreach(i; 0 .. N_CACHE_BLOCKS)
434	        {
(gdb) print/x (BlkInfo **)(tls.ptr + debug_var1)
$7 = 0x3e3344
(gdb) print/x *$7
$8 = 0x9090909
(gdb) set *$7=0
(gdb) print/x *$7
$9 = 0x0
(gdb) delete breakpoints 
Delete all breakpoints? (y or n) [answered Y; input not from terminal]
(gdb) c
Continuing.
start
0
1
enter Thread
leave Thread
end

Program exited normally.
(gdb)

変数cacheに入る値0x9090909を0にした後、ブレークポイントを全部クリアして コンティニューしています。結果、Segfaultはせずに正常終了しています。 ただし、Segfaultしなかったというだけで、プログラムの実行結果が正しい かどうかまでは見てません(^^; しかし、__blkcache_storageの初期化漏れの 線が濃厚じゃなかろうか?と思い始めています。

2011/04/03

AM中に起床。

GCとスレッドの組み合わせによる不具合の再現プログラム。 なんとなく毎回同じようにSegfaultさせられる感じになってみたり。

$ cat -n thread_test.d
     1  import std.stdio;
     2  import std.stream;
     3  import std.string;
     4  
     5  import core.memory;
     6  import core.thread;
     7  
     8  class ThreadEx : Thread {
     9    
    10    this(){
    11      super( &gamethread ) ;
    12    }
    13    
    14    private  void gamethread() {
    15      int[int] array ;
    16      
    17      writef("enter Thread\n") ;
    18      for( int i=0 ; i<9 ; i++ ){
    19        array[i]=i*2 ;
    20      }
    21      for( int j=0 ; j<10 ; j++ ){
    22        if( (j in array)!=null ){
    23          array[j] += 1 ;
    24        }
    25      }
    26      sleep(10_000_000_0) ;  // 10sec
    27      writef("leave Thread\n") ;
    28    }
    29  }
    30  
    31  void main(string[] args)
    32  {
    33    GC.disable();
    34    writef("start\n") ;
    35    auto t = new ThreadEx();
    36    writef("%d\n",t.isRunning()) ;
    37    t.start();
    38    writef("%d\n",t.isRunning()) ;
    39    for( int i=0 ; i<10000000 ; i++ ){}
    40    GC.collect();
    41    writef("end\n") ;
    42  }

$ gdc -g thread_test.d

$ gdb ./a.exe 
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "mingw32".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from C:\cygwin\home\tane\develop\dlang\test/./a.exe...done.
(gdb) run
Starting program: C:\cygwin\home\tane\develop\dlang\test/./a.exe 
[New Thread 4564.0x1340]
[New Thread 4564.0x13cc]

Program received signal SIGSEGV, Segmentation fault.
rt_processGCMarks (tls=...) at ../../../libphobos/rt/lifetime.d:438
438                 if(cache.base != null && gc_isCollecting(cache.base))
(gdb) where
#0  rt_processGCMarks (tls=...) at ../../../libphobos/rt/lifetime.d:438
#1  0x00406c63 in thread_processGCMarks () at ../../../libphobos/core/thread.d:2611
#2  0x00429abb in fullcollect (this=..., stackTop=0x22fc1c) at ../../../libphobos/gc/gcx.d:2507
#3  0x0042a054 in fullcollectshell (this=...) at ../../../libphobos/gc/gcx.d:2325
#4  0x0042a18b in fullCollect (this=...) at ../../../libphobos/gc/gcx.d:1257
#5  0x0041d732 in gc_collect () at ../../../libphobos/gc/gc.d:155
#6  0x00401575 in main (args=...) at thread_test.d:40
#7  0x00417cdb in runMain () at ../../../libphobos/rt/dmain2.d:529
#8  0x00417d50 in tryExec (dg=...) at ../../../libphobos/rt/dmain2.d:480
#9  0x00417e64 in runAll () at ../../../libphobos/rt/dmain2.d:544
#10 0x00417d50 in tryExec (dg=...) at ../../../libphobos/rt/dmain2.d:480
#11 0x004185d9 in _d_run_main (argc=1, argv=0x3e4fb8, main_func=0x40148e <main>) at ../../../libphobos/rt/dmain2.d:554
#12 0x004019a8 in main (argc=1, argv=0x3e4fb8) at ../../../libphobos/rt/cmain.d:5
(gdb) 

狙いどころは次のような感じ。


ステップ実行もかろうじてできるので追いかけられそうでもありますが、GCのコードは なんだか難しくて追い詰められないかも。また、GC.collect()は既に壊れたメモリ管理状態を 踏んだだけの可能性がありますので、本当の原因は配列をアクセスする時なのかも知れません。
随分前にもスレッドとGCの関係が原因でメモリ割り当て が変になるってのを調べた事がありますが、今回のは前のようにスレッドの生成方法がチョンボってる 訳では無いと思ってます。

デバッガで追いかけてみました。その結果、なにやら妙な感じになっていたり。 Segfaultが発生するのはlibphobos/rt/lifetime.d というソースの中の rt_processGCMarks()という関数内で変なアドレスを踏んでいる為でした。 で、少しデバッグコードを足しているのですが、次のような箇所で変になってます。

421 : // we expect this to be called with the lock in place
422 : extern(C) void rt_processGCMarks(void[] tls)
423 : {
424 :     // called after the mark routine to eliminate block cache data when it
425 :     // might be ready to sweep
426 : 
427 :     debug(PRINTF) printf("processing GC Marks, %x\n", tls.ptr);
428 :     int  debug_var1 = __blkcache_offset ;
429 :     uint debug_var2 = cast(uint)(tls.ptr) ;
430 :     auto cache = *cast(BlkInfo **)(tls.ptr + __blkcache_offset);
431 :     if(cache)
432 :     {
433 :        debug(PRINTF) foreach(i; 0 .. N_CACHE_BLOCKS)
434 :        {
435 :            printf("cache entry %d has base ptr %x\tsize %d\tflags %x\n", i, cache[i].base, cache[i].size, cache[i].attr);
436 :        }
437 :        auto cache_end = cache + N_CACHE_BLOCKS;
438 :        for(;cache < cache_end; ++cache)
439 :        {
440 :            if(cache.base != null && gc_isCollecting(cache.base))
:

428行目と429行目は変数を見る為にTANEが足したデバッグコードです。Segfaultが発生するのは 440行目になります。431行目で止めて変数をいくつか表示してみました。

(gdb) print/x debug_var1
$8 = 0x138
(gdb) print/x debug_var2
$9 = 0x3e320c
(gdb) print/x cache
$10 = 0x9090909
(gdb) print/x cache[0].base
Cannot access memory at address 0x9090909

まず変な所から言いますと、変数cacheの値がアレな感じに。奇数アドレスになってるし、 実際に値を表示しようとするとアクセス不能な領域になってます。で、ここからが謎で よくわからない所なのですが、cacheの値の元になっているtls.ptr(==debug_var2)も __blkcache_offset(==debug_var1)も値は見えてるのですが、どう足しても変数cacheに 入っている値0x9090909にはならないという所。うーむ。
因みに、__blkcache_offsetはgdbで直接変数指定して見る事が何故かできませんでした。 この為、デバッグ変数に一時置きしてそれを表示しています。

間違えた(^^; キャストしないと目的のものを見てる事にならないっぽい。
(gdb) print/x *(BlkInfo **)(tls.ptr+debug_var1)
$14 = 0x9090909
(gdb) print/x (BlkInfo **)(tls.ptr+debug_var1)
$15 = 0x3e3344
(gdb) x/16w $15
0x3e3344:	0x09090909	0x09090909	0x09090909	0x09090909
0x3e3354:	0x09090909	0x09090909	0x09090909	0x09090909
0x3e3364:	0x09090909	0x09090909	0x09090909	0x09090909
0x3e3374:	0x09090909	0x09090909	0x0c0c0c09	0x0c0c0c0c
(gdb) x/64w 0x3e3300
0x3e3300:	0x28282828	0x28282828	0x28282828	0x28282828
0x3e3310:	0x28282828	0x28282828	0x08080808	0x0c0c0800
0x3e3320:	0x0c0c0c0c	0x0c0c0c0c	0x0c0c0c0c	0x0c0c0c0c
0x3e3330:	0x0c0c0c0c	0x0c0c0c0c	0x0c0c0c0c	0x0a0a080c
0x3e3340:	0x090a0a0a	0x09090909	0x09090909	0x09090909
0x3e3350:	0x09090909	0x09090909	0x09090909	0x09090909
0x3e3360:	0x09090909	0x09090909	0x09090909	0x09090909
0x3e3370:	0x09090909	0x09090909	0x09090909	0x0c0c0c09
0x3e3380:	0x0c0c0c0c	0x0c0c0c0c	0x0c0c0c0c	0x0c0c0c0c
0x3e3390:	0x0c0c0c0c	0x0c0c0c0c	0x00000c0c	0x00000000
0x3e33a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x3e33b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x3e33c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x3e33d0:	0x00000000	0x00000000	0x00000000	0x62610000
0x3e33e0:	0x66656463	0x6a696867	0x6e6d6c6b	0x7271706f
0x3e33f0:	0x76757473	0x7a797877	0x00000000	0x42410000

なんとなくこのメモリ域って何か別の物が入っているような気がする......

その後、tlsの元を辿っていくと、core/thread.d内の m_tlsという変数に辿りつき、 それを更に辿っていくと、_tlsstartと_tlsendという変数に行き着いたり。 bitbucketでcore/thread.dの更新履歴を見てみると、何故かこのソースだけ最近 手の入った痕跡があったので、前のとの差分を 見てみると なんか影響のありそうな直し方をしているように思ったりも。 なんとなくスレッドとGCとの繋がりが見えた気がしたようなそうでもないような。

2011/04/02

昼頃起床。

先日のビルドはエラー無く終了。std.stdint.oをlibgphobos2に含めるひと手間を入れて make install。で、試してみたのですが状況変わらず。そんな訳で、

┌───────────────────────┬─┐
│platform: compiler                            │RS│
├───────────────────────┼─┤
│linux: gcc-4.5.2 + goshawk-gdc-79ed94287f94   │OK│
├───────────────────────┼─┤
│Windows: dmd 2.052                            │OK│
├───────────────────────┼─┤
│MinGW: gcc-4.5.2 + goshawk-gdc-007de89f7694   │NG│
├───────────────────────┼─┤
│MinGW: gcc-4.5.2 + goshawk-gdc-79ed94287f94   │NG│
└───────────────────────┴─┘

という感じ。 因みに、GC.disable()をmain()の先頭位置に入れると大丈夫です。 やっぱりスレッドとメモリ割り当ての組み合わせにハマり所があるのかも。
どうにかうまく調べられないかとこねくり回してみているのですが、 gdbでうまく止められなかったりハングしたりでうまくゆかず。

その他の話。dmd2.014ベースのphobosではスレッドを停止するのに、.pause()って のがありました。これに対応するものとして、.join()ってのを使ってみたのですが、 例えばスレッドで動作している関数なりメソッドなりの中で無限ループを形成している 場合、これを強制的に停止してプロセスをexitする方法が無いように思ったり。 また、スレッドを.startした後にthread_suspendAll();で一時停止したまま main()を抜けても、プロセスは終了せずにずっと残ってしまうようです。 要するに全スレッドが停止するのをずっと待っている感じ。 これってこういうもんなんだっけ? てか、スレッドを強制的に止める方法って 無いんだっけ?

2011/04/01

早めに帰着。

先日のうまく動かないコードをdmdのオリジナルのコンパイラで試してみたり。 結果はうまく動いてみたり。

あと、r512:79ed94287f94 というバージョンで再度gdcビルドしてみたり。

......ビルドが終わらないのとあまりの眠さで死亡。


TOP PREV