BEVEZETÉSMostanában a SAMP fórumon nagyon fellendült a gépi kód alacsony szintû programozása. Mivel egy ideje én is foglalkozok ezzel, és a DeAMX mûködése a kezdõ scriptereknek rejtély, ezért írtam meg a tutorialt. A tutorial, hiszen kezdõknek készült, koránt sem teljes és nem feladata a megfelelõ szaknyelv használata.
Remélem, aki PAWN-ban scriptel, az tisztába van azzal, hogy a PAWN, mint olyan, egy programozási nyelv. Az általunk kifejezett utasításokat gépi kódokká alakítja. Míg az emberek a szöveges nyelvet egyszerûbben, és hatékonyabban tudják kezelni, a gépek a számokat preferálják. Ezt nevezzük
gépi kódnak.
Ellentétben a közhiedelemmel, a gépi kód nem teljesen értelmetlen zagyvaság, hiszen akkor a számítógép se értené meg. Ellenben igen nehéz olvasnia egy ismeretekkel nem rendelkezõ felhasználónak. Ahhoz, hogy megértsük a problémát, tudnunk kell, mit nevezünk magas és alacsony szintû programnyelveknek. A magas szintû programnyelvek az emberek számára érthetõbbek, jobban programozhatóak, viszont a fordításuk erõforrásigényesebb, a program futása az alsóbb rétegek miatt általában lassabb. Az alacsonyabb szintû nyelvek az ember számára kevésbbé érthetõbbek, viszont gépi kóddá elõbb fordítódnak (hiszen közelebb állnak hozzájuk), és gyorsabbak.
A PAWN, mint olyan, egy magas szintû nyelv, az ember számára könnyen olvasható, és megérthetõ.
new var = 15;
while (var > 0)
{
var = var - 1;
}
A fenti scriptet, ha lefordítjuk, egy futtatható fájlt kapunk (AMX). Az AMX gépi kód, ezért megértése lehetetlen, ha nem ismerjük a felépítését. Ebben segít nekünk az ún. assembly nyelv. Az assembly a legalacsonyabb szintû programozási nyelv: a gépi kód felett állva az egyik legbonyorultabb, de leggyorsabban forduló és a gép számára legésszerûbb nyelv.
A PAWN fordító
-a kapcsolójával az AMX helyett ASM (assembler) kódot kapunk. Ha valakit érdekel, nyisson egy parancssort rendszergazdaként, navigáljon el a pawno mappájába, majd üsse be a
pawncc fájlnév -a parancsot, ahol a fájlnév értelemszerûen a kódunk neve. Ne feledjük, hogy az assembly minden architektúrán változik, az AMX-nek (ami egy absztrakt gép, tehát egy szimulált számítógép) más fajta assemblyje van, mint egy x86-os processzornak. Ha a gépi kódot szeretnétek megtekinteni változatlanul, dobjatok egy
#pragma compress 0-t a módban, és a tiszta kódot kapjátok meg a hex editorban. Én is így csináltam a gépi kódokhoz a példákat.
A fenti PAWN kód így néz ki az AMX assemblyjében:
break
push.c f
break
l.0
break
load.s.pri fffffffc
move.alt
zero.pri
jsgeq 1
break
load.s.pri fffffffc
add.c -1
stor.s.pri fffffffc
jump 0
l.1
És miért mondjuk azt, hogy a gépi kódhoz a legközelebb áll? Egészen egyszerûen azért, mert minden egyes utasítás a gépi kódban megfelel egy két hexadecimális számból álló opkódnak, az utánna álló paraméterek pedig az opkód után álló hexadecimális számoknak.
89 00 00 00 | // break
27 00 00 00 | 0F 00 00 00 // push.c f
Látszódik a fenti két kódrészletbõl, hogy az assemblybõl gépi kódot képezni nem éppen komoly munka. Az utasítást az elsõ négy, míg a paramétereket az utolsó négy bájtban tárolja el. Még ha az utasítás (vagy szebben fogalmazva opkód) csak 1 bájtot (0x00 - 0xFF) foglal el, a maradék három bájtot a kötelezõ formátum miatt nullákkal kell kitölteni. Az utasítás négy bájtja és a paraméterek négy bájtja közé húztam egy vonalat, ez az eredeti gépi kódban nincs ott. Látható hogy a break utasítás opkódja 89, a többi nulla csak a kötelezõ 4 bájt kitöltésére van.
Ugyanígy a második példánál a 27 a push.c opkódja, majd a 4 üres bájt a kötelezõ formátum miatt van. A paramétere 0F, a maradék három bájt a kötelezõ formátum miatt itt is zérus.
A VÁLTOZÓK MIZÉRIÁJAAmennyiben valaki már próbált DeAMX-el kódot visszafejteni, rájöhetett, hogy nem éppen jönnek vissza a változónevek, helyette értelmetlen var0, var4, var16, és egyéb értékeket kap. A helyzet a következõ: a számítógépek nem szeretik a szöveget: bonyorultak, sok memóriát foglalnak, lassítják a kódot, az embereknek viszont jobban megjegyezhetõ, mint egy rakat hexadecimális szám. Viszont a PAWN kód nem arra van, hogy visszafejtsed.
load.s.pri fffffffc 03 00 00 00 FC FF FF FF
add.c -1 57 00 00 00 FF FF FF FF
stor.s.pri fffffffc 11 00 00 00 FC FF FF FF
A fenti kód betölti az 0xfffffffc memóriacím alól a változót, csökkenti az értékét eggyel, majd eltárolja az új értéket a memóriacímben. Mellé írtam a gépi kód azon részét, ahol ez lejátszódik. Láthatjuk, hogy a load.s.pri opkódja a 03, követi három üres bájt, majd az FC FF FF FF felel meg a 0xFFFFFFFC-nek. A gépi kódoknál a hexadecimális számok bájtonkénti fordított sorrendben kerülnek be a listára (lásd Anthony kommentjét). Tehát egy ABCDEF12 hexadecimális szám mint 12 EF CD AB kerül bele a gépi kódba. Ezt hívjuk programozási szaknyelven
kis endiánnak.
A második sorban látjuk, hogy az add.c opkódja 57, követi három üres bájt, majd a -1 signed integer hexadecimális megfelelõje: 0xFFFFFFFF. Arról, hogy ez hogyan lesz végül is, ha valaki tájékozódni akar, javaslom
Anthony tutorialját a bináris mûveletekrõl.
Végül a módosult értéket ugyanabba a memóriacímbe elraktározzuk.
A lényeg, hogy a fenti kód hasonlít ehhez. Mert hogy pont ugyanaz a kettõ:
var = var - 1;
És akkor mégis mi az a 0xfffffffc, és miért ez, nem \"var\"?
MEMÓRIACÍMEK, MEMÓRIA LEFOGLALÁSAArról már volt szó, hogy a számítógép nem szereti a szöveget, és a PAWN nem arra lett tervezve, hogy visszafejtsék. Ezért eldobja azt, ami nem kell. Vegyük példának az alábbi kódot:
new var1;
new var2;
new var3;
new var4;
Ezt a DeAMX a következõképpen adja vissza:
new var12;
new var8;
new var4;
new var0;
Az assembly kódba ugyan nem került bele a három változó, hiszen sosem lettek használva, de kommentekben megtalálhatjuk a nyomát:
;$lcl var1 fffffffc
;$lcl var2 fffffff8
;$lcl var3 fffffff4
;$lcl var4 fffffff0
Láthatjuk, hogy mind a három változó pontosan 4 bájtot (1 cellát/2 szót) foglal el, hiszen a memóriacímek négyesével növekednek (0, 4, 8, C, 10, 14, 18, 1C, 20, ...).
Ez azért van, mivel a korábbi példákból észrevehetjük, hogy a változó korábbi regiszterbe való betöltésekor és módosításakor is egészen pontosan 4 bájtba kellett beírnunk a szám értékét, levonhatjuk tehát a következtetést, hogy a PAWN integer alapból 32 bit = 4 bájt hosszú. Ez azért van, mivel a PAWN-ban minden alapból 1 cella hosszú (ami elméletileg megegyezne a processzorod architektúrájával, a SAMP azonban csak 32 bit hosszú cellákat használ). Minden adattípus a PAWN-ban maximum 4 bájt hosszú.
Ezt próbálja meg a DeAMX ábrázolni, mikor a változónknak var0, var4, var8, var12, var16... neveket ad. A konkrét számok, és a látszólagos fordított kiosztásuk oka a relatív memóriacímekben rejlik, mégpedig abban, hogy az. ún stack memóriacímei, ahol a függvények lokális változói tárolódnak, lentrõl felfele kerülnek kiosztásra: ezekrõl kissé bõvebben az AMX és memóriaképe c. tutorial foglalkozik.
Ugyanez a helyzet a függvényekkel: memóriacímek alapján azonosítják õket. Az egyetlen kivétel az eljárások esete (callback), melyeknek a helyzete elõre nem tudható, ezért a függvény neve és a függvényre mutató pointer tárolódik el a scriptben.
TAGEKMiért van az, hogy a
new Float:var = 5.7;
változó ez lesz fordítás után:
new var0 = 1085695590;
?
A megoldás itt is egyszerû: a tagek csak a fordító számára érdekesek, a gépi kódnak nem. A tagek csak a fordító számára fontosak, hogy egyféle mûveletet különbözõ adatokkal különbözõképpen használjon. Miután a fordítás megtörtént az adatok rögzítõdnek az IEEE 754 szabványnak megfelelõen (egy szabvány ami leírja a lebegõpontos számok bináris ábrázolását). Mikor a DeAMX visszafejti a kódot, mivel mind a lebegõpontos számok, mind az integerek ugyanúgy tárolódnak, nem tudja, hogy az adott cím alatt integer, vagy float van, ezért integerré alakítja.
A szabvány felépítését nem fogom részletezni, mivel egyrészt bonyorult, másrészt érdektelen a tutoriallal kapcsolatban. Ha valakit nagyon érdekel, keressen rá az interneten a szabványra.
ANTI-DEAMXVégül egy gyors mondat az anti-deamx-rõl: ezek mindegyike a compiler hibáját használja ki, egyik sem hivatalos, és ha a SAMP-hoz új compiler jönne ki, akkor egyik se érne sokat.
A loopos példa Y_Less tutorialjából van. Észrevételeket szívesen látok.