1. diel - Úvod do programovania v Assemblera
Vitajte u kurzu, v ktorom sa spolu ponoríme až do hĺbok, kam sa vôbec ako programátori môžeme dostať. Obídeme programovací jazyk aj kompiler a zistíme, ako funguje samotný stroj. Budeme posielať priamo inštrukcie procesora, pochopíme architektúru x86, BIOS, ako sa adresuje pamäť a zavádza operačný systém. Táto problematika súvisí aj s reverse-engineering, crackováním softvéru a samozrejme hackingom. Naučí vás používať nástroje ako disassembler, cheat enginy a lepšie pracovať s debuggery.
Kurz zahájme slávnu citácií:
Nechcem learn to hack, hack to learn!Predpoklady
Kurz predpokladá znalosť aspoň základov fungovania počítača po hardvérovej stránke a skúsenosť s ľubovoľným vysokoúrovňovým programovacím jazykom (napr. C alebo Java).
Prečo sa dnes učiť Assembler?
Vždy ma zaujímalo, ako veci v základe fungujú a namiesto toho, aby som používal niečo, čo už existuje, som si radšej urobil niečo svoje. Napríklad máme doma rádio. A ja, namiesto toho, aby som ho používal, začal som sa stavbou vlastného AM rádia. A alebo väčšina z nás používa operačný systém Windows ... No a čo som asi urobil ... Áno, začal som pracovať na vlastnom operačnom systéme v Assemblera.
Čo sa tu budeme učiť je samozrejme veľmi špeciálna a úzke zameranie. Komerčne dnes tieto znalosti uplatníte vo sfére kyberbezpečnosti alebo pri programovaní pre niektoré nízkej odozvy embedded zariadenia, prípadne pre high-performance programovanie. A alebo keď sa budete chcieť pokúsiť donútiť robiť nejakú cudziu aplikáciu to, čo od nej chcete, prípadne si naprogramovať zavádzač operačného systému. To samozrejme nie je tak ľahké, ale niekde sa začať musí:)
Aby sme pochopili ako vôbec ASM funguje, pozrime sa v skratke na históriu programovacích jazykov.
Vývoj programovacích jazykov
1. generácie jazykov - Strojový kód
Procesor počítača vie vykonávať len obmedzené množstvo jednoduchých inštrukcií, ktoré sú uložené ako sekvencie bitov, sú to teda čísla. Tá sa mu zvyčajne zadávajú v hexadecimálne (šestnástkovej) sústave. Inštrukcie sú tak elementárne, že umožňujú iba napr. Prácu s registrami procesora (to je pamäť v CPU) alebo skoky. Nemožno napr. Jednoducho sčítať dve čísla, musíme sa na čísla pozerať ako na adresy v pamäti a také sčítaní čísel zaberie niekoľko inštrukcií. Program sčítající dve čísla by vyzeral napr. Takto:
2104 1105 3106 7001 0053 FFFE 0000
Inštrukcie sa procesora predloží v binárnej podobe. Takýto kód je samozrejme extrémne nečitateľný a závisí na inštrukčnú sade daného CPU. Každý počítačový program musí byť nakoniec do tohto jazyka preložený, aby mohol byť na procesore počítača spustený.
2. generácia jazykov - Assembler
Assembler čiže JSA (jazyk symbolických
inštrukcií) sa objavil niekedy v polovici 20. storočia. Konkrétne sa jednalo
o jazyk druhej generácie. Od jazykov prvej generácie sa
líšil tým, že namiesto toho, aby sme si museli pamätať číselné
kódy inštrukcií, sme mohli písať ich slovné
kódy (napríklad: MOV
, CMP
,
ADD
).
Vďaka Assemblera bolo písanie programov jednoduchšie a prehľadnejšie, než keby sme ich písali v číslach. Ďalšou výhodou bolo, že adresy v programe sa nemuseli meniť prepisovaním celého programu ako tomu bolo u prvej generácie. Čo sa týka kódu samotného, nemuseli sa opäť napr. Zložito vypočítavať adresy skokov. Kód teda začal byť vôbec ľudsky čitateľný, aj keď nie je o nič jednoduchšie, než pôvodný strojový kód.
Rovnaký program by v ASM vyzeral takto:
ORG 100 LDA A ADD B STA C HLT DEC 83 DEC –2 DEC 0 END
Vidíme, že je to trochu ľudskejší, ale stále nezainteresovaní ľudia vôbec netušia, ako program funguje.
3. generácie jazykov
Jazyky v tretej generácii konečne ponúka užívateľovi určitú abstrakciu nad tým, ako program vidí počítač, zameriavajú sa na to, ako program vidí človek. Sú označované ako tzv. Vyššej programovacie jazyky (anglicky High Level Languages, niekedy skrátene HLL). Naše čísla sú vnímaná už ako premenné, zdrojový kód pripomína matematický zápis.
Jedným z prvých vysokoúrovňových programovacích jazykov bol jazyk C. Aj keď ho predbehol Fortran alebo Pascal, tak to bol práve jazyk C, ktorý dobyl svet. Opäť ten istý programy by v jazyku C vyzeral takto:
int main(void) { int a, b, c; a = 83; b = -2; c = a + b; return 0; }
V kontraste súčasných objektovo orientovaných moderných jazykov je jazyk C často označovaný ako jazyk nízkoúrovňový. Ako to teda je? Od príchodu jazyka C skrátka ubehlo už niekoľko dekád a záleží s čím ho porovnávame. V našom kontexte s ASM je nazývaný vysokoúrovňovým, v kontexte napr. S jazykom C # .NET je naopak nízkoúrovňový.
Všetci asi tušíme, čo program robí, spočíta čísla 83
a
-2
a výsledok uloží do premennej c
. U všetkých
jazykov tretej generácie je samozrejme výhodou vysoká čitateľnosť.
Preklad vyšších jazykov do assembleri
Tieto jazyky teda majú svoj zdrojový kód v jazyku, ktorému ľudia dobre rozumie. Zdrojový kód sa samozrejme musí preložiť do binárneho kódu, aby ho bolo možné na procesore spustiť. Tento preklad zaisťuje prekladač (kompiler), ktorý preloží naraz celý program do assembleri.
Ak budeme vedieť, ako vyzerajú v ASM rutiny napr. C prekladača, budeme schopní pochopiť, ako tieto programy fungujú alebo ich dokonca modifikovať, bez toho aby sme od nich mali zdrojový kód. Jedná sa ale samozrejme o veľmi komplexnú problematiku, pretože sebemenší funkcie vyústi vo veľké množstvo ASM inštrukcií a kompiler vo výslednej podobe ešte vykonáva početné optimalizácie. Poďme im venovať aspoň krátky odsek.
Optimalizácia prekladače
Keď by sme písali v čistom ASM, veľmi pravdepodobne bude náš program pomalší ako ten istý program skompilovaný napr. Z jazyka C. Ako je to možné? Kompiler totiž kód upraví tak, aby bol veľmi rýchly a to často za cenu, že je pre človeka potom zle čitateľný. Určite ste všetci niekedy použili cyklus. Ten má nejakú riadiacu premennú, podmienku a skrátka réžii navyše. Preto sa môže niekedy optimizer rozhodnúť, že bude lepšie cyklus nepřeložit, ale namiesto toho kód len niekoľkokrát zopakovať za sebou. Ak vás problematika zaujíma, odporúčam krásny článok Prekladača pod pokrievkou - optimalizácia.
Špecifiká programovanie v Assemblera
A ako teda také programovanie bez prekladača vyzerá?
Všeobecne v Assemblera (ASM) neexistujú vopred vytvorené
funkcie - Nenájdeme tu žiadne PrintString()
,
ClearScreen()
, ani SetCursorPosition()
. Všetko si
musíme vytvoriť sami. Ak nám beží v mašine nejaká nadstavba (napr.
Operačný systém), môžeme samozrejme volať z ASM jeho API, ale tým už
nepracujeme len čisto s inštrukčnou sadou a prerušeniami BIOSu.
Ešte by som rád na úvod na niečo upozornil! Je možné, že poznáte
prerušenie INT 21H
(k prerušením sa v kurze čoskoro dostaneme).
Toto prerušenie je dostupné iba v operačnom systéme MS-DOS, čo znamená,
že ho (ani ostatné) nebudeme používať. My si vystačíme s prerušeniami,
ktorá ponúka BIOS.
Ukážka kódu
Na záver úvodného dielu si ukážeme a popíšeme zdrojový kód metódy
PrintString
pre výpis textu len pomocou rutín BIOSu. Metódu
budeme používať nabudúce, kde si ju tiež vysvetlíme. Tu sa na ňu môžete
pozrieť len preto, aby ste vedeli, ako programovanie v ASM vyzerá a do čoho
sa to vlastne púšťame.
Metóda PrintString
:
PrintString: ; Začátek metody pro vypisování zprávy lodsb ; Přesuneme řetězec do registru AX or al, al ; Zkontrolujeme, jestli je v registru AL ještě nějaký znak, který bychom mohli vytisknout jz short .Done ; Pokud ne, skočíme na .Done mov ah, 0eh ; Registr AH naplníme hodnotou 0eh mov bx, 7h ; Registr BX naplníme hodnotou 7h int 10h ; Vytiskneme znak jmp PrintString ; Vrátíme se na začátek metody .Done: ; Popisek pro návrat ret ; Vrátíme se na místo, odkud jsme metodu zavolali
No a to je pre dnešok všetko.
V budúcej lekcii, Assembler - Vytvorenie NASM projektu, registre a prerušenie , si založíme ASM projekt a povieme si čo sú to registre a prerušenia.