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

Smazaný obsah Přidaný obsah
Fabcde (diskusia | príspevky)
d Vlozenie medzier za odstavec
Značka: editor wikitextu 2017
Fabcde (diskusia | príspevky)
Program IntToStr
Riadok 23:
Náš prvý program vypíše v príkazovom riadku krátky text a skončí.
 
'''Výpis 11a''' HelloWorld.asm (Verzia pre NASM):<syntaxhighlight lang="nasm" line="1">
; HelloWorld.asm
 
Riadok 131:
 
 
'''Výpis 21b''' HelloWorld.s (Verzia pre GAS):<syntaxhighlight lang="gas" line="1">
# HelloWorld.s
 
Riadok 174:
 
</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. Nakoľko GAS assembler vo Windows nesprávne nahrádza escape sekvenciu pre nový riadok '\n' Unixovým LF (0x0a) namiesto správnej kombinácii CR+LF (0x0d,0x0a), bolo nutné hodnotu premennej message upraviť na "Hello, World!\r\n" (prípadne pomocou osmičkovej sústavy "Hello, World!\15\12").
 
 
 
Řádek 288 ⟶ 287:
Program HelloWorld opravený v súlade s volacou konvenciou.
 
'''Výpis 32a''' HelloWorld.asm (Verzia pre NASM):<syntaxhighlight lang="nasm" line="1">
; HelloWorld.asm
 
Řádek 353 ⟶ 352:
 
 
'''Výpis 2b''' HelloWorld.s (Verzia pre GAS):<syntaxhighlight lang="gas" line="1">
 
'''Výpis 4''' HelloWorld.s (Verzia pre GAS):<syntaxhighlight lang="nasm" line="1">
# HelloWorld.s
 
Řádek 399 ⟶ 397:
add $0x28, %rsp # uvolnenie rezervovaneho miesta
 
</syntaxhighlight>Za povšimnutie stojí zápis nepriamej adresácie (používanej napríklad pri indexovaní poľa). Kým v NASM to bolo: <code>mov qword [rsp + 20h], 0</code>, v GAS je "index" uvedený pred zátvorkou: <code>movq  $0, 0x20(%rsp)</code>
 
V prípade adresy nejakej premennej je situácia identická:
 
NASM: <code>mov  [premenna + rdi * 4], eax</code>
 
GAS:  <code>mov  %eax, premenna( , %rdi, 4)</code>
 
V prípade registrov:
 
NASM: <code>mov  eax, [rbp + rsi]</code>
 
GAS:  <code>mov  (%rbp, %rsi, 1), %eax</code>
 
== Rozširujeme ==
 
=== IntToStr ===
V programe HelloWorld sme do konzolového okna vypísali jednoduchý text. V nasledujúcom programe IntToStr sa pokúsime vypísať kladné celé číslo.
 
Celé číslo je uložené v pamäti v dvojkovej sústave. My ho samozrejme chceme vypísať v desiatkovej sústave. K tomu potrebujeme zistiť jeho jednotlivé cifry a nájsť ich zodpovedajúci ascii znak. Algoritmus je jednoduchý: Číslo vydelíme (celočíselne) desiatimi. S neúplným podielom budeme opakovať delenie až kým nebude rovný nule, zvyšok po delení bude postupne obsahovať jednotlivé cifry - najskôr jednotky, potom desiatky, stovky, atď.
 
Napríklad vydelením čísla 321 : 10 dostaneme neúplný podiel 32, zvyšok je 1. Pokračujeme v delení s neúplným podielom 32 : 10 = 3, zvyšok 2, a nakoniec 3 : 10 = 0, zvyšok 3. Zvyšok postupne obsahoval jednotlivé cifry 1, 2, 3, ktoré musíme previesť na zodpovedajúci ASCII znak.
 
'''Výpis 3a''' IntToStr.asm:<syntaxhighlight lang="nasm" line="1">
; IntToStr.asm
 
; kompilacia:
; nasm -f win64 IntToStr.asm
; linkovanie:
; golink /console /ni /entry main IntToStr.obj kernel32.dll
; alternativne linkovanie:
; ld -e main -s IntToStr.obj -o IntToStr.exe c:\windows\system32\kernel32.dll
 
 
global main
 
extern GetStdHandle
extern WriteFile
extern ExitProcess
 
 
section .data use64 ; Program code
buffer: times 20 db " " ; Najvacsie 64-bitove cislo bez znamienka ma 20 cifier (2**64 - 1 = 18446744073709551615)
enter: db 0xd,0xa
BUFFER_LEN: equ enter-buffer
lpNumberOfBytesWritten: dd 0
 
 
section .text use64 ; Program code
main:
 
IntToStr:
mov rax, 1234567890 ; cislo, ktore potrebujeme vypisat (delenec)
mov rbx, 10 ; zaklad ciselnej sustavy (delitel)
 
lea rdi, [buffer+BUFFER_LEN-1] ; nastavi register rdi na koniec buffera (cifry budeme ziskavat smerom od najnizsieho radu k najvyssiemu)
 
vydel:
xor rdx, rdx ; pred delenim je nutne rdx vynulovat, inak delenie skonci chybou
div rbx ; vydeli rax / rbx, podiel vlozi do rax, zvysok do rdx
add dl, '0' ; pripocitanim 30h prevedie cislo 0-9 na znak '0'-'9'
mov byte [rdi], dl ; ulozi ziskanu cifru do buffera
sub rdi, 1 ; posunie ukazovatel na dalsi rad
or rax, rax
jnz vydel ; opakuje, kym neziska vsetky cifry
 
vypis:
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 buffer ; 2. param _In_ LPCVOID lpBuffer
mov r8d, dword BUFFER_LEN+2 ; 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 ; 1. param _In_ UINT uExitCode UINT je 32 bit aj v 64 bitovom prostredi
call ExitProcess
 
add rsp, 28h ; uvolnenie rezervovaneho miesta
</syntaxhighlight>'''Výpis 3b''' IntToStr.s:<syntaxhighlight lang="gas" line="1">
# IntToStr.s
 
# kompilacia:
# as IntToStr.s -o IntToStr.o
# linkovanie:
# ld -e main -s IntToStr.o -o IntToStr.exe c:\windows\system32\kernel32.dll
# alternativna kompilacia+linkovanie:
# gcc -m64 -nostartfiles -Wl,-emain -o IntToStr.exe IntToStr.s c:\windows\system32\kernel32.dll
 
.global main
 
.section .data
buffer: .ascii " " # Najvacsie 64-bitove cislo bez znamienka ma 20 cifier (2**64 - 1 = 18446744073709551615)
enter: .ascii "\15\12"
BUFFER_LEN = enter - buffer
lpNumberOfBytesWritten: .long 0
 
 
.section .text
 
main:
 
IntToStr:
mov $1234567890, %rax # cislo, ktore potrebujeme vypisat (delenec)
mov $10, %rbx # zaklad ciselnej sustavy (delitel)
 
lea (buffer+BUFFER_LEN-1), %rdi # nastavi register rdi na koniec buffera (cifry budeme ziskavat smerom od najnizsieho radu k najvyssiemu)
 
vydel:
xor %rdx, %rdx # pred delenim je nutne rdx vynulovat, inak delenie skonci chybou
div %rbx # vydeli rax / rbx, podiel vlozi do rax, zvysok do rdx
add $'0', %dl # pripocitanim 30h prevedie cislo 0-9 na znak '0'-'9'
mov %dl, (%rdi) # ulozi ziskanu cifru do buffera
sub $1, %rdi # posunie ukazovatel na dalsi rad
or %rax, %rax
jnz vydel # opakuje, kym neziska vsetky cifry
 
vypis:
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, $buffer, $BUFFER_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 $buffer, %rdx # 2. param _In_ LPCVOID lpBuffer
mov $BUFFER_LEN+2, %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 ==
<references />