逆戻りデバッグ補助のための嵌入的スパイの試作
TRANSCRIPT
逆戻りデバッグ補助のための
嵌入的スパイの試作
神谷 年洋島根大学大学院総合理工学研究科
かんにゅう
IEICE SIGSS 2016年7月研究会 於 札幌神谷年洋, 逆戻りデバッグ補助のための嵌入的スパイの試作, 信学技報, vol. 116, no. 127, pp. 87-92, (2016-07-14)
逆戻りデバッグとは?● 逆戻りデバッグ、backward-in-time debugging, omniscient
debugging[13]● 逆向きにステップ実行ができるデバッガ(によるデバッグ)
� ステップ実行で行き過ぎる(原因であると思われる箇所を通りすぎる) → 戻る
� 出力の文から逆向きにステップ実行していく● 実装: 実行途中の状態をすべて保存する or
状態を復元できる実行トレースを保存する[13] B. Lewis, M. Ducasse, Using Events to Debug Java
Programs Backwards in Time, OOPSLA’03
クラッシュしないバグとは?クラッシュするバグ クラッシュしないバグ
現象 プログラムの実行が中断する
プログラムの実行は終了するが振る舞いが予想外
デバッグの起点
実行が中断した場所がデバッグの起点になる
プログラムのどの部分がその振る舞いに相当するのかを調べるところから
原因 初期化し忘れなどの軽微なエラーも含まれる
ロジックの誤り
⇒ クラッシュしないバグのほうが修正が難しい
クラッシュしないバグの デバッグ作業とは?(1) 選択したプログラム(の一部分)を対象 として、(2) プログラムの実行を通して、さまざまな 入力が与えられ出力が得られるのを観察し、(3) それらの入出力が正しいか(期待した振る 舞いか)を判定する(4) 上の(1)〜(3)を、対象を変化させつつ繰り 返し、不具合の原因を特定する 不具合の原因: 期待通りの入力に対して、期待した 出力をしない小さなプログラムの部分
クラッシュしないバグの特定 ≒ プログラム理解
完全には理解できていない、文書化されていない
期待した振る舞い=仕様仕様の復元、確認
スパイとは?● ユニットテストにおいて、オブジェクト
間でやり取りされるメッセージを捉える機能– Java向けのライブラリMockitoは、
スパイ、モック、スタブといった機能を提供している
● スパイを用いたテスト: プログラムの実行中に実際に呼び出されたメソッドのそれぞれについて、その順序や引数の値が期待されるものと同じか確認する– 実測値(actual) vs 期待値(expected)
「ケース」に対するメソッド呼び出し(実測値):
(1) ProductName()→ [“Oval”, “Duvel”]
(2) qty(“Oval”) → 4
ユーザ在庫管理 ケース
在庫確認
productNames()
["Oval", "Duvel"]
qty("Oval")
4
在庫表示
「ケース」に対するメソッド呼び出し(期待値):
実行トレースの記録● 実行トレースとは
– 対象プログラムを実行し● インストラクション(呼びだされた手続きや計算された式)● 引数や戻り値(評価値)など
を時間順に記録したもの– 手続き呼び出しについては、入る(呼び出しにより)と出る(リ
ターンや例外送出など)の2つのイベントを記録 → コールツリー● 値の記録方法
– アトミックな値(intやboolなど)については、その値の表現(123やTrue)を記録
– オブジェクト(内部構造; フィールドを持つ)については、IDを記録● フィールドの値が必要なときは、そのオブジェクトのフィールドへの参照や代入といったインストラクションを遡ることで再現
実行トレースの記録の方法のバリエーションとしては、オブジェクトの内部のデータまで含めて記録、分岐ごとに1ビットでどちらをたどるか記録、等
:54568 1.1 load_const None:54569 1.1 line file=cal.py line=2:54570 1.1 load_const -1:54571 1.1 load_const <tuple #5606>:88854 1.1 line file=cal.py line=4:88855 1.1 load_const <string #8171> text='Su Mo Tu We Th Fr Sa':88856 1.1 line file=cal.py line=6:88857 1.1 load_const <code #8172> name=print_calendar file=cal.py line=6:88858 1.1 line file=cal.py line=17:88859 1.1 load_const <code #8173> name=main file=cal.py line=17:88860 1.1 line file=cal.py line=22:88861 1.1 load_const <string #2832> text='__main__':88862 1.1 result True:88863 1.1 line file=cal.py line=23:88864 1.1 call <code #8173> name=main file=cal.py line=17:88865 1.1 line file=cal.py line=18:88866 1.1 load_global_from <string #91> text='int':88867 1.1 result <type #92> name=int:88868 1.1 load_global_from <string #2055> text='sys':88869 1.1 result <module #5>:88870 1.1 call $LOAD_ATTR$:88871 1.1 self.0 <module #5>:88872 1.1 arg.1 <string #8174> text='argv':88873 1.1 return <list #8175>:88874 1.1 load_const 1:88875 1.1 call $BINARY_SUBSCR$:88876 1.1 self.0 <list #8175>:88877 1.1 arg.1 1:88878 1.1 return <string #8176> text='2016':88879 1.1 line file=cal.py line=19:88880 1.1 load_global_from <string #91> text='int':88881 1.1 result <type #92> name=int:88882 1.1 load_global_from <string #2055> text='sys':88883 1.1 result <module #5>:88884 1.1 call $LOAD_ATTR$:88885 1.1 self.0 <module #5>:88886 1.1 arg.1 <string #8174> text='argv':88887 1.1 return <list #8175>:88888 1.1 load_const 2:88889 1.1 call $BINARY_SUBSCR$:88890 1.1 self.0 <list #8175>:88891 1.1 arg.1 2:88892 1.1 return <string #3174> text='1':88893 1.1 line file=cal.py line=20
実行トレースの記録● 実行トレースとは
– 対象プログラムを実行し● インストラクション(呼びだされた手続きや計算された式)● 引数や戻り値(評価値)など
を時間順に記録したもの– 手続き呼び出しについては、入る(呼び出しにより)と出る(リ
ターンや例外送出など)の2つのイベントを記録 → コールツリー● 値の記録方法
– アトミックな値(intやboolなど)については、その値の表現(123やTrue)を記録
– オブジェクト(内部構造; フィールドを持つ)については、IDを記録● フィールドの値が必要なときは、そのオブジェクトのフィールドへの参照や代入といったインストラクションを遡ることで再現
実行トレースの記録の方法のバリエーションとしては、オブジェクトの内部のデータまで含めて記録、分岐ごとに1ビットでどちらをたどるか記録、等
:54568 1.1 load_const None:54569 1.1 line file=cal.py line=2:54570 1.1 load_const -1:54571 1.1 load_const <tuple #5606>:88854 1.1 line file=cal.py line=4:88855 1.1 load_const <string #8171> text='Su Mo Tu We Th Fr Sa':88856 1.1 line file=cal.py line=6:88857 1.1 load_const <code #8172> name=print_calendar file=cal.py line=6:88858 1.1 line file=cal.py line=17:88859 1.1 load_const <code #8173> name=main file=cal.py line=17:88860 1.1 line file=cal.py line=22:88861 1.1 load_const <string #2832> text='__main__':88862 1.1 result True:88863 1.1 line file=cal.py line=23:88864 1.1 call <code #8173> name=main file=cal.py line=17:88865 1.1 line file=cal.py line=18:88866 1.1 load_global_from <string #91> text='int':88867 1.1 result <type #92> name=int:88868 1.1 load_global_from <string #2055> text='sys':88869 1.1 result <module #5>:88870 1.1 call $LOAD_ATTR$:88871 1.1 self.0 <module #5>:88872 1.1 arg.1 <string #8174> text='argv':88873 1.1 return <list #8175>:88874 1.1 load_const 1:88875 1.1 call $BINARY_SUBSCR$:88876 1.1 self.0 <list #8175>:88877 1.1 arg.1 1:88878 1.1 return <string #8176> text='2016':88879 1.1 line file=cal.py line=19:88880 1.1 load_global_from <string #91> text='int':88881 1.1 result <type #92> name=int:88882 1.1 load_global_from <string #2055> text='sys':88883 1.1 result <module #5>:88884 1.1 call $LOAD_ATTR$:88885 1.1 self.0 <module #5>:88886 1.1 arg.1 <string #8174> text='argv':88887 1.1 return <list #8175>:88888 1.1 load_const 2:88889 1.1 call $BINARY_SUBSCR$:88890 1.1 self.0 <list #8175>:88891 1.1 arg.1 2:88892 1.1 return <string #3174> text='1':88893 1.1 line file=cal.py line=20
:88870 1.1 call $LOAD_ATTR$:88871 1.1 self.0 <module #5>:88872 1.1 arg.1 <string #8174> text='argv':88873 1.1 return <list #8175>
逆戻りデバッグ補助のための嵌入的スパイアイデア: プログラム理解のための機能を強化したデバッガ
● デバッグ中に動作を理解したいコード断片について
● それをひとつの部品(オブジェクト)と捉えたときの入出力を観察したい
● プログラム実行(振る舞い)の全体(文脈)を意識しながら
● 対話的に利用したい
かんにゅう
[スパイ]
必ずしも手続き単位ではない。手続きの一部や、複数の手続きにまたがることも[嵌入的; intrusive]
逆戻りデバッグ
実行トレースの記録があるので、対話的なUIで(対象プログラムを再度実行することなく)スパイを構成できる
デバッグ作業のモデル● 前提: 再現するバグ▷ 嵌入的スパイによるデバッグ
● (実行トレース上で連続したインストラクションに相当する)コード断片を指定し– 図のS, T, U
その入出力値を観察する– 図のa, b, c, d, e
● 入出力値の観察を通して– コードの仕様を理解する– 値が予想通りか確認することで範囲を絞っていく
m
q
r
a
d e
S
デバッグ作業のモデル● 前提: 再現するバグ▷ 嵌入的スパイによるデバッグ
● (実行トレース上で連続したインストラクションに相当する)コード断片を指定し– 図のS, T, U
その入出力値を観察する– 図のa, b, c, d, e
● 入出力値の観察を通して– コードの仕様を理解する– 値が予想通りか確認することで範囲を絞っていく
m
n
q
r
a
b
d e
S
T
デバッグ作業のモデル● 前提: 再現するバグ▷ 嵌入的スパイによるデバッグ
● (実行トレース上で連続したインストラクションに相当する)コード断片を指定し– 図のS, T, U
その入出力値を観察する– 図のa, b, c, d, e
● 入出力値の観察を通して– コードの仕様を理解する– 値が予想通りか確認することで範囲を絞っていく
m
n
o
p
q
r
a
b
c
d e
S
T
U
「コード断片」実行トレース内のサブシーケンス(連続したインストラクション)を「コード断片」とみなす
● 「コード断片」の入出力値を考えることができる → プログラム理解– 「入力値」: サブシーケンスの外で定義されサブシーケンス内
で利用される値– 「出力値」: サブシーケンスの内で定義されサブシーケンス外
で利用される● 実行トレース⇔ソースコードの対応を利用することで
– 対応するソースコードの断片がループの中にあるとき → 繰り返しの各実行について、「コード断片」入出力値を比較できる
● 比較可能な「コード断片」を探しだして入出力値を比較(未実装)
llll
嵌入的スパイツールの機能ブラウズ: 実行トレースをツリーとして表示/折りたたみソースコード参照: 実行トレースと対応するソースコードを並べて表示検索: 実行トレース内で値、メソッド名、オブジェクトIDにより検索キャプチャーポイント:
– 実行トレース内の指定した位置のメソッド呼び出し等の引数や戻り値の値を表示
– ソースコード内の位置から指定して値を表示/比較実際には、ソースコードの行毎に実行トレースを折りたたみ → 折りたたんだ位置の上でキャプチャーポイントを指定 → 該当するすべての位置にキャプチャーポイントが設定される
実装嵌入的スパイツールは、実行トレース取得ツールと実行トレースビューアからなる
● 実行トレース取得ツール– Python 2.7.11インタープリタ(C言語)に860行の追加・修正– インタプリタに手を入れることで
● 大域変数への代入や参照を記録● 実行トレース全体で一意なオブジェクトIDを生成
● 実行トレースビューア– 実行トレースの表示や折りたたみ、キャプチャーポイント、検索
– lessコマンドに似せたUI– 2375行のPythonコードにより実装
デバッガのデバッグって難しぃ�
まとめと展望● バグ位置の特定 ≒ プログラム理解● 逆戻りデバッグのための嵌入的スパイを提案した
– プログラム理解のための機能を強化したデバッガ– 入出力からコード断片(サブシーケンス)の機能を調べる
● 簡単な実装をおこなった展望
● 大規模な実行トレースに対応できるように– データ構造とアルゴリズムの設計
● 視覚的なフィードバックの強化● 比較可能なサブシーケンスを発見する機能