Hi!
I'm trying to learn assembly programming, and this is so far the most complex thing I've written. It's a function (named Write_int) that takes a 32-bit signed integer as parameter and prints it to the console (using only Windows API). I use this as a .dll so I can call the function from my other nasm-programs. It seems to be working correctly, but I'd like to have some feedback on what I could improve, are there any possible bugs that I've missed, and so on. I also have a few questions:
1. How do functions like printf in C do this at the machine code level? Is it similar to this, and is it more efficient or less efficient?
2. What names should I use for labels that are part of a function?
Here's the code:
extern GetProcessHeap
extern HeapAlloc
extern HeapFree
extern GetStdHandle
extern WriteConsoleA
export Write_int
section .rdata ;this data can't be modified
const10: dd 10
section .data
numCharsWritten: dd 0
testNum: dd 0
section .text
bits 64
;Write_int(rcx number)
Write_int:
sub rsp, 48
mov [rsp + 32], rcx ;save the number for later use
;allocate the string that stores the number
call GetProcessHeap ;heap handle now in rax
;LPVOID WINAPI HeapAlloc( _In_ HANDLE hHeap, _In_ DWORD dwFlags, _In_ SIZE_T dwBytes)
mov rcx, rax ;heap handle
mov rdx, 0 ;no flags
mov r8, 15 ;enough memory for string that represents 32-bit signed integer (11bytes)+ string length (4bytes)
call HeapAlloc ;pointer to allocated memory in rax (rax = *string, rax + 11 = *stringLength)
;convert the number to a string
mov rcx, [rsp + 32] ;number
push rax ;save the pointer to string
push rax ;we need it 2 times
mov rdx, rax ;*string
add rax, 11 ;because string length stored after string
mov r8, rax ;*stringLength
call IntToString
;write the string to console
;HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle )
mov rcx, -11 ; -11 = STD_OUTPUT_HANDLE
call GetStdHandle ;handle is now stored in rax
pop r9 ;pointer to string
; BOOL WINAPI WriteConsole(
; _In_ HANDLE hConsoleOutput,
; _In_ const VOID *lpBuffer,
; _In_ DWORD nNumberOfCharsToWrite,
; _Out_ LPDWORD lpNumberOfCharsWritten,
; _Reserved_ LPVOID lpReserved ) ;
mov rcx, rax ;this is the handle returned from GetStdHandle
mov rdx, r9 ;*string
add r9, 11 ;because string length stored after string
mov r8, [r9] ;string length
mov r9, numCharsWritten
mov [rsp + 32], dword 0 ;Reserved must be 0
call WriteConsoleA
;deallocate the string
call GetProcessHeap ;heap handle now in rax
;BOOL WINAPI HeapFree(_In_ HANDLE hHeap, _In_ DWORD dwFlags, _In_ LPVOID lpMem)
mov rcx, rax ;heap handle
mov rdx, 0 ;no flags
pop r8 ;pointer to string
call HeapFree
add rsp, 48
ret
;IntToString(rcx number, rdx *string, r8 *stringLength)
IntToString:
test ecx, ecx
js IntToString1 ;if number was < 0
jmp UintToString ;otherwise just do UintToString
IntToString1:
mov [r8], byte 0
neg ecx ; convert to positive
inc byte [r8] ;increase stringLength because of sign
mov [rdx], byte 45 ;45 is the ascii code for minus-sign
inc rdx
jmp UintToString
;UintToString(rcx number, rdx *string, r8 *stringLength)
UintToString:
mov r9, rdx ;r9 now contains the memory address of the string
xor rdx, rdx ;rdx = 0, rdx will be used to store one digit
mov eax, ecx ;dividend (32-bit integer) in eax
xor rcx, rcx ;rcx = 0, rcx will store the string length
xor r10, r10 ;r10 = 0, r10 will be used as a loop counter
UintToString1:
inc rcx ;string length++
div dword [const10] ;quotient now in eax, remainder in edx
push rdx ;remainder (which is one digit of the number)
xor rdx, rdx
test eax, eax ;test if quotient is zero
jz UintToString2 ;if it is, we are done
jmp UintToString1
UintToString2:
pop rdx ;get the digit that was saved last (the most significant digit)
add rdx, 48 ;add 48 to get ascii representation of the digit
mov [r9], dl ;save the digit to memory
inc r9 ;r9 now contains memory address of next digit
inc r10
cmp rcx, r10 ;compare rcx and r10 for equality
jne UintToString2 ;if they are not equal (hasn't yet looped for each digit), loop again
add [r8], cl ;save the string length to memory
ret