85 votes

Pourquoi le pad GCC fonctionne-t-il avec des NOP?

Je travaille avec C depuis peu de temps et je me suis récemment lancé dans l’ASM. Quand je compile un programme:

 int main(void)
  {
  int a = 0;
  a += 1;
  return 0;
  }
 

Le désassemblage de objdump a le code, mais nops après le ret:

 ...
08048394 <main>:
 8048394:       55                      push   %ebp
 8048395:       89 e5                   mov    %esp,%ebp
 8048397:       83 ec 10                sub    $0x10,%esp
 804839a:       c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%ebp)
 80483a1:       83 45 fc 01             addl   $0x1,-0x4(%ebp)
 80483a5:       b8 00 00 00 00          mov    $0x0,%eax
 80483aa:       c9                      leave  
 80483ab:       c3                      ret    
 80483ac:       90                      nop
 80483ad:       90                      nop
 80483ae:       90                      nop
 80483af:       90                      nop
...
 

D'après ce que j'ai appris, les nops ne font rien, et depuis après, ils ne seraient même pas exécutés.

Ma question est: pourquoi déranger? ELF (linux-x86) ne pourrait-il pas fonctionner avec une section .text (+ main) de toute taille?

J'apprécierais n'importe quelle aide, essayant juste d'apprendre.

94voto

NPE Points 169956

Tout d'abord, gcc n'est pas toujours le faire. Le rembourrage est contrôlée par -falign-functions, ce qui est activée automatiquement par -O2 et -O3:

-falign-functions
-falign-functions=n

Aligner le début de fonctions à la puissance de deux supérieure n, en sautant n octets. Par exemple, -falign-functions=32 harmonise les fonctions, à la prochaine 32-frontière d'octet, mais -falign-functions=24 permettrait d'aligner à la prochaine 32-frontière d'octet seulement si cela peut être fait en sautant de 23 octets ou moins.

-fno-align-functions et -falign-functions=1 sont équivalentes et dire que les fonctions ne seront pas alignés.

Certains assembleurs seulement de soutenir cet indicateur lorsque n est une puissance de deux; dans ce cas, il est arrondi.

Si n n'est pas spécifié ou est égal à zéro, utilisez un dépendant de la machine par défaut.

Permis à des niveaux -O2-O3.

Il pourrait y avoir plusieurs raisons pour cela, mais la principale sur x86 est probablement ce:

La plupart des processeurs chercher des instructions dans aligné 16 octets ou 32 octets de blocs. Il peut être avantageux pour aligner critique de la boucle et les entrées de sous-routine entrées par 16 dans le but de minimiser le nombre de 16 octets limites dans le code. Assurez-vous également qu'il n'existe pas de 16 octets à la frontière dans les premières instructions après une critique de la boucle d'entrée ou de la sous-routine d'entrée.

(Cité dans "l'Optimisation des sous-routines en assembleur la langue" par Agner Brouillard.)

edit: Voici un exemple qui illustre l'padding:

// align.c
int f(void) { return 0; }
int g(void) { return 0; }

Lors de la compilation avec gcc 4.4.5 avec les paramètres par défaut, j'obtiens:

align.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <f>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 00 00 00 00          mov    $0x0,%eax
   9:   c9                      leaveq 
   a:   c3                      retq   

000000000000000b <g>:
   b:   55                      push   %rbp
   c:   48 89 e5                mov    %rsp,%rbp
   f:   b8 00 00 00 00          mov    $0x0,%eax
  14:   c9                      leaveq 
  15:   c3                      retq   

La spécification -falign-functions donne:

align.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <f>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 00 00 00 00          mov    $0x0,%eax
   9:   c9                      leaveq 
   a:   c3                      retq   
   b:   eb 03                   jmp    10 <g>
   d:   90                      nop
   e:   90                      nop
   f:   90                      nop

0000000000000010 <g>:
  10:   55                      push   %rbp
  11:   48 89 e5                mov    %rsp,%rbp
  14:   b8 00 00 00 00          mov    $0x0,%eax
  19:   c9                      leaveq 
  1a:   c3                      retq   

15voto

hamstergene Points 14041

Ceci est fait pour aligner la fonction suivante de 8, 16 ou 32-frontière d'octet.

De "l'Optimisation des sous-programmes en langage d'assemblage" par A. de Brouillard:

11.5 l'Alignement de code

La plupart des microprocesseurs extraire le code dans aligné 16 octets ou 32 octets de blocs. Si un importantsubroutine entrée ou de sauter à l'étiquette se trouve être près de la fin de 16 octets du bloc puis themicroprocessor obtenez seulement quelques octets de code lors de l'extraction de ce bloc de code. Itmay récupérer les 16 octets trop avant de pouvoir décoder les premières instructions après thelabel. Ceci peut être évité par l'alignement important de sous-programme et les entrées de boucle d'entrées par 16.

[...]

L'alignement d'une sous-routine d'entrée est aussi simple que de mettre autant d' NOP de 's que nécessaire avant de thesubroutine de l'adresse divisible par 8, 16, 32 ou 64 selon votre choix.

5voto

mco Points 103

Autant que je me souvienne, les instructions sont en pipeline dans le processeur et différents blocs de processeur (chargeur, décodeur, etc.) traitent les instructions suivantes. Lorsque les instructions RET sont en cours d'exécution, seules quelques instructions suivantes sont déjà chargées dans le pipeline de l'unité centrale. C'est une supposition, mais vous pouvez commencer à creuser ici et si vous le découvrez (peut-être le nombre spécifique de NOP s qui sont en sécurité, partagez vos découvertes s'il vous plaît.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X