きりかノート 3冊め

おあそびプログラミング

Mountain LionでboehmgcがSEGVする件を修正

(2012/10/25 追記)8月にリリースされたgc-7.2dで修正されたようです。該当のコミットはこれかな?parent見るとけっこうおおがかりに手をいれたみたいですね。

あるいは、プログラムを理解しないまま修正する手法について。

ということで昨日のw3mのつづきでboehmgc。今日の成果物の修正パッチ。

1箇所volatileを追加だけです。納得いかねーーー!

調査の記録

昨日わかったことは、boehmgcを-O0でコンパイルすればSEGVしなくなるということでした。今までの経験的に、こういうケースではvolatileを足すと直るようです。

じゃあどこなのよ、というのが問題。rubyのときには通らないテストから該当の関数をわりとすぐ特定できた(rubyのソース構成をある程度知っていた)のですが、boehmgcとかさっぱりわからん。使うコード書いたこともないし。

幸いboehmgcにもテスト(make check)が用意されているので、それを利用して問題が発生している箇所をゴリ押しで特定していきます。

まずはMakefileをいじって、最適化のフラグをパラメータで指定しやすくします。

   % diff -u Makefile{.orig,}
   --- Makefile.orig	2012-07-31 22:24:13.000000000 +0900
   +++ Makefile	2012-07-31 22:24:42.000000000 +0900
   @@ -70,7 +70,7 @@
    # Modified by: Petter Urkedal <petter.urkedal@nordita.dk>


   -
   +OPTFLAGS = -O2


    pkgdatadir = $(datadir)/gc
   @@ -392,15 +392,15 @@
    CC = /usr/bin/clang
    CCAS = /usr/bin/clang
    CCASDEPMODE = depmode=gcc3
   -CCASFLAGS = -pipe -O2 -arch x86_64 $(DEFS)
   +CCASFLAGS = -pipe $(OPTFLAGS) -arch x86_64 $(DEFS)
    CCDEPMODE = depmode=gcc3
   -CFLAGS = -pipe -O2 -arch x86_64 -fno-strict-aliasing
   +CFLAGS = -pipe $(OPTFLAGS) -arch x86_64 -fno-strict-aliasing
    CPP = /usr/bin/clang -E
    CPPFLAGS = -I/opt/local/include -D_XOPEN_SOURCE=600 -D_DARWIN_C_SOURCE
    CXX = /usr/bin/clang++
    CXXCPP = /usr/bin/clang++ -E
    CXXDEPMODE = depmode=gcc3
   -CXXFLAGS = -pipe -O2 -arch x86_64
   +CXXFLAGS = -pipe $(OPTFLAGS) -arch x86_64
    CXXINCLUDES =
    CYGPATH_W = echo
    DEFS = -DHAVE_CONFIG_H

これで、`make OPTFLAGS=-O0'のようにして、いつでも最適化フラグを変更することができます。

次に以下のシェルスクリプトを用意しました。

  • 一度-O2でmake allした状態から開始
  • アルファベット順に[.o|.lo]を-O0で再コンパイル
  • コンパイルごとにmake checkを実行
  • make checkが成功したら終了する

というものです。ようするに-O0でコンパイルする必要がある(=> -O2で問題が起きている)プログラム(.c)を特定するのが目的です。

   % cat test-O0.sh
   #!/bin/zsh

   # % ls -1 *.o
   OBJFILES='
   allchblk.o
   alloc.o
     :  # 略
   thread_local_alloc.o
   typd_mlc.o
   '

   for OBJ in `echo ${OBJFILES}`
   do
       echo "testing ${OBJ} ..."
       rm ${OBJ} ${OBJ:r}.lo
       make OPTFLAGS=-O0 all >& /dev/null
       make check >& /dev/null
       if [ $? -eq 0 ]
       then
           echo "[passed] ${OBJ} -  OK OK OK OK OK OK OK OK OK"
           exit 0
       else
           echo "[FAILURE] ${OBJ}"
       fi
       echo "--------------------------------------"
   done

さて実施。

   % sudo port build boehmgc
   % cd `port work boehmgc`
   % cd gc-7.2
   % make clean
   % make
   % cp Makefile{,.orig}
   % vi Makefile # 上述のOPTFLAGSの編集
   % ./test-O0.sh
   testing allchblk.o ...
   [FAILURE] allchblk.o
   --------------------------------------
   testing alloc.o ...
   [FAILURE] alloc.o
   --------------------------------------
     :  # 略
   --------------------------------------
   testing mark_rts.o ...
   [FAILURE] mark_rts.o
   --------------------------------------
   testing misc.o ...
   [passed] misc.o -  OK OK OK OK OK OK OK OK OK
   %

ほいきた!

misc.cがあやしいですね。次はmisc.cだけを-O0でコンパイルしてみます。これでだめなら、この状態からまたスクリプトを実行するということを繰り返していけば、最終的に最適化の影響で問題がでている.cをひととおり特定できることになります。

   % make clean && make
   % rm misc.o misc.lo
   % make OPTFLAGS=-O0 all
   % make check
     :  # 略
   ===================
   All 11 tests passed
   ===================
   %

おお、misc.cだけでいけるようですね。

あとはmisc.cのどっかにvolatileを追加すれば直ると思うのですが、misc.cは1896行あります。読めない量ではありませんが、内容のわからないプログラムであることを考えると無謀です。

とりあえず、make check中にSEGVしたときのクラッシュレポートを見てみましょう。こんだけあります。

   gctest_2012-07-31-....crash
   middletest_2012-07-31-....crash
   realloc_test_2012-07-31-....crash
   smashtest_2012-07-31-....crash
   staticrootstest_2012-07-31-....crash
   test_cpp_2012-07-31-....crash
   threadkey_test_2012-07-31-....crash
   Process:         gctest [81258]
   Path:            /Volumes/VOLUME/*/gctest
   Identifier:      gctest
   Version:         0
   Code Type:       X86-64 (Native)
   Parent Process:  sh [81257]
     :  # 略
   Thread 6 Crashed:
   0   libgc.1.dylib                 	0x000000010d7aa6f6 GC_clear_stack_inner + 54
   1   libgc.1.dylib                 	0x000000010d7aa706 GC_clear_stack_inner + 70
   2   libgc.1.dylib                 	0x000000010d7aa706 GC_clear_stack_inner + 70
   3   libgc.1.dylib                 	0x000000010d7aa706 GC_clear_stack_inner + 70
     :  # 略
   295 libgc.1.dylib                 	0x000000010d7aa706 GC_clear_stack_inner + 70
   296 libgc.1.dylib                 	0x000000010d7aa706 GC_clear_stack_inner + 70
   297 libgc.1.dylib                 	0x000000010d7aa787 GC_clear_stack + 87
   298 gctest                        	0x000000010d77009d run_one_test + 973
   299 gctest                        	0x000000010d7708b9 thr_run_one_test + 9
   300 libgc.1.dylib                 	0x000000010d7afdca GC_inner_start_routine + 90
   301 libgc.1.dylib                 	0x000000010d7abce9 GC_call_with_stack_base + 25
   302 libsystem_c.dylib             	0x00007fff8ac36782 _pthread_start + 327
   303 libsystem_c.dylib             	0x00007fff8ac231c1 thread_start + 13

見た感じ、どれもGC_clear_stack_inner()が無限ループしてるようなログです。ここからは勘でGC_clear_stack_inner()のまわりを「んー、ここかな?」というカンジでvolatileを足してみてはmake check、という手順で効果のある場所を探していきます。今回はラッキーだったのか、30分程度でパッチをつくることができました。

テストはマジ偉大。プログラム理解してなくても修正できたかどうかがわかります。