最初に (2) に答えてください。なぜなら、実際に何が起こったのかを理解することが、クラッシュの根本原因に関する詳細情報を見つけるために重要だからです。
実際には、実行時のマシンのレジスタ自体が 0 です。ただし、レジスタ自体が破損したわけではありません。むしろ、メモリが破損し、その破損したメモリがレジスタにコピーされ、最終的にクラッシュが発生しました。
起こっていることは次のようなものです: (a) 具体的には RA を含むスタックが破損し、スタック メモリに格納されている間にゼロになります。次に、関数が戻る準備ができたら、(b) スタックから RA レジスタを復元します。したがって、RAレジスタは現在 0 です。(c) RA にジャンプして戻り、PC も次のように設定します。 0 を指します。RA と PC の両方が 0 である間に、次の命令がクラッシュを引き起こします。
RA がスタックに格納され、そこから復元されるというビジネスについては、たとえばhttp://logos.cs.uic.edu/366/notes/mips%20quick%20tutorial.htm (強調は私のもの)で説明されています。
レジスタ $ra に格納されたアドレスを返します。サブルーチンが他のサブルーチンを呼び出す場合、または再帰的である場合、戻りアドレスを $ra からスタックにコピーして保存する必要があります。jal は常にこのレジスタに戻りアドレスを配置し、以前の値を上書きするためです。
PC と RA の両方が 0 の場合にクラッシュするプログラムの例を次に示します。これは、上記のシーケンスをうまく示しています (システムによっては、正確な数値を微調整する必要がある場合があります)。
#include <string.h>
int bar(void)
{
char buf[10] = "ABCDEFGHI";
memset(buf, 0, 50);
return 0;
}
int foo(void)
{
return bar();
}
int main(int argc, char *argv[])
{
return foo();
}
foo() の逆アセンブルを見ると、次のようになります。
(gdb) disas foo
Dump of assembler code for function foo:
0x00400408 <+0>: addiu sp,sp,-32
0x0040040c <+4>: sw ra,28(sp)
0x00400410 <+8>: sw s8,24(sp)
0x00400414 <+12>: move s8,sp
0x00400418 <+16>: jal 0x4003a0 <bar>
0x0040041c <+20>: nop
0x00400420 <+24>: move sp,s8
0x00400424 <+28>: lw ra,28(sp)
0x00400428 <+32>: lw s8,24(sp)
0x0040042c <+36>: addiu sp,sp,32
0x00400430 <+40>: jr ra
0x00400434 <+44>: nop
End of assembler dump.
<+4> sw ra,28(sp)
関数の先頭で RA がスタックに格納され ( )、最後に復元され ( <+28> lw ra,28(sp)
)、ジャンプして返される ( )ことが非常によくわかります<+40> jr ra
。短いので foo() を示しましたが、まったく同じ構造が bar() にも当てはまります。ただし、bar() には中央に memset() もあり、スタック上にある間に RA を上書きします (それはサイズ 10 の配列に 50 バイトを書き込む); そして、レジスタに復元されるのは 0 であり、最終的にクラッシュを引き起こします。
これで、クラッシュの根本的な原因が何らかのスタックの破損であることがわかりました。これにより、質問 (1) に戻ります。クラッシュしたスレッドに関する詳細情報を取得する方法はありますか?
まあ、これは少し難しく、デバッグが科学というよりはむしろ芸術になるところですが、心に留めておくべき原則は次のとおりです。
- 基本的な考え方は、スタックの破損の原因を突き止めることです。おそらく、上記の例のように、何らかのローカル バッファへの書き込みです。
- フローのどこで破損が発生しているかをできる限り特定するようにしてください。ロギングはここで非常に役立ちます: 最後に表示されるログは明らかにクラッシュの前に発生したものです (ただし、必ずしも破損の前であるとは限りません!) -- 疑わしい領域にさらにロギングを追加して、クラッシュの場所をゼロにします。もちろん、デバッガーにアクセスできる場合は、コードをステップ実行して、クラッシュしている場所を特定することもできます。
- クラッシュの場所を見つけたら、そこから逆方向に作業する方がはるかに簡単です。まず、クラッシュの前に、PC はまだ 0 に設定されていないため、バックトレースを確認できるはずです (ただし、バックトレースはそれ自体は、スタックに格納された値を使用して「計算」されます-- それらが破損すると、破損を超えてバックトレースを計算することはできません. しかし、これは実際にこの場合に役立ちます: これは、メモリ内のどこで破損が発生したかを非常に正確に伝えることができます.つまり、バックトレースが切り捨てられるポイントは、破損した RA (スタック上) です。)
- 破損しているものを見つけても、破損の原因がわからない場合は、ウォッチポイントを使用します。最終的に上書きされる RA をスタックに配置する関数に入るとすぐに、それにウォッチポイントを設定します。破損が発生するとすぐに中断が発生するはずです...
お役に立てれば!