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

Smazaný obsah Přidaný obsah
Fabcde (diskusia | príspevky)
d formulacie
Fabcde (diskusia | príspevky)
Bez shrnutí editace
Riadok 6:
</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 pripomína priraďovací príkaz vyšších programovacích jazykov <code>AX = 0</code>, syntax AT&T skôr niečo ako <code>0 -> AX</code>.)
 
Skompilovaním zdrojového súboru príslušným kompilátorom (nasm resp. as) vznikne [https://cs.wikipedia.org/wiki/Objektov%C3%BD_k%C3%B3d objektový súbor], ktorý je následne pomocou [https://cs.wikipedia.org/wiki/Linker linkeru] golink, resp. 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 ===
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) aleboprípadne aj 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 prostredníctvom [[wikipedia:MS-DOS_API|MS-DOS API]]. Tieto sa volali pomocou [https://cs.wikipedia.org/wiki/P%C5%99eru%C5%A1en%C3%AD#Softwarov.C3.A9_p.C5.99eru.C5.A1en.C3.AD softvérového prerušenia] inštrukciou INT, napríklad <code>INT 21h</code><ref>[http://www.cs.cmu.edu/~ralf/files.html Ralf Brown's Interrupt List]</ref>. KeďžeNakoľko 64-bitové verzie OS Windows režim Virtual 8086 nepodporujú, služby operačného systému je možné zabezpečiť jedinevýlučne volaním funkcií [https://cs.wikipedia.org/wiki/Windows_API 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]).
 
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. Kvôli veľkému množstvu funkcií, ktoré sú k dispozícii (bežný prípad knižníc), bolo nutné zaviesť nejaké spoločné pravidlá - [https://cs.wikipedia.org/wiki/Volac%C3%AD_konvence volacie konvencie] (calling convention).
Toto sú niektoré z najčastejšie používaných [[wikipedia:X86_calling_conventions|volacích konvencií (calling convention)]] v prostredí MS Windows:
 
Toto sú niektoré z najčastejšie používaných [[wikipedia:X86_calling_conventions|volacích konvencií (calling convention)]] v prostredí 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 21 ⟶ 23:
Náš prvý program vypíše v príkazovom riadku krátky text a skončí.
 
'''Výpis 1''' HelloWorld.asm (Verzia pre NASM):<syntaxhighlight lang="nasm" line="1">
; HelloWorld.asm
 
Řádek 67 ⟶ 69:
; ExitProcess(0)
; VOID WINAPI ExitProcess( _In_ UINT uExitCode)
xormov ecx, ecx0 ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
Řádek 82 ⟶ 84:
15.07.2017 13:27 2 193 HelloWorld.asm
15.07.2017 13:27 1 536 HelloWorld.exe
15.07.2017 13:27 548551 HelloWorld.obj
 
G:\>HelloWorld.exe
Řádek 107 ⟶ 109:
mov ecx, -11
call GetStdHandle
</syntaxhighlight>naplnia register ECX hodnotou -11 (STD_OUTPUT_HANDLE) a zavolajú funkciu [https://docs.microsoft.com/en-us/windows/console/getstdhandle GetStdHandle].
 
Funkcia vráti v registry RAX (v súlade s volacou konvenciou) handle zariadenia STDOUT.
Řádek 123 ⟶ 125:
 
Nakoniec<syntaxhighlight lang="nasm" line="1" start="46">
xormov ecx, ecx0 ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
</syntaxhighlight>vynuluje obsah registra ECX a ukončí program. Jediným argumentom funkcie ExitProcess (uložený v registry ECX) je exit code programu.
 
'''Výpis 2''' HelloWorld.s (Verzia pre GNU assemblerGAS):<syntaxhighlight lang="gas" line="1">
# HelloWorld.s
 
Řádek 166 ⟶ 168:
/* ExitProcess(0) */
/* VOID WINAPI ExitProcess( _In_ UINT uExitCode) */
xormov %ecx$0, %ecx # 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
 
</syntaxhighlight>V GAS je každý neznámy symbol defaultne považovaný za externý, preto nie je potrebné názvy API funkcií deklarovať direktívou extern.
</syntaxhighlight>Kompilácia:<syntaxhighlight>
 
</syntaxhighlight>Kompilácia:<syntaxhighlight>
G:\>as HelloWorld.s -o HelloWorld.o
</syntaxhighlight>Linkovanie:<syntaxhighlight>
Řádek 195 ⟶ 199:
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 preto register RAX uložený:<syntaxhighlight>
| |
+==============+
| 0. bajt | <- RSP (= Vrchol zásobníka) po ... (nižšia adresa)
+--------------+
| 1. bajt | ^
Řádek 214 ⟶ 218:
| 7. bajt | |
+==============+
| . | <- RSP (= Vrchol zásobníka) pred
+--------------+
| . |
+--------------+
| . |
+==============+ <- Dno zásobníka ... (vyššia adresa)
</syntaxhighlight>Z vrcholu zásobníka sa hodnoty vyberajú inštrukciou POP. Inštrukcia <code>POP RAX</code> z vrcholu 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">
MOV RAX, [RSP]
Řádek 229 ⟶ 233:
</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 [https://cs.wikipedia.org/wiki/UNIX_System_V System V] umožňuje používať aj [[wikipedia:Red_zone_(computing)|Red zone]] - oblasť 128 bajtov tesne nad vrcholom zásobníka.
 
=== Podprogram ===
Řádek 250 ⟶ 254:
 
''Obsah zásobníka po zavolaní WinAPI funkcie :''<syntaxhighlight>
+---------------------+ ... (nižšia adresa \)
| zarovnanie, | \
| lokálne premenné a | > volaná funkcia
| volatile registre | /
Řádek 270 ⟶ 275:
| posledný argument | | |
+---------------------+ | |
| zarovnanie, | | |
| lokálne premenné a | | |
| volatile registre | / /
+=====================+ ... (vyššia adresa)
</syntaxhighlight>
 
Řádek 278 ⟶ 284:
Program HelloWorld opravený v súlade s volacou konvenciou.
 
'''Výpis 3''' HelloWorld.asm (Verzia pre NASM):<syntaxhighlight lang="nasm" line="1">
; HelloWorld.asm
 
Řádek 334 ⟶ 340:
</syntaxhighlight>Piaty argument potom samozrejme nie je možné vložiť na požadovanú pozíciu inštrukciou PUSH, ale:<syntaxhighlight lang="nasm" line="1" start="42">
mov qword [rsp+20h], 0 ; 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
</syntaxhighlight>ZásobníkZinštásobník je pripravený aj pre funkciu ExitProcess, stačí ju zavolať a až potom uvoľniť rezervované miesto:<syntaxhighlight lang="nasm" line="1" start="50">
add rsp, 28h ; uvolnenie rezervovaneho miesta
</syntaxhighlight>Poslednou zmenou je nahradenie inštrukcie<syntaxhighlight lang="nasm" line="1" start="46">
mov ecx, 0 ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
</syntaxhighlight>inštrukciou<syntaxhighlight lang="nasm" line="1" start="47">
xor ecx, ecx ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
</syntaxhighlight>Je to pokus o optimalizáciu kódu, kedže inštrukcia xor ecx,ecx tiež vynuluje obsah registra ECX, ale po preložení zaberá menej bajtov. Optimalizácia kódu je však dnes kvôli prúdovému spracovaniu inštrukcií ([https://cs.wikipedia.org/wiki/Pipelining pipelining]), hypertredingu, cache pamäti, atď mimoriadne zložitá a vyžaduje hlboké znalosti. Spravidla dobrý kompilátor/optimalizátor jazyka C dokáže vytvoriť rýchlejší kód než hoci aj kratší ale neoptimalizovaný kód v jazyku symbolických adries.<ref>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</ref><ref>http://frdsa.fri.uniza.sk/~janosik/Kniha/Prudove_sprac.html</ref><ref>https://forum.root.cz/index.php?topic=2388.0</ref>
 
'''Výpis 4''' HelloWorld.s (Verzia pre GAS):<syntaxhighlight lang="nasm" line="1">
# HelloWorld.s
 
# kompilacia:
# as HelloWorld.s -o HelloWorld.o
# linkovanie:
# ld -e main -s HelloWorld.o -o HelloWorld.exe c:\windows\system32\kernel32.dll
# alternativna kompilacia+linkovanie:
# gcc -m64 -nostartfiles -Wl,-emain -o HelloWorld.exe HelloWorld.s c:\windows\system32\kernel32.dll
 
.global main
 
.section .bss
lpNumberOfBytesWritten: .space 4
 
.section .text
message: .ascii "Hello, World!\15\12"
MESSAGE_LEN = . - message
 
main:
sub $0x28, %rsp # 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 $-11, %ecx # 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 %rax, %rcx # 1. param _In_ HANDLE hFile
mov $message, %rdx # 2. param _In_ LPCVOID lpBuffer
mov $MESSAGE_LEN, %r8d # 3. param _In_ DWORD nNumberOfBytesToWrite
mov $lpNumberOfBytesWritten, %r9 # 4. param _Out_opt_ LPDWORD lpNumberOfBytesWritten
movq $0, 0x20(%rsp) # 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
call WriteFile
 
/* ExitProcess(0) */
/* VOID WINAPI ExitProcess( _In_ UINT uExitCode) */
xor %ecx, %ecx # 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
 
add $0x28, %rsp # uvolnenie rezervovaneho miesta
 
</syntaxhighlight>
== Referencie ==