OpenBoot では、Forth 言語逆コンパイラ、マシン言語逆アセンブラ、レジスタ表示用コマンド、シンボリックデバッガ、ブレークポイント関係コマンド、Forth ソースレベルデバッガ、高水準言語パッチ機能、例外追跡を含むデバッグ用ツールを提供しています。この章では、IEEE 規格 1275-1994 に指定された機能について説明します。
組み込み Forth 言語逆コンパイラを使用して、どのような定義済み Forth ワードのソースコードでも表示できます。
ok see old-name |
このコマンドは、old-name のソースの (ソースコメントなしの) リストを表示します。
see は (see) と対をなしています。つまり、(see) の機能は Forth ワードの逆コンパイルを行い、その実行トークンはスタックから取り出されます。次の例を参照してください。
ok ' old-name (see) |
(see) は see と同じ書式のリストを生成します。
ok see see : see " ["] (see) catch if drop then ; ok see (see) defer (see) is : (f0018a44) 40 rmargin ! dup dup (f00189c4) dup (f0018944) (f0018980) (f0018658) ??cr ; ok f0018a44 (see) : (f0018a44) 40 rmargin ! dup dup (f00189c4) dup (f0018944) (f0018980) (f0018658) ??cr ; |
上記のリストは、次のことを示しています。
see そのものは、fcode-debug? が true に設定されて external または headers としてコンパイルされた Forth ソースワードだけで構成されています。
(see) は据え置きワードです。(see) 内には、headerless としてコンパイルされた、したがって、かっこで囲まれた 16 進アドレスとして表示されているワードもあります。
(see) でワードを逆コンパイルすると、see が生成するものと同じリストが生成されます。
Forth アセンブラ言語に実装されているワードについては、see は Forth アセンブラのリストを表示します。たとえば、dup を逆コンパイルすると、次のように表示されます。
ok see dup code dup f0008c98 sub %g7, 8, %g7 f0008c9c stx %g4, [%g0 + %g7] f0008ca0 ld [%g5], %l0 f0008ca4 jmp %l0, %g2, %g0 f0008ca8 add %g5, 4, %g5 |
組み込み逆アセンブラはメモリーの内容を、対応するアセンブラ言語に翻訳します。
表 6-1に、メモリーの内容を対応するオペコードに逆アセンブルするコマンドを示します。
表 6-1 逆アセンブラコマンド
コマンド |
スタックダイアグラム |
説明 |
---|---|---|
+dis |
( -- ) |
最後に逆アセンブルを終了したところから逆アセンブルを継続します。 |
dis |
( addr -- ) |
指定されたアドレスから逆アセンブルを開始します。 |
dis は指定する任意のアドレスから、メモリーの内容の逆アセンブルを開始します。システムは次の場合に中断します。
中断したら、逆アセンブルを停止することも、+dis コマンドを使用して最後に逆アセンブルが停止したところから逆アセンブルを継続することもできます。
メモリーアドレスは通常は 16 進で示されますが、シンボルテーブルがある場合は、メモリーアドレスは可能なかぎりシンボルで表示されます。
プログラムがクラッシュしたり、ユーザーが中止したり、あるいはブレークポイントに遭遇した結果、プログラムの実行途中でユーザーインタフェースに入ってしまうことがあります。(ブレークポイントについては、「ブレークポイント」で説明します。) こういった場合には、ユーザーインタフェースは自動的にすべての CPU データレジスタの値をバッファー領域に保存します。デバッグの目的のためにこれらの値は調べたり、変更することができます。
表 6-2に SPARC のレジスタコマンドを示します。
表 6-2 SPARC レジスタコマンド表 6-3 SPARC V9 レジスタコマンド
コマンド |
スタック ダイアグラム |
説明 |
---|---|---|
%fprs %asi %pstate %tl-c %pil %tstate %tt %tba %cwp %cansave %canrestore %otherwin %wstate %cleanwin |
( -- value ) |
指定されたレジスタの値を返します。 |
.pstate |
( -- ) |
プロセッサ状態レジスタを特定書式で表示します。 |
.ver |
( -- ) |
バージョンレジスタを特定書式で表示します。 |
.ccr |
( -- ) |
%ccr レジスタを特定書式で表示します。 |
.trap-registers |
( -- ) |
トラップレジスタを表示します。 |
これらのレジスタの値はすべて保存され、to で変更できます。値の確認や変更が終わったら、go コマンドを使用してプログラムの実行を継続できます。保存したレジスタの値 (変更したものを含めて) は、(コピーして) CPU に戻され、保存されたプログラムカウンタによって指定された位置から実行が再開されます。
to を使用して %pc を変更する場合は、%npc も変更する必要があります。(set-pc の方が両レジスタを自動的に変更するので簡単です。)
SPARC V9 システムでは、N が現在のウィンドウの場合、N-1 は呼び出し元のウィンドウを指定し、N-2 は呼び出し元の呼び出し元を指定します。
ユーザーインタフェースは、スタンドアロンプログラムの開発とデバッグの支援用として、ブレークポイント機能を備えています。(オペレーティングシステムのもとで実行されるプログラムは、一般的にこの機能は使用しないで、オペレーティングシステムのもとで動作するほかのデバッガを使用します。) ブレークポイント機能では、テストプログラムを停止させたい場所で停止することができます。プログラムの実行が停止した後は、レジスタまたはメモリーを調べたり、変更できるほか、ブレークポイントを新たに設定またはクリアすることができます。プログラムの実行は go コマンドで再開できます。
表 6-4に、プログラム実行の制御、監視用のブレークポイントコマンドを示します
表 6-4 ブレークポイントコマンド
ブレークポイントを使用してプログラムをデバッグするには、次の手順に従います。
テストプログラムをメモリーへ読み込みます。
詳細は、第 5 章「プログラムの読み込みと実行」を参照してください。各レジスタの値が自動的に初期化されます。
(省略可能) ダウンロードされたプログラムを逆アセンブルして、ファイルが正しく読み込まれているかどうかを確認します。
step コマンドを使用してテストプログラムを 1 命令ずつ実行します。
さらに、ブレークポイントを設定し、実行したり (たとえば、addr +bp および go コマンドを実行します)、ほかの方法で実行することもできます。
ソースレベルデバッガでは、Forth プログラムのシングルステップ実行およびトレースが可能です。各実行ステップが 1 つの Forth ワードに対応します。
表 6-5にこのデバッガのコマンドを示します。
表 6-5 Forth ソースレベルデバッガコマンド
すべての Forth ワードはそれぞれに、「コンポーネント」ワードと呼べる 1 つまたは複数の一連のワードとして定義されています。指定されたワードをデバッグしている間に、デバッガは、そのワードの各「コンポーネント」ワードを実行中にスタックの内容に関する情報を表示します。各コンポーネントワードを実行する直前に、デバッガはスタックの内容と、実行されようとしているコンポーネントワードの名前を表示します。
トレースモードでは、そのコンポーネントワードがそこで実行され、プロセスは次のコンポーネントワードに引き継がれます。
ステップモード (デフォルト) では、ユーザーがデバッガの実行動作を制御します。各コンポーネントワードの実行前に、ユーザーはプロンプトで表 6-5にあるキー操作のどれかを求められます。
OpenBoot では、アセンブル済みの Forth ワードの定義を高水準の Forth 言語を使用して変更できます。変更は、通常、該当するソースコードで行われるのに対して、patch 機能はデバッグ時に見つけられなかった誤りを迅速に修正する手段を提供します。
patch は次に示す各情報から入力ストリームを読みます。
挿入される新しいコードの名前
置き換えられる古いコードの名前
古いコードが入っているワードの名前
たとえば、次の例を考えてください。この例では、ワード test が数値 555 に置き換えられます。
ok : patch-me test 0 do i . cr loop ; ok patch 555 test patch-me ok see patch-me : patch-me h# 555 0 do i . cr loop ; |
patch を使用するときは、十分注意して正しいワードを選択し、置き換える必要あります。置き換えようとするワードがターゲットワード内で数回使用され、ターゲットワード内で最初に出てくるのものでなく、何番目かに出てくるワードである場合には、とくに注意が必要です。そのような場合は、なんらかの回避手段が必要です。
ok : patch-me2 dup dup dup ( This third dup should be drop) ; ok : xx dup ; ok patch xx dup patch-me2 ok patch xx dup patch-me2 ok patch drop dup patch-me2 ok see patch-me2 : patch-me2 xx xx drop ; |
patch のもう 1 つの用途は、パッチするワードに、完全に廃棄する必要がある特定の機能がある場合です。そのような場合は、機能を削除する最初のワードに対してワード exit をパッチします。たとえば、次のような定義をもつワードを考えてみてください。
ok : foo good bad unneeded ; |
この例では、bad の機能は誤っており、unneeded の機能は廃棄する必要があります。foo をパッチしようとするとき、最初は、ワード right 内に exit を使用すれば unneeded を実行できなくできると期待して、次のように入力するかも知れません。
ok : right this that exit ; ok patch right bad foo |
残念ながら、exit はそれが入っているワード、この場合 right の実行を終了させてしまいます。foo の正しいパッチ方法は次のようになります。
ok : right this that ; ok patch right bad foo ok patch exit unneeded foo |
(patch) は、その引数をスタックから得る点を除いて patch と同じです。(patch) のスタックダイアグラムは次のとおりです。
( new-n1 num1? old-n2 num2? xt -- )
ただし、
new-n1 と old-n2 は実行トークン、数値のどちらでも差し支えありません。
num1? と num2? はフラグであり、それぞれ、new-n1 または old-n2 が数値であるかどうかを示します。
xt はパッチを実行するワードの実行トークンです。
たとえば、次の例を考えてみてください。ここでは、数値 555 を test に置き換えて、最初の patch の例の効果を逆にしています。
ok see patch-me : patch-me h# 555 0 do i . cr loop ; ok ["] test false 555 true ["] patch-me (patch) ok see patch-me : patch-me test 0 do i . cr loop ; |
ftrace コマンドは、最後の例外割り込み時に実行されていた Forth ワード処理を表示します。次に ftrace の例を示します。
ok : test1 1 ! ; ok : test2 1 test1 ; ok test2 Memory address not aligned ok ftrace ! Called from test1 at ffeacc5c test1 Called from test2 at ffeacc6a (ffe8b574) Called from (interpret at ffe8b6f8 execute Called from catch at ffe8a8ba ffefeff0 0 ffefebdc catch Called from (fload) at ffe8ced8 0 (fload) Called from interact at ffe8cf74 execute Called from catch at ffe8a8ba ffefefd4 0 ffefebdc catch Called from (quit at ffe8cf98 |
上の例では、test2 が test1 を呼び出し、test1 は境界に合わないアドレスに値を格納しようとします。その結果、Memory address not aligned という例外が発生します。
ftrace の出力の 1 行目は、例外を発生させた最後のコマンドを示しています。2 行目以降は、その後のコマンドが呼び出されようとしていたメモリーアドレスを示しています。
最後の数行は、通常どの ftrace の出力とも同じですが、これは、それが Forth インタプリタが入力ストリームからワードを解釈するときに有効な呼び出し処理であるからです。