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

Smazaný obsah Přidaný obsah
Fabcde (diskusia | príspevky)
2. Kapitola
Fabcde (diskusia | príspevky)
Revízia textu
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 operačný[https://cs.wikipedia.org/wiki/Po%C4%8D%C3%ADta%C4%8Dov%C3%A1_platforma systém '''Windows x64''' na procesoreplatformu] '''[[w:X86-64|x86-64]]''' – '''Windows x64'''. ProgramyVäčšinoupísanéuvedené dve verzie programov, prvá pre [[w:Prekladač|prekladač]] (kompilátor) [[w:Netwide_Assembler|Netwide Assembler]] (NASM), alebodruhá pre [https://cs.wikipedia.org/wiki/GNU_Assembler GNU Assembler] (GAS).
 
[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 je súčasťou [[w:GNU_Compiler_Collection|gcc]].
 
Kvôli jednoduchšiemu rozlíšeniu majú [[w:Zdrojový_kód|zdrojové súbory]] programov pre kompilátor NASM príponu .asm, objektové .obj, pre GNU Assembler .s, resp .o.
 
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>.
 
[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 je 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 konvencie ===
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). PodprogramuFunkcii (funkciipodprogramu) je zvyčajne potrebné väčšinou 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 dátúdajov medzi volajúcim a volaným podprogramom. Je možné zvoliť akýkoľvek fungujúci spôsob ([https://cs.wikipedia.org/wiki/Volac%C3%AD_konvence volacia konvencia]), či už pomocou registrov, pamäti, zásobníka, atď, len treba vedieť o každej jednej 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]).
 
Toto sú niektoré z najčastejšie používaných [[wikipedia:X86_calling_conventions|volacích konvencií (calling convention)]] vov sveteprostredí MS Windows]]:
* [[wikipedia:X86_calling_conventions#cdecl|cdecl]] - C declaration, pochádza z jazyka C, parametre sú ukladané na vrchol zásobníka postupne sprava doľava (kvôli podpore premenlivého počtu argumentov), výsledok je uložený buď v registry EAX (integer) alebo ST0 (float), zásobník čistí volajúca funkcia
 
Řádek 36 ⟶ 34:
extern WriteFile
extern ExitProcess
 
 
section .bss use64 ; neinicializovana datova oblast
lpNumberOfBytesWritten: resd 1
 
 
section .text use64 ; Program code
message: db "Hello, World!",0xd,0xa
MESSAGE_LEN: equ $-message
 
main:
; rax = GetStdHandle(-11)
Řádek 61 ⟶ 66:
xor ecx, ecx ; UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
 
</syntaxhighlight>Program síce nealokuje miesto v zásobníku, ako to vyžaduje volacia konvencia Microsoft x64 (podrobnosti ďalej), napriek tomu sa dal zostaviť aj spustiť.
message: db "Hello, World!",0xd,0xa
MESSAGE_LEN: equ $-message
 
 
section .bss use64 ; neinicializovana datova oblast
 
lpNumberOfBytesWritten: resd 1
 
</syntaxhighlight>Program síce nealokuje miesto v zásobníku, ako to vyžaduje volacia konvencia Microsoft x64 (podrobnosti ďalej), napriek tomu sa dá zostaviť aj spustiť.
 
Kompilácia:<syntaxhighlight>
Řádek 80 ⟶ 77:
</syntaxhighlight>Ak kompilácia a linkovanie prebehli úspešne, môžme vyskúšať náš prvý 64-bitový program:<syntaxhighlight>
G:\>dir
15.07.2017 13:27 2 189193 HelloWorld.asm
15.07.2017 13:27 1 536 HelloWorld.exe
15.07.2017 13:27 548 HelloWorld.obj
Řádek 94 ⟶ 91:
Direktíva <code>extern GetStdHandle</code> deklaruje symbol GetStdHandle (WinAPI funkcia) ako externý, čiže nachádzajúci sa v niektorom z ďalších pripojených súborov, v tomto prípade v dynamicky linkovanej knižnici kernel32.dll. (Napriek tej 32 v názve, vo Windows x64 je to 64-bitová knižnica.)
 
ADirektíva nakoniec,<code>section v.bss oblastiuse64</code> definuje oblasť neinicializovaných dát. V nej je vyhradený priestor 4 bajty pre uloženie počtu úspešne zapísanýchvypísaných bajtov textu pozdravu. Program túto hodnotu ďalej nepoužíva.
Direktíva <code>section .text use64</code> uvádza nasledujúci segment ako programový, readonly.
 
Direktíva <code>section .text use64</code> uvádza nasledujúci segment ako programový, readonly.
Inštrukcie<syntaxhighlight lang="nasm" line="1" start="23">
 
Na adrese mesage je uložený text pozdravu. Pristupuje sa k nej síce ako k premennej, nachádza sa však v sekcii programu, preto je readonly.
 
Konštanta MESSAGE_LEN obsahuje počet bajtov (dĺžku textu), ktorý chceme vypísať. Konštanta, na rozdiel od premennej, nie je uložená na nejakej konkrétnej adrese, ale v čase kompilácie bude každý odkaz na ňu, napríklad <code>mov r8d, dword MESSAGE_LEN</code> nahradený jej skutočnou hodnotou <code>mov r8d, 15</code> (podobne ako <code>#define MESSAGE_LEN 15</code> v jazyku C).
 
Inštrukcie<syntaxhighlight lang="nasm" line="1" start="2330">
mov ecx, -11
call GetStdHandle
Řádek 103 ⟶ 106:
Funkcia vráti v registry RAX (v súlade s volacou konvenciou) handle zariadenia STDOUT.
 
Inštrukcie<syntaxhighlight lang="nasm" line="1" start="2936">
mov rcx, rax                   ; 1. param _In_ HANDLE hFile
mov rdx, qword message         ; 2. param _In_ LPCVOID lpBuffer
Řádek 114 ⟶ 117:
Ďalšia inštrukcia <code>add rsp, 8</code> uvoľní miesto v zásobníku obsadené inštrukciou <code>push qword 0</code>.
 
Nakoniec<syntaxhighlight lang="nasm" line="1" start="3946">
xor ecx, ecx
call ExitProcess
</syntaxhighlight>vynuluje obsah registra ECX a ukončí program. Jediným argumentom funkcie ExitProcess (uložený v registry ECX) je exit code programu.
 
Verzia pre GNU assembler:<syntaxhighlight lang="gas" line="1">
Na adrese mesage je uložený text pozdravu. Pristupuje sa k nej síce ako k premennej, nachádza sa však v sekcii programu, preto je readonly.
 
Konštanta MESSAGE_LEN obsahuje počet bajtov (dĺžku textu), ktorý chceme vypísať. Konštanta, na rozdiel od premennej, nie je uložená na nejakej konkrétnej adrese, ale v čase kompilácie bude každý odkaz na ňu, napríklad <code>mov r8d, dword MESSAGE_LEN</code> nahradený jej skutočnou hodnotou <code>mov r8d, 15</code> (ako <code>#define MESSAGE_LEN 15</code> v jazyku C).
 
A nakoniec, v oblasti neinicializovaných dát je vyhradený priestor 4 bajty pre uloženie počtu úspešne zapísaných bajtov. Program túto hodnotu ďalej nepoužíva.
 
V nasledujúcej kapitole sa dozvieme viac o volacej konvencii Microsoft x64 a opravíme spomínanú chybu, ktorej sme sa dopustili v programe HelloWorld. Najskôr však ešte verzia pre GNU assembler:<syntaxhighlight lang="gas" line="1">
# HelloWorld.s
 
Řádek 136 ⟶ 133:
 
.global main
 
.section .bss
lpNumberOfBytesWritten: .space 4
 
.section .text
message: .ascii "Hello, World!\n"
MESSAGE_LEN = . - message
 
main:
/* rax = GetStdHandle(-11) */
Řádek 160 ⟶ 163:
xor %ecx, %ecx # 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
 
message: .ascii "Hello, World!\n"
MESSAGE_LEN = . - message
 
.section .bss
lpNumberOfBytesWritten: .space 4
 
</syntaxhighlight>Kompilácia:<syntaxhighlight>
Řádek 175 ⟶ 172:
</syntaxhighlight>Výsledok:<syntaxhighlight>
G:\>dir
15.07.2017 15:32 2 193198 HelloWorld.s
16.07.2017 12:48 614584 HelloWorld.o
16.07.2017 12:48 1 536 HelloWorld.exe
 
Řádek 184 ⟶ 181:
 
== Volacia konvencia Microsoft x64 ==
TátoV kapitolatejto uvádzakapitole sú uvedené niektoré ďalšie podrobnosti volacej konvencie Microsoft x64. Najskôr však niečo o zásobníku.
 
=== Zásobník ===
Zásobník (stack) je pamäťová štruktúra typu LIFO a vo všeobecnosti môže byť realizovaný rôznymi spôsobmi. 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 POP. V ďalšom texte sa pod zásobníkom budeme rozumieťrozumie vždy hardvérový zásobník, nie nejakúnejaká jeho softvérovúsoftvérová implementáciuimplementácia.<!-- Tu pokračovať revíziou -->
 
Zásobník používa inštrukcia CALL na uloženie návratovej adresy z podprogramu (funkcie). Je ideálny aj na ukladanie lokálnych premenných, často sa používa aj na odovzdávanie argumentov podprogramu.