Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write hello world in assembler under Windows?

This example shows how to go directly to the Windows API and not link in the C Standard Library.

    global _main
    extern  _GetStdHandle@4
    extern  _WriteFile@20
    extern  _ExitProcess@4

    section .text
_main:
    ; DWORD  bytes;    
    mov     ebp, esp
    sub     esp, 4

    ; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
    push    -11
    call    _GetStdHandle@4
    mov     ebx, eax    

    ; WriteFile( hstdOut, message, length(message), &bytes, 0);
    push    0
    lea     eax, [ebp-4]
    push    eax
    push    (message_end - message)
    push    message
    push    ebx
    call    _WriteFile@20

    ; ExitProcess(0)
    push    0
    call    _ExitProcess@4

    ; never here
    hlt
message:
    db      'Hello, World', 10
message_end:

To compile, you'll need NASM and LINK.EXE (from Visual studio Standard Edition)

   nasm -fwin32 hello.asm
   link /subsystem:console /nodefaultlib /entry:main hello.obj 

NASM examples.

Calling libc stdio printf, implementing int main(){ return printf(message); }

; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits.  It needs to be linked with a C library.
; ----------------------------------------------------------------------------

    global  _main
    extern  _printf

    section .text
_main:
    push    message
    call    _printf
    add     esp, 4
    ret
message:
    db  'Hello, World', 10, 0

Then run

nasm -fwin32 helloworld.asm
gcc helloworld.obj
a

There's also The Clueless Newbies Guide to Hello World in Nasm without the use of a C library. Then the code would look like this.

16-bit code with MS-DOS system calls: works in DOS emulators or in 32-bit Windows with NTVDM support. Can't be run "directly" (transparently) under any 64-bit Windows, because an x86-64 kernel can't use vm86 mode.

org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'

Build this into a .com executable so it will be loaded at cs:100h with all segment registers equal to each other (tiny memory model).

Good luck.


These are Win32 and Win64 examples using Windows API calls. They are for MASM rather than NASM, but have a look at them. You can find more details in this article.

This uses MessageBox instead of printing to stdout.

Win32 MASM

;---ASM Hello World Win32 MessageBox

.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib

.data
title db 'Win32', 0
msg db 'Hello World', 0

.code

Main:
push 0            ; uType = MB_OK
push offset title ; LPCSTR lpCaption
push offset msg   ; LPCSTR lpText
push 0            ; hWnd = HWND_DESKTOP
call MessageBoxA
push eax          ; uExitCode = MessageBox(...)
call ExitProcess

End Main

Win64 MASM

;---ASM Hello World Win64 MessageBox

extrn MessageBoxA: PROC
extrn ExitProcess: PROC

.data
title db 'Win64', 0
msg db 'Hello World!', 0

.code
main proc
  sub rsp, 28h  
  mov rcx, 0       ; hWnd = HWND_DESKTOP
  lea rdx, msg     ; LPCSTR lpText
  lea r8,  title   ; LPCSTR lpCaption
  mov r9d, 0       ; uType = MB_OK
  call MessageBoxA
  add rsp, 28h  
  mov ecx, eax     ; uExitCode = MessageBox(...)
  call ExitProcess
main endp

End

To assemble and link these using MASM, use this for 32-bit executable:

ml.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main

or this for 64-bit executable:

ml64.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main

Why does x64 Windows need to reserve 28h bytes of stack space before a call? That's 32 bytes (0x20) of shadow space aka home space, as required by the calling convention. And another 8 bytes to re-align the stack by 16, because the calling convention requires RSP be 16-byte aligned before a call. (Our main's caller (in the CRT startup code) did that. The 8-byte return address means that RSP is 8 bytes away from a 16-byte boundary on entry to a function.)

Shadow space can be used by a function to dump its register args next to where any stack args (if any) would be. A system call requires 30h (48 bytes) to also reserve space for r10 and r11 in addition to the previously mentioned 4 registers. But DLL calls are just function calls, even if they're wrappers around syscall instructions.

Fun fact: non-Windows, i.e. the x86-64 System V calling convention (e.g. on Linux) doesn't use shadow space at all, and uses up to 6 integer/pointer register args, and up to 8 FP args in XMM registers.


Using MASM's invoke directive (which knows the calling convention), you can use one ifdef to make a version of this which can be built as 32-bit or 64-bit.

ifdef rax
    extrn MessageBoxA: PROC
    extrn ExitProcess: PROC
else
    .386
    .model flat, stdcall
    include kernel32.inc
    includelib kernel32.lib
    include user32.inc
    includelib user32.lib
endif
.data
caption db 'WinAPI', 0
text    db 'Hello World', 0
.code
main proc
    invoke MessageBoxA, 0, offset text, offset caption, 0
    invoke ExitProcess, eax
main endp
end

The macro variant is the same for both, but you won't learn assembly this way. You'll learn C-style asm instead. invoke is for stdcall or fastcall while cinvoke is for cdecl or variable argument fastcall. The assembler knows which to use.

You can disassemble the output to see how invoke expanded.


Flat Assembler does not need an extra linker. This makes assembler programming quite easy. It is also available for Linux.

This is hello.asm from the Fasm examples:

include 'win32ax.inc'

.code

  start:
    invoke  MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK
    invoke  ExitProcess,0

.end start

Fasm creates an executable:

>fasm hello.asm
flat assembler  version 1.70.03  (1048575 kilobytes memory)
4 passes, 1536 bytes.

And this is the program in IDA:

enter image description here

You can see the three calls: GetCommandLine, MessageBox and ExitProcess.