Почему GCC pad работает с NOP? - PullRequest
       1

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

76 голосов
/ 27 октября 2011

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

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

В коде разборки objdump есть код, но после повторения нет:

...
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
...

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

Мой вопрос: зачем? Не может ли ELF (linux-x86) работать с секцией .text (+ main) любого размера?

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

Ответы [ 3 ]

86 голосов
/ 27 октября 2011

Прежде всего, 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    $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   

Задание -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    $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   
13 голосов
/ 27 октября 2011

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

Из «Оптимизация подпрограмм на ассемблере» А.Фог:

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

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

[...]

Выравнивание записи подпрограммы так же просто, как и NOP Это необходимо до ввода подпрограммы, чтобы сделать адрес делимым на 8, 16, 32 или 64, по желанию.

6 голосов
/ 27 октября 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...