This article shows "Hello, world!" in Windows x64 assembly using MASM with Visual Studio 20221 (the full code is found here). We’ll start by defining some external Windows API functions to let us write to the console and exit our main function2.

extern GetStdHandle : PROC
extern WriteConsoleA : PROC
extern ExitProcess : PROC

Create a data segment to define some constants.

.data

The carriage return (\r) and line feed characters (\n).

CARRIAGE_RETURN equ 0Dh
LINE_FEED equ 0Ah

A constant for GetStdHandle to get a pointer to the standard output.

STD_OUTPUT_HANDLE equ -11

A static byte array for our “hello world” message along with a newline and null terminator. The sizeof function is needed as WriteConsoleA needs to know how many bytes to print3.

HELLO_MSG db 'Hello, World!', CARRIAGE_RETURN, LINE_FEED, 0
HELLO_MSG_LEN = sizeof HELLO_MSG

Start the code segment and begin the main function definition.

.code
main PROC

As our main function will be calling other functions we must first allocate some memory on the stack for these calls. The Windows x64 calling convention requires allocating 32 bytes to store the first four eight-byte function parameters (regardless of whether they exist)4. The x64 call instruction pushes the return address on the stack5 which adds another eight bytes to be stack allocated. Finally, Windows uses a 16 byte stack alignment so we must increase our 40 bytes (32 + 8) to 48 to ensure correct alignment.

sub rsp, 28h                  ; Allocate shadow space for function calls

We can write to the console using WriteConsoleA3 however its first parameter is a pointer to the standard output so we must first call GetStdHandle2 to get it.

mov rcx, STD_OUTPUT_HANDLE    ; Set up the first argument for GetStdHandle
call GetStdHandle             ; Get the handle to the console.

We then set up the four arguments for WriteConsoleA in registers. The return value from GetStdHandle (and all Windows functions) is stored in rax.

mov rcx, rax                  ; Store the handle in rcx
lea rdx, HELLO_MSG            ; Load address of the message
mov r8, HELLO_MSG_LEN         ; Num bytes to write
mov r9, 0                     ; nullptr for the number of bytes written

Now we can write our string to the console!

call WriteConsoleA            ; Call Windows API to print the message

Finally we deallocate our 40 bytes on the stack, return from the function, and end our function/code definitions.

    add rsp, 28h                  ; Clean up stack
    mov rcx, 0                    ; Return zero
    call ExitProcess
main ENDP
END