Adresowanie
-----------
Użycie adresów pamięci w instrukcjach asemblera oznacza się nawiasami
kwadratowymi ([adres]). Oznacza to, że wartość ma być wzięta spod tego
adresu. Np.
mov eax, [0x1234]

Należy zwrócić uwagę, że w przypadku instrukcji CALL, wywołanie
"call 0x1234" oznacza skok do funkcji pod adres 0x1234 (czyli wykonywanie
kodu począwszy od adresu 0x1234), a wywołanie "call [0x1234]" oznacza
rozpoczęcie wykonywania od adresu zapisanego w komórce pamięci 0x1234.

W niektórych instrukcjach niemożliwe jest określenie na podstawie argumentów
jaki ma być rozmiar argumentu (8 bitów, 16 bitów czy 32 bity). Wówczas
należy przed adresem podać słowo kluczowe byte, word albo dword.
Np. instrukcja:
mov  [0x1234], 1
powoduje błąd:
error: operation size not specified
ponieważ nie można określić rozmiaru argumentu.
Należy dodać przedrostek rozmiaru w ten sposób:
mov dword [0x1234], 1

Przy podawaniu adresu można też użyć rejestru, np.
mov [eax], 1
Można też przeskalować i dodać zawartość innego rejestru, w skrajnym
przypadku wyrażenie może mieć postać:
rejestr32 + skala * rejestr_indeksowy + wyrażenie_stałe
np.
mov dword [eax + 4 * esi + 10], 1
Przydaje się to np. przy adresowaniu tablic.
Dokładniejsze informacje o możliwych trybach adresowania znajdują się na
stronie http://www.mimuw.edu.pl/~marpe/arch/adresowanie.html i w liście
instrukcji asemblera.

Instrukcja LEA (load effective address) pozwala wyliczyć adres argumentu na
podstawie rejestru, skali oraz przesunięcia i zapisać je w rejestrze do
późniejszego użycia, np.
lea eax, [eax + 4 * esi + 10]
mov byte [eax], 1
mov byte [eax + 1], 2

Stos
----
Stos służy do przechowywania tymczasowych wartości, np. przy wyliczaniu
wyrażeń jeśli nie mieszczą się w rejestrach.

PUSH - odłożenie wartości na stos
POP - zdjęcie wartości ze stosu i zapisanie we wskazanym miejscu
PUSHA/POPA - odłożenie/zdjęcie ze stosu rejestrów ogólnych
PUSHF/POPF - odłożenie/zdjęcie ze stosu rejestru znaczników

Żeby zdjąć wartość ze stosu bez zapisywania gdziekolwiek, wystarczy dodać
odpowiednią liczbę do rejestru ESP. 
Należy pamiętać, że w architekturze i386 stos rośnie w dół, więc dodanie
wartości do rejestru powoduje zdjęcie ze stosu, a odjęcie odkłada na stos.

Wywoływanie funkcji i zmienne lokalne
-------------------------------------
Jest wiele konwencji wywoływania funkcji. Konwencja określa:
- czy argumenty są odkładane na stosie czy przekazywane w rejestrach
- w jakiej kolejności są odkładane (od prawej do lewej czy odwrotnie)
- czy rejestry są odtwarzane przy wyjściu z funkcji i które
- kto odpowiada za zdjęcie argumentów funkcji ze stosu (wołający czy wołany)
- jak jest przekazywany wynik funkcji

Istnieje wiele konwencji, w zależności od architektury, systemu operacyjnego
języka programowania i kompilatora. Tutaj omówiona zostanie konwencja stosowana 
na Linuksie w kompilatorze GCC dla architektury i386 i języka C - konwencję
cdecl. Więcej informacji o konwencjach można znaleźć na stronie
http://en.wikipedia.org/wiki/X86_calling_conventions

Konwencja:
- wszystkie argumenty są odkładane na stosie
- argumenty są odkładane w kolejności od prawej do lewej
- rejestry EAX, ECX, EDX nie muszą być odtworzone przy wyjściu z funkcji,
pozostałe muszą być zachowane, jeśli są modyfikowane.
- za zdjęcie argumentów ze stosu odpowiada wołający, bo tylko on wie, ile
odłożył argumentów
- wynik jest zwracany w rejestrze EAX

Kod wywołującego:
; odłożenie argumentów, od prawej do lewej
push argn
...
push arg1
; wywołanie funkcji
call nazwa_funkcji
; zdjęcie argumentów - tyle bajtów jaki był rozmiar argumentów
add esp, rozmiar_argumentów

Należy pamiętać, że po powrocie z wywołania funkcji rejestry
EAX, ECX i EDX mogą zawierać śmieci z wywołania funkcji (nie są odtwarzane),
więc wszelka zawartość przechowywana w nich będzie stracona. Jeśli przed
wywołaniem zawierają one znaczące wartości, które chcemy zachować, należy je
przechować na stosie i odtworzyć po powrocie z funkcji.

Kod wywoływanego (w funkcji):
; protokół wejściowy - odłożenie EBP do odtworzenia przy wyjściu
; i wpisanie do EBP aktualnego wskaźnika stosu
push ebp
mov ebp, esp

Od tej chwili można adresować argumenty poprzez:
[ebp + 8 + przesunięcie_argumentu]
+8, ponieważ na stosie został odłożony adres powrotu (4 bajty) i zawartość
EBP (4 bajty).
Przesunięcie argumentu liczy się dodając rozmiary argumentów. Pierwszy
argument (najbardziej lewy w sygnaturze funkcji) ma przesunięcie 0, kolejny
ma przesunięcie większe o rozmiar poprzedniego argumentu.

Zmienne lokalne tworzy się poprzez zostawienie odpowiedniej wielkości
pustego miejsca na stosie za pomocą instrukcji:
sub esp, rozmiar_zmiennych_lokalnych
Adresuje się je względem rejestru EBP, tylko trzeba odjąć odpowiednie
przesunięcie zmiennej, np.
mov dword [ebp - 4], 5
wpisuje 5 do pierwszej zmiennej lokalnej.

Następnie należy zachować rejestry, które będziemy modyfikować, o ile to
potrzebne.

Tak więc ramka stosu w funkcji ma postać:
parametrN
...
parametr2
parametr1
poprzednie EIP
poprzednie EBP
zmienna_lokalna1
zmienna_lokalna2
...
zmienna_lokalnaN
zachowany_rejestr1
...
zachowany_rejestrN

Przy wychodzeniu z funkcji należy wykonać protokół wyjścia.
Najpierw należy odtworzyć zachowane rejestry (o ile były zachowywane).
A potem odtworzyć EBP i ESP za pomocą kodu:
mov esp, ebp            ; protokol wyjscia
pop ebp
I wyjść z funkcji:
ret

Zdjęciem argumentów zajmuje się wywołujący.

Zadania:
- Napisać funkcję, która bierze 3 argumenty i wylicza wyrażenie 
(arg1 + arg2) mod arg3
- Napisać rekurencyjną funkcję, która bierze jeden argument i wylicza jego
silnię.
- Napisać funkcję, która wywołuje funkcję silnia 5 razy pod rząd bez
umieszczania argumentów za każdym razem na stosie.
- Napisać funkcję, która wywołuje funkcję silnia 5 razy i sumuje zwrócone
wyniki na zmiennej lokalnej.
- Napisać funkcję o zmiennej liczbie argumentów, której pierwszy argument to
liczba parametrów, pozostałych jest tyle ile wskazane w pierwszym
argumencie i funkcja zwraca sumę tych argumentów.
