나는 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 코드 정렬
대부분의 마이크로 프로세서는 정렬 된 16 바이트 또는 32 바이트 블록으로 코드를 가져옵니다. 중요한 서브 루틴 항목 또는 점프 레이블이 16 바이트 블록의 끝 근처에있는 경우 마이크로 프로세서는 해당 코드 블록을 가져올 때 몇 바이트의 유용한 코드 만 가져옵니다. 레이블 뒤의 첫 번째 명령어를 디코딩하려면 다음 16 바이트도 가져와야 할 수 있습니다. 이것은 중요한 서브 루틴 항목과 루프 항목을 16으로 정렬하여 피할 수 있습니다.
[…]
서브 루틴 항목을 정렬하는 것은 원하는대로 주소를 8, 16, 32 또는 64로 나눌 수 있도록 서브 루틴 항목 앞에 필요한만큼 많은 NOP를 넣는 것만 큼 간단합니다.
답변
내가 기억하는 한 명령어는 cpu에서 파이프 라인되고 다른 cpu 블록 (로더, 디코더 등)은 후속 명령어를 처리합니다. RET
명령이 실행될 때 다음 명령이 이미 cpu 파이프 라인에로드되지 않습니다. 추측이지만 여기에서 파헤 치기 시작할 수 있습니다. 그리고 만약 당신이 알아 내면 (아마도 NOP
안전한 s 의 특정 수 , 당신의 발견을 공유하십시오.