GDBには、リバースデバッグをサポートする新しいバージョンがあります( http://www.gnu.org/software/gdb/news/reversible.html を参照)。それがどのように機能するのか疑問に思いました。
リバースデバッグを機能させるには、各ステップでメモリを含むマシン全体の状態を保存する必要があるようです。これは、多くのメモリを使用することは言うまでもなく、パフォーマンスを非常に遅くします。これらの問題はどのように解決されますか?
私はgdbのメンテナーであり、新しいリバースデバッグの作成者の1人です。その仕組みについてお話しさせていただきます。複数の人が推測したように、後で復元できるように十分なマシン状態を保存する必要があります。いくつかのスキームがあり、そのうちの1つは、単純に各機械語命令によって変更されるレジスターまたはメモリー位置を保存することです。次に、その命令を「取り消す」には、それらのレジスタまたはメモリ位置のデータを元に戻します。
はい、それは高価ですが、現代のCPUは非常に高速であるため、とにかくインタラクティブ(ステップ実行やブレークポイントを実行している)の場合は、それほど気付かないでしょう。
リバース実行を実装するには、シミュレータ、仮想マシン、およびハードウェアレコーダーの使用を忘れないでください。
それを実装するもう1つのソリューションは、ハードウェアベースのデバッガーでGreenHillsとLauterbachによって行われるような、物理ハードウェアでの実行をトレースすることです。各命令のアクションのこの固定トレースに基づいて、各命令の影響を順番に削除することにより、トレースの任意のポイントに移動できます。これは、デバッガーに表示される状態に影響を与えるすべてのものをトレースできることを前提としています。
別の方法は、チェックポイント+再実行メソッドを使用することです。これは、VmWare Workstation 6.5およびVirtutech Simics 3.0以降で使用され、Visual Studio 2010に付属しているようです。ここでは、仮想マシンまたはシミュレーターを使用します。システムの実行に関する間接的なレベルを取得します。状態全体を定期的にディスクまたはメモリにダンプし、まったく同じプログラムパスを決定論的に再実行できるシミュレータに依存します。
簡略化すると、次のように機能します。システムの実行中の時刻Tにいるとします。時間T-1に移動するには、ポイントt <Tからいくつかのチェックポイントを取得し、(T-t-1)サイクルを実行して、以前のサイクルの1サイクルを終了します。これは非常にうまく機能させることができ、ディスクIOを実行し、カーネルレベルのコードで構成され、デバイスドライバーの作業を実行するワークロードにも適用できます。重要なのは、すべてのプロセッサ、デバイス、メモリ、IOを備えたターゲットシステム全体を含むシミュレータを用意することです。詳細については、gdbメーリングリストで gdbメーリングリスト とそれに続くディスカッションを参照してください。私はこのアプローチをかなり定期的に使用して、特にデバイスドライバーや初期のOSブートで、トリッキーなコードをデバッグしています。
もう1つの情報源は チェックポイントに関するVirtutechのホワイトペーパー です(これは完全に開示されています)。
EclipseConセッション中に、JavaのChronon Debuggerを使用してこれを行う方法についても尋ねました。これは実際にはステップバックすることを許可しませんが、記録されたプログラムの実行を感じるように再生できます。逆デバッグのように。 (主な違いは、Chrononデバッガーでは実行中のプログラムを変更できないことですが、他のほとんどのJavaデバッガーでは変更できます。)
私がそれを正しく理解していれば、実行中のプログラムのバイトコードを操作して、プログラムの内部状態のすべての変更が記録されるようにします。外部状態を追加で記録する必要はありません。それらが何らかの形でプログラムに影響を与える場合は、その外部状態に一致する内部変数が必要です(したがって、その内部変数で十分です)。
再生時間中は、基本的に、記録された状態変化から実行中のプログラムのすべての状態を再作成できます。
興味深いことに、状態の変化は、一見したときに予想されるよりもはるかに小さいものです。したがって、条件付きの「if」ステートメントがある場合、プログラムがthenステートメントとelseステートメントのどちらを取ったかを記録するには、少なくとも1ビットが必要であると考えるでしょう。多くの場合、それらの異なるブランチに戻り値が含まれている場合のように、それを回避することもできます。次に、戻り値(とにかく必要になる)のみを記録し、戻り値自体から実行された分岐に関する決定を再計算するだけで十分です。
この質問は古いですが、ほとんどの回答も古く、 reverse-debugging は興味深いトピックのままなので、2015年の回答を投稿します。私の修士論文の第1章と第2章 コンピュータプログラミングにおける視覚的思考に向けたリバースデバッグとライブプログラミングの組み合わせ 、リバースデバッグの歴史的なアプローチの一部(特にスナップショット(またはチェックポイント)とリプレイのアプローチに焦点を当てたもの)をカバーし、それと全知的なデバッグの違いを説明します。
ある時点までプログラムを順方向に実行したコンピュータは、それに関する情報を本当に私たちに提供できるはずです。このような改善は可能であり、いわゆる全知型デバッガに見られます。それらは通常リバースデバッガーとして分類されますが、プログラマーが実行中のプログラムを実際に時間をさかのぼるのではなく、実行中に情報を記録して後で表示または照会するだけなので、より正確に「履歴ロギング」デバッガーとして説明されます。 。 「Omniscient」は、記録されたプログラムのすべての状態履歴が、実行後にデバッガで利用できるという事実に由来します。その場合、プログラムを再実行する必要はなく、手動でコードをインストルメンテーションする必要もありません。
ソフトウェアベースの全知的なデバッグは、「デバッグ時の履歴再生」と呼ばれる1969年のEXDAMSシステムから始まりました。 GNUデバッガー、GDBは、2009年から全プロセスのデバッグとデバッグをサポートしており、「レコードの処理と再生」機能を備えています。TotalView、UndoDB、およびChrononは、現在入手可能な最高の全知デバッガーですが、商用です。システムのJava用のTODは、部分的な確定的再生、および部分的なトレースキャプチャと分散データベースを使用して、関係する大量の情報の記録を可能にする、最良のオープンソースの代替手段のようです。
記録のナビゲーションを可能にするだけでなく、実際に実行時間を後退させることができるデバッガーも存在します。それらは、より正確に、バックインタイム、タイムトラベル、双方向またはリバースデバッガとして説明できます。
最初のそのようなシステムは、1981年のCOPEプロトタイプでした...
mozilla rr
は、GDBリバースデバッグのより堅牢な代替手段です
GDBの組み込みの記録と再生には、重大な制限があります。 AVX命令のサポートなし: 「プロセスレコードはアドレスの命令0xf0dをサポートしていません」でgdbリバースデバッグが失敗します
Rrの利点:
rrは、スレッドスイッチなどのすべての非決定的イベントで発生したことを記録する方法でプログラムを最初に実行することにより、これを実現します。
次に、2回目の再生実行中に、驚くほど小さいトレースファイルを使用して、元の非決定論的実行で発生したことを正確に再構築しますが、順方向または逆方向に決定論的な方法で行います。
rrはもともとMozillaによって開発されたもので、翌日の夜間のテストで発生したタイミングのバグを再現するのに役立ちます。しかし、リバースデバッグの側面は、実行中に数時間だけ発生するバグがある場合にも重要です。これは、前の状態が後の失敗につながった原因を調べるために後戻りしたい場合が多いためです。
次の例は、その機能の一部、特にreverse-next
、reverse-step
、reverse-continue
コマンドを示しています。
Ubuntu 18.04にインストールします。
Sudo apt-get install rr linux-tools-common linux-tools-generic linux-cloud-tools-generic
Sudo cpupower frequency-set -g performance
# Overcome "rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is 3."
echo 'kernel.perf_event_paranoid=1' | Sudo tee -a /etc/sysctl.conf
Sudo sysctl -p
テストプログラム:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int f() {
int i;
i = 0;
i = 1;
i = 2;
return i;
}
int main(void) {
int i;
i = 0;
i = 1;
i = 2;
/* Local call. */
f();
printf("i = %d\n", i);
/* Is randomness completely removed?
* Recently fixed: https://github.com/mozilla/rr/issues/2088 */
i = time(NULL);
printf("time(NULL) = %d\n", i);
return EXIT_SUCCESS;
}
コンパイルして実行:
gcc -O0 -ggdb3 -o reverse.out -std=c89 -Wextra reverse.c
rr record ./reverse.out
rr replay
これでGDBセッション内に残り、デバッグを適切に元に戻すことができます。
(rr) break main
Breakpoint 1 at 0x55da250e96b0: file a.c, line 16.
(rr) continue
Continuing.
Breakpoint 1, main () at a.c:16
16 i = 0;
(rr) next
17 i = 1;
(rr) print i
$1 = 0
(rr) next
18 i = 2;
(rr) print i
$2 = 1
(rr) reverse-next
17 i = 1;
(rr) print i
$3 = 0
(rr) next
18 i = 2;
(rr) print i
$4 = 1
(rr) next
21 f();
(rr) step
f () at a.c:7
7 i = 0;
(rr) reverse-step
main () at a.c:21
21 f();
(rr) next
23 printf("i = %d\n", i);
(rr) next
i = 2
27 i = time(NULL);
(rr) reverse-next
23 printf("i = %d\n", i);
(rr) next
i = 2
27 i = time(NULL);
(rr) next
28 printf("time(NULL) = %d\n", i);
(rr) print i
$5 = 1509245372
(rr) reverse-next
27 i = time(NULL);
(rr) next
28 printf("time(NULL) = %d\n", i);
(rr) print i
$6 = 1509245372
(rr) reverse-continue
Continuing.
Breakpoint 1, main () at a.c:16
16 i = 0;
複雑なソフトウェアをデバッグする場合、クラッシュポイントに達して、ディープフレーム内に陥る可能性があります。その場合は、より高いフレームでreverse-next
になることを忘れないでください。最初に次のことを行う必要があります。
reverse-finish
そのフレームまでは、通常のup
を実行するだけでは不十分です。
私の意見では、rrの最も深刻な制限は次のとおりです。
UndoDBはrrの商用代替手段です。 https://undo.io どちらもトレース/リプレイベースですが、機能とパフォーマンスの点でどのように比較されるのかわかりません。
ネイサンフェルマンは書いている:
しかし、リバースデバッグでは、入力した次のコマンドとステップコマンドのみをロールバックできますか?それとも、任意の数の指示を元に戻すことができますか?
指示はいくつでも取り消すことができます。たとえば、前進しているときに停止したポイントで停止するだけに限定されません。新しいブレークポイントを設定して、逆方向に実行できます。
たとえば、命令にブレークポイントを設定してそれまで実行した場合、スキップした場合でも、前の命令にロールバックできますか?
はい。ブレークポイントに到達する前に記録モードをオンにしている限り。
ここ は、ODBと呼ばれる別のリバースデバッガーのしくみです。エキス:
Omniscient Debuggingは、プログラム内の各「関心のあるポイント」(値の設定、メソッドの呼び出し、例外のスロー/キャッチ)で「タイムスタンプ」を収集し、プログラマがそれらのタイムスタンプを使用して、そのプログラム実行の履歴。
ODB ...は、プログラムがロードされるときにプログラムのクラスにコードを挿入し、プログラムの実行時にイベントが記録されます。
私はgdbも同じように機能すると思います。
リバースデバッグとは、プログラムを逆方向に実行できることを意味します。これは、問題の原因を追跡するのに非常に役立ちます。
各ステップの完全なマシン状態を保存する必要はなく、変更のみを保存します。それはおそらくまだかなり高価です。