Почему GCC pad работает с NOPs?


Я работал с C в течение короткого времени и совсем недавно начал входить в ASM. Когда я компилирую программу:

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

разборка objdump имеет код, но nops после ret:

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

из того, что я узнал, nops ничего не делают, и так как после ret даже не будет выполнен.

мой вопрос: зачем? Не удалось ELF (linux-x86) работать с a .текстовый раздел (+main) любого размера?

Я был бы признателен за любую помощь, просто пытаясь учить.

3 74

3 ответа:

прежде всего,gcc не всегда это делает. Заполнение контролируется -falign-functions, который автоматически включается -O2 и -O3:

-falign-functions
-falign-functions=n

выровнять начало функций до следующей степени два больше, чем n, пропустив срок до n байт. Например, -falign-functions=32 выравнивает функции до следующей 32-байтовой границы, но -falign-functions=24 будет выравниваться только до следующей 32-байтовой границы если это можно сделать, пропустив 23 байт или меньше.

-fno-align-functions и -falign-functions=1 эквивалентны и означают, что функции не будут выровнены.

некоторые ассемблеры поддерживают этот флаг только тогда, когда n-степень двух; в в этом случае, он округляется.

если n не указано или равно нулю, используйте значение по умолчанию, зависящее от компьютера.

включено на уровнях-O2, - O3.

там может быть несколько причин для этого, но главный на x86, вероятно, это:

большинство процессоров извлекают инструкции в выровненных 16-байтовых или 32-байтовых блоках. Это может быть выгодно выровнять записи критического цикла и записи подпрограммы на 16 для минимизации число 16-байтовых границ в коде. Кроме того, убедитесь, что в первых нескольких инструкциях после записи критического цикла или записи подпрограммы нет 16-байтовой границы.

(цитата из "оптимизация подпрограммы на ассемблере язык " Агнера Фога.)

edit: вот пример, который демонстрирует подклада:

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

при компиляции с использованием gcc 4.4.5 с настройками по умолчанию, я получаю:

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    x0,%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    x0,%eax
  14:   c9                      leaveq 
  15:   c3                      retq   

задание -falign-functions выдает:

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    x0,%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    x0,%eax
  19:   c9                      leaveq 
  1a:   c3                      retq   

Это делается для выравнивания следующей функции по 8, 16 или 32-байтовой границе.

из "оптимизация подпрограмм на языке ассемблера" А. фог:

11.5 выравнивание кода

большинство микропроцессоров получают код в выровненных 16-байтовых или 32-байтовых блоках. Если запись importantsubroutine или метка перехода оказывается ближе к концу 16-байтового блока, то themicroprocessor получит только несколько полезных байтов кода при извлечении этого блока кода. Он может нужно также извлечь следующие 16 байт, прежде чем он сможет декодировать первые инструкции после метки. Этого можно избежать, выровняв важные элементы подпрограммы и элементы цикла на 16.

[...]

выравнивание записи подпрограммы так же просто, как положить столько НОП 's по мере необходимости перед записью thesubroutine, чтобы сделать адрес делится на 8, 16, 32 или 64, по желанию.

насколько я помню, инструкции передаются по конвейеру в cpu и различные блоки cpu (загрузчик, декодер и т. д.) обрабатывают последующие инструкции. Когда RET инструкции выполняется несколько инструкций уже загружены в конвейер процессора. Это предположение, но вы можете начать копать здесь, и если вы узнаете (возможно, определенное количество NOPs, которые безопасны, поделитесь своими выводами, пожалуйста.