1

私は C++ で例外ハンドラーに取り組んでいました。これは、最大限の信頼性を得るためにメッセージング プロトコルで使用されます。プロセスがセグメンテーション違反をキャッチした場合でも、常にメッセージを受信したプロセスから応答を返したいと考えています。C++ ではヌル ポインターの逆参照やゼロ除算の例外が発生しないことはわかっていますが、C で記述された Java では例外が発生します。それは私に考えさせ、周りを見回させました。最終的に、この小さなライブラリを見つけました:

https://code.google.com/p/segvcatch/source/browse/trunk/lib/

私の質問はファイル x86_64-signal.h に関するものです。HANDLE_DIVIDE_OVERFLOW の詳細がよくわかりません。REXバイト?それは何ですか?RESTORE2 は何をしますか? _Jv_catch_segv と _Jv_catch_fpe とは何ですか? * catch *の基本的な意味は理解できましたが、どこで定義されているのでしょうか?

理想的には、誰かがファイルを上から下まで調べて重要なポイントに対処できれば、それは素晴らしいことです. ありがとう。

4

1 に答える 1

2

私はこの問題の専門家ではありませんが、できる限りお答えします。

REX バイト テストから始めます。

if ((_rip[0] & 0xf0) == 0x40)  /* REX byte present.  */               
  {                                                                   
    unsigned char _rex = _rip[0] & 0x0f;                              
    _is_64_bit = (_rex & 0x08) != 0;                                  
    _rip++;                                                           
  }                          

REX バイトは、64 ビット モードで使用される命令プレフィックスです。命令の最初のバイトの上位 4 ビットが一致0x40する場合、REX プレフィックス バイトがあることがわかります。また、ビット 3 (W フィールド) が 1 に設定されている場合は、オペランド サイズが 64 ビットであることを意味します。_rip++プレフィックスをスキップするだけです。

if (_rip[0] == 0xf7)                                                  

F7これは、ある種の整数除算命令であることがわかります。

  {                                                                   
    bool _min_value_dividend = false;                                 
    unsigned char _modrm = _rip[1];                                   

次のバイトは ModR/M バイトで、通常はオペランドの詳細を示しますが、この場合は除算命令のタイプも決定します。

    if (((_modrm >> 3) & 7) == 7)                                   

ModR/M バイトの REG フィールド (ビット 3 ~ 5) は通常レジスタを表しますが、ここでは命令のオペコードの拡張です。7 の場合、これは符号付き除算であることを意味します。

      {                                                               
        if (_is_64_bit)                                               
          _min_value_dividend =                                       
            _gregs[REG_RAX] == (greg_t)0x8000000000000000UL;          
        else                                                          
          _min_value_dividend =                                       
            (_gregs[REG_RAX] & 0xffffffff) == (greg_t)0x80000000UL;   
      }                                                  

0x80000000UL0x8000000000000000ULは、それぞれ 32 ビットと 64 ビットで可能な最小の負の数です。eax レジスタ (被除数) がその値と一致する場合、それは最小の可能な被除数があることを意味します。

    if (_min_value_dividend)                                          
      {                                                               
        unsigned char _rm = _modrm & 7;                               
        _gregs[REG_RDX] = 0; /* the remainder is zero */              

可能な限り最小の被除数がある場合、剰余 (edx) がゼロに設定され、被除数が結果として eax に残されます。

        switch (_modrm >> 6)                                          
          {                                                           
          case 0:  /* register indirect */                            
            if (_rm == 5)   /* 32-bit displacement */                 
              _rip += 4;                                              
            if (_rm == 4)  /* A SIB byte follows the ModR/M byte */   
              _rip += 1;                                              
            break;                                                    
          case 1:  /* register indirect + 8-bit displacement */       
            _rip += 1;                                                
            if (_rm == 4)  /* A SIB byte follows the ModR/M byte */   
              _rip += 1;                                              
            break;                                                    
          case 2:  /* register indirect + 32-bit displacement */      
            _rip += 4;                                                
            if (_rm == 4)  /* A SIB byte follows the ModR/M byte */   
              _rip += 1;                                              
            break;                                                    
          case 3:                                                     
            break;                                                    
          }                                                           
        _rip += 2;                                                    
        _gregs[REG_RIP] = (greg_t)_rip;                               
        return;                                                       
      }                                                               

コードの残りの部分は、ModR/M バイトを調べて、除数オペランドで使用されるバイト数を判断し、命令ポインターを次の命令に進めることができるようにするだけです。

基本的に、これはコメントに記載されていることを正確に実行しています。被除数が最大の負の整数である場合、結果は被除数と等しくなり、例外は発生しません。

_Jv_catch_segvとについて_Jv_catch_segvは、segvpatch.cpp で定義されています。

SIGNAL_HANDLER(catch_segv)
{
    unblock_signal(SIGSEGV);
    MAKE_THROW_FRAME(nullp);
    handle_segv();
}

SIGNAL_HANDLER(catch_fpe)
{
    unblock_signal(SIGFPE);
#ifdef HANDLE_DIVIDE_OVERFLOW
    HANDLE_DIVIDE_OVERFLOW;
#else
    MAKE_THROW_FRAME(arithexception);
#endif
    handle_fpe();
}

マクロは x86_64-signal.h で定義され、次のSIGNAL_HANDLERように展開されます。

static void _Jv_catch_segv (int, siginfo_t *, void *_p __attribute__ ((__unused__)))

最後に、RESTORE2基本的に から呼び出されるマクロは、次のRESTORE (restore_rt, __NR_rt_sigreturn)ように展開されます。

asm                                             
  (                                             
   ".text\n"                                    
   ".byte 0  # Yes, this really is necessary\n" 
   ".align 16\n"                                
   "__restore_rt:\n"                             
   "    movq $__NR_rt_sigreturn, %rax\n"             
   "    syscall\n"                              
   );

これにより、シグナルハンドラから戻るために使用される sigreturn syscall が作成されます。これは、次のrestore_rt行で関数に変換されます。

void restore_rt (void) asm ("__restore_rt")

これは、このコードで復元関数ポインターとして設定されます。

act.k_sa_restorer = restore_rt;

INIT_SEGVこれは、との 2 つのシグナル ハンドラを初期化するときに使用されますINIT_FPE

これですべての質問がカバーされると思いますが、不明な点がある場合、または特定の側面について詳しく説明してほしい場合は、コメントでお知らせください。

于 2013-05-27T22:40:03.800 に答える