27

1. 問題の背景

最近、オンライン検索サーバーの 1 つでコア ダンプが発生しました。コアはmemset()、無効なアドレスに書き込もうとしたために発生し、SIGSEGV 信号を受信しました。次の情報は dmsg からのものです。

is_searcher_ser[17405]: segfault at 000000002c32a668 rip 0000003da0a7b006 rsp 0000000053abc790 error 6

弊社オンラインサーバーの環境は以下の通りです。

  • OS:RHEL5.3
  • カーネル: 2.6.18-131.el5.custom、x86_64 (64 ビット)
  • GCC: 4.1.2 20080704 (レッドハット 4.1.2-44)
  • glibc: glibc-2.5-49.6

以下は、関連するコード スニペットです。

CHashMap<…&gt;::CHashMap(…)
{
     …
     typedef HashEntry *HashEntryPtr;              
     m_ppEntry = new HashEntryPtr[m_nHashSize];   // m_nHashSize is 389 when core
     assert(m_ppEntry != NULL);
     memset(m_ppEntry, 0x0, m_nHashSize*sizeof(HashEntryPtr)); // Core in this memset() invocation 
     …
}

上記のコードのアセンブリ コードは次のとおりです。

…
0x000000000091fe9e <+110>:   callq  0x502638 <_Znam@plt>  // new HashEntryPtr[m_nHashSize]
0x000000000091fea3 <+115>:   mov    0xc(%rbx),%edx         // Get the value of m_nHashSize
0x000000000091fea6 <+118>:   mov    %rax,%rdi               // Put m_ppEntry pointer to %rdi for later memset invocation
0x000000000091fea9 <+121>:   mov    %rax,0x20(%rbx)        // Store the pointer to m_ppEntry member variable(%rbx holds the this pointer)
0x000000000091fead <+125>:   xor    %esi,%esi               // Generate 0
0x000000000091feaf <+127>:   shl    $0x3,%rdx               // m_nHashSize*sizeof(HashEntryPtr)
0x000000000091feb3 <+131>:   callq  0x502b38 <memset@plt> // Call the memset() function
…

コア ダンプでは、アセンブリmemset@pltは次のとおりです。

(gdb) disassemble 0x502b38
Dump of assembler code for function memset@plt:
    0x0000000000502b38 <+0>:     jmpq   *0x771b92(%rip)        # 0xc746d0 <memset@got.plt>
    0x0000000000502b3e <+6>:     pushq  $0x53
    0x0000000000502b43 <+11>:    jmpq   0x5025f8
End of assembler dump.
 (gdb) x/ag 0x0000000000502b3e+0x771b92
    0xc746d0 <memset@got.plt>:      0x3da0a7acb0 <memset>
 (gdb) disassemble 0x3da0a7acb0
 Dump of assembler code for function memset:
    0x0000003da0a7acb0 <+0>:     cmp    $0x1,%rdx
    0x0000003da0a7acb4 <+4>:     mov    %rdi,%rax
    …

上記の GDB 分析では、のアドレスがmemset()再配置 PLT テーブルで解決されていることがわかります。つまり、jmpq *0x771b92(%rip)最初は function の最初の命令に直接ジャンプしますmemset()。さらに、このプログラムはオンラインでほぼ 1 日実行されていたため、移転先の住所はmemset()以前に解決されているはずでした。

2. 奇妙な現象

このコア=> 0x0000003da0a7b006 <+854>: mov %rdx,-0x8(%rdi)は、memset(). 実際には、これは の最初のパラメータである をバッファの右開始位置にmemset()設定する命令です。0memset()

cored の場合、フレーム 0 の値$rdi0x2c32a670、および$raxです0x2c32a668。アセンブリ解析とオフライン テストから、$raxのソース バッファmemset、つまり の最初のパラメータを保持する必要がありますmemset()

したがって、この例で$raxは、 は のアドレスと同じである必要がありm_ppEntry、その値は最初にthisオブジェクトに格納されます (thisポインターは に格納されます) 。ただし、 の値はです。%rbxmemsetm_ppEntry0x2ab02c32a668

次に、info filesGDB コマンドを使用して、アドレス0x2c32a668が実際に無効 (マップされていない) であり、アドレス0x2ab02c32a668が有効なアドレスであることを確認します。

3. なぜ変なの?

このコアの奇妙な点は次のとおりです: の実際のアドレスがmemset既に解決されている場合 (非常に可能性が高い)、ポインター値を挿入する操作m_ppEntryとその試行の間に命令がほとんどありませんmemset。そして実際には、$raxこれらの命令の間、(渡されたバッファアドレスを保持する) レジスタの値はまったく変更されません。では、どうしてm_ppEntryと等しくないの$raxでしょうか?

さらに奇妙なのは、コアの場合、$rax( 0x2c32a668) の値は実際には ( ) の下位 4 バイトの値であるということm_ppEntryです0x2ab02c32a668。2 つの値の間に何らかの関係がある場合、m_ppEntry渡されたパラメーターはmemset切り捨てられますか? ただし、関連するいくつかの命令はすべて%raxではなく を使用します%eax。ところで、この問題をオフラインで再現することはできません。

そう、

1) 有効な住所はどれですか? 0x2c32a668有効な場合は? いくつかの命令の間でヒープが破損していませんか? m_ppEntryの値がであると言い換える方法と0x2ab02c32a668、この 2 つの値の下位 4 バイトが同じなのはなぜですか?

2)0x2ab02c32a668が有効な場合、64 ビットに渡されるときにアドレスが切り捨てられるのはなぜmemset()ですか? このエラーはどのような状況で発生しますか? これをオフラインで再現することはできません。この問題は既知のバグですか? Googleで見つけられませんでした。

%rdi3) または、渡された上位 4 バイトをmemsetゼロにするのは、ハードウェアまたは電源の問題によるものですか? (私はこれを信じるのが非常に気が進まない)。

最後に、このコアに関するコメントをお待ちしております。

ありがとう、

ゲイリー・フー

4

1 に答える 1

1

ある日の実行についてのあなたの言及を考えると、ほとんどの場合、このコードは正常に機能すると思います。シグナルは検査する価値があることに同意します。ポインターの切り捨てが別の場所で発生しているように見えます。

私が考えている他のことだけが、新しいものの問題である可能性があります。オーバーロードされた新しい演算子を呼び出すことになる可能性はありますか?また、完全を期すために、m_ppEntryの宣言は何ですか?私はあなたがnothrownewを使用していると仮定しています、さもなければそれはassert(m_ppEntry != NULL);無意味でしょう。

于 2012-12-08T16:40:46.113 に答える