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

Smazaný obsah Přidaný obsah
Bez shrnutí editace
Fabcde (diskusia | príspevky)
formulacie, HelloWorld v.2
Riadok 1:
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 ===
Väčšinou sú tu 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). Kým 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ýmAsi znajvýraznejsím rozdielovrozdielom týchto dvoch syntaxí je opačnéprehodené poradie argumentov v niektorých inštrukciáchinštrukcií.<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>, syntax AT&T syntax (GAS) skôr niečo ako <code>0 -> AX</code>.)
 
Skompilovaním zdrojového súboru príslušným kompilátorom vznikne [https://cs.wikipedia.org/wiki/Objektov%C3%BD_k%C3%B3d Objektovéobjektový súborysúbor], ktorý zlinkovanéje do spustiteľného exe súborunásledne pomocou [https://cs.wikipedia.org/wiki/Linker linkeru] golink, aleboresp. ld zlinkovaný do výsledného spustiteľného súboru. Kompilátor as (GNU Assembler) a linker ld sú súčasťou [[w:GNU_Compiler_Collection|gcc]]. Kvôli jednoduchšiemu rozlíšeniu majú tu uvedené [[w:Zdrojový_kód|zdrojové súbory]] programov určených pre kompilátor NASM príponu .asm, objektové .obj, a pre GNU Assembler príponu .s, resp .o.
 
=== Volanie WinAPI ===
Riadok 68:
call ExitProcess
</syntaxhighlight>Program síce nealokuje miesto v zásobníku, tak ako to vyžaduje volacia konvencia Microsoft x64 (podrobnosti ďalej), napriek tomu sa dal zostaviť aj spustiť.
 
Kompilácia:<syntaxhighlight>
Riadok 88:
</syntaxhighlight>
 
Obsah objektového aj spustiteľného súboru sa dá zobraziť programom objdump, napríklad:<syntaxhighlight>
Direktíva <code>global main</code> deklaruje návestie main ako globálne a linker ho môže použiť ako štartovaciu adresu programu.
G:\>objdump -fhD HelloWorld.obj
</syntaxhighlight>Direktíva <code>global main</code> deklaruje návestie main ako globálne a linker ho môže použiť ako štartovaciu adresu programu.
 
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.)
Řádek 191 ⟶ 193:
SUB RSP, 8
MOV [RSP], RAX
</syntaxhighlight>Keďže architektúra x86/x86-64 používa na ukladanie viac-bajtových hodnôt usporiadanie [[w:Endianita|little-endian]], t.j. na nižšej adrese je uložený menej významný/nižší bajt, v zásobníku bude register RAX uložený:<syntaxhighlight>
| |
+==============+
Řádek 221 ⟶ 223:
</syntaxhighlight>Ku hodnotá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:<syntaxhighlight lang="nasm">
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. Pre bezpečné uloženie údajov do zásobníka je preto nutné vždy najskôr alokovať potrebný priestor:<syntaxhighlight lang="nasm">
SUB RSP, n
</syntaxhighlight>Nepotrebné miesto na vrchole zásobníka uvoľní:<syntaxhighlight lang="nasm">
ADD RSP, n
</syntaxhighlight>Poznámka: Rozhranie X86-64 ABI použité v System V umožňuje používať aj [[wikipedia:Red_zone_(computing)|Red zone]] - oblasť 128 bajtov tesne nad vrcholom zásobníka.
</syntaxhighlight>
 
=== Volacia konvencia Microsoft x64 ===
Volanie funkcie (podprogramu) sa realizuje inštrukciou CALL. Inštrukcia CALL najskôr uložívloží na vrchol zásobníka hodnotu registra RIP, v ktorom sa už nachádza adresa nasledujúcej inštrukcie. Následne zmenou hodnoty registra RIP sa zrealizuje skok na požadovanú adresu. Podprogram končí inštrukciou RET, ktorá vyberie z vrcholu zásobníka návratovú adresu (pôvodnú hodnotu registra RIP) a vloží ju späť do registra RIP.
 
64-bitové verzie funkcií WindowsAPI používajú volaciu konvenciu Microsoft x64. Prvé štyri argumenty sú uložené v registroch (64-bitová architektúra x86-64 má oproti 32-bitovej architektúre x86 k dispozícii viac registrov). V prípade celočíselných hodnôt, vrátane ukazovateľov, v RCX, RDX, R8 a R9 (v tomto poradí), v prípade argumentov s pohyblivou desatinnou čiarkou v XMM0, XMM1, XMM2, XMM3. Prvý argument je teda uložený buď v registry RCX alebo v 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 a preto ich netreba nulovať. Napríklad prvý parameter typu integer (aj v x86-64 je to 32 bitové celé číslo) stačí uložiť do ECX. Ďalšie argumenty sa ukladajú do zásobníka v poradí 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, kam volaná API funkcia niekedy ukladá obsah registrov RCX, RDX, R8, R9. Tento 32 bajtový 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 (na rozdiel od konvencie stdcall).
 
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 (nonvolatile). Za ich obnovenie do pôvodného stavu (v prípade ich zmeny) je zodpovednázodpovedá volaná funkcia.
 
Funkcia vracia celočíselný výsledok v registri RAX, desatinný v XMM0.<syntaxhighlight>
Řádek 259 ⟶ 261:
| volatile registre | /
+---------------------+
</syntaxhighlight>
 
=== Hello, World! v.2 ===
<syntaxhighlight lang="nasm" line="1">
; HelloWorld.asm
 
; kompilacia:
; nasm -f win64 HelloWorld.asm
; linkovanie:
; golink /console /ni /entry main HelloWorld.obj kernel32.dll
; alternativne linkovanie:
; ld -e main -s HelloWorld.obj -o HelloWorld.exe c:\windows\system32\kernel32.dll
 
 
global main
 
extern GetStdHandle
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:
sub rsp, 28h ; rezervovanie miesta v zasobniku pre 5-ty argument, shadow space a zarovnanie
 
; rax = GetStdHandle(-11)
; HANDLE hStdHandle = WINAPI GetStdHandle (_In_ DWORD nStdHandle)
; nStdHandle: STD_INPUT_HANDLE=-10 , STD_OUTPUT_HANDLE=-11 , STD_ERROR_HANDLE=-12
mov ecx, -11 ; 1. param _In_ DWORD nStdHandle
call GetStdHandle
 
; rax = WriteFile(%rax, $message, $MESSAGE_LEN, %rsp-4, 0)
; BOOL bErrorFlag = WINAPI WriteFile (_In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped)
; WriteConsole(handle, &msg[0], 13, &written, 0)
mov rcx, rax ; 1. param _In_ HANDLE hFile
mov rdx, qword message ; 2. param _In_ LPCVOID lpBuffer
mov r8d, dword MESSAGE_LEN ; 3. param _In_ DWORD nNumberOfBytesToWrite
mov r9, lpNumberOfBytesWritten ; 4. param _Out_opt_ LPDWORD lpNumberOfBytesWritten
mov qword [rsp+20h], 0 ; 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
call WriteFile
 
; ExitProcess(0)
; VOID WINAPI ExitProcess( _In_ UINT uExitCode)
xor ecx, ecx ; UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
 
add rsp, 28h ; uvolnenie rezervovaneho miesta
 
</syntaxhighlight>
 
Řádek 268 ⟶ 325:
* http://frdsa.fri.uniza.sk/~janosik/Kniha/ProgJSA.html
* https://www.pcrevue.sk/a/ASSEMBLER-pod-Windows--Uvod--1--cast
* https://www.zive.cz/clanky/vyznejte-se-v-procesoru--velky-prehled-technologii/historie-procesoru-cache-a-skalarni-procesory/sc-3-a-147124-ch-66129/default.aspx#articleStart
* https://forum.root.cz/index.php?topic=2388.0
* https://cs.wikipedia.org/wiki/Pipelining
* https://en.wikipedia.org/wiki/Branch_predictor
 
== Pozri aj ==