11

関数に入ったばかりのときにセグメンテーション違反が発生する原因は何ですか?

入力された関数は次のようになります。

21:  void eesu3(Matrix & iQ)
22:  {

はどこMatrixですかstruct。GDB で実行すると、バックトレースは以下を生成します。

(gdb) backtrace 
#0  eesu3 (iQ=...) at /home/.../eesu3.cc:22
#1  ...

GDBは何が何であるかを言いませんiQ...文字通りそこにあります。何が原因でしょうか?

GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3

で構築されたプログラム-O3 -g

呼び出し元は次のようになります。

Matrix q;
// do some stuff with q
eesu3(q);

ここでは特別なことは何もありません

valgrind でプログラムを再実行しました。

valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes <prgname>

出力:

==2240== Warning: client switching stacks?  SP change: 0x7fef7ef68 --> 0x7fe5e3000
==2240==          to suppress, use: --max-stackframe=10076008 or greater
==2240== Invalid write of size 8
==2240==    at 0x14C765B: eesu3( Matrix &) (eesu3.cc:22)
...
==2240==  Address 0x7fe5e3fd8 is on thread 1's stack
==2240== 
==2240== Can't extend stack to 0x7fe5e2420 during signal delivery for thread 1:
==2240==   no stack segment
==2240== 
==2240== Process terminating with default action of signal 11 (SIGSEGV)
==2240==  Access not within mapped region at address 0x7FE5E2420
==2240==    at 0x14C765B: eesu3( Matrix&) (eesu3.cc:22)
==2240==  If you believe this happened as a result of a stack
==2240==  overflow in your program's main thread (unlikely but
==2240==  possible), you can try to increase the size of the
==2240==  main thread stack using the --main-stacksize= flag.
==2240==  The main thread stack size used in this run was 8388608.

スタックが破損しているようです。

    Dump of assembler code for function eesu3( Matrix & ):
   0x00000000014c7640 <+0>: push   %rbp
   0x00000000014c7641 <+1>: mov    %rsp,%rbp
   0x00000000014c7644 <+4>: push   %r15
   0x00000000014c7646 <+6>: push   %r14
   0x00000000014c7648 <+8>: push   %r13
   0x00000000014c764a <+10>:    push   %r12
   0x00000000014c764c <+12>:    push   %rbx
   0x00000000014c764d <+13>:    and    $0xfffffffffffff000,%rsp
   0x00000000014c7654 <+20>:    sub    $0x99b000,%rsp
=> 0x00000000014c765b <+27>:    mov    %rdi,0xfd8(%rsp)

分かりやすく言えば、Matrix のデータはヒープ上に存在します。基本的に、データへのポインターを保持します。構造体は小さく、32 バイトです。(チェックしただけ)

ここで、さまざまな最適化オプションを使用してプログラムを再構築しました。

-O0: エラーは表示されません。

-O1: エラーが表示されます。

-O3: エラーが表示されます。

- アップデート

-O3 -fno-inline -fno-inline-functions: エラーは表示されません。

なるほどね。関数へのインライン化が多すぎるため、スタックが過剰に使用されました。

問題はスタックオーバーフローによるものでした

4

4 に答える 4

15

関数に入るだけでセグメンテーション違反が発生する原因は何ですか?

最も頻繁な原因はスタックの枯渇です。(gdb) disasクラッシュポイントで行います。クラッシュした命令が、デクリメントされた後のスタック位置への最初の読み取りまたは書き込みである場合%rsp、スタックの枯渇がほぼ間違いなく原因です。

解決策には通常、より大きなスタックでスレッドを作成するか、いくつかの大きな変数をスタックからヒープに移動するか、またはその両方が含まれます。

別の考えられる原因:Matrix非常に大きな配列が含まれている場合、それをスタックに置くことはできません。カーネルはスタックを現在より128K以上拡張しません(つまり、正確な値を覚えていません)Matrixがその制限よりも大きい場合、それをスタックに置くことはできません。

アップデート:

   0x00000000014c7654 <+20>:    sub    $0x99b000,%rsp
=> 0x00000000014c765b <+27>:    mov    %rdi,0xfd8(%rsp)

この分解により、診断が確認されます。

さらに、スタックに0x99b000バイトを予約しています(これはほぼ10MBです)。eesu3ルーチンのスタックに配置しようとしている巨大なオブジェクトがいくつかあるはずです。そうしないでください。

「カーネルは現在のスタックを超えてスタックを拡張しない」とはどういう意味ですか

スタックをたとえば1MB拡張(デクリメント%rsp)してから、そのスタックの場所に触れようとすると、メモリにアクセスできなくなります(カーネルはオンデマンドでスタックを拡張します)。これにより、ハードウェアトラップが生成され、制御がカーネルに転送されます。カーネルが何をすべきかを決定するとき、それは見ます

  1. 現在%rsp
  2. アプリケーションがアクセスしようとしたメモリの場所
  3. 現在のスレッドのスタック制限

障害のあるアドレスが現在%rspよりも小さいが、128K(または同様の大きさの他の定数)以内の場合、カーネルは単にスタックを拡張します(そのような拡張がスタック制限を超えない場合)。

障害のあるアドレスが現在より128K以上低い%rsp場合(ここでの場合のように)、を取得しますSIGSEGV

これはすべて、ほとんどのプログラムでうまく機能します。再帰的なプロシージャで大量のスタックを使用する場合でも、通常、スタックを小さなチャンクで拡張します。しかし、単一のルーチンでそのすべてのスタックを予約しようとした同等のプログラムはクラッシュしていました。

とにかく、(gdb) info localsクラッシュポイントで実行し、ローカルが10MBのスタックを必要としている可能性があるかどうかを確認します。次に、それらをヒープに移動します。

アップデート2:

地元の人はいません

eesu3ああ、プログラムはおそらく地元の人がいるのに十分なほど進んでいません。

-O0でビルドすると、エラーは消えます。GCCのバグ?

これはGCCのバグである可能性がありますが、GCCが他の多くのルーチンをにインライン化しておりeesu3、インライン化された各ルーチンには独自のNKBのスタックが必要である可能性があります。を含むソースをビルドすると、問題は解消eesu3され-fno-inlineますか?

残念ながら、そのような動作のトリアージと適切な回避策の把握、またはGCCの修正には、コンパイラの専門知識が必要です。-fdump-tree-all生成されたファイルをコンパイルして確認することから始めることができ<source>.*t.*ます。これらには、コンパイルプロセスのさまざまな段階でのGCC内部表現のテキストダンプが含まれています。あなたはそれを十分に理解してさらに進歩することができるかもしれません。

于 2012-05-08T14:13:16.710 に答える
5

スタックオーバーフローです。

eesu3スタックに非常に大きなものを割り当てようとします。これは、アセンブリ コードで確認できます。

sub    $0x99b000,%rsp

これは、10MB を超えるスタック スペースが消費されていることを意味します。

問題はeesu3それが呼び出す関数内または関数内にある可能性があり、コンパイラはインライン化を選択します。

私の推測では、問題は関数eesu3呼び出しにありますが、テストする場合 (デバッグ関数ですか?)ではありません。これ
は、最適化なしでは発生しないためだと思います。スタック。これがないと、関数はインライン化されないため、実際に呼び出されたときにのみ問題が発生します。eesu3eesu3

于 2012-05-08T15:17:07.527 に答える
0

マトリックスの場合は、アクセスしようとしているインデックスを確認してください。Matrix オブジェクトの次元を超える要素にアクセスしている可能性がありますか?

于 2012-05-08T13:42:14.783 に答える
0

おそらく関数でいくつかの変数が初期化されています

void eesu3(Matrix & iQ)

デバッガーは変数宣言をステップ実行する可能性がありますが、それらはおそらくスコープ (つまり関数) の開始で初期化されます。次のように非常に大きなバッファを宣言する場合:

char * buffer[268435456];

スタック オーバーフローが発生する可能性があります。のようにメモリを割り当てたほうがいいかもしれません。

void * pvBuffer = malloc(268435456);

大きなバッファを宣言しましたか? 大きすぎてスタックに載せられないのはどれ? アーキテクチャが異なると、バッファの最大サイズが異なる可能性があります (64 ビット OS と 32 ビット OS)。異なるカーネル?あなたが言ったように、プログラムは一方のマシンでは正常に動作しますが、もう一方のマシンでは動作しません。

于 2012-10-11T10:30:04.987 に答える