program story

GCC 패드가 NOP와 함께 작동하는 이유는 무엇입니까?

inputbox 2020. 10. 17. 10:28
반응형

GCC 패드가 NOP와 함께 작동하는 이유는 무엇입니까?


저는 C와 잠시 동안 일해 왔으며 최근에 ASM에 들어가기 시작했습니다. 프로그램을 컴파일 할 때 :

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

objdump 디스 어셈블리에는 코드가 있지만 ret 후에는 nops입니다.

...
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)에서도 작동하지 않습니까?

도움을 주시면 감사하겠습니다.


우선, gcc항상 이렇게하는 것은 아닙니다. 패딩은에 의해 제어 -falign-functions되며 -O2에 의해 자동으로 설정됩니다 -O3.

-falign-functions
-falign-functions=n

함수의 시작을 다음 2의 제곱보다 큰 값 n으로 정렬하고 n바이트 까지 건너 뜁니다 . 예를 들어 -falign-functions=32는 다음 32 바이트 경계에 함수를 정렬하지만 -falign-functions=2423 바이트 이하를 건너 뛰어 수행 할 수있는 경우에만 다음 32 바이트 경계에 정렬합니다.

-fno-align-functions-falign-functions=1동등하며 기능이 정렬되지 않음을 의미합니다.

일부 어셈블러는 n이 2의 거듭 제곱 일 때만이 플래그를 지원합니다. 이 경우 반올림됩니다.

n이 지정되지 않았거나 0이면 기계 종속 기본값을 사용하십시오.

-O2, -O3 수준에서 활성화됩니다.

이렇게하는 데는 여러 가지 이유가있을 수 있지만 x86의 주된 이유는 다음과 같습니다.

대부분의 프로세서는 정렬 된 16 바이트 또는 32 바이트 블록으로 명령어를 가져옵니다. 코드에서 16 바이트 경계 수를 최소화하기 위해 중요 루프 항목과 서브 루틴 항목을 16 씩 정렬하는 것이 유리할 수 있습니다. 또는 중요한 루프 항목 또는 서브 루틴 항목 이후 처음 몇 개의 명령어에 16 바이트 경계가 없는지 확인하십시오.

(Agner Fog의 "어셈블리 언어에서 서브 루틴 최적화"에서 인용)

편집 : 다음은 패딩을 보여주는 예입니다.

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

이것은 다음 함수를 8, 16 또는 32 바이트 경계로 정렬하기 위해 수행됩니다.

A.Fog의 "어셈블리 언어에서 서브 루틴 최적화"에서 :

11.5 코드 정렬

Most microprocessors fetch code in aligned 16-byte or 32-byte blocks. If an importantsubroutine entry or jump label happens to be near the end of a 16-byte block then themicroprocessor will only get a few useful bytes of code when fetching that block of code. Itmay have to fetch the next 16 bytes too before it can decode the first instructions after thelabel. This can be avoided by aligning important subroutine entries and loop entries by 16.

[...]

Aligning a subroutine entry is as simple as putting as many NOP 's as needed before thesubroutine entry to make the address divisible by 8, 16, 32 or 64, as desired.


As far as I remember, instructions are pipelined in cpu and different cpu blocks (loader, decoder and such) process subsequent instructions. When RET instructions is being executed, few next instructions are already loaded into cpu pipeline. It's a guess, but you can start digging here and if you find out (maybe the specific number of NOPs that are safe, share your findings please.

참고URL : https://stackoverflow.com/questions/7912464/why-does-gcc-pad-functions-with-nops

반응형