2016. dec 26.

Assembly helloworld

írta: Ra0k
Assembly helloworld

Windows 10 operációs rendszer alatt

Sziasztok!

Pár hete kedvet kaptam mélyre merülni az assembly nyelvben leginkább abból a célból, hogy mélységeiben megismerjem a számítógép és az operációs rendszerek működését. Belevetettem magam az interneten elérhető magyar irodalomba a témában, de elkeserítő volt az amit találtam. Arra jó volt, hogy az assembly nyelv működését megértsem, de ahhoz már kevés, hogy egy "Hello World" szöveget a képernyőn megjelenítsek. A probléma ugyanis az, hogy ezen könyvek döntő többsége elavultak és MS-DOS programozásáról szólnak, ahol sokkal egyszerűbb az élet assembly szempontból mint manapság. Tehát a nyelv 'megismerése' után, úgy gondoltam írok egy helloworld programot. (aki új lenne a programozásban, a helloworld mindig egy hosszú utazás kezdetét jelenti, amikor is a programozó egyre nehezedő problémákat old meg egy új nyelven) Tehát ott tartottam, hogy megpróbáltam írni egy helloworld programot ami sajnos nem jött össze, hiszen a könyvben leírt módszerek nem működtek. A régi MS-DOS-os módszer:

 

org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'

Tisztán látszik, hogy ez a program megszakításokat (interrupts), használ, erre utal az int 21h utasítás (MS-DOS), és mivel az ah regiszterbe 9-et raktunk jeleztünk a szándékunkat, hogy a standard kimenetre akarunk szöveget (string-et) írni, méghozzá dx regiszterbe tett (mov dx, msg) memóriahelytől kezdődően. Alapértelmezett módon karakterig bezáródóan fogja kiírni a karaktereket. A következő megszakításnál pedig a program bezárását kérjük (mov ah, 4Ch). Az org 100h, parancs szorulhat még némi magyarázatra, amibe most nem megyünk bele annyira, elég annyit tudni, hogy a .COM programok 100h hexadecimális memóriahelynél kezdődnek el.

Problémánk ezzel: A Windows 10 operációs rendszer nem képes futtatni a .com fájlokat. Illetve tudtommal nem támogatja a megszakításokat sem, hanem az úgynevezett winapi hívások segítségével érjük el többek között azokat a funkciókat amiket anno MS-DOS alatt a megszakításokkal elértünk. Na ez volt az a rész, amiről a magyar irodalomban semmit sem találtam és ez okból döntöttem úgy, hogy blogot írok a próbálkozásaimról. :)

Verejtékes munkával, rengeteg sikertelen próbálkozás után ez lett a kód ami működik is:


global _main
extern _GetStdHandle@4
extern _WriteFile@20
extern _ExitProcess@4

section .text
_main:
; DWORD bájt, lokális változó a WriteFile függvény &bytes paraméteréhez
mov ebp, esp
sub esp, 4

; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
push -11
call _GetStdHandle@4
mov ebx, eax

; WriteFile( hstdOut, message, length(message), &bytes, 0);
push 0
lea eax, [ebp-4]
push eax
push (message_end - message)
push message
push ebx
call _WriteFile@20

; ExitProcess(0)
push 0
call _ExitProcess@4

; never here
hlt
message:
db 'Hello World'
message_end:

A fordítása pedig: (a fejlesztői környezet telepítéséről itt olvashattok bővebben)
nasm -fwin32 helloworld.asm
gcc helloworld.obj -o helloworld.exe

A kód működését most nem részletezném, szerintem nem túl nehéz megérteni ha vannak alapvető ismereteink az assembly nyelvvel kapcsolatosan. A lényeges dolog az, hogy a winapi függvény hívásokat végzünk, a paramétereket pedig veremben adjuk át. A _GetStdHandle@4 hívással -11-es paraméterrel megkapjuk "a standard kimenet kezelőjét" (igen, elég hülyén hangzik magyarul), aminek a segítségével, _WriteFile@20 hívással a kimenetre írjuk az üzenetünket, majd az _ExitProcess@4 segítségével kilépünk a programból 0-ás visszatérési értékkel.

 Ha nem vagyunk ilyen mazochisták ugyanezt az eredményt ennyivel is elérhetjük, azzal, hogy a C könyvtár printf függvényét használjuk, persze ebben az esetben elveszti a szépségét az, hogy közvetlenül a winapi-t használtuk volna:

global _main
extern _printf

[section] .text
_main:
push message
call _printf
add esp, 4
ret
message:
db 'Hello World', 0

Fordítása pedig:  (a fejlesztői környezet telepítéséről itt olvashattok bővebben)
nasm -fwin32 helloworld.asm
gcc helloworld.obj -o helloworld.exe

Fontos, hogy abban az esetben, ha gcc-vel fordítjuk az objektumunkat gépi kódra, fontos, hogy az assembly fájlban a _main címkénél fog kezdődni a programunk, és ha nem talál ilyen belépési pontot a fordítás sem lesz sikeres.

Összefoglalva tehát, most boldogság van, mert sikeresen kiírtuk a Hello World szöveget a standard kimenetre, winapi közvetlen hívásával és a C könyvtár használatával is. Mérésem szerint a direkt winapi hívásos módszer gyorsabb mint a C könyvtáras módszer. (bár a különbség nem sok, 20000 futtatás esetén 4 másodperc :)

Remélem tetszett az írás, a jövőben igyekszek egy gyors összefoglalót írni az assembly nyelvről is, illetve picit komolyabb, összetettebb alkalmazásokat is írni, akár LINUX alá is.

Szabó Dávid

Szólj hozzá

Programozás GCC Helloworld Assembly Windows programozás NASM