セグメンテーション:GDBスクリプト


我々は最近、我々は非常に散発的には、全体のテストスイートを実行するときにセグメンテーション違反を取得することを発見VAST Platform (VA Smalltalk) Linuxで.
我々の最初の調査の間、我々はこれが何か新しいものでないと理解しました、しかし、問題は長年存在しました.それはLinux(Windowsでない)だけで起こります、そして、すべてのプログラム変種で:JITコンパイラの有無にかかわらず、メモリ共有、32と64ビットなどの有無にかかわらず、それは起こります.
最後に、バグを見つけて修正しました.このポストでは、私たちが行った実験のいくつかを概説し、最終的にGDBのアプローチで結論を下します.

テストスイート全体を実行する試み


広大なプラットフォームの内部開発のために、我々は16000の合計テストの巨大なテストスイートを使用します.このような問題を診断するとき、最初の明白なステップはちょうどスイートを走らせて、我々が「セグメンテーション違反」を得るかどうか見ることでした.明らかに我々はしなかった.さもなければ、我々はこれをずっと前に修理したでしょう.その後、テストの順序を無作為化しようとした、複数の回、スイートを実行しています.
この問題に関する特徴の1つは、セグメンテーション違反を膨大にする可能性のあるシナリオはほとんどないということです.しかし、それが起こると、通常はvmtrap.log デバッグに役立つ情報がたくさんあります.残念ながら,この場合,ログは生成されなかった.そのうえ、このセグメンテーション違反を得るとき、それは広大であると思われました.
それは、問題が仮想マシンの「出口コード」に関連していると思うようにしました.次のステップは、多くのイメージの開始と終了を行うbashスクリプトを書くことでした.

#!/bin/bash

# "Settings"
vaRoot="/usr/local/VASmalltalk/10.0.0x64"
vaVMPath="$vaRoot/bin/es"
workingDirectoryPath="$PWD"
log="$workingDirectoryPath/ctest.log"

# copied them from environments directory.
# The icx needs to have SUnitOneClick map loaded
imagePath="$workingDirectoryPath/abt.icx"
iniPath="$workingDirectoryPath/abt.ini"

cnfPath="$vaRoot/abt.cnf"
# The -no_break is strictly necessary because else it looks like
# the VM randomly freezes and we get defunct processes ..
# so the only workaround for now is to disable the break process.
vaVMArguments=" -no_break -mcd -msd -i$imagePath -ini:$iniPath"
# This testcases.txt file can be autogenerated with the Smalltalk code:
# CtestSunitRunner basicNew createTestCaseList
testCaseFile="$workingDirectoryPath/testcases.txt"
echo "Testcase file: $testCaseFile"

# Doing a cd is important here because else the ICs won't be found in the
# defined IC_ROOT in the .ini
cd $vaRoot
# If we don't specify this LANG or any other compatible one
# we would get quite some test failures
export LANG=en_US.iso88591
# Necessary, else the esvm40.so for example is not found
export LD_LIBRARY_PATH="$vaRoot/bin"
# Just to always start with a fresh log
[-e "$log"] && rm "$log"

echo " ======== Starting process ======="

set -e
while IFS='' read -r line || [[-n "$line"]]; do
    # Print some status on the console...the rest..directly into the log.
    echo "Line: $line"
    # Below two lines create forces a new abt.cnf files and appends
    # the necessary code to run the test with Ctest
    echo "PostStartUp ! " > $cnfPath
    # This line just extracts the class name from the line and appends the
    # code to abt.cnf
    echo "SunitRunner run: #`echo "$line" | cut -d '#' -f2- | cut -d '-' -f1` !" >> $cnfPath

    # Start VA VM with all the necessary arguments and redirect stdout/stderr
    # to the specified log file
    $vaVMPath $vaVMArguments < /dev/null >> "$log" 2>&1

    echo "Line finished"

done < "$testCaseFile"

echo " ======== Process finished ======="
echo `cat "$log" | grep "CTestFailure"`

このポストの焦点ではないので、私はGoryの詳細を知りたくありません、しかし、基本的に、スクリプトは以下を行います
  • ファイルを読み込むtestcases.txt これにはTestCase テストスイート全体のサブクラスです.
  • 各テストクラスに対してabt.cnf これは起動時に自動的に読み込まれ、そのファイルにはそのテストを実行するコードが含まれています.SunitRunner run: #MyTestCaseSubclassExample ) .
  • 各テストクラスの場合は、広大な起動します.起動時には、テストを実行します.完了したら、テスト結果を印刷して終了します.
  • それは、ステップ2と3の繰り返しを続けるでしょうtestcases.txt
  • 最後に、このbashスクリプトを実行するとき、我々は散発的にその分割故障を再現することができました.(時々クラッシュを起こすためにこのスクリプトを何度か走らせる必要がありました.

    gdbで動く


    ・・・私たちはなんとかして、「リリース」VMでクラッシュを再現することができました.次のステップは、“デバッグ”モードで広大なVMをコンパイルし、我々はまだバグを再現することができるだろう我々の指を横切ることだった.(時々人生は簡単ではありません、そして、バグはデバッグ味で現れません).
    私は通常、gdbの下に巨大なVMをコンパイルして実行します.
    export VM_COMPILED_BIN=/home/mpeck/Instantiations/git/vm-devel/devel/build/bin; \
    export VA_ROOT=/usr/local/VASmalltalk/10.0.0x64; \
    cd $VA_ROOT ; \
    export LANG=en_US.iso88591 ; \
    export LD_LIBRARY_PATH=$VM_COMPILED_BIN:$VA_ROOT/bin; \
    gdb -ex run --args \
    $VM_COMPILED_BIN/es -no_break -ini:/home/mpeck/Instantiations/SUnitOneClick/abt.ini -i/home/mpeck/Instantiations/SUnitOneClick/abt.icx |& tee gdb.log
    
    そのため、bashスクリプトの上で変更し、改行を試みました.
    
    $vaVMPath $vaVMArguments < /dev/null >> "$log" 2>&1
    
    
    以下のようにします.
    
      gdb -ex run --args $vaVMPath $vaVMArguments >> "$log" 2>&1
    
    
    さて、bashスクリプトの新しいバージョンを実行した後、我々は良いと悪いニュースを受けました.良いニュースは、バグがまだデバッグVMで現れているということでした.悪い知らせはgdb bashスクリプトで実行された場合はセッションを終了します.したがって、セグメンテーション違反が引き起こされたなら、私はGDBコンソールに入ることができませんでした.

    再現可能なケースを狭める


    Bashスクリプトによるクラッシュを再現することは、VMの「終了コード」に関する我々の疑いのより多くを煽りました.テストの全体のスイートを実行する必要はなかったかもしれませんが、1回のテストクラスを何回か実行してもまだ再現できませんでした.
    言い換えるとtestcases.txt 単一のテストで1行でTestProcessPrims 私たちの場合では、プロセスを1000回繰り返すために、bashにアウターループを加えます.
    我々の飢えは正しかったです、そして、それはまだ問題を再現しました.今、これはずっと簡単になるだろう.

    GDBで1000回プログラムを実行する


    最後に、我々はこのポストの最も有用な部分に近づいています.
    私たちは、私たちがする必要があるのは、gdbabt.cnf 以下のように、セグメンテーション違反が打たれたとき、GDBセッションを終えることなく.
    
    PostStartUp ! 
    SunitRunner run: #TestProcessPrims !
    
    
    これをする方法をオンラインで調査して、我々は2000年に到着しましたthis useful post GDBスクリプトを書く方法を示します.
    ファイルを作成しました/tmp/script_file.txt 次のようにします.
    
    set pagination off
    handle SIGUSR1 noprint nostop
    handle SIGUSR2 noprint nostop
    set $n = 1000
    while $n-- > 0
      printf "\n\n=========================\nstarting program: %d\n", 1000-$n
      run
      if $_siginfo
        printf "Received signal %d, stopping\n", $_siginfo.si_signo
        loop_break
      else
        printf "program exited\n"
      end
    end
    
    
    それからgdbを実行して、このように広大にしてください.
    
    export VM_COMPILED_BIN=/home/mpeck/Instantiations/git/vm-devel/devel/build/bin; \
    export VA_ROOT=/usr/local/VASmalltalk/10.0.0x64; \
    cd $VA_ROOT ; \
    export LANG=en_US.iso88591 ; \
    export LD_LIBRARY_PATH=$VM_COMPILED_BIN:$VA_ROOT/bin; \
    gdb -x /tmp/script_file.txt --args \
    $VM_COMPILED_BIN/es -no_break -ini:/home/mpeck/Instantiations/SUnitOneClick/abt.ini -i/home/mpeck/Instantiations/SUnitOneClick/abt.icx
    
    
    注意gdb -x /tmp/script_file.txt .
    最後に!それを実行するとき、我々はセグメンテーション違反を打つことができました.

    ライン96estimer.c 我々は問題を理解し、それを修正するために必要なものだった.
    これは古代のバグでした.MUSICのメインスレッドは' done 'フラグを後で設定します.estimer.c ) を実行し続ける.場合によっては、メインスレッドがシャットダウンされたときにまれな状態が表示され、' done 'フラグが存在する'プラットフォームglobals '構造体をきれいにします.このように、'遅延'スレッドが' done 'フラグを読み取ろうとする時には、すでに解放されたメモリを読んでセグメンテーションを引き起こします.簡単な修正は' Done 'スレッドが完了した後に' done 'フラグを設定するのを待つことです.どのような場合でも、バグと修正自体は、このブログのポストの目的ではなく、むしろ我々はどのように問題といくつかのヒントをGDBのアプローチを示しています.

    結論


    バグを再生し、それらをデバッグするのは、通常のように簡単ではない.この特定のケースでは、それはGDBの中からプログラムを複数回実行する方法の私のための学習体験でした.