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


81

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

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

내가 배운 것에서 nops는 아무것도하지 않으며 이후 ret은 실행되지 않을 것입니다.

내 질문은 : 왜 귀찮게? ELF (linux-x86)는 어떤 크기의 .text 섹션 (+ main)에서도 작동하지 않습니까?

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


NOP가 계속 진행됩니까? 에서 멈 추면 80483af다음 함수를 8 또는 16 바이트로 정렬하기위한 패딩 일 수 있습니다.
Mysticial

더 4 NOP를 후에는 기능 해협 간다 : __libc_csu_fini
올리

1
NOP을가 GCC에 의해 삽입하게되면 그때부터 크기 변수 많은 NOP를가 있기 때문에 그것은 단지 0x90를 사용합니다 생각하지 않습니다 1-9 바이트 (10의 경우 사용 가스 구문 )
phuclv

답변:


89

우선, 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의 "어셈블리 언어에서 서브 루틴 최적화"에서 인용)

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

기본 설정으로 gcc 4.4.5를 사용하여 컴파일하면 다음과 같은 결과가 나타납니다.

지정 -falign-functions하면 다음이 제공됩니다.


1
나는 -O 플래그, 간단한 "gcc -o test test.c"를 사용하지 않았습니다.
olly

1
@olly : 64 비트 Ubuntu에서 gcc 4.4.5로 테스트했으며 테스트에서는 기본적으로 패딩이 없으며 -falign-functions.
NPE 2011 년

@aix : 저는 centOS 6.0 (32 비트)에 있고 플래그없이 패딩이 있습니다. 누구든지 내 전체 "objdump -j .text -d ./test"출력을 덤프하길 원하십니까?
olly

1
추가 테스트에서 객체로 컴파일 할 때 : "gcc -c test.c". 패딩은 없지만 링크하면 "gcc -o test test.o"가 나타납니다.
olly

2
@olly : 그 패딩은 main실행 파일에 뒤 따르는 함수의 정렬 요구 사항을 충족하기 위해 링커에 의해 삽입됩니다 (내 경우에는 함수가 __libc_csu_fini).
NPE 2011 년

15

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

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

11.5 코드 정렬

대부분의 마이크로 프로세서는 정렬 된 16 바이트 또는 32 바이트 블록으로 코드를 가져옵니다. 중요한 서브 루틴 항목 또는 점프 레이블이 16 바이트 블록의 끝 근처에있는 경우 마이크로 프로세서는 해당 코드 블록을 가져올 때 몇 바이트의 유용한 코드 만 가져옵니다. 레이블 뒤의 첫 번째 명령어를 디코딩하려면 다음 16 바이트도 가져와야 할 수 있습니다. 이것은 중요한 서브 루틴 항목과 루프 항목을 16으로 정렬하여 피할 수 있습니다.

[...]

서브 루틴 항목을 정렬하는 것은 원하는대로 주소를 8, 16, 32 또는 64로 나눌 수 있도록 서브 루틴 항목 앞에 필요한만큼 많은 NOP를 넣는 것만 큼 간단합니다.


25-29 바이트 (메인 용)의 차이입니다. 더 큰 것을 말하는 것입니까? 텍스트 섹션과 마찬가지로 readelf를 통해 364 바이트로 나타 났습니까? 또한 _start에서 14 개의 nops를 발견했습니다. 왜 "as"는 이런 일을하지 않습니까? 저는 신인입니다. 죄송합니다.
olly

@olly : 컴파일 된 기계 코드에서 전체 프로그램 최적화를 수행하는 개발 시스템을 보았습니다. 함수의 주소 foo가 0x1234이면 리터럴 0x1234에 근접하여 해당 주소를 사용하는 코드는 결국 mov ax,0x1234 / push ax / mov ax,0x1234 / push ax최적화 프로그램이 mov ax,0x1234 / push ax / push ax. 이러한 최적화 후에 함수를 재배치해서는 안되므로 명령을 제거하면 실행 속도가 향상되지만 코드 크기는 향상되지 않습니다.
supercat

5

내가 기억하는 한 명령어는 cpu에서 파이프 라인되고 다른 cpu 블록 (로더, 디코더 등)은 후속 명령어를 처리합니다. RET명령이 실행될 때 다음 명령이 이미 cpu 파이프 라인에로드되지 않습니다. 추측이지만 여기에서 파헤 치기 시작할 수 있습니다. 그리고 만약 당신이 알아 내면 (아마도 NOP안전한 s 의 특정 수 , 당신의 발견을 공유하십시오.


@ninjalj : 응? 이 질문은 파이프 라인 된 x86에 대해 묻는 것입니다 (mco가 말했듯이). 많은 최신 x86 프로세서는 아마도 이러한 nops를 포함하여 실행 "해서는 안되는"명령을 추측 적으로 실행합니다. 다른 곳에 댓글을 달고 싶으신가요?
David Cary

3
@DavidCary : x86에서는 프로그래머에게 완전히 투명합니다. 잘못 추측 된 추측에 의해 실행 된 명령어는 결과와 효과 만 버립니다. MIPS에는 "추측"부분이 전혀 없으며 분기 지연 슬롯의 명령어가 항상 실행되며 프로그래머는 지연 슬롯을 채워야합니다 (또는 어셈블러가 수행하도록하여 아마도 nops 가 될 수 있음 ).
ninjalj

@ninjalj : 예, 추측에 의해 잘못 실행 된 연산 및 정렬되지 않은 명령의 효과는 출력 데이터 값에 영향을 미치지 않는다는 점에서 투명합니다. 그러나 둘 다 프로그램의 타이밍에 영향을 미치므로 gcc가 x86 코드에 nops를 추가하는 이유 일 수 있습니다.
David Cary

1
@DavidCary : 그게 이유라면, 무조건 ret.
ninjalj

1
이것은 이유가 아닙니다. (도서 구매 미스에) 간접 점프의 대체 예측은 다음 명령이지만, 그 이외의 명령 쓰레기 정지에 권장 최적화의 경우 잘못된 추측과 같은 지침이다 ud2또는 int3프런트 엔드 대신 디코딩 중지 알 수 있도록, 그 항상 고장 div예를 들어 , 잠재적으로 비싸 거나 가짜 TLB-miss 부하를 파이프 라인 에 공급하는 것 입니다. 이것은 함수 끝에서 ret또는 직접 jmp마무리 호출 후에 필요하지 않습니다 .
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.