Essayer de suivre pas à pas un programme avec trap-flag et trap-signal-handler, planter vsyscall

Je voudrais créer une trace d’instructions complète de l’exécution d’un programme, collecter des statistiques, etc. J’ai d’abord essayé d’utiliser la fonctionnalité ptrace de linux pour parcourir un programme (en utilisant le tutoriel ici ). Cela crée deux processus, le suivi et le débogueur, qui communiquent via des signaux. Je n’ai eu que 16K instructions par seconde (sur 1,6 GHz Atom), donc c’est trop lent pour tout ce qui n’est pas sortingvial.

Je pensais que la communication interprocessus via les signaux était trop lente, j’ai donc essayé de configurer le débogage dans le même processus que l’exécution: définir l’indicateur d’interruption et créer un gestionnaire de signaux. Lorsqu’une interruption logicielle est utilisée pour faire un appel système, l’indicateur d’interruption doit être enregistré, le kernel utilise ses propres indicateurs – alors j’ai pensé. Mais mon programme est en quelque sorte tué par le signal SIGTRAP.

C’est ce que j’ai mis en place:

#include  #include  #include  int cycle = 0; void trapHandler(int signum) { if (cycle % 262144 == 0) { write(STDOUT_FILENO," trap\n",6); } cycle += 1; } void startTrace() { // set up signal handler signal(SIGTRAP, trapHandler); // set trap flag asm volatile("pushfl\n" "orl $0x100, (%esp)\n" "popfl\n" ); } void printRock() { char* s = "Rock\n"; asm( "movl $5, %%edx\n" // message length "movl %0, %%ecx\n" // message to write "movl $1, %%ebx\n" // file descriptor (stdout) "movl $4, %%eax\n" // system call number (sys_write) "int $0x80\n" // sycall : // no output regs : "r"(s) // input text : "edx","ecx","ebx","eax" ); } int main() { startTrace(); // some computation int x = 0; int i; for (i = 0; i < 100000; i++) { x += i*2; } printRock(); write(STDOUT_FILENO,"Paper\n",6); write(STDOUT_FILENO,"Scissors\n",9); } 

En cours d’exécution, cela donne:

  trap trap trap Rock Paper trap Trace/breakpoint trap (core dumped) 

Nous avons donc maintenant environ 250 000 instructions par seconde, des exécutions toujours lentes mais non sortingviales sont possibles. Mais il y a ce dump de base qui semble se produire entre les deux appels d’écriture. Dans GDB, nous voyons où cela se passe:

 Dump of assembler code for function __kernel_vsyscall: 0xb76f3414 : push %ecx 0xb76f3415 : push %edx 0xb76f3416 : push %ebp 0xb76f3417 : mov %esp,%ebp 0xb76f3419 : sysenter 0xb76f341b : nop 0xb76f341c : nop 0xb76f341d : nop 0xb76f341e : nop 0xb76f341f : nop 0xb76f3420 : nop 0xb76f3421 : nop 0xb76f3422 : int $0x80 => 0xb76f3424 : pop %ebp 0xb76f3425 : pop %edx 0xb76f3426 : pop %ecx 0xb76f3427 : ret 

Et la trace:

 Program terminated with signal SIGTRAP, Trace/breakpoint trap. #0 0xb77c5424 in __kernel_vsyscall () #1 0xb76d0553 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:81 #2 0x0804847d in trapHandler (signum=5) at count.c:8 #3  #4 0xb77c5424 in __kernel_vsyscall () #5 0xb76d0553 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:81 #6 0x08048537 in main () at count.c:49 

Il semble que les appels système qui se produisent via int 80 conviennent, mais les appels d’écriture utilisent d’une manière ou d’une autre la pause VIDSO / vsyscall du kernel (je ne connaissais pas cette fonctionnalité, décrite plus en détail ici ). Cela peut être lié à l’utilisation de sysenter plutôt qu’à int 80 , peut-être que l’indicateur d’ sysenter survit lors de l’entrée dans le kernel. Je ne comprends pas très bien ce qui se passe avec les appels récursifs __kernel_vsyscall . Je ne comprends pas non plus pourquoi il existe un appel int 80 dans la fonction __kernel_vsyscall .

Quelqu’un a-t-il une suggestion sur ce qui se passe et comment résoudre ce problème? Peut-être est-il possible de désactiver le VDSO / vsysicall? Ou est-il possible de remplacer la fonction __kernel_vsyscall par une autre qui utilise int 80 plutôt que sysenter ?

Répondre à sa propre question. Je n’ai pas compris ce qui se passait ni expliqué en détail, mais j’ai trouvé une solution de contournement: désactiver VDSO. Cela peut être fait via

 sudo sysctl vm.vdso_enabled=0 

Avec cela, toute cette procédure pas à pas dans un programme fonctionne, y compris en passant par les appels système. Disclaimer: ne me blâmez pas si les choses vont mal.

EDIT: Après avoir mis à jour mon Linux (32 bits x86) beaucoup plus tard, cette erreur ne se produit plus. Peut-être que c’était un bug qui a été corrigé.