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=24
23 바이트 이하를 건너 뛰어 수행 할 수있는 경우에만 다음 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 NOP
s that are safe, share your findings please.
참고URL : https://stackoverflow.com/questions/7912464/why-does-gcc-pad-functions-with-nops
'program story' 카테고리의 다른 글
Rust는 가비지 수집기 대신 무엇을 가지고 있습니까? (0) | 2020.10.17 |
---|---|
std :: map 기본값 (0) | 2020.10.17 |
RequireJS : "requirejs"와 "require"함수의 차이점 (0) | 2020.10.17 |
C # Generics는 위임 형식 제약 조건을 허용하지 않습니다. (0) | 2020.10.17 |
TikZ를 사용한 독립형 다이어그램? (0) | 2020.10.17 |