Programovanie v assembleri vo Windows x64 (x86-64)

Mysli slobodne. Uč sa slobodne. — Zo slobodnej knižnice Wikibooks ~ Wikiknihy.
Prejsť na: navigácia, hľadanie

Toto sú poznámky k niekoľkým jednoduchým programom napísaných v jazyku symbolických adries (assembly language) určených pre platformu x86-64Windows x64.

Stručný prehľad[upraviť]

Syntax a kompilácia[upraviť]

Väčšina programov v tomto dokumente je uvedená v dvoch verziách - prvá pre prekladač (kompilátor) Netwide Assembler (NASM), druhá pre GNU Assembler (GAS). 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. Asi najmätúcejším rozdielom týchto dvoch syntaxí je prehodené poradie argumentov niektorých inštrukcií.[1] Napríklad inštrukcia "vlož hodnotu nula do registra AX" sa v NASM zapisuje MOV AX, 0, v GAS MOV $0, %AX. (Intelovská syntax pripomína priraďovací príkaz vyšších programovacích jazykov AX = 0, syntax AT&T skôr niečo ako 0 -> AX.)

Skompilovaním zdrojového súboru príslušným kompilátorom (nasm resp. as) vznikne objektový súbor, ktorý je následne pomocou 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 gcc. Kvôli jednoduchšiemu rozlíšeniu majú tu uvedené zdrojové súbory programov určených pre NASM príponu .asm, objektové .obj, a pre GNU Assembler príponu .s, resp .o.

Volanie WinAPI[upraviť]

Programy bežiace v Reálnom móde (operačný systém MS-DOS) prípadne aj v móde (režime) Virtual 8086 (operačný systém Windows) mohli využívať služby operačného systému MS-DOS prostredníctvom MS-DOS API. Tieto sa volali pomocou softvérového prerušenia inštrukciou INT, napríklad INT 21h[2]. Nakoľko 64-bitové verzie OS Windows režim Virtual 8086 nepodporujú, služby operačného systému je už možné zabezpečiť výlučne 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. 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á - volacie konvencie (calling convention).

Toto sú niektoré z najčastejšie používaných volacích konvencií v prostredí MS Windows:

  • 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
  • pascal - parametre sú ukladané na vrchol zásobníka zľava doprava, zásobník čistí volaná funkcia (napríklad inštrukciou RET n)
  • stdcall - štandard pre Win32 API, parametre sú ukladané na vrchol zásobníka sprava doľava (ako cdecl), ale zásobník čistí volaná funkcia (ako pascal)
  • Microsoft x64 - volania WinAPI v 64-bitových programoch pre MS Windows, prvé štyri parametre sú uložené v RCX/XMM0, RDX/XMM1, R8/XMM2, R9/XMM3 (integer/float), zvyšné v zásobníku sprava doľava, výsledok je vrátený v registry RAX alebo XMM0, zásobník čistí volajúca funkcia

Hello, World![upraviť]

Náš prvý program vypíše v príkazovom riadku krátky text a skončí.

Výpis 1 HelloWorld.asm (Verzia pre NASM):

 1 ; HelloWorld.asm
 2 
 3 ; kompilacia:
 4 ;   nasm -f win64 HelloWorld.asm
 5 ; linkovanie:
 6 ;   golink /console /ni /entry main HelloWorld.obj kernel32.dll
 7 ; alternativne linkovanie:
 8 ;   ld -e main -s HelloWorld.obj -o HelloWorld.exe c:\windows\system32\kernel32.dll
 9 
10 
11 global main
12 
13 extern GetStdHandle
14 extern WriteFile
15 extern ExitProcess
16 
17 
18         section .bss use64             ; neinicializovana datova oblast
19 lpNumberOfBytesWritten:  resd      1
20 
21 
22         section .text use64            ; Program code
23 message:                 db      "Hello, World!",0xd,0xa
24 MESSAGE_LEN:             equ     $-message
25 
26 main:
27         ; rax = GetStdHandle(-11)
28         ; HANDLE hStdHandle = WINAPI GetStdHandle (_In_ DWORD nStdHandle)
29         ; nStdHandle: STD_INPUT_HANDLE=-10 , STD_OUTPUT_HANDLE=-11 , STD_ERROR_HANDLE=-12
30         mov ecx, -11                   ; 1. param _In_ DWORD nStdHandle
31         call GetStdHandle
32 
33         ; rax = WriteFile(%rax, $message, $MESSAGE_LEN, %rsp-4, 0)
34         ; BOOL bErrorFlag = WINAPI WriteFile (_In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped)
35         ; WriteConsole(handle, &msg[0], 13, &written, 0)
36         mov rcx, rax                   ; 1. param _In_ HANDLE hFile
37         mov rdx, qword message         ; 2. param _In_ LPCVOID lpBuffer
38         mov r8d, dword MESSAGE_LEN     ; 3. param _In_ DWORD nNumberOfBytesToWrite
39         mov r9, lpNumberOfBytesWritten ; 4. param _Out_opt_ LPDWORD lpNumberOfBytesWritten
40         push qword 0                   ; 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
41         call WriteFile
42         add rsp, 8                     ; uvolnenie miesta v zasobniku
43 
44         ; ExitProcess(0)
45         ; VOID WINAPI ExitProcess( _In_ UINT uExitCode)
46         mov ecx, 0                     ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
47         call ExitProcess

Program síce nealokuje miesto v zásobníku (shadow space, podrobnosti ďalej) tak ako to vyžaduje volacia konvencia Microsoft x64, napriek tomu sa dal zostaviť aj spustiť. Kompilácia:

G:\>nasm -f win64 HelloWorld.asm

Linkovanie:

G:\>golink /console /ni /entry main HelloWorld.obj kernel32.dll

alebo:

G:\>ld -e main -s HelloWorld.obj -o HelloWorld.exe c:\windows\system32\kernel32.dll

Ak kompilácia a linkovanie prebehli úspešne, môžme vyskúšať náš prvý 64-bitový program:

G:\>dir
15.07.2017  13:27             2 193 HelloWorld.asm
15.07.2017  13:27             1 536 HelloWorld.exe
15.07.2017  13:27               551 HelloWorld.obj

G:\>HelloWorld.exe
Hello, World!

Obsah objektového aj spustiteľného súboru sa dá zobraziť programom objdump, napríklad:

G:\>objdump -fhD HelloWorld.obj

Direktíva global main deklaruje návestie main ako globálne a linker ho môže použiť ako štartovaciu adresu programu.

Direktíva extern GetStdHandle 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.)

Direktíva section .bss use64 definuje oblasť neinicializovaných dát. V nej je vymedzený priestor 4 bajty pre uloženie počtu úspešne vypísaných bajtov textu pozdravu. Program túto hodnotu ďalej nepoužíva.

Direktíva section .text use64 uvádza nasledujúci segment ako programový.

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 mov r8d, dword MESSAGE_LEN nahradený jej skutočnou hodnotou mov r8d, 15 (podobne ako #define MESSAGE_LEN 15 v jazyku C).

Inštrukcie

30         mov ecx, -11
31         call GetStdHandle

naplnia register ECX hodnotou -11 (STD_OUTPUT_HANDLE) a zavolajú funkciu GetStdHandle.

Funkcia vráti v registry RAX (v súlade s volacou konvenciou) handle zariadenia STDOUT.

Inštrukcie

36         mov rcx, rax                   ; 1. param _In_ HANDLE hFile
37         mov rdx, qword message         ; 2. param _In_ LPCVOID lpBuffer
38         mov r8d, dword MESSAGE_LEN     ; 3. param _In_ DWORD nNumberOfBytesToWrite
39         mov r9, lpNumberOfBytesWritten ; 4. param _Out_opt_ LPDWORD lpNumberOfBytesWritten
40         push qword 0                   ; 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
41         call WriteFile

vložia prvé štyri argumenty funkcie WriteFile do príslušných registrov, piaty do zásobníka a zavolajú ju.

Ďalšia inštrukcia add rsp, 8 uvoľní miesto v zásobníku obsadené inštrukciou push qword 0.

Nakoniec

46         mov ecx, 0                     ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
47         call ExitProcess

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 GAS):

 1 # HelloWorld.s
 2 
 3 # kompilacia:
 4 #   as HelloWorld.s -o HelloWorld.o
 5 # linkovanie:
 6 #   ld -e main -s HelloWorld.o -o HelloWorld.exe c:\windows\system32\kernel32.dll
 7 # alternativna kompilacia+linkovanie:
 8 #   gcc -m64 -nostartfiles -Wl,-emain -o HelloWorld.exe HelloWorld.s c:\windows\system32\kernel32.dll
 9 
10         .global main
11 
12         .section .bss
13 lpNumberOfBytesWritten:   .space      4
14 
15         .section .text
16 message:                  .ascii  "Hello, World!\n"
17 MESSAGE_LEN = . - message
18 
19 main:
20         /* rax = GetStdHandle(-11) */
21         /* HANDLE hStdHandle = WINAPI GetStdHandle (_In_ DWORD nStdHandle) */
22         /* nStdHandle: STD_INPUT_HANDLE=-10 , STD_OUTPUT_HANDLE=-11 , STD_ERROR_HANDLE=-12 */
23         mov $-11, %ecx                   # 1. param _In_ DWORD nStdHandle
24         call GetStdHandle
25 
26         /* rax = WriteFile(%rax, $message, $MESSAGE_LEN, %rsp-4, 0) */
27         /* BOOL bErrorFlag = WINAPI WriteFile (_In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped) */
28         /* WriteConsole(handle, &msg[0], 13, &written, 0) */
29         mov %rax, %rcx                   # 1. param _In_ HANDLE hFile
30         mov $message, %rdx               # 2. param _In_ LPCVOID lpBuffer
31         mov $MESSAGE_LEN, %r8d           # 3. param _In_ DWORD nNumberOfBytesToWrite
32         mov $lpNumberOfBytesWritten, %r9 # 4. param _Out_opt_ LPDWORD lpNumberOfBytesWritten
33         pushq $0                         # 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
34         call WriteFile
35         add $8, %rsp                     # uvolnenie miesta v zasobniku
36 
37         /* ExitProcess(0) */
38         /* VOID WINAPI ExitProcess( _In_ UINT uExitCode) */
39         mov $0, %ecx                     # 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
40         call ExitProcess

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. Kompilácia:

G:\>as HelloWorld.s -o HelloWorld.o

Linkovanie:

G:\>ld -e main -s HelloWorld.o -o HelloWorld.exe c:\windows\system32\kernel32.dll

alebo:

G:\>gcc -m64 -nostartfiles -Wl,-s,-emain -o HelloWorld.exe HelloWorld.s c:\windows\system32\kernel32.dll

Výsledok:

G:\>dir
15.07.2017  15:32             2 198 HelloWorld.s
16.07.2017  12:48               584 HelloWorld.o
16.07.2017  12:48             1 536 HelloWorld.exe

G:\>HelloWorld.exe
Hello, World!

Zásobník[upraviť]

Zásobník (stack) je pamäťová štruktúra typu LIFO. Hardvérový zásobník je realizovaný priamo v operačnej pamäti počítača. V ďalšom texte zásobníkom budeme rozumieť vždy hardvérový zásobník, nie nejakú jeho softvérovú implementáciu.

Zásobník je možné používať ľubovoľne podľa potreby. Bežne sa používa na ukladanie lokálnych premenných, alebo dočasných, pomocných hodnôt. Často sa používa aj na odovzdávanie argumentov podprogramu. Inštrukcia CALL na vrchol zásobníka 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 (Stack Pointer). RSP tak obsahuje najnižšiu adresu, na ktorej je niečo uložené.

Inštrukcia PUSH vloží novú hodnotu na vrchol zásobníka tak, že zmenší hodnotu registra RSP o počet vložených bajtov, a na túto adresu uloží novú hodnotu. Napríklad inštrukciu PUSH RAX si môžme predstaviť ako dvojicu inštrukcií

SUB RSP, 8
MOV [RSP], RAX

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 preto register RAX uložený:

|              |
+==============+
|   0. bajt    |    <- RSP = Vrchol zásobníka po (nižšia adresa)
+--------------+
|   1. bajt    |    ^
+--------------+    |
|   2. bajt    |    |
+--------------+    |
|   3. bajt    |    |
+--------------+    |
|   4. bajt    |    |  RSP - 8
+--------------+    |
|   5. bajt    |    |
+--------------+    |
|   6. bajt    |    |
+--------------+    |
|   7. bajt    |    |
+==============+
|      .       |    <- RSP = Vrchol zásobníka pred
+--------------+
|      .       |
+--------------+
|      .       |
+==============+    <- Dno zásobníka (vyššia adresa)

Z vrcholu zásobníka sa hodnoty vyberajú inštrukciou POP. Inštrukcia POP RAX 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:

MOV RAX, [RSP]
ADD RSP, 8

Ku hodnotám uloženým v zásobníku je stále možné pristupovať aj priamo, ako ku hocijakým iným dátam uloženým kdekoľvek v operačnej pamäti, napríklad pomocou relatívneho odkazu na vrchol zásobníka:

MOV EAX, [RSP+4]

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:

SUB RSP, n

Nepotrebné miesto na vrchole zásobníka uvoľní:

ADD RSP, n

Poznámka: Rozhranie X86-64 ABI použité v System V umožňuje používať aj Red zone - oblasť 128 bajtov tesne nad vrcholom zásobníka.

Podprogram[upraviť]

Volanie funkcie (podprogramu) sa v jazyku symbolických adries realizuje inštrukciou CALL. Inštrukcia najskôr vloží na vrchol zásobníka hodnotu registra RIP (Instruction Pointer, niekedy nazývaný aj PC - Program Counter), v ktorom sa už nachádza adresa ďalšej inštrukcie. Následne zmenou hodnoty registra RIP sa zrealizuje skok na požadovanú adresu. Podprogram končí inštrukciou RET, ktorá odoberie z vrcholu zásobníka návratovú adresu (pôvodnú hodnotu registra RIP) a vloží ju späť do registra RIP. Program potom pokračuje tam, kde bol prerušený podprogramom, čiže inštrukciou bezprostredne nasledujúcou za inštrukciou CALL.

Volacia konvencia Microsoft x64[upraviť]

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, netreba ich 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.

Non-leaf funkcia, čiže funkcia, ktorá tiež volá nejakú funkciu, vyžaduje zarovnanie zásobníka na 16 bajtov.

Volacia konvencia Microsoft x64 ďalej vyžaduje, aby v zásobníku bolo alokovaných dodatočný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) zodpovedá volaná funkcia.

Funkcia vracia celočíselný výsledok v registri RAX, desatinný v XMM0.

Obsah zásobníka po zavolaní WinAPI funkcie :

+---------------------+    (nižšia adresa)
| zarovnanie,         |                                   \
| lokálne premenné a  |                                    > volaná funkcia
| volatile registre   |                                   /
+=====================+
| návratová adresa    |   CALL                            \
| z podprogramu (RIP) |                                   |
+---------------------+                                   |
| 32. bajtov    (RCX) |    \                              |
| shadow space  (RDX) |    |                              |
|               (R8)  |    |                              |
|               (R9)  |    |                              |
+---------------------+    |                              |
| 5. argument         |    |                               > volajúca funkcia
+---------------------+    |                              |
| 6. argument         |     > zodpovednosť volajúceho     |
+---------------------+    |  za alokovanie a uvoľnenie   |
|          .          |    |                              |
+---------------------+    |                              |
| posledný argument   |    |                              |
+---------------------+    |                              |
| zarovnanie,         |    |                              |
| lokálne premenné a  |    |                              |
| volatile registre   |    /                              /
+=====================+    (vyššia adresa)

Hello, World! v.2[upraviť]

Program HelloWorld opravený v súlade s volacou konvenciou.

Výpis 3 HelloWorld.asm (Verzia pre NASM):

 1 ; HelloWorld.asm
 2 
 3 ; kompilacia:
 4 ;   nasm -f win64 HelloWorld.asm
 5 ; linkovanie:
 6 ;   golink /console /ni /entry main HelloWorld.obj kernel32.dll
 7 ; alternativne linkovanie:
 8 ;   ld -e main -s HelloWorld.obj -o HelloWorld.exe c:\windows\system32\kernel32.dll
 9 
10 
11 global main
12 
13 extern GetStdHandle
14 extern WriteFile
15 extern ExitProcess
16 
17 
18         section .bss use64             ; neinicializovana datova oblast
19 lpNumberOfBytesWritten:  resd      1
20 
21 
22         section .text use64            ; Program code
23 message:                 db      "Hello, World!",0xd,0xa
24 MESSAGE_LEN:             equ     $-message
25 
26 main:
27         sub rsp, 28h                  ; rezervovanie miesta v zasobniku pre 5-ty argument, shadow space a zarovnanie
28 
29         ; rax = GetStdHandle(-11)
30         ; HANDLE hStdHandle = WINAPI GetStdHandle (_In_ DWORD nStdHandle)
31         ; nStdHandle: STD_INPUT_HANDLE=-10 , STD_OUTPUT_HANDLE=-11 , STD_ERROR_HANDLE=-12
32         mov ecx, -11                  ; 1. param _In_ DWORD nStdHandle
33         call GetStdHandle
34 
35         ; rax = WriteFile(%rax, $message, $MESSAGE_LEN, %rsp-4, 0)
36         ; BOOL bErrorFlag = WINAPI WriteFile (_In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped)
37         ; WriteConsole(handle, &msg[0], 13, &written, 0)
38         mov rcx, rax                   ; 1. param _In_ HANDLE hFile
39         mov rdx, qword message         ; 2. param _In_ LPCVOID lpBuffer
40         mov r8d, dword MESSAGE_LEN     ; 3. param _In_ DWORD nNumberOfBytesToWrite
41         mov r9, lpNumberOfBytesWritten ; 4. param _Out_opt_ LPDWORD lpNumberOfBytesWritten
42         mov qword [rsp+20h], 0         ; 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
43         call WriteFile
44 
45         ; ExitProcess(0)
46         ; VOID WINAPI ExitProcess( _In_ UINT uExitCode)
47         xor ecx, ecx                   ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
48         call ExitProcess
49 
50         add rsp, 28h                   ; uvolnenie rezervovaneho miesta

Rozdiel v porovnaní s prvou verziou spočíva v rezervovaní miesta v zásobníku tak, aby sa sem vošli argumenty (8 bajtov pre piaty argument funkcie WriteFile) a shadow space (32 bajtov). Vďaka inštrukcii CALL, ktorá ešte na vrchol zásobníka vloží obsah RIP (8 bajtov) bude zásobník zarovnaný na požadovaných 16 bajtov:

27         sub rsp, 28h                  ; rezervovanie miesta v zasobniku pre 5-ty argument, shadow space a zarovnanie

Piaty argument potom samozrejme nie je možné vložiť na požadovanú pozíciu inštrukciou PUSH, ale:

42         mov qword [rsp+20h], 0         ; 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped

Zinštásobník je pripravený aj pre funkciu ExitProcess, stačí ju zavolať a až potom uvoľniť rezervované miesto:

50         add rsp, 28h                  ; uvolnenie rezervovaneho miesta

Poslednou zmenou je nahradenie inštrukcie

46         mov ecx, 0                     ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi

inštrukciou

47         xor ecx, ecx                   ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi

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í (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.[3][4][5] Výpis 4 HelloWorld.s (Verzia pre GAS):

 1 # HelloWorld.s
 2 
 3 # kompilacia:
 4 #   as HelloWorld.s -o HelloWorld.o
 5 # linkovanie:
 6 #   ld -e main -s HelloWorld.o -o HelloWorld.exe c:\windows\system32\kernel32.dll
 7 # alternativna kompilacia+linkovanie:
 8 #   gcc -m64 -nostartfiles -Wl,-emain -o HelloWorld.exe HelloWorld.s c:\windows\system32\kernel32.dll
 9 
10         .global main
11 
12         .section .bss
13 lpNumberOfBytesWritten:   .space      4
14 
15         .section .text
16 message:                  .ascii  "Hello, World!\15\12"
17 MESSAGE_LEN = . - message
18 
19 main:
20         sub $0x28, %rsp                  # rezervovanie miesta v zasobniku pre 5-ty argument, shadow space a zarovnanie
21 
22         /* rax = GetStdHandle(-11) */
23         /* HANDLE hStdHandle = WINAPI GetStdHandle (_In_ DWORD nStdHandle) */
24         /* nStdHandle: STD_INPUT_HANDLE=-10 , STD_OUTPUT_HANDLE=-11 , STD_ERROR_HANDLE=-12 */
25         mov $-11, %ecx                   # 1. param _In_ DWORD nStdHandle
26         call GetStdHandle
27 
28         /* rax = WriteFile(%rax, $message, $MESSAGE_LEN, %rsp-4, 0) */
29         /* BOOL bErrorFlag = WINAPI WriteFile (_In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped) */
30         /* WriteConsole(handle, &msg[0], 13, &written, 0) */
31         mov %rax, %rcx                   # 1. param _In_ HANDLE hFile
32         mov $message, %rdx               # 2. param _In_ LPCVOID lpBuffer
33         mov $MESSAGE_LEN, %r8d           # 3. param _In_ DWORD nNumberOfBytesToWrite
34         mov $lpNumberOfBytesWritten, %r9 # 4. param _Out_opt_ LPDWORD lpNumberOfBytesWritten
35         movq $0, 0x20(%rsp)              # 5. param _Inout_opt_ LPOVERLAPPED lpOverlapped
36         call WriteFile
37 
38         /* ExitProcess(0) */
39         /* VOID WINAPI ExitProcess( _In_ UINT uExitCode) */
40         xor %ecx, %ecx                   # 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
41         call ExitProcess
42 
43         add $0x28, %rsp                  # uvolnenie rezervovaneho miesta

Referencie[upraviť]

  1. Ram Narayan (2007-10-17). "Linux assemblers: A comparison of GAS and NASM"
  2. Ralf Brown's Interrupt List
  3. 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
  4. http://frdsa.fri.uniza.sk/~janosik/Kniha/Prudove_sprac.html
  5. https://forum.root.cz/index.php?topic=2388.0

Ďalšie zdroje[upraviť]

Pozri aj[upraviť]