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:
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óbTo 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.
andl
to osiąga? 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.
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.
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.
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.
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.
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.
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