2011-10-27 10 views
71

Pracuję z C przez krótki czas i bardzo niedawno zacząłem wchodzić w ASM. Kiedy skompilować program:Dlaczego poduszki GCC działają z NOPami?

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

objdump demontaż ma kod, ale NoPS po ret:

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

Z tego co dowiedziałem NoPS nic nie robić, a ponieważ po ret nie byłoby nawet wykonany.

Moje pytanie brzmi: po co? Czy ELF (linux-x86) nie mógł pracować z sekcją .text (+ main) o dowolnym rozmiarze?

Byłbym wdzięczny za każdą pomoc, po prostu próbując się uczyć.

+0

Czy te NOPy nadal działają? Jeśli zatrzymają się na '80483af', to może to dopełnienie do wyrównania następnej funkcji do 8 lub 16 bajtów. – Mysticial

+0

no po 4 węzłach idzie cieśnina do funkcji: __libc_csu_fini – olly

+1

Jeśli NOPs zostały wstawione przez gcc wtedy nie sądzę, że będzie używał tylko 0x90, ponieważ istnieje wiele NOPów o zmiennej wielkości od [1-9 bajtów] (http://software.intel.com/en-us/forums/topic/307174) (10 jeśli używam [składnia gazu] (http://stackoverflow.com/a/12564044/995714)) –

Odpowiedz

83

Po pierwsze, gcc nie zawsze to robi. Wyściółka jest kontrolowana przez -falign-functions, która jest automatycznie włączona -O2 i -O3:

-falign-functions
-falign-functions=n

wyrównać początek funkcji do następnej power-of-two większa niż n, omijając w górę do n bajtów. Na przykład: -falign-functions=32 wyrównuje funkcje do następnej granicy 32-bajtowej, ale -falign-functions=24 zostanie wyrównane do następnej granicy 32-bajtowej tylko , jeśli można to zrobić, pomijając 23 bajty lub mniej.

-fno-align-functions i -falign-functions=1 są równoważne i oznaczają, że funkcje nie będą wyrównane.

Niektóre asemblery obsługują tę flagę, gdy n jest potęgą dwóch; w tym przypadku jest on zaokrąglany w górę.

Jeśli n nie jest określone lub wynosi zero, użyj domyślnego ustawienia zależnego od urządzenia.

Włączone na poziomach -O2, -O3.

Nie może być wiele powodów ku temu, ale głównym z nich na x86 jest prawdopodobnie to:

Większość procesorów pobrać instrukcje w wyrównanych 16-bajtowych lub 32-bajtowych bloków. Może to być korzystne dla wyrównania krytycznych pozycji pętli i wpisów podprogramów przez 16, aby zminimalizować liczbę 16-bajtowych granic w kodzie. Alternatywnie, upewnij się, że nie ma granicy 16 bajtów w pierwszych kilku instrukcjach po wejściu krytycznej pętli lub wpisie podprogramu.

(Cytat z "Optymalizacja podprogramów w montażu języka" przez Agner Fog).

edit: Oto przykład, który pokazuje padding:

// align.c 
int f(void) { return 0; } 
int g(void) { return 0; } 

Kiedy skompilowany przy użyciu gcc 4.4.5 z ustawieniami domyślnymi otrzymuję:

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 

Podanie -falign-functions podaje:

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 
+0

Nie użyłem żadnych flag -O, prosty "gcc -o test test.c". – olly

+0

@olly: Przetestowałem go z gcc 4.4.5 na 64-bitowym systemie Ubuntu i w moich testach domyślnie nie ma padding, a tam jest dopełnienie z '-falign-functions'. – NPE

+0

@Aix: Jestem na centOS 6.0 (32-bit) i bez żadnych flag mają dopełnienie. Czy ktoś chce, żebym zrzucił moje pełne dane wyjściowe "objdump -j .text -d ./test"? – olly

6

O ile pamiętam, instrukcje są przesyłane potokowo w procesorze i różne bloki procesora (program ładujący, dekoder itp.) Przetwarzają kolejne instrukcje. Kiedy wykonywane są instrukcje RET, kilka następnych instrukcji jest już załadowanych do potoku procesora. Zgadnij, ale możesz zacząć kopać tutaj i jeśli się dowiesz (być może konkretny numer NOP s, który jest bezpieczny, podziel się swoimi odkryciami,

+0

Moja pierwsza myśl również , aby oczyścić rurociąg ... – bdares

+3

-1, to nie jest MIPS ISA I. – ninjalj

+0

@ninjalj: Huh? Pytanie to dotyczy x86, który jest potokowy (jak powiedział mco). Wiele współczesnych procesorów x86 również spekulacyjnie wykonuje instrukcje, które "nie powinny" być wykonywane, być może włączając te nopy. Być może chciałeś skomentować gdzie indziej? –

13

Czynności te mają na celu wyrównanie następnej funkcji o 8, 16 lub 32 -bajtowych granica

od „Optymalizacja podprogramów w języku asemblera” przez A.Fog.

11,5 Dostosowanie kodu

Większość mikroprocesory pobrać kod w wyrównanych 16-bajtowych lub 32-bajtowych bloków. Jeśli znacznik wejścia lub skoku importantuboutine jest blisko koniec bloku 16-bajtowego, wtedy procesor pobiera tylko kilka użytecznych bajtów kodu podczas pobierania tego bloku kodu. Może on również pobrać następne 16 bajtów, zanim zdoła odkodować pierwsze instrukcje po thelabel. Można tego uniknąć przez dostosowanie ważne dane podprogramów i wpisy pętlę 16.

[...]

Wyrównywanie pozycję podprogram jest proste wprowadzenie aż NOP jest w razie potrzeby przed wejściem do thesubroutine uczynić adres podzielny przez 8, 16, 32 lub 64, zgodnie z potrzebami.

+0

To jest różnica między 25-29 bajtów (dla głównego), czy mówisz o czymś większym? Podobnie jak w sekcji tekstowej, przez readelf znalazłem 364 bajty? Zauważyłem również 14 nops na _start. Dlaczego "tak" nie robi tych rzeczy? Jestem nowicjuszem, przepraszam. – olly

+0

@olly: Widziałem systemy programistyczne, które wykonują optymalizację całego programu na skompilowanym kodzie maszynowym.Jeśli adres funkcji 'foo' to 0x1234, to kod, który będzie używał tego adresu w bliskiej odległości od literalnego 0x1234, może wygenerować kod maszynowy, taki jak: mov ax, 0x1234/push ax/mov ax, 0x1234/push ax' który optymalizator mógłby następnie zastąpić "mov ax, 0x1234/push ax/push ax". Należy zauważyć, że funkcje nie mogą być przenoszone po takiej optymalizacji, więc eliminacja instrukcji zwiększyłaby szybkość wykonywania, ale nie rozmiar kodu. – supercat

Powiązane problemy