【问题标题】:Need explanation of the usage of assembler and Linux signal processing需要解释汇编器和Linux信号处理的用法
【发布时间】:2013-05-22 13:22:34
【问题描述】:

我正在使用 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* 的基本含义,但是它们是在哪里定义的呢?

理想情况下,如果有人可以从上到下浏览文件并解决关键点,那就太好了。谢谢。

【问题讨论】:

    标签: c linux assembly operating-system


    【解决方案1】:

    我不是这方面的专家,但我会尽力回答。

    从 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();
    }
    

    SIGNAL_HANDLER 宏在 x86_64-signal.h 中定义并扩展为如下内容:

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

    最后,基本上是从RESTORE (restore_rt, __NR_rt_sigreturn) 调用的RESTORE2 宏扩展为:

    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 系统调用,用于从信号处理程序返回。这被转换为函数restore_rt 与这一行:

    void restore_rt (void) asm ("__restore_rt")
    

    在这段代码中设置为restorer函数指针:

    act.k_sa_restorer = restore_rt;
    

    在初始化INIT_SEGVINIT_FPE 中的两个信号处理程序时使用。

    我认为这涵盖了您的所有问题,但如果有任何不清楚的地方或者您希望我在任何特定方面进行扩展,请在 cmets 中告诉我。

    【讨论】:

    • 非常感谢您的回答。我确实有一个后续问题:根据此页面“man7.org/linux/man-pages/man2/sigaction.2.html”,sa_restorer 已过时,不应使用。你碰巧知道还有什么可以代替它的吗?还是完全删除了该功能?或者主要区别可能在于结构'k_sa_sigaction'的类型,即内核sigaction。我的最后一个问题 - 这个文件是基于一些基本的汇编知识,还是我应该阅读一些关于高级 OS 编程的知识?
    • 您应该能够通过阅读Intel Instruction Set Reference 来找出程序集。信号处理更复杂。我没有做太多的 Unix 编程,所以这有点不合我意,但我认为你需要查看内核源代码才能真正理解发生了什么——它因一种架构而异。在某些平台上删除 sa_restorer 看起来很安全,但在 x86-64 上,它看起来仍然是必需的。
    猜你喜欢
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-30
    • 1970-01-01
    • 2011-01-28
    • 1970-01-01
    相关资源
    最近更新 更多