Computing: DOS, OS/2 & Windows Programming

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:

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.

Assembly programming on Windows: Installation of SASM

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).

Assembly programming on Windows: SASM IDE - Main window
Assembly programming on Windows: SASM IDE - Build settings

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:

The screenshot below shows the execution of a 32-bit NASM program from within the SASM IDE.

Assembly programming on Windows: SASM IDE - Running a 32-bit NASM program

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 nameEAX registerEDX register
io_print_dec
io_print_udec
io_print_hex
input: number 
io_print_charinput: character 
io_print_stringinput: address 
io_newline  
io_get_dec
io_get_udec
io_get_hex
output: number 
io_get_charoutput: character 
io_get_stringinput: addressinput: 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 nameDescriptionParameters
PRINT_DEC size, dataPrint 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, dataPrint 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, dataPrint 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 chPrint symbol chch: number or symbol constant, name of variable, register or address
PRINT_STRING dataPrint null-terminated text stringdata: string constant, name of variable or address
NEWLINEPrint newline 
GET_DEC size, dataInput number data in signed decimal representation from stdinsize: size of data in bytes - 1, 2, 4 or 8 (x64)
data: name of variable or register or address
GET_UDEC size, dataInput number data in unsigned decimal representation from stdinsize: size of data in bytes - 1, 2, 4 or 8 (x64)
data: name of variable or register or address
GET_HEX size, dataInput number data in hexadecimal representation (0x prefix) from stdinsize: size of data in bytes - 1, 2, 4 or 8 (x64)
data: name of variable or register or address
GET_CHAR dataInput symbol from stdindata: name of variable or register or address
GET_STRING data, maxsizeInput 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:

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:

Assembly programming on Windows: FASM 32-bit sample program: Fibonacci series

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:

Assembly programming on Windows: FASM 64-bit sample program: Factorial calculation

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.