2013-05-27 3 views
1

Je travaillais sur un gestionnaire d'exceptions en C++. Il sera utilisé dans un protocole de messagerie pour une fiabilité maximale - je veux obtenir une réponse d'un processus qui a reçu un message à tout moment - même si le processus attrape un segfault. Je sais que C++ ne soulève pas d'exceptions pour le déréférencement du pointeur nul, ou la division par zéro, mais Java, qui est écrit dans C fait. Cela m'a fait réfléchir et regarder autour de moi. Finalement, je trouve cette petite bibliothèque:Besoin d'explication de l'utilisation de l'assembleur et du traitement du signal Linux

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

Ma question est en ce qui concerne x86_64-déposer signal.h. Je ne comprends pas complètement les détails de HANDLE_DIVIDE_OVERFLOW. REX octet? Qu'est-ce que c'est? Que fait RESTORE2? Qu'est-ce que _Jv_catch_segv et _Jv_catch_fpe? Je comprends la signification de base de * attraper *, mais où sont-ils définis?

Idéalement, si quelqu'un pouvait parcourir le fichier de haut en bas et aborder les points clés qui seraient géniaux. Merci.

Répondre

2

Je ne suis pas un expert dans ce domaine, mais je répondrai de mon mieux.

en commençant par le test d'octets REX:

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

L'octet REX est un préfixe d'instruction est utilisé en mode 64 bits. Si les 4 bits élevés du premier octet d'une instruction correspondent 0x40, vous savez que vous avez un préfixe REX. Et si le bit 3 (le champ W) est défini sur 1, cela signifie que la taille de l'opérande est de 64 bits. _rip++ saute juste le dessus du préfixe.

if (_rip[0] == 0xf7)             

F7 nous dit ceci est une instruction de division entière de quelque sorte.

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

L'octet suivant est l'octet ModR/M qui donne habituellement les détails des opérandes, mais dans ce cas détermine également le type d'instruction de division.

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

Le champ REG (bits 3 à 5) de l'octet ModR/M représente habituellement un registre, mais ici il est une extension de code d'opération de l'instruction. Si c'est 7, cela signifie que c'est une division signée.

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

0x80000000UL et 0x8000000000000000UL sont les plus petits nombres négatifs possibles en 32 bits et 64 bits respectivement. Si le registre eax (le dividende) correspond à cette valeur, cela signifie que vous avez le dividende minimum possible.

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

Si vous avez le dividende minimum possible, cet ensemble le reste (EDX) à zéro, et laisse le dividende en EAX comme résultat.

 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;              
     }                

Le reste du code est juste inspectait l'octet ModR/M pour déterminer le nombre d'octets utilisés par l'opérande diviseur afin qu'il puisse faire avancer le pointeur d'instruction à l'instruction suivante. Fondamentalement, cela fait exactement ce qu'il dit dans les commentaires. Si le dividende est l'entier négatif de la plus grande magnitude possible, le résultat est égal au dividende et aucune exception ne se produit.

Comme pour _Jv_catch_segv et _Jv_catch_segv, ceux-ci sont définis dans 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(); 
} 

La macro SIGNAL_HANDLER est définie dans x86_64-signal.h et étend à quelque chose comme ceci:

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

Enfin, la macro RESTORE2, qui est essentiellement appelé à partir RESTORE (restore_rt, __NR_rt_sigreturn), se déplie:

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"        
    ); 

Cela crée un appel système de sigreturn qui est utilisée pour renvoyer à partir d'un gestionnaire de signaux. Ceci est transformé en fonction restore_rt avec cette ligne:

void restore_rt (void) asm ("__restore_rt") 

qui est défini comme le pointeur de fonction de rétablissement dans le code suivant:

act.k_sa_restorer = restore_rt; 

qui est utilisé lors de l'initialisation des deux gestionnaires de signaux en INIT_SEGV et INIT_FPE .

Et je pense que couvre de toutes vos questions, mais s'il y a quelque chose qui est pas clair ou vous voulez que je développiez un aspect particulier, laissez-moi savoir dans les commentaires.

+0

Merci beaucoup pour votre réponse. Je n'ai suivi la question: Selon cette page « http://man7.org/linux/man-pages/man2/sigaction.2.html » le sa_restorer est obsolète et ne doit pas être utilisé. Savez-vous quoi d'autre peut être utilisé à sa place? Ou cette fonctionnalité a-t-elle été complètement supprimée? Ou la différence principale pourrait-elle être dans le type de la structure 'k_sa_sigaction', c'est-à-dire la sigaction du noyau. Et ma dernière question - est ce fichier basé sur des connaissances en assembleur de base, ou devrais-je lire un peu sur la programmation du système d'exploitation avancé? – NindzAI

+0

Vous devriez être en mesure de comprendre l'assemblage en lisant simplement la [Intel Instruction Set Reference] (http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html). La gestion du signal est plus compliquée. Je ne fais pas beaucoup de programmation Unix, donc c'est un peu hors de ma ligue, mais je pense que vous devez regarder la source du noyau pour vraiment comprendre ce qui se passe - il diffère d'une architecture à l'autre. Il semble sûr de déposer sa_restorer sur certaines plates-formes, mais x86-64 par exemple, on dirait qu'il est toujours nécessaire. –

Questions connexes