32bit and 64bit assembly programming made easy with SASM.
There are lots of assemblers available for Windows, and you may ask yourself, which is the best for an assembly beginner. You may also ask yourself, where to find an IDE for the assembly programming language. I would highly recommend to give SASM a try. No, it's not yet another assembler, but a "simple crossplatform IDE for NASM, MASM, GAS, FASM assembly languages", including all that you need to be able to create assembly programs "out of the box". Here are some of the highlights of SASM:
- SASM is an assembly IDE with assembly syntax highlighting, build and debug features for NASM, MASM, GAS and FASM.
- SASM includes the NASM, GAS and FASM assemblers and linkers, so no need to install other software.
- SASM doesn't need any special configuration. Just choose the target platform (32-bit, or 64-bit) and the assembler that you want to use, and you are ready to build your project.
- For NASM projects, you can use the io.inc/io64.inc macros, or the io_ functions (32-bit only) to perform input-output operations from the keyboard resp. to the screen. With the other assemblers, you can use the functions of the C library (GCC is included with SASM) for input-output operations.
- SASM includes two minimalist program samples for each assembler on both a 32-bit and a 64-bit platform. You can use them as templates for your own programs, as they show the general layout of the assembly source of a given assembler for a given target, as well as how to use the input-output features included.
Note: MASM is not included with SASM because of its license. On the SASM website, they describe, how you can install it (if you absolutely want to use MASM). To note, that I did not succeed to perform this installation!
This document is not an assembly language tutorial; if you are new to assembly, search the Internet to find some book that describes the NASM (or other) assembly language. The tutorial shows how to install SASM 3.14 on Windows 11, and how to build NASM and FASM programs for both a 32-bit and a 64-bit platform. The tutorial should apply to other versions of SASM and Windows, too. Click the following link, if you want to download the source code of the sample programs of the tutorial.
You can download SASM from github. The download file is a wizard-based installer. Just accept the default when asked for some settings.
As you can see on the screenshot on the left, the GUI is extremely simple. Essentially, the possibility to create a new or open an existing project, the possibility to build, debug or run a project, and – important – the Settings menu, where you select the target platform, Windows 32-bit or Windows 64-bit, and the assembler to use, NASM, FASM, MASM or GAS (screenshot on the right).
Except if you want to do something "special", all you have to do to configure the build of a project, is to select the target platform and the assembler to use: All build options (linker to use and assembler specific command line options) are preset!
Some important notes concerning the work with SASM:
- Besides building a project, you can also run the executable created from within SASM. If all goes well, the message "The program finished normally" will be displayed in the Messages tab. And the program output appears in the Output tab
- To communicate with a program run from within the IDE, you can enter the user input data in the Input tab. Note, that this has to be done before you start the program (not when the running program asks for data).
- The executable created is stored in some folder of the user's app data. To get the executable and store it at your favorite location within the filesystem (and run it in Command Prompt), use the item Save .exe from the File menu.
The screenshot below shows the execution of a 32-bit NASM program from within the SASM IDE.
Building a project using NASM.
Here is the global structure for a 32-bit NASM project (using the io_ functions for input-output):
section .data
...
section .bss
...
section .text
extern <io_functions>
global main
main:
mov ebp, esp
...
xor eax, eax
ret
The io_functions used (cf. table) have to be declared as extern. With the first instruction mov ebp, esp, we put the current address, that the Stack Pointer points to, into EBP, because this is the start of our new Stack Frame. This is primarily done as a debugging aid, in some cases for exception handling. The program is terminated with a ret instruction. Before returning, we execute the instruction xor eax, that sets EAX to 0; in fact, for the calling function, EAX contains the return code from the subroutine, that should be 0 in the case of normal termination.
NASM 32-bit input-output functions.
Function name | EAX register | EDX register |
---|---|---|
io_print_dec io_print_udec io_print_hex | input: number | |
io_print_char | input: character | |
io_print_string | input: address | |
io_newline | ||
io_get_dec io_get_udec io_get_hex | output: number | |
io_get_char | output: character | |
io_get_string | input: address | input: size |
During execution of the above functions the values of the registers EBX, EBP, ESP, EDI, ESI do not change, the values of other registers can be changed.
Concerning input-output of numbers (_dec -> decimal signed, _udec -> decimal unsigned, _hex -> hexadecimal), they are 32-bit in size. Concerning the output of a string, note that this one has to be terminated by 00h (if you forget, output will continue with display of some or lots of "garbage")! Concerning the input of strings, cf. the program sample NASMHello2.asm further down in the text.
Here is the global structure for a 64-bit NASM project (using the io64 macros for input-output):
%include "io64.inc"
section .data
...
section .bss
...
section .text
global main
main:
mov rbp, rsp
...
xor rax, rax
ret
The first line of this code tells the NASM preprocessor to include the io64 macros for input-output operations. The other instructions are the same as for a 32-bit target, except that the 64-bit registers have to be used.
NASM io.inc/io64.inc macro library.
Macro name | Description | Parameters |
---|---|---|
PRINT_DEC size, data | Print number data in signed decimal representation | size: size of data in bytes - 1, 2, 4 or 8 (x64) number or symbol constant, name of variable, register or address |
PRINT_UDEC size, data | Print number data in unsigned decimal representation | size: size of data in bytes - 1, 2, 4 or 8 (x64) number or symbol constant, name of variable, register or address |
PRINT_HEX size, data | Print number data in hexadecimal representation | size: size of data in bytes - 1, 2, 4 or 8 (x64) number or symbol constant, name of variable, register or address |
PRINT_CHAR ch | Print symbol ch | ch: number or symbol constant, name of variable, register or address |
PRINT_STRING data | Print null-terminated text string | data: string constant, name of variable or address |
NEWLINE | Print newline | |
GET_DEC size, data | Input number data in signed decimal representation from stdin | size: size of data in bytes - 1, 2, 4 or 8 (x64) data: name of variable or register or address |
GET_UDEC size, data | Input number data in unsigned decimal representation from stdin | size: size of data in bytes - 1, 2, 4 or 8 (x64) data: name of variable or register or address |
GET_HEX size, data | Input number data in hexadecimal representation (0x prefix) from stdin | size: size of data in bytes - 1, 2, 4 or 8 (x64) data: name of variable or register or address |
GET_CHAR data | Input symbol from stdin | data: name of variable or register or address |
GET_STRING data, maxsize | Input string with length less than maxsize
characters from stdin Reading stops on EOF or newline and "\n" is written into the buffer 00h is added to the end of the string |
data: name of variable or address maxsize: register or number constant |
General purpose registers are not modified during execution of these macros.
NASMHello2.asm: Simple NASM 32-bit "Hello user" program.
The program asks the user for their name. If the user enters a name, a personal greeting message is displayed; otherwise (user just hit ENTER), the display will be "Hello World!".
section .data
maxlen equ 25
quser db 'Please, enter your name? ', 00h
huser db 'Hello, '
hname times maxlen + 2 db 00h
hworld db 'Hello, world!', 00h
section .bss
buffer resb maxlen + 1
section .text
extern io_get_string, io_print_string, io_newline
global main
main:
mov ebp, esp
; Ask for name
mov eax, quser
call io_print_string
; Get name from keyboard input
mov eax, buffer
mov edx, maxlen + 1
call io_get_string
; Prepare copy of name from buffer to greeting area
lea esi, [buffer]
lea edi, [hname]
mov al, [esi]
; If there is no input (user just hit ENTER key),
; the general greeting will be displayed
cmp al, 0Ah ; 0Ah (linefeed) indicates end of input
je world
copychar:
; Copy name from buffer to greeting area
; This is done character by character, until LF or end of string is detected
mov [edi], al
inc esi
inc edi
mov al, [esi]
cmp al, 0Ah ; check if character is LF
je done ; if yes, we're done
cmp al, 00h ; check if end of string
je done ; if yes, we're done
jmp copychar ; if not, continue with the next character
done:
mov byte [edi], '!' ; add exclamation mark at the end of the name
mov eax, huser ; load AX with address of personal greeting (for display)
jmp display
world:
; General greeting
mov eax, hworld ; load AX with address of general greeting (for display)
display:
; Display (general or personal) greeting and exit
call io_print_string
call io_newline
xor eax, eax
ret
If you have some knowledge of the assembly programming language (that is supposed here), you should understand the code without further explanations. However, a closer look at the io_get_string function is required. When this function is called, the program execution is suspended, waiting for user input from the keyboard. The characters entered by the user are stored into a temporary buffer, until either the end-of-file is reached (in this case, this means that maxlen - 1 characters have been entered), or the ENTER key has been pressed. The transfer of the temporary buffer's content to the program memory area is actually done, when the user hits ENTER. Before this transfer, a 00h character is added to the end of the string.
In practice, this means:
- Because of the supplementary 00h character, the size value to be loaded into the EDX register has to be the maximum number of characters that you want to consider plus 1 (in our case: for a name of maxlen = 25 characters, the buffer size has to be maxlen + 1 = 26 bytes).
- If the user hits ENTER before maxlen is reached, an end-of-line character is added to the string (and so transferred to the program memory area). This end-of-line character actually is a linefeed (0Ah), not a CR+LF (0Dh 0Ah), as usual on Windows.
- Keyboard input continues after maxlen - 1 characters have been entered. However, if the maximum input length is reached, the end-of-file condition becomes true, and any subsequent character (including the end-of-line character) will be ignored.
- As the end-of-line character is part of the string in this case, to test for an empty input, we have to check if the first character entered is 0Ah.
- To find the end of the string, we can (and must) always check for a 00h character, as this one is always added at the end of the string.
- On the other hand, the string may or may not contain an end-of-line character. If we don't want to include this character in the string (as is the case in our sample program), we'll have to check for 0Ah, too, and the end of the string is reached when we find it.
NASMPalindrome.asm: A NASM 32-bit "check for palindrome" program.
Nothing new to learn with this program; just sample code that I wrote when playing around with SASM and that I want to share here. In fact, the program is a Windows version of PALDR.ASM included with my 32-bit assembly programming using NASM and GCC on DOS tutorial. Here is the code.
section .data
maxlen equ 100
rword db "Please, enter a word? ", 00h
buffer times maxlen + 1 db 00h
pal db "This word is a palindrome", 00h
nopal db "This word isn't a palindrome", 00h
section .text
extern io_get_string, io_print_string, io_newline
global main
main:
mov ebp, esp
; Execute the loop until there is an empty input (user just hit ENTER)
loop:
; Ask for word
mov eax, rword
call io_print_string
; Get name from keyboard input
mov eax, buffer
mov edx, maxlen + 1
call io_get_string
; If no input (user just hit ENTER), then exit
lea esi, [buffer]
mov bl, [esi]
cmp bl, 0Ah ; 0AH (linefeed) indicates end of input
je exit
xor ecx, ecx ; CL register will be used as character counter
ccount:
; Count actual number of characters
inc cl
inc esi
mov bl, [esi]
cmp bl, 0Ah ; check if end of input
jne ccount ; if not, continue with next character
; Compare the word read from left to right with the word read from right to left
; Initialization: ESI pointing to first character, EDI to last character of the word
lea esi, [buffer]
lea edi, [buffer + ecx - 1]
check:
; Compare the word with its reverse character by character
; Do this until all characters have been done,
; or until the two characters aren't equal anymore
mov byte bl, [esi] ; actual character of the word
mov byte dl, [edi] ; actual character of the reversed word
inc esi ; ESI pointing to next character (reading the word is from left to right)
dec edi ; EDI pointing to previous character (reading the reversed word is from right to left)
dec cl ; decrement the character counter
cmp cl, 0 ; if the character couner is zero, comparison is done
je done
cmp bl, dl ; compare character of word with charater of reversed word
je check ; if they are still equal, continue with next character
done:
; If the character counter is zero at this point, all characters of the word
; are equal to those of the reversed word and the word is a palindrome;
; otherwise the word isn't a palindrome
cmp cl, 0
je ispal
; The word isn't a palindrome
mov eax, nopal
jmp display
ispal:
; The word is a palindrome
mov eax, pal
display:
; Display if word is or is not a palindrome,
; then redo the loop (asking user for another word)
call io_print_string
call io_newline
jmp loop
exit:
; Program termination
xor eax, eax
ret
Building a project using FASM.
Except if, for one reason or another, you don't want to use the functions of the C library, the (probably) best assembler choice is FASM. I suppose that using the C functions also works with NASM, but I guess that you'll have to change the build command string in SASM settings. FASM is preconfigured to use the C library, and FASM supports the syntax of several assemblers; in particular, it is fully compatible with NASM and, except for the general program structure, that you'll have to adapt, you can successfully build your NASM code with FASM.
Here is the global structure for a 32-bit FASM project:
format ELF
section '.data' writeable
...
section '.bss' writeable
...
section '.text' executable
public _main
extrn <C-functions>
_main:
mov ebp, esp
...
mov esp, ebp
xor eax, eax
ret
ELF is the format of the object file created by FASM; I guess it's this format that the 32-bit GCC C compiler awaits. The section declarations are somewhat different as with NASM. The underscore (_) used with the public routine entry point _main is part of the 32-bit C calling convention; you'll have also to use it, when calling a function of the C library (e.g. _printf). C functions have to be declared as extrn (note the syntax difference with NASM!). With the instruction mov ebp, esp at the beginning of the program, we create a new Stack Frame, and the instruction esp, ebp at its end, "purges the stack" from all "local" variables. The program is terminated with a ret instruction; before returning, we set the return code (in register EAX) to 0 using, for example, the instruction xor eax, eax.
The global structure for a 64-bit FASM project is similar, but with some essential differences:
format ELF64
section '.data' writeable
...
section '.bss' writeable
...
section '.text' executable
public main
extrn <C-functions>
main:
mov rbp, rsp
sub rsp, 32
and rsp, -16
...
mov rsp, rbp
xor rax, rax
ret
The object file format has to be ELF64 for 64-bit programs. Also, if building 64-bit programs, containing assembly and C code, there must be no underscore prefixing the routine entry point main, nor the functions of the C library called by the assembly routine. The registers to be used in the instructions at the beginning and the end of the program, have to be 64-bit registers. An important point is that on a 64-bit Windows platform, you have to create 32 bytes of shadow space and the stack has to be aligned at a 16 byte boundary. This is a rather complex topic, and I think that you'll have to understand and deal with it only if you are really serious about assembly programming. Adding the two instructions sub rsp, 32 and and rsp, -16 should ensure that your program works correctly. For details concerning Windows x64 stack alignment, shadow space, register usage, parameter passing, etc, having a look at the article 64-bit Windows Assembly on github might be helpful.
FASMFibonacci.asm: A FASM 32-bit "Fibonacci series" program.
As code example, here the Windows FASM 32-bit version of of the C/NASM program for DJGPP FIBONA.C/FIBONA.ASM, as described in my 32-bit assembly programming using NASM and GCC on DOS tutorial.
format ELF
section '.data' writeable
max equ 45
five dd 5
count db 2
n1 dd 1
n2 dd 1
gtitle db 'Fibonacci series.', 00h
format1 db '%10u ', 00h
format2 db '%10u', 0Dh, 0Ah, 00h
formats db '%s', 0Dh, 0Ah, 00h
section '.text' executable
public _main
extrn _printf
_main:
mov ebp, esp
push dword gtitle
push dword formats
call _printf
add esp, 8
mov eax, 1
push eax
push dword format1
call _printf
add esp, 8
mov eax, 1
push eax
push dword format1
call _printf
add esp, 8
next:
mov eax, [n1]
mov ebx, [n2]
add eax, ebx
mov [n1], ebx
mov [n2], eax
div dword [five]
cmp edx, 0
mov eax, [n2]
push eax
je cont1
push dword format1
jmp cont2
cont1:
push dword format2
cont2:
call _printf
add esp, 8
mov cl, [count]
inc cl
mov [count], cl
cmp cl, max
jl next
mov esp, ebp
xor eax, eax
ret
And here is the program output in Windows 11 Command Prompt:
As I said at the beginning of this document, this is not a tutorial about how to write programs using the assembly language. It is supposed that the reader has the necessary knowledge to understand the code above, and also to know the basics of how an assembly routine communicates with a C program. If you are an assembly newbie, or never used assembly together with C, I suggest that you have a look at my NASM on DOS tutorial mentioned above. First, there are lots of details on how to proceed to call the printf function. And the way, how I do it here with FASM on Windows 11, is exactly the same as with NASM on DOS: push the function parameters (display format, data to display) in reverse order onto the stack, call the function and on return remove the parameters from the stack. Second, the program shown here not only works the same way than the one described (and explained) in the NASM DOS tutorial, but the code is quite the same in both programs.
FASMFactorialx64.asm: A FASM 64-bit "factorial" program.
Another code example, this time using FASM 64-bit: Calculation of the factorial of a number between 1 and 20. For those who don't know what
a factorial is, here is the Wikipedia definition: In mathematics, the factorial of a non-negative integer n, denoted by n!, is the product of all positive integers
less than or equal to n. The factorial of n also equals the product of n with the next smaller factorial:
n! = n × (n − 1) × (n − 2) × (n − 3) × ⋯ × 3 × 2 × 1 = n × (n − 1)!
format ELF64
section '.data' writeable
inum db 'Please, enter an integer number from 1 to 20? ', 00h
oerr db 'Error: Number out of range!', 00h
ofact db 'The factorial of this number is %llu', 00h
formatn db '%u', 00h
formats db '%s', 00h
num dq 0
section '.text' executable
public main
extrn printf
extrn scanf
main:
mov rbp, rsp
sub rsp, 32
and rsp, -16
mov rcx, formats
mov rdx, inum
call printf
mov rcx, formatn
mov rdx, num
call scanf
mov rax, [num]
cmp rax, 1
jl invalid
cmp rax, 20
jg invalid
mov rbx, rax
next:
dec rbx
cmp rbx, 0
je done
mul rbx
jmp next
done:
mov rcx, ofact
mov rdx, rax
jmp exit
invalid:
mov rcx, formats
mov rdx, oerr
exit:
call printf
mov rsp, rbp
xor rax, rax
ret
And here is the program output in Windows 11 Command Prompt:
The logic of the program isn't difficult to understand: After checking if the number entered by the user (loaded into RAX) is within the interval [1; 20], the program enters a loop, where RBX (initialized with RAX - 1) is decremented during each iteration and RAX multiplied by the actual value in RBX, the loop terminating when RBX reaches 0.
With 32-bit assembly, we normally use the stack to pass parameters to a function (as seen in the FASMFibonacci.asm sample program); with Windows 64-bit assembly, we use the registers RCX, RDX, R8 and R9, in this order (if there are more parameters, the stack comes in to play...). So, in our FASMFactorialx64,asm sample program, to call the C function printf, we put the output format in RCX and the address of the data to be displayed into RDX. To call scanf, we place the input format into RCX and the address of the input buffer (memory location "num") into RDX.
Of course, this tutorial is rudimentary and does not include all the information that you need to write serious 32-bit or 64-bit assembly programs on Windows. But, I think that is a good starting point for beginners, in particular an easy way to start writing your own simple NASM or FASM programs.
If you find this text helpful, please, support me and this website by signing my guestbook.