2017-01-28 69 views
13

Próbuję nauczyć się trochę montażu, ponieważ potrzebuję go do klasy Computers Architecture. Napisałem kilka programów, takich jak drukowanie sekwencji Fibonacciego, itp Poznałem, że kiedy piszę program, używam te 3 linie (jak dowiedziałem się od porównywania kodu assemblera generowany gcc na to C odpowiednik):Jaki jest cel rejestru RBP w asemblerze x86_64?

pushq %rbp 
movq %rsp, %rbp 
subq $16, %rsp 

I Masz 2 pytania na ten temat:

  1. Po pierwsze, dlaczego używam %rbp? Czy nie jest prostsze w użyciu %rsp, ponieważ jego zawartość zostanie przeniesiona do %rbp w drugiej linii?
  2. Dlaczego muszę odjąć wszystko od %rsp? To znaczy, że nie zawsze jest to 16 (kiedy była printf ing linia 7 lub 8 zmienne, to wold odejmowanie 24 lub 28

użyć Manjaro 64 bitowej w Vitrual Maszyna (4 GB RAM) Intel 64-bitowy procesor

+0

Nie zapomniałeś włączyć optymalizacji. Co do ilości odejmowania, która zależy od wymagań wyrównania i czy możesz użyć czerwonej strefy. – Jester

+0

@Jester Włączenie optymalizacji niekoniecznie oznacza, że ​​pominięto wskaźnik ramki. –

+3

Możliwy duplikat [Czym dokładnie jest wskaźnik bazowy i wskaźnik stosu? Do czego oni wskazują?] (Http://stackoverflow.com/questions/1395591/what-is-exactly-the-base-pointer-and-stack-pointer-to-what-do-they-point). IOW jest taki sam jak w kodzie x86_32. – usr2564301

Odpowiedz

9

rbp jest wskaźnik ramki na x86_64. W swojej wygenerowanego kodu, robi zrzut wskaźnika stosu (rsp) tak, że gdy dokonano korekty rsp (tj rezerwowania miejsca na zmienne lokalne lub wartości push ING na do stos), zmienne lokalne i parametry funkcji są nadal dostępne ze stałego przesunięcia od rbp.

Wiele kompilatorów oferuje pominięcie wskaźnika ramki jako opcję optymalizacji; spowoduje to wygenerowanie generowanych zmiennych dostępu do kodu zestawu w odniesieniu do rsp i zwolnienie rbp jako kolejnego rejestru ogólnego przeznaczenia do użycia w funkcjach.

W przypadku GCC, w którym domyślam się, że używasz z AT asemblera AT &, ten przełącznik to . Spróbuj skompilować swój kod za pomocą tego przełącznika i sprawdź, jaki kod assemblera otrzymasz. Prawdopodobnie zauważysz, że gdy uzyskujesz dostęp do wartości w odniesieniu do rsp zamiast rbp, przesunięcie od wskaźnika zmienia się w funkcji.

+1

Dokładnie używa '% rsp' !. Ale to f.e, gdy "movl" pierwsza stała, f.e "movl 10" przesuwa ją do "4 (% rsp)". Dlaczego nie "-4"? I przy okazji, dlaczego nadal odejmuje jakąś wartość od '% rsp'? Nie zrozumiałem tego poprzez komentarze – FrynioS

+0

@FrynioS Compiler alokuje trochę miejsca dla lokalnych wartości na wejściu funkcji. Dlatego odejmuje wartość od% rsp na wejściu. Nie zależy to od tego, czy% rbp jest używany jako wskaźnik ramki. Następnie to miejsce jest używane z dodatnimi przesunięciami w% rsp. Ponadto, jeśli ta funkcja wywoła inną,% rsp będzie wyrównane na 16-bajtowej granicy dla każdego wywołania, tak więc w tym przypadku kompilator odejmie 8 od% rsp przy każdym wprowadzeniu. – Netch

+0

@FrynioS zauważ także, że istnieje przestrzeń 128-bajtowa ("czerwona strefa") przed% rsp, która zachowuje swoją zawartość między wywołaniami funkcji, ale zachowana przez system operacyjny podczas przerwania. Tak więc bardzo tymczasowe wartości (między wywołaniami funkcji) mogą być używane z ujemnymi przesunięciami do% rsp. Nie wszystkie kompilatory wykorzystują to. – Netch

14

Linux wykorzystuje system V ABI dla architektury x86-64 (AMD64); patrz: System V ABI at OSDev Wiki w celu uzyskania szczegółowych informacji.

Oznacza to, że stos rośnie; mniejsze adresy są "wyżej" w stosie. Typowe funkcje C są kompilowane do

 pushq %rbp  ; Save address of previous stack frame 
     movq %rsp, %rbp ; Address of current stack frame 
     subq $16, %rsp ; Reserve 16 bytes for local variables 

     ; ... function ... 

     movq %rbp, %rsp ; \ equivalent to the 
     popq %rbp  ;/'leave' instruction 
     ret 

The ilość pamięci zarezerwowanej dla zmiennych lokalnych jest zawsze wielokrotnością 16 bajtów, aby utrzymać wyrównany stos do 16 bajtów. Jeśli nie ma miejsca na stosie dla zmiennych lokalnych, nie ma żadnej instrukcji.

(Należy zauważyć, że adres zwrotny i poprzedni %rbp popychany na stos są zarówno 8 bajtów, 16 bajtów Łącznie).

Podczas %rbp wskazuje na bieżącej ramce stosu, %rsp wskazuje na górze stos. Ponieważ kompilator zna różnicę między wartościami %rbp i %rsp w dowolnym punkcie tej funkcji, może użyć dowolnego z nich jako podstawy dla zmiennych lokalnych.

Ramka stosu to tylko plac zabaw lokalny: obszar stosu, którego używa aktualna funkcja.

Obecne wersje GCC wyłączają ramkę stosu, gdy używane są optymalizacje. Ma to sens, ponieważ w przypadku programów napisanych w języku C klatki stosu są najbardziej przydatne do debugowania, ale nie wiele więcej. (Można użyć np. -O2 -fno-omit-frame-pointer, aby zachować klatki stosu, jednocześnie umożliwiając optymalizację.)

Mimo że ta sama ABI dotyczy wszystkich plików binarnych, bez względu na język, w którym są napisane, niektóre inne języki wymagają ramek stosu dla "rozwinięcia "(na przykład, aby" wyrzucić wyjątki "do dzwoniącego przodka bieżącej funkcji); tj. "odwijać" ramki stosów, że jedna lub więcej funkcji może zostać przerwanych, a sterowanie przekazane do funkcji nadrzędnej, bez pozostawiania niepotrzebnych rzeczy na stosie.

Kiedy ramki stosu są pomijane - dla GCC - zmienia implementacją funkcji zasadniczo

 subq $8, %rsp ; Re-align stack frame, and 
          ; reserve memory for local variables 

     ; ... function ... 

     addq $8, %rsp 
     ret 

Ponieważ nie ma ramki stosu (%rbp jest używany do innych celów, a jego wartość nie jest wciśnięty do stosu), każde wywołanie funkcji przesuwa tylko adres powrotu do stosu, który jest 8-bajtową ilością, więc musimy odjąć 8 od %rsp, aby zachować wielokrotność 16. (Ogólnie rzecz biorąc, wartość odjęta od i dodana to %rsp jest nieparzystą wielokrotnością liczby 8.)

Parametry funkcji są zwykle przekazywane w rejestrach. Patrz odnośnik ABI na początku tej odpowiedzi o szczegóły, ale w skrócie, integralne typy i wskaźniki są przekazywane w rejestrach %rdi, %rsi, %rdx, %rcx, %r8 i %r9 z argumentów zmiennoprzecinkowych w %xmm0 do %xmm7 rejestrów.

W niektórych przypadkach pojawi się rep ret zamiast rep. Nie mylić: rep ret oznacza dokładnie to samo, co ret; prefiks rep, chociaż zwykle używany w instrukcjach ciągów (powtarzające się instrukcje), nic nie robi po zastosowaniu do instrukcji ret. Po prostu niektóre predyktory gałęzi procesorów AMD nie lubią przeskakiwania do instrukcji ret, a zalecanym rozwiązaniem jest użycie zamiast tego rep ret.

Na koniec pominąłem red zone powyżej górnej części stosu (128 bajtów pod adresami mniejszymi niż %rsp). Dzieje się tak dlatego, że nie przydaje się to w przypadku typowych funkcji: w przypadku normalnej ramki stosu, będziesz chciał, aby lokalny materiał znajdował się w ramce stosu, aby umożliwić debugowanie. W przypadku omit-frame-frame wymagania dotyczące wyrównania stosu oznaczają już, że musimy odjąć 8 od %rsp, więc uwzględnienie pamięci potrzebnej dla zmiennych lokalnych w tym odejmowaniu nic nie kosztuje.

+0

Wow, stary, to mi bardzo pomogło! Dzięki za odpowiedź (szkoda, że ​​zaznaczam inną odpowiedź jako zaakceptowaną: D) – FrynioS

+0

@FrynioS: Bez obaw, wszystko jest dobrze! Uznałem, że jest to bardziej "informacja w tle, która może być dla niektórych użyteczna", lub * rzeczy, które prawdopodobnie chcesz wiedzieć, jeśli zastanawiasz się nad tymi rzeczami *, podczas gdy Govind Parmar zadawał pytania, które podałeś. –

+0

@NominalAnimal proszę poprawić literówkę: poprawną opcją jest '-fomit-frame-pointer'. – Netch