Programowanie w języku Assembler

Pierwszy program

  • 2010-04-08 10:03
  • 13 komentarzy
  • 30080 odsłon
  • Oceń ten tekst jako pierwszy
Słowo wstępu

Asembler jest najniższym z możliwych "mostem" łączącym nas programistów i komputer. Jest to język maszynowy, a ściślej mówiąc język procesora. Operuje on na dwóch stanach 1 i 0 (prąd płynie albo nie płynie).
Sam język Asembler nie jest jednakże tylko i wyłącznie zbiorem komend i rozkazów, dotyka on bezpośrednio zagadnienia jakim jest architektura komputerów. Przeplatają się tu zagadnienia rejestrów, taktowania, koprocesora i wielu innych.

Dlaczego warto umieć assemblera?
Programy asemblerowe są nieporównanie szybsze od tych pisanych w HLL (High-Level-Language). Odpowiednio zoptymalizowany program pisany w asm może być dziesiątki razy szybszy od swoich odpowiedników pisanych w innych językach. Również pamięciochłonność takiego programu zależy tylko i wyłącznie od programisty, przeciętnie program taki jest dziesięciokrotnie mniejszy od programów pisanych w C++ lub Pascal.
Prymitywność? Asembler mimo, że jest językiem niskiego poziomu i został wymyślony dawno temu nie jest językiem prymitywnym, przeciwnie - ma on wręcz nieograniczone możliwości. Znaczna liczba wirusów jest pisana właśnie w Asemblerze.


Rejestry procesora:
Rejestry są to fizycznie istniejące w komputerze elementy elektroniczne mające zdolność zapamiętywania.

Procesory 8086/8088 mają ich 14.

To za ich pomocą realizowane są polecenia w programie. Więcej informacji o rejestrach: Rejestry.

Program, który omówię będzie liczył średnią arytmetyczną z 12 elementowej tablicy liczb naturalnych.
Jest to tylko przykład programu asemblerowego oraz prostego algorytmu, program nie wyświetla natomiast wyniku.
Zamiana liczby na ciąg znaków ASCII to już osobna historia.
Wersja TASM. Przed przystąpieniem do pracy należy zaopatrzyć się w "ulubiony" edytor tekstowy, programy do kompilacji i linkowania oraz koniecznie TruboDebuger.
tasm.zip (459,49 KB)



.model  TINY                                 ;dyrektywa model, mowi kompilatorowi
                                                   ;na jakim modelu pamieci bedzie operowal
 
.code                                            ;dyrektywa .code rozpoczyna czesc kodu
org    100h                                    ;dyrektywa org 100h, jest informacja
                                                    ;dla kompilatora, ze program jest typu .com
Start:                  
        mov        bx, OFFSET Tablica     ;przeslanie do bx offsetu tablicy "Tablica"
        mov        cx, DL_TABLICY         ;przeslanie do cx (licznika petli) dlugosci tablicy
        xor        ax, ax                         ;zerowanie rejestru ax poprzez xor
 
Petla:                                            ;etykieta petli
        add        al, byte ptr [ds:bx]       ;dodaj do al bajt z tablicy
        inc        bx                                ;przesun sie o jedno miejsce dalej  w tablicy
loop    Petla                                   ;skocz do "Petla" póki cx <> 0
 
        mov        bx, dl_tablicy              ;przeslij do bx dlugosc tablicy
        div        bx                               ;podziel ax przez bx, wynik w ax
        mov        [Srednia], ax              ;przeslij zawartosc ax, do miejsca w pamieci
                                                    ;oznaczonego jako Srednia
        mov        ah, 4Ch                      ;zakonczenie programu
        int        21h
;==========================================================================
;miejsce z danymi
dl_tablicy        EQU     12        
Tablica                DB      01h, 02h, 00h, 10h, 09h, 30h
                DB      13h, 08h, 12h, 08h, 0Ah, 05h
Srednia                DW      00h
 
END Start






Po kolei..

.model TINY

Dyrektywa ta określa na jakim modelu pamięci będzie operować program.
Jeden segment pamięci to 65535 bajtów czyli 64KB, wynika to z faktu, że
maksymalną wartość jaką można zapisać w rejestrach to 2^16.
Rozróżnia się kilka modeli pamięci:

TINY, SMALL, MEDIUM, COMACT, LARGE oraz HUGE

Wybór odpowiedniego modelu może zależeć od rozmiaru danych, "kaprysu" programisty oraz specyficznych zastosować programu.


.code

Wskazuje początek segmentu gdzie znajduje się kod programu. Nie oznacza to, że nie można umieszczać tam również danych. Otóż można, tak jak zrobiliśmy to my, należy umieszczać je w takim miejscu aby nie stały się częścią kodu programu. Gdyby procesor potraktował daną jako instrukcje mogłoby to mieć fatalne konsekwencje.
W bardziej rozbudowanych programach wykorzystuje się również .data - określający segment danych oraz .stack - stos programu.


org 100h

Ta linia jest informacją dla kompilatora, że program wynikowy będzie typu .com. Informuje ona również o tym, że nasz kod znajdować się będzie po uruchomieniu pod adresem 100h (256 dziesiętnie).
Więcej informacji na temat adresowania oraz przechowywania programu w pamięci można znaleźć w internecie oraz w książkach o asm.


Start:
mov        bx, OFFSET Tablica                        
mov        cx, DL_TABLICY
xor        ax, ax        

Start: może być to dowolna nazwa z dwukropkiem. Określa ona początek kodu w programie.
mov jest to rozkaz przesyłania zawartości prawego argumentu do lewego argumentu. Argumentami mogą być rejestry, pamięć lub wartość bezpośrednia.

OFFSET jest to operator wydzielający z wyrażenia (np. tabela) adres początku względem początku segmentu.
tak więc

mov bx, OFFSET Tablica
jest to więc przesłanie do rejestru bx, "adresu" tablicy "Tablica".

Użyte przeze mnie słowo adres nie jest do końca prawidłowe, de facto jest to offset adresu.
Adres: nazywany adresem fizycznym uzyskuje się na podstawie zapisu Segment:Offset i wyraża się wzorem
adres = 16*segment + offset.

mov cx, DL_TABLICY
jest to przesłanie liczby zdefiniowanej jako DL_TABLICY do licznika pętli cx. Rejestr ten będzie wykorzystany w następnych liniach programu jako licznik pętli.

xor ax, ax
jedna z metod zerowania rejestrów, nad matematycznym znaczeniem tego rozkazu nie ma sensu się rozpisywać.


Petla:
       add            al, byte ptr [ds:bx]
       inc            bx
loop    Petla

add rozkaz dodawania dwóch argumentów, wynik dodawania zapisywany jest w lewym argumencie.
Umieszczony w nawiasach kwadratowych adres [ds:bx] jest adresem fizycznym (segment:offset) i jest wskazaniem na miejsce w pamięci znajdujące się pod tym adresem. W naszym przypadku jest to wskazanie na pierwszy element tablicy "Tablica".
inc bx skrót od increment (zwiększ), zwiększa operand o 1.

loop Petla skok do etykiety Petla gdy CX≠0.

mov        bx, dl_tablicy
div        bx
mov        [Srednia], ax

div bx dzielenie argumentów bez znaku (tj. liczb nieujemnych). Jest to dzielenie 32 bitowe
(AX←DX:AX\dzielnik), wynik zapisywany w ax.

mov [Srednia], ax przesłanie do komórki pamięci operacyjnej znajdującej się pod adresem o nazwie "Srednia" wartości rejestru ax.

mov        ah, 4Ch
int        21h

Jest to funkcja 4Ch przerwania 21 (DOS) - Zakończenie procesu. Funkcja kończy wykonywany proces, zamykając wszystkie otwarte pliki i zwalniając przydzieloną pamięć.
Czym są właściwie przerwania? Są to podprogramy realizujące pewne usługi, np. wyświetlanie tekstu na ekranie, pobieranie znaku, zapisywanie na dysku. Stanowią one podstawą języka Asembler, a już na pewno programisty. Można powiedzieć, że miarą umiejętności programowania w tym języku jest znajomość przerwań.  

dl_tablicy        EQU     12        
Tablica                DB      01h, 02h, 00h, 10h, 09h, 30h
                DB      13h, 08h, 12h, 08h, 0Ah, 05h
Srednia                DW      00h
 
END

Deklaracje zmiennych w programach odbywa się według schematu:
nazwa typ wartość
Nazwa jest to dowolny napis akceptowalny przez kompilator.
Typ:

DB - zmienna składająca się z jednego/wielu bajtów
DW - zmienna składająca się z jednego/wielu słów (2 bajty = 16 bitów)
DD - zmienna składająca się z jednego/wielu słów podwójnych (4 bajty = 32 bity)

Wartość mogą to być oddzielone przecinkami liczby lub też ciąg liter zgrupowany w cudzysłów
Napis DB "Napis"

Asembler ma możliwość definiowania zmiennych, przypisać nazwie symbolicznej określoną wartość podaną jako argument.  Jest to odpowiednik define w C++.

END Start zakończenie kodu.






Do pisania programów w asemblerze można wykorzystać dowolny edytor tekstowy. Wygodnie jest aby numerował on wiersze, ja korzystam z "dev C++".
Program zapisujemy z rozszerzeniem .asm
Kompilacja programu odbywa się pod DOSem więc uruchamiamy konsole dosową. (Start→Uruchom→cmd).
Przenosimy się do folderu gdzie znajduje się nasz program oraz kompilator TASM.

X:\myprog>tasm myprog.asm
Turbo Assembler Version 3.1 Copyright <c> 1988, 1992 Borland International
Assembling  file:                     myprog.asm 
Error messages:                None
Warning messages:        None
Passes :                             1
Remaining  memory:        452k
 
X:\myprog>tlink/t myprog.obj
Turbo Link Version 5.1 Copyright <c> 1992 Borland International
 
X:\myprog>


Po uruchomieniu programu oczywiście nic się nie wyświetli.
Efekty obejrzymy w Turbo Debugerze.

X:\myprog>td myprog.com

Jeżeli wszystko zostało poprawnie skompilowane powinno ukazać się okno turbodebugera.
Klikamy ok, możemy powiększyć okienko - klawisz F5.
Kolejne komendy wywołujemy klawiszem F7 (dokładne śledzenie) lub F8 (to samo co F7 tylko pomija pętle).
W głównym oknie jest lista kolejnych rozkazów, po prawej stronie aktualny stan rejestrów.
Dla porównania: Ten program zajmuje 56 bajtów, program w C++ lub Pascal 56KB.


Dla bezpieczeństwa własnego dodam że nie ponoszę odpowiedzialności za ten program, ale trzeba by mieć wielkiego pecha żeby zniszczyć sobie komputer tym programem, jest tam zaledwie parę operacji dodawania i dzielenia.

13 komentarzy

Brak avatara
Zrozpaczony Nauczyciel 2014-02-15 22:50

Chciałbym zauważyć że assembler nie jest najniższym językiem programowania. Jest to język drugiej generacji. Assembler to NIE jest kod maszynowy. Kod maszynowy jest 'językiem' programowania pierwszej generacji. Assembler jest kompilowany/potrzebuje kompilatora, dzięki czemu przechodzi analizę składniową podczas tłumaczenia, innymi słowy nie jest bezpośrednio wpisywany w plik wykonywalny. Natomiast na przykład pisanie programów heksadecymalnie lub binarnie plik zostanie zapisany bezpośrednio do np .exe lub .com i gdy jest w nim błąd może wyskoczyć okno iż plik nie jest prawidłową aplikacją systemu. Można zauważyć różnicę między kodem maszynowym a assemblerem gdy próbujemy skompilować kod assemblera pod .exe. Wtedy kod jest dodatkowo tłumaczony, co zwiększa wielkość pliku (np .com ma 20 bajtów a .exe będzie już miał ok 60-70 bajtów) Assembler został stworzony dla programistów, dla zwiększenia czytelności kodu. Radzę wszystkim którzy się tym bawią poczytać o generacjach języków i komputerów.

mode13h 2013-07-20 00:48

Jeżeli komuś nie działa powyższa wersja.. [mnie na przykład nie działało] to wystarczy
div bx
zamienić na
div bl

LUB zostawić bx
a przed tym wstawić -> xor dx, dx <-
u mnie oba rozwiązania się sprawdzają.

RaveStar 2012-08-16 23:20

@Ciamajda.

Procesor nie interpretuje języka maszynowego tylko go wykonuje (interpretacja to jest tu na forum - odgadywanie co niedoszły programista miał na myśli). Ustawia bramki (których grupy nazywane są rejestrami) w zależności od wartości zer i jedynek w bajcie, który sobie odczyta z pamięci.

"że dobry kompilator robi lepszą optymalizację skomplikowanych algorytmów niż jest to w stanie zrobić człowiek"
Kolejna bzdura. Ciekawe dlaczego ten człowiek robi wstawki ASMowe bo się kod z goownianego C++ po optymalizacji nie wyrabia (krytyczne części systemów)? Albo dlaczego Java potrafi być szybsza od C/C++?
NIGDY nie spotkałem się z sytuacją, w której kodu z kompilatora nie dałoby się poprawić.

" Nie sądzę, by wirusy były pisane w asemblerze. "
A w czym? W JavaScripcie?

Ehh to pokolenie Windowsa ...

D.F. 2010-04-08 21:31

bordeux. Tak, jest możliwość uszkodzenia. Szczególnie należy uważać na instrukcje mov i add, hehe xD

bordeux 2009-08-27 21:52

szkoda ze to ryzykowna zabawka. Moze ktos napisac jak bardzo? Jest mozliwosc uszkodzenia procka?

flammin 2009-02-04 20:56

@Ciamajda,
Czy jest to język maszynowy? Odsyłam do specyfikacji procesorów z rodziny x88, x86 i tak dalej.
W asemblerze posługujesz się mnemoniką: mov, xor, shl i tak dalej, które są niczym innym odpowiednimi poleceniami procesora? No tak... a to w takim razie cout w C++ jest też mnemoniką procka.. ale nie jest, ponieważ na cout składa się kilkanaście linii kodu.
Co do wydajności to w pewnych przypadkach jest on dużo szybszy. Sam przeprowadzałem test szybkości na sortowaniu bąbelkowym, już dla 65000 elementów różnica jest mniej więcej 10:6 (w porównaniu z C++). Również wykorzystanie koprocesora dla funkcji sinus daje znaczne rezultaty w porównaniu z innymi językami i jest to potwierdzone.
Kolego, asm to nie tylko konsola, możliwa jest również praca okienkowa pozwalająca na pełną kontrolę obsługi zdarzeń oraz pisanie bibliotek DLL odwołuje tu do NASM.
Wirusy mogą być pisane w asm, a czy są? zapytaj się tych którzy je piszą i zapytaj się tych którzy znają asm na dostatecznie wysokim poziomie:).
... i bynajmniej nie upieram się przy asemblerze. Programowanie w nim nie jest ani łatwe ani piękne mimo to jego stosowanie może niekiedy pomóc, szczególnie tam gdzie liczy się szybkość i pełna kontrola nad kodem.

Ciamajda 2009-01-06 17:52

Sprostowanie: asembler NIE JEST językiem maszynowym. Język maszynowy to ciąg bajtów, który jest interpretowany przez procesor. Asembler jest prostym językiem, który umożliwia stosowanie zamiast wartości liczbowych prostych mnemoników, np. add, inc, mov.

Sprostowanie drugie: autor pisze "Odpowiednio zoptymalizowany program pisany w asm może być dziesiątki razy szybszy od swoich odpowiedników pisanych w innych językach. Również pamięciochłonność takiego programu zależy tylko i wyłącznie od programisty, przeciętnie program taki jest dziesięciokrotnie mniejszy od programów pisanych w C++ lub Pascal." Gdzie jakieś porównania? Czy autor rzeczywiście to sprawdzał? Skąd ma takie wiadomości? Czy nie są to aby życzenia oparte na tezie "to wiedzą wszyscy, więc tak jest"? Jestem programistą i wiem (ale też nie powiem skąd ;P), że dobry kompilator robi lepszą optymalizację skomplikowanych algorytmów niż jest to w stanie zrobić człowiek ;) Chyba, że mówimy tu o optymalizacji dla konkretnej rodziny procesorów (np. P4 z HT), ale tego autor nie zaznaczył ;)

Sprostowanie trzecie: "ma on wręcz nieograniczone możliwości. Znaczna liczba wirusów jest pisana właśnie w Asemblerze." Nie sądzę, by wirusy były pisane w asemblerze. Z tego, co jest mi wiadome, są one pisane po prostu w czystym API, czyli bez korzystania z dodatkowych bibliotek wprost wywołują funkcje systemowe. Taki program (możliwy do napisania nawet w C++Builder), zajmuje kilka do kilkunastu kilobajtów (sam exec), więc do osiągnięcia małej objętości wcale asembler nie jest potrzebny.

Ogólnie jednak pochwalam :) Programiści asemblerowi nie są może bardzo poszukiwani na rynku pracy, ale jak już jest taka potrzeba (np. sterowniki do specjalistycznych urządzeń), to znaleziony programista może dyktować warunki, bo konkurencja jest niewielka :) Warto, żeby wiedza ta nie zginęła :)

DALAILAMER 2008-09-21 08:25

ej manfredek zdaje sie ze cos porabales , z tego co wiem to do wartosci konkretnej cyfry trzeba dodac dec(48) zeby uzyskac kod ASCII danej cyfry np.  ASCII cyfry zero = 41 dec = 30 hex
... a jezeli wyswietlanie liczb jest proste dla ciebie to sie swietnie squada bo za cholere nie umiem napisac funkcji wyswietlajacej rejestr st(0) w postaci dec .... to moze moglbys pomoc

Potwoor_ 2008-06-24 01:53

może źle się kompilowało cy coś, sprawdzę też na XP ale trochę boję się o klumpa po moim pierwszym doświadczeniu z tasm'em =D ( patrz pierwszy komentarz )

flammin 2008-06-23 10:40

@manfredek, wypisanie cyfry polega na dodaniu '0', wypisanie liczby na ekran jest to po kolei dzielenie liczb i wypisywanie reszty z dzielenia na ekran i tak dalej...

@Potwoor_, ja testowałem na 2ch maszynach z XP

Pozdrawiam.

Potwoor_ 2008-06-23 09:41

hej, ja jakoś tak z ciekawości ztasmowałem ten przykładowy kod, odpaliłem .com'a i dostałem coś takiego:

"Program spowodował błąd przepełnienia przy dzieleniu, jeśli problem się powtórzy, zwróć sie do dostawcy"

=D ( to było na win98 na maszynie testowej )

manfredek 2008-06-23 08:10

<quote=autor>Zamiana liczby na ciąg znaków ASCII to już osobna historia.</quote>

Tak, tylko dodanie '0' (kod ASCII zera, a nie samego zera!) do liczby...

Potwoor_ 2008-06-22 21:41

tasm jest super =) cieszę się że bawiłem sie nim na maszynie do pisania ( P133 ) bo grzebałem sobie w menu, kliknąłem Run, czarny ekran, kompik ucichł a mnie zatkało =D wciskam Power a tu nic, to cisnę jeszcze kilka razy, wciąż to samo, ale ożył po odłączeniu i podłączeniu zasilania =D

a więc 2 minuty zabawy w asemblerze i popsułem komputer, niezłe osiągniecie według mnie =D