Co to znaczy wyrównać stos?

Byłem programistą wysokiego poziomu, a architektury są dla mnie całkiem nowe, więc postanowiłem przeczytać tutorial o Assembly tutaj:

Http://en.wikibooks.org/wiki/X86_Assembly/Print_Version

Daleko w samouczku, instrukcje jak przekonwertować Hello World! program

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

W odpowiedni kod zestawu podano i wygenerowano:

        .text
LC0:
        .ascii "Hello, world!\12\0"
.globl _main
_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        call    __alloca
        call    ___main
        movl    $LC0, (%esp)
        call    _printf
        movl    $0, %eax
        leave
        ret

Dla jednej z linii,

andl    $-16, %esp

Wyjaśnienie brzmiało:

To kod " i " S ESP z 0xfffff0, wyrównywanie stosu z następnym najniższa 16-bajtowa granica. Na badanie kodu źródłowego Mingw ujawnia, że może to być dla SIMD instrukcje pojawiające się w "_main" rutyny, które działają tylko na wyrównanych adresy. Ponieważ nasza rutyna nie zawiera instrukcje SIMD, ta linia to niepotrzebne.

Nie rozumiem tego punktu. Czy ktos moze mi wyjasnic co to znaczy wyrównac stos z nastepnym 16-bajtem granica i dlaczego jest to wymagane? I w jaki sposób andl to osiąga?
Author: yoozer8, 2010-11-14

6 answers

Załóżmy, że stos wygląda tak po wpisaniu do _main (adres wskaźnika stosu jest tylko przykładem):

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230

Wciśnij %ebp i odjmij 8 od %esp, aby zarezerwować miejsce dla zmiennych lokalnych:

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230
|      %ebp       |
+-----------------+  <--- 0xbfff122c
:    reserved     :
:     space       :
+-----------------+  <--- 0xbfff1224

Teraz, Instrukcja andl zeruje niskie 4 bity %esp, Które mogą je zmniejszyć; w tym konkretnym przykładzie ma to wpływ na rezerwację dodatkowych 4 bajtów:

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230
|      %ebp       |
+-----------------+  <--- 0xbfff122c
:    reserved     :
:     space       :
+ - - - - - - - - +  <--- 0xbfff1224
:   extra space   :
+-----------------+  <--- 0xbfff1220

Chodzi o to, że są jakieś " SIMD "(Single Instrukcja, Multiple Data) Instrukcje (Znane również w x86-land jako " SSE " dla "Streaming SIMD Extensions"), które mogą wykonywać równoległe operacje na wielu słowach w pamięci, ale wymagają, aby te słowa były blokami zaczynającymi się od adresu, który jest wielokrotnością 16 bajtów.

Ogólnie rzecz biorąc, kompilator nie może założyć, że określone przesunięcia z %esp spowodują podanie odpowiedniego adresu (ponieważ stan %esp podczas wprowadzania funkcji zależy od kodu wywołującego). Ale przez celowo wyrównując wskaźnik stosu w ten sposób kompilator wie, że dodanie dowolnej wielokrotności 16 bajtów do wskaźnika stosu spowoduje wyrównanie 16-bajtowego adresu, który jest bezpieczny do użycia z tymi instrukcjami SIMD.

 51
Author: Matthew Slattery,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-13 23:58:06

To nie wydaje się być specyficzne dla stosu, ale wyrównanie w ogóle. Być może pomyśl o pojęciu liczby całkowitej.

Jeśli masz w pamięci elementy o rozmiarze bajtu, jednostek 1, to powiedzmy, że wszystkie są wyrównane. Rzeczy o rozmiarze dwóch bajtów, wtedy liczby całkowite razy 2 zostaną wyrównane, 0, 2, 4, 6, 8, itd. I wielokrotności bez liczby całkowitej, 1, 3, 5, 7 nie będą wyrównane. Pozycje o rozmiarze 4 bajtów, wielokrotności całkowite 0, 4, 8, 12, itd. są wyrównane, 1,2,3,5,6,7, itd. są nie. To samo dotyczy 8, 0,8,16,24 i 16 16,32,48,64, i tak dalej.

Oznacza to, że możesz spojrzeć na adres bazowy elementu i określić, czy jest on wyrównany.

size in bytes, address in the form of 
1, xxxxxxx
2, xxxxxx0
4, xxxxx00
8, xxxx000
16,xxx0000
32,xx00000
64,x000000
and so on

W przypadku kompilatora mieszającego dane z instrukcjami wsegment tekstu dość proste jest wyrównywanie danych w razie potrzeby (cóż, zależy od architektury). Ale stos jest runtime, kompilator nie może normalnie określić, gdzie stos będzie w czasie uruchomienia. Więc w czasie wykonywania, jeśli mieć zmienne lokalne, które muszą być wyrównane, kod musi być programowo dopasowany do stosu.

Załóżmy na przykład, że masz dwa 8 bajtowe elementy na stosie, łącznie 16 bajtów i naprawdę chcesz, aby były wyrównane (na granicach 8 bajtów). Po wprowadzeniu funkcja odjęłaby 16 od wskaźnika stosu, jak zwykle, aby zrobić miejsce dla tych dwóch elementów. Ale aby je wyrównać, musiałoby być więcej kodu. Jeśli chcemy te dwa 8-bajtowe elementy wyrównane do 8-bajtowych granic i stosu wskaźnik po odjęciu 16 był 0xFF82, cóż dolne 3 bity nie są 0 więc nie jest wyrównany. Dolne trzy bity to 0b010. W sensie ogólnym chcemy odjąć 2 od 0xff82, aby otrzymać 0xFF80. Jak ustalimy, że jest to 2 będzie przez anding z 0b111 (0x7) i odjęcie tej kwoty. Oznacza to, że operacje alu i i odjąć. Ale możemy skorzystać ze skrótu, jeśli my i z jedynką wartość dopełniacza 0x7 (~0X7 = 0xFFFF...FFF8) otrzymujemy 0xFF80 używając jednej operacji alu (tak długo jak kompilator i procesor mają do tego jeden sposób kodowania, jeśli nie, może to kosztować więcej niż and i odjąć).

Wygląda na to, że to właśnie robił Twój program. Anding z -16 jest taki sam jak anding z 0xFFFF....FFF0, co daje adres wyrównany na granicy 16 bajtów.

Więc aby to zakończyć, jeśli masz coś w rodzaju typowego wskaźnika stosu, który działa na dół pamięci z wyższych adresów do niższych adresów, To chcesz

 
sp = sp & (~(n-1))

Gdzie n to liczba bajtów do wyrównania(musi być potęgą, ale jest to w porządku większość wyrównań zwykle obejmuje potęgi dwóch). Jeśli say zrobił malloc (adresy rosną z Niskiego na wysoki) i chcesz wyrównać adres czegoś (pamiętaj, aby malloc więcej niż potrzebujesz o co najmniej Rozmiar wyrównania) to

if(ptr&(~(n-)) { ptr = (ptr+n)&(~(n-1)); }

Lub jeśli chcesz po prostu wziąć if tam i wykonać Dodaj i maska za każdym razem.

Wiele/większość architektur innych niż x86 ma reguły i wymagania wyrównania. x86 jest zbyt elastyczny, jeśli chodzi o zestaw instrukcji, ale jeśli chodzi o wykonanie, możesz / zapłacisz karę za niepodpisany dostęp na x86, więc nawet jeśli możesz to zrobić, powinieneś starać się pozostać w zgodzie z każdą inną architekturą. Być może właśnie to robił ten kod.

 15
Author: old_timer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-14 06:07:11

Ma to związek z wyrównaniem bajtów . Niektóre architektury wymagają, aby adresy używane do określonego zestawu operacji były wyrównane do określonych granic bitów.

Oznacza to, że jeśli chcesz na przykład wyrównać 64-bitowy wskaźnik, możesz koncepcyjnie podzielić całą adresowalną pamięć na 64-bitowe kawałki, zaczynając od zera. Adres byłby "wyrównany", gdyby pasował dokładnie do jednego z tych kawałków, a nie wyrównany, gdyby wziął część jednego kawałka i część kolejny.

Istotną cechą wyrównania bajtów (zakładając, że liczba jest potęgą 2) jest to, że najmniej znaczącex bity adresu są zawsze zerowe. Pozwala to procesorowi reprezentować więcej adresów z mniejszą liczbą bitów, po prostu nie używając dolnych x bitów.

 7
Author: tylerl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-14 00:56:04

Wyobraź sobie ten "rysunek"

addresses
 xxx0123456789abcdef01234567 ...
    [------][------][------] ...
registers

Wartości pod adresami wielokrotność 8 "wsuwanych" łatwo do (64-bitowych) rejestrów

addresses
         56789abc ...
    [------][------][------] ...
registers

oczywiście rejestruje "spacer" w krokach po 8 bajtów

Teraz jeśli chcesz umieścić wartość pod adresem xxx5 w rejestrze jest znacznie trudniejsze: -)


Edit andl -16

-16 to 11111111111111111111111111111111110000 w binarnym

Gdy " i " cokolwiek z -16 otrzymasz wartość z ostatnich 4 bitów ustawionych na 0 ... lub multitple z 16.

 5
Author: pmg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-14 00:08:53

Powinno być tylko pod parzystymi adresami, a nie nieparzystymi, ponieważ istnieje deficyt wydajności.

 3
Author: AndreKR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-13 23:32:23

Gdy procesor ładuje dane z pamięci do rejestru, musi uzyskać dostęp przez adres bazowy i rozmiar. Na przykład pobierze 4 bajty z adresu 10100100. Zauważ, że na końcu tego przykładu znajdują się dwa zera. Dzieje się tak dlatego, że cztery bajty są przechowywane tak, że 101001 wiodących bitów są znaczące. (Procesor naprawdę uzyskuje do nich dostęp poprzez" nie obchodzi", pobierając 101001XX.)

Więc wyrównanie czegoś w pamięci oznacza zmianę kolejności danych (zwykle poprzez padding) aby adres żądanego elementu miał wystarczającą ilość zero bajtów. Kontynuując powyższy przykład, nie możemy pobrać 4 bajtów z 10100101, ponieważ dwa ostatnie bity nie są równe zeru; to spowodowałoby błąd magistrali. Musimy więc zwiększyć adres do 10101000(i zmarnować trzy lokalizacje adresów w procesie).

Kompilator robi to za ciebie automatycznie i jest reprezentowany w kodzie złożenia.

Zauważ, że jest to oczywiste jako optymalizacja w C / C++:

struct first {
    char letter1;
    int number;
    char letter2;
};

struct second {
    int number;
    char letter1;
    char letter2;
};

int main ()
{
    cout << "Size of first: " << sizeof(first) << endl;
    cout << "Size of second: " << sizeof(second) << endl;
    return 0;
}

Wyjście is

Size of first: 12
Size of second: 8

Przearanżowanie dwóch char oznacza, że int będzie wyrównane prawidłowo, więc kompilator nie musi bump adres bazowy przez padding. Dlatego rozmiar drugiego jest mniejszy.

 3
Author: chrisaycock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-13 23:58:27