Co będzie potrzebne do programowania w assemblerze?

Potrzebny nam kompilator np MASM od Microsoftu, edytor tekstu i debugger. Jednak wygodnie jest korzystać z rozbudowanego środowiska VisualStudio 2008 które zawiera wszystko a nawet więcej niż potrzebujemy na nasze skromne potrzeby.

Teraz wypada napisać dlaczego Assembler? Wiele osób pewnie już słyszało, że programy pisane w języku Assembler słyną z tego, ze są małe i szybkie. Są takie, gdyż mają one taki kod, jaki stworzymy, a nie taki jak kompilator „przetłumaczy”, nie posiadają również zbędnych bibliotek.
Programując w Assemblerze pozna się jak działa procesor, jak program jest zbudowany i inne rzeczy o których nie ma pojęcia programista np. takiej Javy. Dlatego każdy szanujący się programista powinien poznać przynajmniej podstawy Assemblera.

I. Systemy liczbowe

Do programowania w Assemblerze przydatna będzie na pewno wiedza o dwóch dodatkowych systemach liczbowych (prócz dziesiętnego) takich jak: dwójkowy (binarny), szesnastkowy (heksadecymalny). Opiszę krótko te dwa systemy:

  • binarny – podstawą systemu jest są dwie cyfry: 0 i 1. W tym systemie działa komputer, w tym systemie jest zapisywany kod programu. Liczby w tym systemie przyjęło się zapisywać z literą b na końcu (np. 10001011b).
  • heksadecymalny – podstawą systemu są cyfry od 0 do 9 i litery od A do F (w sumie szesnaście znaków). Dla człowieka o wiele bardziej wygodny od systemu binarnego. Liczby w tym systemie przyjęło zapisywać się z literą h na końcu (np. 23E0B3h).

To wszystko na temat systemów, jak ktoś chce może doczytać coś więcej o nich, a także nauczyć się je ręcznie przeliczać. Jednak programując szybciej i wygodniej jest przeliczać te systemy za pomocą kalkulatora.

II. Jednostki informacji pamięci komputerowej

Bit – najmniejsza część danych. Przyjmuje wartość 0 lub 1.

Półbajt (ang. nibble) – są to 4 bity. Warto znać to określenie (nibble), bo można je spotkać w artykułach w języku angielskim.
Bajt – jest to 8 bitów. Jego maksymalna wartość to 255d.
Word (Słowo) – 2 bajty, czyli 16 bitów. Maksymalna wartość to 0FFFFh (lub 65535d).
Double-Word (Podwójne słowo) – Dwa słowa, czyli 4 bajty (32 bity). Maksymalna wartość to 0FFFFFFFFh (lub 4294967295d).
Quad-Word (Poczwórne słowo) – Cztery słowa, czyli 8 bajtów (64 bity).
Kilobajt – 1024 bajty.
Megabajt – 1024 kilobajty,  1048576 bajtów.
Rozmiary i zakresy wartości podstawowych zmiennych w C++

Nazwa typu Rozmiar w bajtach Zakres wartości
char 1 -128 ÷127
int 2

4 w trybie 32 bit

-32768 ÷ 32767

-2147483648 ÷ 2147483647

short 2 -32768 ÷ 32767
long 4 -2147483648 ÷ 2147483647
unsigned char 1 0 ÷ 255
unsigned 2

4 w trybie 32 bit

0 ÷ 65535

0 ÷ 4294967295

unsigned short 2 0 ÷ 65535
unsigned long 4 0 ÷ 4294967295
enum 2 -32768 ÷ 32767
float 4 1,2E-38 ÷ 3,4E+38

dokładność 7 cyfr

double 8 2,2E-308 ÷ 1,8E+308

dokładność 15 cyfr

long double 10 3,4E-4932 ÷ 1,2E+4932

dokładność 19 cyfr

 

III. Rejestry procesora

Rejestry są to komórki pamięci wewnątrz procesora służące mu do wykonywania różnych operacji. Warto wspomnieć, że operacje na rejestrach są o wiele szybsze niż na zmiennych w pamięci. Rejestry ogólnego przeznaczenia są cztery (EAX, EBX, ECX i EDX). Mają one po 32 bity (4 bajty). Każdy z nich dzieli się na dwie części po 16 bitów (części te nazywają się Starsze słowo (HIGH-WORD) i Młodsze słowo (LOW-WORD)). Weźmy na przykładzie rejestru EAX: Młodsze słowo to  rejestr AX, który dzieli się znów na dwa rejestry 8 bitowe: AH i AL. Warto wspomnieć też, że te rejestry możemy używać do czego chcemy, ale każdy z nich ma swoje specjalne przeznaczenie:

  • EAX (Akumulator) – do obliczeń arytmetycznych/logicznych
  • EBX (Rejestr bazowy) – do adresowania
  • ECX (Rejestr licznika) – np. jako licznik w pętlach
  • EDX (Rejestr danych) – np. do przechowywania adresów zmiennych

Dla lepszego zobrazowania tego załączam obrazek:

WAŻNE: Bity numerujemy od zera, czyli najmłodszy jest bit zerowy.

Lecz to nie wszystkie rejestry. Są jeszcze dwa rejestry indeksowe (EDI (rejestr przeznaczenia) i ESI (rejestr źródła)), które dzielą się na DI i SI. Używa się ich do operacji na łańcuchach (np. na tekście). ESI przechowuje źródło (ang. Source), a EDI miejsce docelowe (ang. Destination). EBP (wskaźnik bazowy) i ESP (wskaźnik stosu) to natomiast rejestry wskaźnikowe pierwszy z nich służy do adresowania, drugi przechowuje wskaźnik wierzchołka stosu.

Ważne: Jeżeli modyfikujemy zawartość rejestrów ESI, EDI, EBP lub ESP musimy je przywrócić zanim wrócimy do Windows.

Podobnie jak wyżej załączam ilustracje:

Pozostałe rejestry to:

– rejestry segmentowe (16 bitowe): CS (Code Segment), DS (Data Segment), ES (Extra Segment), SS (Stack Segment), FS, GS,

– rejestr flag: EFLAGS (flagi, inaczej znaczniki będą nas interesować, gdy będziemy debugować swoje programy),

  • CF (Carry Flag) – znacznik przeniesienia. Flaga zostaje ustawiona, gdy podczas działania nastąpiło przeniesienie z bitu najbardziej znaczącego poza dostępny zakres zapisu. W przeciwnym wypadku znacznik ma wartość zero.
  • PF (Parity Flag) – znacznik parzystości. Ustawiany jest, gdy w wyniku wykonania działania liczba bitów o wartości 1 w mniej znaczącym bajcie jest parzysta.
  • AF (Auxillary Flag) – znacznik przeniesienia pomocniczego. Ustawiany, gdy nastąpiło przeniesienie z bitu numer 3 na bit numer 4 lub pożyczenie z bitu numer 4.
  • ZF (Zero Flag) – znacznik zerowy. Przyjmuje wartość 1, gdy wynik działania ostatnio wykonanego dał w wyniku zero.
  • SF (Sign Flag) – znacznik zmiany znaku. Ustawiany jest, gdy najbardziej znaczący bit (inaczej: najstarszy bit, nazywany bitem znaku) w otrzymanym wyniku równa się jeden.
  • OF (Overflow Flag) – znacznik nadmiaru/przepełnienia. Ustawiany jest, gdy w danym działaniu nastąpiło przeniesienie na najstarszy bit, lub nastąpiło pożyczenie z tego bitu.
  • TF (Trap Flag) – ustawia się przy debugowaniu. Procesor wywołuje wtedy tylko jedną instrukcję, a następnie wywołuje przerwanie, co pozwala dołączonego debuggerowi zbadać program.
  • IF (Interrupt Flag) – zezwolenie na przerwanie.
  • DF (Direction Flag) – znacznik kierunku.
  • IOPL – I/O privilege level (286+ only). Informuje o priorytecie dostępu do portów Wejścia-Wyjścia. CPL (Current Priority Level) musi być większe lub równe od tego znacznika, aby program miał dostęp do portów Wejścia-Wyjścia.
  • NT – Nested task flag (286+ only).
  • RF – Resume flag (386+ only). Znacznik wznowienia.
  • VM – Virtual 8086 mode flag (386+ only). Włączenie trybu wirtualnego.
  • AC – Alignment check (486SX+ only).
  • VIF – Virtual interrupt flag (Pentium+).
  • VIP – Virtual interrupt pending (Pentium+).
  • ID – Identification (Pentium+). Odczyt rodzaju procesora.

Umiejscowienie znaczników w rejestrze EFLAGS:

– rejestry koprocesora: ST0…ST7, do operacji na liczbach zmiennoprzecinkowych (zobacz artykuł: Koprocesor),

– EIP – rejestr zawierający adres aktualnie wykonywanej instrukcji.

To już wszystkie rejestry, które by nas interesowały.

 

Wykonując działania arytmetyczne należy pamiętać o ograniczeniach rejestrów

W rejestrach 8-bitowych można zapisać tylko jeden bajt, a więc liczby od 0 do 255,
16-bitowychod 0 do 65 535,
w 32-bitowychod 0 do 4 294 967 295.

 

IV. Stos

Jest to część pamięci, gdzie można przechowywać różne rzeczy, aby później móc z nich skorzystać. Jak sama nazwa wskazuje wartości są układane tam jak na stosie. Ostatnia odłożona wartość będzie zdjęta jako pierwsza. Możesz sobie to wyobrazić jak pudełko do którego kładziesz kartki papieru. Ostatnia kartka jest wyciągana jako pierwsza. Instrukcja push odkłada wartość na stos, natomiast pop zdejmuje i zapisuje do rejestru/pamięci.

V. Podstawowe instrukcje

Zacznijmy od najczęściej spotykanej instrukcji – MOV.

MOV (kopiuj)

Składnia: MOV destination, source

Chyba najczęściej spotykana instrukcja. Kopiuje wartość z source do destination. Z czego pierwsza wartość  pozostaje niezmieniona. Przykład:

1
2
3
mov eax, 25 ;eax przyjmuje wartość 25
mov ecx, eax ;skopiuj do ecx wartość z eax
mov zmienna, eax ;skopiuj do zmiennej wartość z eax

Ważne: dlaczego nie ma „mov zmienna, zmienna”? Jest tak, gdyż procesor nie potrafi wykonać czegoś takiego. Musimy posłużyć się rejestrem pomocniczym (skopiować najpierw wartość do rejestru, a potem do zmiennej) lub odłożyć wartość na stos (instrukcja push), a następnie instrukcją pop zdjąć ją ze stosu i umiejscowić w drugiej zmiennej.

Instrukcje arytmetyczne:
ADD (Dodawanie) Składnia: ADD operand1, operand2 Instrukcja ADD dodaje wartość do rejestru lub komórki pamięci.

Przykładowe użycie:

1
2
3
add eax, 124 ;dodaje 124 do wartości w rejestrze eax
add ecx, ebx ;dodaje wartość z ebx do wartości w ecx
add zmienna, 25 ;dodaje 25 do wartości w zmiennej "zmienna"

SUB (odejmowanie)

Składnia: SUB operand1, operand2

Odejmuje wartość drugiego operandu od pierwszego i wynik zapisuje w pierwszym.

MUL (mnożenie)

Składnia: MUL operand1

Mnoży rejestr eax (lub ax, lub al) przez podaną wartość. Mnoży liczby bez znaku.

IMUL (mnożenie ze znakiem)

Składnia: IMUL wartość
IMUL wynik, wartość, wartość
IMUL wynik, wartość

Mnoży wartość z eax z podaną wartością (IMUL wartość), lub mnoży dwie wartości i wynik zachowuje w pierwszym operandzie (IMUL wynik, wartość, wartość), lub mnoży rejestr z wartością (IMUL wynik, wartość).

DIV (dzielenie bez znaku)

Składnia: DIV dzielnik

Instrukcja ta dzieli wartość w rejestrze eax przez wartość podaną jako dzielnik. Dzielona jest zawsze wartość w eax, wynik też będzie w eax. Reszta z dzielenia natomiast w edx.

1
2
3
mov eax, 32 ;eax przyjmuje wartość 32
mov ecx, 2 ;ecx przyjmuje wartość 2
div ecx ;podziel eax przez ecx

IDIV (dzielenie ze znakiem)

Składnia: IDIV dzielnik

Działa podobnie jak instrukcja div, tylko, że dzieli ze znakiem.

Inkrementacja i dekrementacja

INC (inkrementacja)

Składnia: INC rejestr lub zmienna

Działa odwrotnie do instrukcji DEC, zwiększa wartość rejestru lub zmiennej o 1.

DEC (dekrementacja)

Składnia: DEC rejestr lub zmienna

Bardzo prosta instrukcja. Zmniejsza podaną wartość o 1 (zmienna=zmienna-1). Przykład:

1
2
dec eax ;zmniejsza wartość w eax o 1
dec [zmienna] ;zmniejsza wartość zmiennej o 1

Niżej będziemy mieć instrukcje logiczne i instrukcje przesunięć. Będziemy ich używać, gdy na przykład będziemy chcieli wydobyć jakąś wartość z części rejestru. Wtedy wyzerujemy na przykład wszystkie bity, czy poprzesuwamy je tak, aby pozostała nam tylko interesująca nas wartość.

Instrukcje logiczne:

AND (Koniunkcja)

Składnia: and operand1, operand2

1 and 1 = 1
1 and 0 = 0
0 and 1 = 0
0 and 0 = 0

Wykonamy dla przykładu koniunkcje dla dwóch liczb binarnych:

11101010100 and 01010110000 = 01000010000

Przy ręcznym liczeniu najwygodniej napisać sobie je jedna pod drugą i sprawdzać wg. tabelki poszczególne bity.

OR (Alternatywa)

Składnia: or operand1, operand2

1 or 1 = 1
1 or 0 = 1
0 or 1 = 1
0 or 0 = 0

Dla przykładu, mamy dwie liczby binarne:

10100011100 or 01110010010 = 11110011110

Czyli bierzemy po kolei dwa odpowiadające sobie bity i sprawdzamy wg. tabelki jaki będzie dla nich wynik.

XOR (Alternatywa wykluczająca)

Składnia: xor operand1, operand2

1 xor 1 = 0
1 xor 0 = 1
0 xor 1 = 1
0 xor 0 = 0

Będziemy często używać tej instrukcji do czyszczenia zawartości jakiegoś rejestru (gdyż wartość xor-owana z tą samą wartością daje zero).

NOT (Zaprzeczenie)

Składnia: not operand1

NOT 1 = 0
NOT 0 = 1

Krótko mówiąc odwraca wszystkie bity w rejestrze (zamiast 1 daje 0, zamiast 0 daje 1).

Instrukcje przesunięć bitowych

SAL/SHL (Przesunięcie arytmetyczne/logiczne w lewo)

Składnia: SAL operand1, operand2
SHL operand1, operand2

Przesuwa w lewo bity operandu pierwszego o podaną ilość bitów w operandzie drugim. Starszy przesunięty bit, zostaje zachowany w fladze CF (Carry Flag), a młodsze bity są wypełniane zerami.

Przykład. Przesuniemy wartość w eax o 1 bit w lewo.

1
2
mov eax, 1 ;do eax dajemy wartość 1
shl eax, 1 ;eax będzie równy 2 (10 binarnie) ;gdyż jedynka się przemieściła, ;a na jej miejscu mamy zero. ;co daje wartość 10b (2h).

Obraz pokazujący jak przesuwane są bity (SAL/SHL).

SAR/SHR (Przesunięcie arytmetyczne/logiczne w prawo)

Składnia: SAR operand1, operand2
SHR operand1, operand2

Instrukcja SAR przesuwa w prawo bity operandu pierwszego o podaną ilość bitów w operandzie drugim. Najmłodszy przesunięty bit, zostaje zachowany w fladze CF, a najstarszy bit zostaje zachowany. Instrukcja SHR działa podobnie, różni się tylko tym, że zeruje najstarszy bit (nazywany również bitem znaku).

Przykład. Przesuniemy wartość w eax o 1 bit w prawo.

1
2
mov eax, 1 ;do eax idzie wartość 1
shr eax, 1 ;eax będzie równy 0, ;flaga CF zostanie ustawiona

Obraz pokazujący jak przesuwane są bity (SAR).

Obraz pokazujący jak przesuwane są bity (SHR).

ROL/RCL (Rotacja w lewo bez użycia flagi CF/z użyciem flagi CF)

Składnia: ROL operand1, operand2
RCL operand1, operand2

Pierwsza instrukcja przesuwa logicznie w lewo operand1 o ilość bitów podaną w operandzie drugim. Nie używa flagi CF, więc najmłodszy bit przyjmuje wartość najstarszego bitu. Druga instrukcja robi tą samą czynność, ale przy użyciu flagi CF, gdzie najmłodszy bit przyjmuje wartość, która była w CF, a znacznik CF przyjmuje wartość najstarszego bitu.

Schemat działania instrukcji ROL:

Schemat działania instrukcji RCL:

ROR/RCR (Rotacja w prawo bez użycia flagi CF/z użyciem flagi CF)

Składnia: ROR operand1, operand2
RCR operand1, operand2

Pierwsza instrukcja przesuwa logicznie w prawo operand1 o ilość bitów podaną w operandzie drugim. Nie używa flagi CF, więc najstarszy bit przyjmuje wartość najmłodszego bitu. Druga instrukcja robi tą samą czynność, ale przy użyciu flagi CF, gdzie najstarszy bit przyjmuje wartość, która była w CF, a znacznik CF przyjmuje wartość najmłodszego bitu.

Schemat działania instrukcji ROR:

Schemat działania instrukcji RCR:

SHLD/SHRD (Przesunięcie logiczne w lewo/w prawo podwójnego rejestru)

Składnia: SHLD operand1, operand2, operand3
SHRD operand1, operand2, operand3

Pierwsza instrukcja (SHLD) przesuwa w lewo bity w operandzie pierwszym o ilość bitów podaną w operandzie trzecim. Nastarsze bity operandu pierwszego zostają wyrzucone (flaga CF przyjmuje wartość ostatniego wyrzuconego bitu), a najmłodsze bity przyjmują wartości najstarszych bitów z drugiego operandu. Ale uwaga! Bity operandu drugiego pozostają niezmienione. SHRD natomiast przesuwa bity operandu pierwszego o ilość bitów podaną w operandzie trzecim. Najmłodsze bity operandu pierwszego zostają wyrzucone, a najstarsze bity przyjmują wartość najmłodszych bitów z operandu drugiego. Bity operandu drugiego pozostają niezmienione.

Przykład dla instrukcji SHLD:

1
2
mov ecx, 0 ;zerujemy ecx mov eax, 0FFFFFFFFh ;do eax wartość 0FFFFFFFFh
shld ecx, eax, 2 ;eax będzie równy 0FFFFFFFCh, gdyż jego ;dwa najmłodsze bity, zostały zastąpione ;przez dwa najstarsze bity rejestru ecx, ;które miały wartość zero.

Obraz pokazujący jak przesuwają się bity (SHLD):

Przykład dla instrukcji SHRD:

1
2
mov ecx, 0 ;zerujemy ecx mov eax, 0FFFFFFFFh ;do eax wartość 0FFFFFFFFh
shrd ecx, eax, 2 ;eax będzie równy 3FFFFFFFh, gdyż ;dwa jego najstarsze bity zostały ;zastąpione dwoma najmłodszymi bitami z ;rejestru ecx, które miały wartość zero.

Obraz pokazujący jak przesuwają się bity (SHRD):

Instrukcja porównania i instrukcje skoku

CMP (porównanie)

Składnia: CMP wartość1, wartość2

Przykładowe użycie:

1
2
cmp eax, 1 ;czy eax jest równe 1?
cmp eax, ebx ;czy eax jest równe ebx?

Po tej instrukcji używa się instrukcji skoku, która wykona się lub nie, zależnie od wyniku porównania. O instrukcji skoku będzie dalej. JMP (skok do..) Składnia: JMP nazwa_etykiety Skoków będziemy używać po instrukcji porównania. Najlepiej wytłumaczyć to na przykładzie:

1
2
3
4
5
6
7
mov eax, 1 ;eax przyjmuje wartość 1
mov ecx, 2 ;ecx przyjmuje wartość 2
add eax, ecx ;dodaj ecx do eax (eax będzie równy 3)
cmp eax, 3 ;czy eax jest równy 3? je etykieta ;jeżeli tak to skocz do etykiety, jeżeli nie wykonuj dalej instrukcje (...)
;tutaj jakieś instrukcje (...)
etykieta:
;tutaj jakieś instrukcje

Skoków są różne rodzaje, oto kilka podstawowych:

  • JE (jump if equal) – wykonaj skok, jeżeli równe
  • JNE (jump if not equal) – wykonaj skok, jeżeli nierówne
  • JG (jump if greater) – wykonaj skok, jeżeli większe
  • JGE (jump if greater or equal) – wykonaj skok, jeżeli większe lub równe
  • JL (jump if less) – wykonaj skok, jeżeli mniejsze
  • JLE (jump if less or equal) – wykonaj skok, jeżeli mniejsze lub równe.
  • JC – wykonaj skok, jeżeli Carry Flag równe 1;
  • JO – wykonaj skok, jeżeli Overflow Flag równe 1;
  • JP – wykonaj skok, jeżeli Parity Flag równe 1;
  • JS – wykonaj skok, jeżeli Overflow Flag równe 1;
  • JZ – wykonaj skok, jeżeli Overflow Flag równe 1;

 

Instrukcje dotyczące stosu

Instrukcje dotyczące stosu

POP (zdejmij ze stosu)

Składnia: POP rejestr/zmienna

Zdejmuje wartość ze stosu i zapisuje ją w rejestrze lub zmiennej.

PUSH (odłóż na stos)

Składnia: PUSH wartość/rejestr/zmienna

Odkłada wartość na stos. Jako operand możemy podać konkretną wartość,  rejestr lub zmienną.

Przykład użycia stosu:

1
2
3
4
5
6
7
push    ebp         ; zapisanie aktualnej wartości ebp na stosie
mov        ebp, esp    ; zapisanie ESP

; przykładowe pobranie argumentów
mov eax, dword ptr [ebp + 4] ; +4, bo +0 to adres powrotu

pop ebp   ; przywrócenie pierwotnego EBP

Instrukcje związane z procedurami

CALL (wywołanie funkcji/podprogramu)

Składnia: CALL nazwa_funkcji

Służy do wywołania jakieś funkcji. Przykład użycia tej instrukcji to:

1
push 0 call ExitProcess

Czyli jak widzimy – odkładamy na stos wartość zero i wywołujemy funkcję, która zakończy program. Przed instrukcją call odkładamy właśnie argumenty funkcji na stos za pomocą znanej już nam instrukcji push. Pamiętajmy, żeby odkładać argumenty w odpowiedniej kolejności tzn. jak funkcje przedstawia się tak: JakasFunkcja(argument1, argument2, argument3) to wywołanie jej będzie następujące (zaczynamy od końcowego argumentu):

push argument3 push argument2 push argument1 call JakasFunkcja

RET (powrót)

Składnia: RET

Wychodzi z danej funkcji/procedury i wraca do miejsca jej wywołania. Będziemy używać tej instrukcji na końcu procedur.

Instrukcje związane z pętlami

LOOP (pętla)

Składnia: LOOP Etykieta

Przykładowe użycie:

1
2
3
4
mov cx,10
etykieta:
;kod powtórzony 10 razy
loop etykieta

co robi powyższy kod zaledwie dwie rzeczy: Po pierwsze – zmniejsza wartość CX o jeden (DEC CX), po drugie – jeśli CX jest większe od 0 powoduje bezwarunkowy przeskok do „etykieta”.

LOOP ETYKIETA jest skrótem poniższych komend:

1
2
3
DEC CX
CMP CX,0
JNE ETYKIETA

Nie trzeba tu filozofa by stwierdzić, że LOOP umożliwia tylko budowę pętli typu „downto” czy – jak w basicu – „step -1” – a po ludzku pętli, w której licznik maleje a nie rośnie.

Wyróżniamy różne typy pętli w zależności od typu instrukcji warunkowej:

LOOPE – JE (jump if equal) – wykonaj skok, jeżeli równe
LOOPNE – JNE
(jump if not equal) – wykonaj skok, jeżeli nierówne
LOOPZ – JZ
(jump if zero flag = 1) – wykonaj skok jeśli zero flag = 1
LOOPNZ – JNZ
(jump if zero flag = 1) – wykonaj skok jeśli zero flag = 1

Przykładowa funkcja wywołana z parametrem i zwracająca wartość przez referencję

Projekt.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include

extern "C" {
//deklaracja zewnetrznej funkcji dolinkowanej z asemblera
void funkcja(int* z, int x,int y);
}

int main()
{
int x=10;
int y=21;
int z;
funkcja(&z,x,y);
cout << z; endl;
system("PAUSE");
return 0;
}

program.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.386
.MODEL FLAT, C
.CODE
funkcja PROC

; trzeci argument - przekazywany przez wartosc
mov eax, [esp+12]
; drugi argument - przekazywany przez wartosc
mov ebx, [esp+8]
add eax, ebx
; pierwszy argument - przekazywany przez referencje tak by mozna bylo go zmodyfikowac
mov ebx, [esp+4]
mov [ebx], eax

ret
funkcja ENDP
END

Wykorzystane źródła:
http://coder.org.pl/index.php?p=article&category=8&id=1
Wikipedia Assembler Instrukcje