web-dev-qa-db-ja.com

GDB破損したスタックフレーム-デバッグ方法

次のスタックトレースがあります。これからデバッグに役立つものを作ることは可能ですか?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

Segmentation faultを取得し、スタックトレースがあまり役に立たない場合、どこからコードを見始めますか?

注:コードを投稿すると、SOの専門家から回答が得られます。 SOからガイダンスを取り、自分で答えを見つけたいので、ここにはコードを投稿しません。謝罪。

111

これらの偽のアドレス(0x00000002など)は、実際にはPC値であり、SP値ではありません。偽の(非常に小さな)PCアドレスでこの種のSEGVを取得すると、99%が偽の関数ポインタを介して呼び出しているためです。 C++の仮想呼び出しは関数ポインターを介して実装されるため、仮想呼び出しの問題はすべて同じ方法で発生する可能性があります。

間接呼び出し命令は、呼び出しの後にPCをスタックにプッシュし、PCをターゲット値(この場合は偽)に設定するだけなので、これis何が起こったのか、スタックからPCを手動でポップすることで簡単に元に戻すことができます。 32ビットx86コードでは、次のようにします。

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

必要な64ビットx86コード

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

次に、btを実行して、コードが実際にどこにあるかを把握できるはずです。

他の1%の時間、エラーは、通常、スタックに格納された配列をオーバーフローさせることにより、スタックを上書きすることによるものです。この場合、 valgrind のようなツールを使用して、状況をより明確にすることができます。

152
Chris Dodd

状況がかなり単純な場合、 Chris Doddの答え が最適です。 NULLポインターを飛び越えたように見えます。

ただし、クラッシュする前にプログラムが足、膝、首、目を撃った可能性があります。スタックを上書きしたり、フレームポインターを台無しにしたり、その他の悪を行います。もしそうなら、ハッシュを解いてもじゃがいもや肉が見えないでしょう。

より効率的な解決策は、デバッガーでプログラムを実行し、プログラムがクラッシュするまで関数をステップオーバーすることです。クラッシュする関数が特定されたら、もう一度起動してその関数にステップインし、呼び出す関数がクラッシュの原因であるかを判断します。問題のある1行のコードが見つかるまで繰り返します。 75%の場合、修正は明らかです。

他の25%の状況では、いわゆる問題のあるコード行は赤いニシンです。これは、以前に多くの行をセットアップした(無効な)条件に反応するでしょう。その場合、選択される最適なコースは多くの要因に依存します。主にコードの理解とコードの経験です。

  • おそらく、デバッガーウォッチポイントを設定するか、重要な変数に診断printfを挿入すると、必要になりますA ha!
  • 異なる入力でテスト条件を変更すると、デバッグよりも多くの洞察が得られる可能性があります。
  • 2番目の目があれば、仮定を確認したり、見落とされた証拠を収集したりすることになるでしょう。
  • 時には、夕食に行き、集められた証拠について考えるだけです。

幸運を!

42
wallyk

スタックポインターが有効であると仮定して...

SEGVがバックトレースからどこで発生するかを正確に知ることは不可能かもしれません-最初の2つのスタックフレームは完全に上書きされると思います。 0xbffff284は有効なアドレスのようですが、次の2つはそうではありません。スタックを詳しく見るには、次を試してください。

gdb $ x/32ga $ rsp

またはバリアント(32を別の番号に置き換えます)。これは、アドレス(a)としてフォーマットされた巨大(g)サイズのスタックポインターから始まるいくつかのワード(32)を出力します。形式の詳細については、「help x」と入力してください。

この場合、いくつかのセンチネル「printf」でコードをインスツルメントすることは悪い考えではないかもしれません。

25
manabear

他のレジスタを調べて、そのうちの1つにスタックポインタがキャッシュされているかどうかを確認します。そこから、スタックを取得できる場合があります。また、これが埋め込まれている場合、非常に多くの場合、スタックは非常に特定のアドレスで定義されます。それを使用して、まともなスタックを取得することもできます。これはすべて、ハイパースペースにジャンプしたときに、プログラムが途中でメモリを一気に吐き出していないことを前提としています...

6
Michael Dorgan

スタックの上書きである場合、値はプログラムから認識可能な何かに対応している可能性があります。

たとえば、私は自分自身がスタックを見ているのを見つけました

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

0x342dは13357であり、アプリケーションログをgrepしたときにノードIDであることが判明しました。これにより、スタックの上書きが発生した可能性のある候補サイトをすぐに絞り込むことができました。

2
Craig Ringer