Programovanie v assembleri vo Windows x64 (x86-64): Rozdiel medzi revíziami

Smazaný obsah Přidaný obsah
Fabcde (diskusia | príspevky)
Fabcde (diskusia | príspevky)
dBez shrnutí editace
Riadok 1:
== Úvod ==
Toto sú poznámky k niekoľkým jednoduchým programom napísaných v jazyku [[w:Jazyk_symbolických_inštrukcií|symbolických adries]] (assembly language) určených pre [https://cs.wikipedia.org/wiki/Po%C4%8D%C3%ADta%C4%8Dov%C3%A1_platforma platformu] '''[[w:X86-64|x86-64]]''' – '''Windows x64'''. Väčšinou sú uvedené dve verzie programov, prvá pre [[w:Prekladač|prekladač]] (kompilátor) [[w:Netwide_Assembler|Netwide Assembler]] (NASM), druhá pre [https://cs.wikipedia.org/wiki/GNU_Assembler GNU Assembler] (GAS).
 
== Stručný prehľad ==
 
=== Syntax a kompilácia ===
NASM používa syntax Intelu, dominujúcu v prostredí MS-DOS a Windows, GNU Asembler používa syntax AT&T, prevládajúcu v Unixovom svete. Jedným z rozdielov týchto dvoch syntaxí je opačné poradie argumentov v niektorých inštrukciách.<ref>Ram Narayan (2007-10-17). [https://www.ibm.com/developerworks/library/l-gas-nasm/index.html "Linux assemblers: A comparison of GAS and NASM"]
</ref> Napríklad inštrukcia "vlož hodnotu nula do registra AX" sa v NASM zapisuje <code>MOV AX, 0</code>, v GAS <code>MOV $0, %AX</code>. Intelovská syntax (NASM) pripomína priraďovací príkaz vyšších programovacích jazykov <code>AX=0</code>, AT&T syntax (GAS) skôr niečo ako <code>0->AX</code>.
Řádek 7 ⟶ 9:
[https://cs.wikipedia.org/wiki/Objektov%C3%BD_k%C3%B3d Objektové súbory] sú zlinkované do spustiteľného exe súboru pomocou [https://cs.wikipedia.org/wiki/Linker linkeru] golink, alebo ld. Kompilátor as (GNU Assembler) a linker ld sú súčasťou [[w:GNU_Compiler_Collection|gcc]]. Kvôli jednoduchšiemu rozlíšeniu majú [[w:Zdrojový_kód|zdrojové súbory]] programov určených pre kompilátor NASM príponu .asm, objektové .obj, pre GNU Assembler .s, resp .o.
 
=== Volacie konvencieVolanie WinAPI ===
Programy bežiace v [https://cs.wikipedia.org/wiki/Re%C3%A1ln%C3%BD_re%C5%BEim Reálnom móde] (operačný systém MS-DOS) alebo v móde (režime) [https://cs.wikipedia.org/wiki/Virtual_8086_mode Virtual 8086] (operačný systém Windows) mohli využívať služby operačného systému MS-DOS (MS-DOS API). Tieto sa volali pomocou softvérového prerušenia inštrukciou INT, napríklad <code>INT 21h</code>. 64-bitové verzie OS Windows režim Virtual 8086 nepodporujú. Služby operačného systému je preto možné zabezpečiť jedine volaním funkcií Windows API (WinAPI). Funkcii (podprogramu) je zvyčajne potrebné nejakým spôsobom odovzdať argumenty a opačným smerom zase výsledok. V zásade nie sú žiadne obmedzenia týkajúce sa spôsobu odovzdávania údajov medzi volajúcim a volaným podprogramom. Je možné zvoliť akýkoľvek fungujúci spôsob, či už pomocou registrov, pamäti, zásobníka, atď, len treba o každej volanej funkcii (podprograme) vedieť, kde očakáva argumenty a kam ukladá výsledok ([https://cs.wikipedia.org/wiki/Volac%C3%AD_konvence volacia konvencia]).
 
Řádek 179 ⟶ 181:
Hello, World!
</syntaxhighlight>
 
== Volacia konvencia Microsoft x64 ==
V tejto kapitole sú uvedené niektoré ďalšie podrobnosti volacej konvencie Microsoft x64. Najskôr však niečo o zásobníku.
 
=== Zásobník ===
<!-- pokracovat v revizii -->Zásobník (stack) je pamäťová štruktúra typu LIFO. Hardvérový zásobník je realizovaný priamo v operačnej pamäti, ako jedna súvislá oblasť, ku ktorej sa pristupuje pomocou inštrukcií procesora PUSH a POPpočítača. V ďalšom texte sa pod zásobníkom rozumie vždy hardvérový zásobník, nie nejaká jeho softvérová implementácia.
 
Zásobník je možné používať ľubovoľne podľa potreby. Bežne sa používa na ukladanie lokálnych premenných a aj dočasných, pomocných hodnôt. Používa sa aj na odovzdávanie argumentov podprogramu. Inštrukcia CALL na vrchol zásobníka tiež ukladá návratovú adresu z podprogramu.
 
V architektúrach x86 a x86-64 sa ku zásobníku pristupuje pomocou inštrukcií PUSH a POP. Zásobník rastie (plní sa) smerom od vyššej adresy k nižšej. Na jeho vrchol ukazuje register RSP. (RSP obsahuje najnižšiu adresu, na ktorej je niečo uložené).
Zásobník sa zvykne používať na ukladanie lokálnych premenných, často aj na odovzdávanie argumentov podprogramu. Používa ho aj inštrukcia CALL, ktorá na vrchol zásobníka ukladá návratovú adresu z podprogramu, t.j. adresu inštrukcie nasledujúcej za inštrukciou CALL, t.j. hodnotu registra RIP než bola nahradená adresou začiatku podprogramu.
 
VInštrukcia architektúrachPUSH x86vloží anovú x86-64hodnotu zásobník rastie smerom od vyššej adresy k nižšej. Nana vrchol zásobníka, čiže poslednú (najnižšiu) adresutak, na ktorej je niečo uložené, ukazuje register RSP.že Novúzmenší hodnotu uložíme do zásobníka inštrukciou PUSH. Táto posunie vrchol zásobníka (zníži hodnotu uloženú v registriregistra RSP) o príslušný počet vložených bajtov, a na taktotúto vzniknuté miestoadresu uloží novú hodnotu. Napríklad inštrukciu <code>PUSH RAX</code> si môžme predstaviť ako dvojicu inštrukcií<syntaxhighlight lang="nasm">
SUB RSP, 8
MOV [RSP], RAX
</syntaxhighlight>Keďže architektúra x86/x86-64 používa na ukladanie viac-bajtových hodnôt usporiadanie little-endian, t.j. (na nižšej adrese je uložený menej významný/nižší bajt), v zásobníku bude uloženéRAX uložený:<syntaxhighlight>
| |
+==============+
Řádek 213 ⟶ 214:
+--------------+
| . |
+-------------------+
| . |
+==============+ <- Dno zásobníka
</syntaxhighlight>Z vrcholu zásobníka sa hodnoty vyberajú inštrukciou POP. Inštrukcia <code>POP RAX</code> z vrcholavrcholu zásobníka prečíta hodnotu, vloží ju do registra RAX, a následne uvoľní miesto v zásobníku, podobne ako:<syntaxhighlight lang="nasm">
</syntaxhighlight>K dátam uloženým v zásobníku je možné stále pristupovať aj priamo, ako ku hocijakým iným dátam uloženým kdekoľvek v pamäti, napríklad pomocou relatívneho odkazu na vrchol zásobníka <code>MOV EAX, [RSP+4]</code>.
 
Z vrcholu zásobníka sa hodnoty vyberajú inštrukciou POP. Inštrukcia <code>POP RAX</code> z vrchola zásobníka prečíta hodnotu, vloží ju do registra RAX, a následne uvoľní miesto, podobne ako<syntaxhighlight lang="nasm">
MOV RAX, [RSP]
ADD RSP, 8
</syntaxhighlight>KKu dátamhodnotám uloženým v zásobníku je možné stále pristupovať aj priamo, ako ku hocijakým iným dátam uloženým kdekoľvek v pamäti, napríklad pomocou relatívneho odkazu na vrchol zásobníka :<code>MOVsyntaxhighlight EAX, [RSP+4]</codelang="nasm">.
</syntaxhighlight>Pamäť zásobníka mimo rozsahu vymedzenom registrom RSP je nestála (volatile) a môže ju prepísať OS alebo debuger. Pri ukladaní údajov na vrchol zásobníka je preto nutné vždy najskôr alokovať potrebný priestor (napríklad <code>SUB RSP, n</code>) a až do takto alokovanej pamäte možno potom niečo bezpečne uložiť. Nepotrebné miesto na vrchole zásobníka uvoľní <code>ADD RSP, n</code>.
MOV EAX, [RSP+4]
 
</syntaxhighlight>Pamäť zásobníka mimo rozsahu vymedzenom registrom RSP je nestála (volatile) a môže ju prepísať OS alebo debuger. Pri ukladaníPre bezpečné uloženie údajov na vrcholdo zásobníka je preto nutné vždy najskôr alokovať potrebný priestor (napríklad :<code>SUBsyntaxhighlight RSP, n</code>) a až do takto alokovanej pamäte možno potom niečo bezpečne uložiť. Nepotrebné miesto na vrchole zásobníka uvoľní <code>ADD RSP, n</codelang="nasm">.
=== Microsoft x64 ===
SUB RSP, n
Volacia konvencia Microsoft x64 sa používa pri volaní funkcií WindowsAPI v 64-bitovom režime.
</syntaxhighlight>Nepotrebné miesto na vrchole zásobníka uvoľní:<syntaxhighlight lang="nasm">
ADD RSP, n
</syntaxhighlight>
 
=== Volacia konvencia Microsoft x64 ===
Volanie funkcie (podprogramu) sa realizuje štandardne inštrukciou CALL. Inštrukcia CALL najskôr uloží na vrchol zásobníka hodnotu registra RIP, čižev adresuktorom sa už nachádza adresa nasledujúcej inštrukcie. aNásledne vzápätízmenou zrealizujehodnoty skokregistra naRIP adresusa podprogramu,zrealizuje zmenouskok hodnotyna registrapožadovanú RIPadresu. Podprogram sa končí inštrukciou RET, ktorá vyberie z vrcholu zásobníka návratovú adresu (uschovaný obsah registra RIP) a vloží ju späť do registra RIP.
 
Na64-bitové rozdielverzie odfunkcií volacejWindowsAPI konvenciepoužívajú stdcall,volaciu používanejkonvenciu vMicrosoft 32-bitovom režime, súx64. prvéPrvé štyri argumenty uložené v registroch. (Architektúra64-bitová x64architektúra x86-64 má oproti x3232-bitovej architektúre x86 k dispozícii viac registrov.). V prípade celočíselných argumentovhodnôt, (vrátane ukazovateľov), v registroch RCX, RDX, R8 a R9 (v tomto poradí), v prípade argumentov s pohyblivou desatinnou čiarkou v registroch XMM0, XMM1, XMM2, XMM3. Prvý argument je teda uložený buď v registry RCX alebo v registry XMM0, druhý v RDX alebo v XMM1, tretí v R8 alebo v XMM2, štvrtý v R9 alebo v XMM3. Parametre menšie než 64 bitov ignorujú vyššie bity, netrebaa preto ich netreba nulovať. Napríklad prvý parameter typu integer (aj v x64x86-64 je to 32 bitové celé číslo) stačí uložiť do ECX. Prípadné ďalšieĎalšie argumenty sa potom ukladajú do zásobníka, av to smeromporadí sprava doľava, rovnako ako pri stdcall.
 
Volacia konvencia Microsoft x64 vyžaduje, aby v zásobníku bolo alokovaných ďalších 32 bajtov. Volaná funkcia sem potom, vkam prípadevolaná potreby,API funkcia môženiekedy uložiťukladá hodnotyobsah argumentovregistrov RCX, RDX, R8, R9. Tento 32 bajtový dodatočný priestor (shadow space) musí volajúci alokovať vždy, a to aj v prípade, že funkcia má menej ako štyri parametre. Za uvoľnenie tohto miesta rovnako ako aj miesta pre ďalšie argumenty zodpovedá volajúci.
 
Registre RAX, RCX, RDX, R8, R9, R10, R11 sú považované za nestále (volatile) a volaná funkcia ich môže kedykoľvek trvalo zmeniť. Volajúci ich samozrejme môže po návrate z funkcie obnoviť, ak si predtým uschoval ich hodnoty. Naopak, registre RBX, RBP, RDI, RSI, RSP, R12, R13, R14 a R15 sú považované za stále, za(nonvolatile). Za ich obnovenie do pôvodného stavu (v prípade ich zmeny) je zodpovedná volaná funkcia.
 
Volaná funkciaFunkcia vracia celočíselný výsledok v registri RAX, desatinný v XMM0.<syntaxhighlight>
| lokálne premenné a |
| volatile a registre |
+---------------------+
| volatile registre |
| návratová adresa | CALL
+-------------------+
| z podprogramu (RIP) |
| návratová adresa | CALL
+---------------------+
| z podprogramu (RIP) |
| 32. bajtov (R9RCX) | |\
+-------------------+
| shadow space (RCXRDX) | \|
| rezervované (RDXR8) | |
| 32. bajtov (R8R9) | |
+---------------------+ |
| (R9) | |
| 65. argument | |
+-------------------+ |
+---------------------+ |
| 56. argument | > zodpovednosť volajúceho za alokovanie a uvoľnenie
+-------------------+ |
+---------------------+ |
| 6. argument | |
| . | |
+-------------------+ |
+---------------------+ |
| . | |
| posledný argument | /|
+-------------------+ |
+---------------------+ |
| posledný argument | /
| lokálne premenné a | |
+-------------------+
| volatile registre | /
| lokálne premenné |
+---------------------+
| a |
| volatile registre |
+-------------------+
</syntaxhighlight>