Hello again everyone. This time I decided to be curious about what Windows API programming was like in 64-bit mode. What I have done is nothing fancy with GUI programs, which I assume is why most people even bother with the Win API. I only care about console or terminal programs.
Instead, the goal was getting my test suite I have developed which tests all the important functions in my chastelib library. You have seen this library before as part of my chastehex, chastecmp, and chastdin programs.
I had a hard time understanding the stack alignment requirements for 64-bit Windows. Most of this code was copied from the 64-bit Linux test suite which did not require the stack to be aligned to a multiple of 16 bytes nor was "shadow space" required.
This program AKA known as my test suite contains 3 parts.
- main.asm (main function or part of program)
- chastelib-w64.asm (the library I developed from scratch)
- stdout.txt (the output the program generates when assembled)
As I have stated before, the purpose of the chastelib library is to quickly print arbitrary zero terminated strings of text or convert integers in bases two to thirty-six to or from strings.
And maybe I am just dumb or don't know where to look but I have not seen very good examples for FASM users who want to write a console Hello World program. However, now that my putstring function is ported to Windows, I can confidently say that I have far exceeded this simple goal because it can automatically calculate the length and print any string.
main.asm
format PE64 console
entry main
include 'win64ax.inc' ;includes standard Windows 64-bit definitions and macros
include 'chastelib-w64.asm' ;include standard functions by Chastity
main:
mov rax,main_string
call putstring
mov qword[radix],16 ;I can choose the radix for integer output!
mov qword[int_width],1 ;and the width of each integer for padded zeros
mov rax,input_string_int ;address of input string to convert to integer
call strint ;call strint to return the string in rax register
mov rbx,rax ;rbx=rax (copy the converted value returned in rax to rbx)
mov rax,0
loop0:
mov qword[radix],2 ;set radix to binary
mov qword[int_width],8 ;width of 8 bits
call putint
call putspace
mov qword[radix],16 ;set radix to hexadecimal
mov qword[int_width],2 ;width of 2 hex digits
call putint
call putspace
mov qword[radix],10 ;set radix to decimal (what humans read)
mov qword[int_width],3 ;width of 3 decimal digits
call putint
cmp al,0x20 ;check if al is in printable range
jb not_char ;if not then jump to not_char label
cmp al,0x7E
ja not_char
call putspace
call putchar ;print the character if it is in the range 0x20 to 0x7E
not_char: ;jump here if character is outside range to print
call putline ;print newline before the next loop
inc rax
cmp rax,rbx;
jnz loop0
mov rax,main_string
call putstring
sub rsp,40 ;align stack (required in windows 64-bit)
mov rcx,0 ;exit code for operating system
call [ExitProcess] ;Exit the process with code 0
;A string to test if output works
main_string db 'test suite for 64 bit Windows Assembly version of chastelib.',0x0D,0x0A,0
;test string of integer for input
input_string_int db '100',0
;FASM builds the Import Address Table (IAT) directly in the source file
section '.idata' import data readable writeable
library kernel32, 'KERNEL32.DLL'
import kernel32,\
GetStdHandle, 'GetStdHandle',\
WriteFile, 'WriteFile',\
ExitProcess, 'ExitProcess'
chastelib-w64.asm
; chastelib assembly header file for 64 bit Windows
; This file is where I keep the source of my most important Assembly functions
; These are my string and integer output and conversion routines.
; To simplify documentation. The Accumulator/Arithmetic register
; (ax,eax,rax) depending on bit size shall be referred to as register A
; for the description of these core functions because the A register
; is treated special both by the Intel company and my code;
; putstring; Prints a zero terminated string from the address pointer to by A register.
; intstr; Converts the number in A into a zero terminated string and points A to that address
; putint; Prints the integer in A by calling intstr and then putstring.
; strint; Converts the zero terminated string into an integer and sets A to that value
; Now, the source of the functions begins, with comments included for parts that I felt needed explanation.
write_count dq 0 ;variable to store how many bytes were written
putstring: ;print string pointed to by rax register
push rax
push rbx
push rcx
push rdx
mov rbx,rax ;copy eax to ebx to be used as index to the string
putstring_strlen_start: ;this loop finds the length of the string as part of the putstring function
cmp [rbx],byte 0 ;compare byte at address ebx with 0
jz putstring_strlen_end ;if comparison was zero, jump to loop end because we have found the length
inc rbx
jmp putstring_strlen_start
putstring_strlen_end:
sub rbx,rax ;subtract start pointer from current pointer to get length of string
sub rsp,40 ;align stack before Win API functions(required in windows 64-bit)
mov rdx,rax ;pointer to message
mov rcx, -11 ; STD_OUTPUT_HANDLE
call [GetStdHandle] ; Get Standard Output Handle
mov rcx,rax ; copy handle to ecx
mov r8,rbx ;message length
mov r9,write_count ;address to store how many bytes are written
mov qword [rsp + 32], 0 ; Parameter 5: Must be placed on the stack
call [WriteFile]
add rsp,40 ;restore stack now that WinAPI calls are done
pop rdx
pop rcx
pop rbx
pop rax
ret ;this is the end of the putstring function return to calling location
; This is the location in memory where digits are written to by the intstr function
; The string of bytes and settings such as the radix and width are global variables defined below.
int_string db 64 dup '?' ;reserve bytes for characters string for 64-bit binary integer
int_string_end db 0 ;zero byte terminator for the integer string
radix dq 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
int_width dq 8 ;default width of integers. Extra zeros prefixed if more than 1
;this function creates a string of the integer in rax
;it uses the above radix variable to determine base from 2 to 36
;it then loads rax with the address of the string
;this means that it can be used with the putstring function
intstr:
mov rbx,int_string_end-1 ;find address of lowest digit
mov rcx,1
digits_start:
mov rdx,0;
div qword [radix]
cmp rdx,10
jb decimal_digit
jnb hexadecimal_digit
decimal_digit: ;we go here if it is only a digit 0 to 9
add rdx,'0'
jmp save_digit
hexadecimal_digit:
sub rdx,10
add rdx,'A'
save_digit:
mov [rbx],dl
cmp rax,0
jz intstr_end
dec rbx
inc rcx
jmp digits_start
intstr_end:
prefix_zeros:
cmp rcx,[int_width]
jnb end_zeros
dec rbx
mov [rbx],byte '0'
inc rcx
jmp prefix_zeros
end_zeros:
mov rax,rbx ;point eax register to this string for putstring
ret
;function to print string form of whatever integer is in rax
;The radix determines which number base the string form takes.
;Anything from 2 to 36 is a valid radix
;in practice though, only bases 2,8,10,and 16 will make sense to other programmers
;this function does not process anything by itself but calls the combination of my other
;functions in the order I intended them to be used.
putint:
push rax
push rbx
push rcx
push rdx
call intstr
call putstring
pop rdx
pop rcx
pop rbx
pop rax
ret
;this function converts a string pointed to by rax into an integer returned in rax instead
;it is a little complicated because it has to account for whether the character in
;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
;it also checks for both uppercase and lowercase letters for bases 11 to 36
;finally, it checks if that letter makes sense for the base.
;For example, G to Z cannot be used in hexadecimal, only A to F can
;The purpose of writing this function was to be able to accept user input as integers
;This function is improved with error checking and uses the new strint_error variable
;The program can check this value after the call and see how many errors happened.
strint_error db 0 ;declare a byte variable that keeps track of errors
strint:
mov rbx,rax ;copy string address from rax to rbx because rax will be replaced soon!
mov rax,0
mov byte[strint_error],0 ;set errors to 0 at the start of this function
read_strint:
mov rcx,0 ;zero rcx so only lower 8 bits are used
mov cl,[rbx]
inc rbx
cmp cl,0 ;compare this byte with 0
jz strint_end ; if comparison was zero, this is the end of string
;if char is below '0' or above '9', it is outside the range of these and is not a digit
cmp cl,'0'
jb not_digit
cmp cl,'9'
ja not_digit
;but if it is a digit, then correct and process the character
is_digit:
sub cl,'0'
jmp process_char
not_digit:
;it isn't a decimal digit, but it could be perhaps an alphabet character
;which could be a digit in a higher base like hexadecimal
;we will check for that possibility next
;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
cmp cl,'A'
jb not_upper
cmp cl,'Z'
ja not_upper
is_upper:
sub cl,'A'
add cl,10
jmp process_char
not_upper:
;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
cmp cl,'a'
jb not_lower
cmp cl,'z'
ja not_lower
is_lower:
sub cl,'a'
add cl,10
jmp process_char
not_lower:
;if we have reached this point, result invalid and end function with error
jmp strint_end_error
process_char:
cmp rcx,[radix] ;compare char with radix
jnb strint_end_error ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
mov rdx,0 ;zero rdx because it is used in mul sometimes
mul qword [radix] ;mul rax with radix
add rax,rcx
jmp read_strint ;jump back and continue the loop if nothing has exited it
strint_end_error: ;we jump here if there was an error with one of the chars
inc byte[strint_error] ;increment error counter because char invalid
strint_end: ;we jump here when no errors happened
ret
;The utility functions below simply print a space or a newline.
;these help me save code when printing lots of strings and integers.
space db ' ',0 ;a string containing only a space
putspace:
push rax
mov rax,space
call putstring
pop rax
ret
line db 0x0D,0x0A,0 ;a string containing only a newline
;the next function which pushes rax to the stack
;moves the address of the line string and prints it with putstring
;then it pops the original value of rax back from the stack before the function returns
;this allows me to print a newline anywhere in the code without a single register changing
putline:
push rax
mov rax,line
call putstring
pop rax
ret
;a function for printing a single character that is the value of al
char: db 0,0
putchar:
push rax
mov [char],al
mov rax,char
call putstring
pop rax
ret
;a small function just for the common operation of
;printing an integer followed by a space
;this saves a few bytes in the assembled code
;by reducing the number of function calls in the main program
putint_and_space:
call putint
call putspace
ret
;a small function just for the common operation of
;printing an integer followed by a line feed
;this saves a few bytes in the assembled code
;by reducing the number of function calls in the main program
putint_and_line:
call putint
call putline
ret
;a small function just for the common operation of
;printing a string followed by a line feed
;this saves a few bytes in the assembled code
;by reducing the number of function calls in the main program
;it also means we don't need to include a newline in every string!
putstr_and_line:
call putstring
call putline
ret
stdout.txt
test suite for 64 bit Windows Assembly version of chastelib.
00000000 00 000
00000001 01 001
00000010 02 002
00000011 03 003
00000100 04 004
00000101 05 005
00000110 06 006
00000111 07 007
00001000 08 008
00001001 09 009
00001010 0A 010
00001011 0B 011
00001100 0C 012
00001101 0D 013
00001110 0E 014
00001111 0F 015
00010000 10 016
00010001 11 017
00010010 12 018
00010011 13 019
00010100 14 020
00010101 15 021
00010110 16 022
00010111 17 023
00011000 18 024
00011001 19 025
00011010 1A 026
00011011 1B 027
00011100 1C 028
00011101 1D 029
00011110 1E 030
00011111 1F 031
00100000 20 032
00100001 21 033 !
00100010 22 034 "
00100011 23 035 #
00100100 24 036 $
00100101 25 037 %
00100110 26 038 &
00100111 27 039 '
00101000 28 040 (
00101001 29 041 )
00101010 2A 042 *
00101011 2B 043 +
00101100 2C 044 ,
00101101 2D 045 -
00101110 2E 046 .
00101111 2F 047 /
00110000 30 048 0
00110001 31 049 1
00110010 32 050 2
00110011 33 051 3
00110100 34 052 4
00110101 35 053 5
00110110 36 054 6
00110111 37 055 7
00111000 38 056 8
00111001 39 057 9
00111010 3A 058 :
00111011 3B 059 ;
00111100 3C 060 <
00111101 3D 061 =
00111110 3E 062 >
00111111 3F 063 ?
01000000 40 064 @
01000001 41 065 A
01000010 42 066 B
01000011 43 067 C
01000100 44 068 D
01000101 45 069 E
01000110 46 070 F
01000111 47 071 G
01001000 48 072 H
01001001 49 073 I
01001010 4A 074 J
01001011 4B 075 K
01001100 4C 076 L
01001101 4D 077 M
01001110 4E 078 N
01001111 4F 079 O
01010000 50 080 P
01010001 51 081 Q
01010010 52 082 R
01010011 53 083 S
01010100 54 084 T
01010101 55 085 U
01010110 56 086 V
01010111 57 087 W
01011000 58 088 X
01011001 59 089 Y
01011010 5A 090 Z
01011011 5B 091 [
01011100 5C 092 \
01011101 5D 093 ]
01011110 5E 094 ^
01011111 5F 095 _
01100000 60 096 `
01100001 61 097 a
01100010 62 098 b
01100011 63 099 c
01100100 64 100 d
01100101 65 101 e
01100110 66 102 f
01100111 67 103 g
01101000 68 104 h
01101001 69 105 i
01101010 6A 106 j
01101011 6B 107 k
01101100 6C 108 l
01101101 6D 109 m
01101110 6E 110 n
01101111 6F 111 o
01110000 70 112 p
01110001 71 113 q
01110010 72 114 r
01110011 73 115 s
01110100 74 116 t
01110101 75 117 u
01110110 76 118 v
01110111 77 119 w
01111000 78 120 x
01111001 79 121 y
01111010 7A 122 z
01111011 7B 123 {
01111100 7C 124 |
01111101 7D 125 }
01111110 7E 126 ~
01111111 7F 127
10000000 80 128
10000001 81 129
10000010 82 130
10000011 83 131
10000100 84 132
10000101 85 133
10000110 86 134
10000111 87 135
10001000 88 136
10001001 89 137
10001010 8A 138
10001011 8B 139
10001100 8C 140
10001101 8D 141
10001110 8E 142
10001111 8F 143
10010000 90 144
10010001 91 145
10010010 92 146
10010011 93 147
10010100 94 148
10010101 95 149
10010110 96 150
10010111 97 151
10011000 98 152
10011001 99 153
10011010 9A 154
10011011 9B 155
10011100 9C 156
10011101 9D 157
10011110 9E 158
10011111 9F 159
10100000 A0 160
10100001 A1 161
10100010 A2 162
10100011 A3 163
10100100 A4 164
10100101 A5 165
10100110 A6 166
10100111 A7 167
10101000 A8 168
10101001 A9 169
10101010 AA 170
10101011 AB 171
10101100 AC 172
10101101 AD 173
10101110 AE 174
10101111 AF 175
10110000 B0 176
10110001 B1 177
10110010 B2 178
10110011 B3 179
10110100 B4 180
10110101 B5 181
10110110 B6 182
10110111 B7 183
10111000 B8 184
10111001 B9 185
10111010 BA 186
10111011 BB 187
10111100 BC 188
10111101 BD 189
10111110 BE 190
10111111 BF 191
11000000 C0 192
11000001 C1 193
11000010 C2 194
11000011 C3 195
11000100 C4 196
11000101 C5 197
11000110 C6 198
11000111 C7 199
11001000 C8 200
11001001 C9 201
11001010 CA 202
11001011 CB 203
11001100 CC 204
11001101 CD 205
11001110 CE 206
11001111 CF 207
11010000 D0 208
11010001 D1 209
11010010 D2 210
11010011 D3 211
11010100 D4 212
11010101 D5 213
11010110 D6 214
11010111 D7 215
11011000 D8 216
11011001 D9 217
11011010 DA 218
11011011 DB 219
11011100 DC 220
11011101 DD 221
11011110 DE 222
11011111 DF 223
11100000 E0 224
11100001 E1 225
11100010 E2 226
11100011 E3 227
11100100 E4 228
11100101 E5 229
11100110 E6 230
11100111 E7 231
11101000 E8 232
11101001 E9 233
11101010 EA 234
11101011 EB 235
11101100 EC 236
11101101 ED 237
11101110 EE 238
11101111 EF 239
11110000 F0 240
11110001 F1 241
11110010 F2 242
11110011 F3 243
11110100 F4 244
11110101 F5 245
11110110 F6 246
11110111 F7 247
11111000 F8 248
11111001 F9 249
11111010 FA 250
11111011 FB 251
11111100 FC 252
11111101 FD 253
11111110 FE 254
11111111 FF 255
test suite for 64 bit Windows Assembly version of chastelib.