Assembly language examples using NASM on Windows
Extra information
The %define preprocessor directive
My code examples use
%define to replace local variables and registers with a more descriptive word.
NASM will automatically replace these words with their proper meaning.
Note
This could possibly lead to more code readability issues than it solves, as the instruction may look invalid, or have a double meaning.
Example
Local variables are usually given as an offset to EBP, but this is not descriptive of what it represents.
%define OrderNumber EBP - 4
mov EAX, dword [OrderNumber]
Example
x64 has 16 general purpose registers, where possible, use registers before resorting to memory variables.
Renaming a register to something more descriptive will make the code objectives easier to follow.
%define Form R8
%define Quantity R9
%define BaseAddress R10
%define Offset R11
%define Position R12
mov Form, 27B6h
mov Quantity, 400h
lea Position, [BaseAddress + Offset]
Why use EAX instead of RAX?
For shorter instruction encodings, use the 8
Extended registers instead of an
R register, but only for data up to 32 bits.
The upper 32 bits of the
R register will be set to zero.
mov EAX, 1
mov RAX, 1
Where appropriate, NASM performs this optimisation automatically.
32 bit Windows API functions
Function names are decorated with a
_ prefix and an
@ +
number suffix.
For example, if the Windows API documentation calls it
WriteFile, we would use
_WriteFile@20.
@20 is the total number of bytes used by its parameters. WriteFile takes
5 double words =
20 bytes.
The parameters are pushed onto the stack in reverse order.
WriteFile (hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped) would be
push lpOverlapped
push lpNumberOfBytesWritten
push nNumberOfBytesToWrite
push lpBuffer
push hFile
call _WriteFile@20
Stack alignment for 64 bit Windows API functions
The stack should be 16 byte aligned before calling a Windows API function, but the stack will be 8 byte aligned on entry.
To align to 16 bytes, use the following as your program's first instruction. From then on, you should keep the stack balanced.
Start:
sub RSP, 8
64 bit Windows API functions
Function names are not decorated as they are in 32 bit.
The first 4 parameters are passed in
RCX, RDX, R8 and R9. The other parameters are put on to the stack.
There should be enough space on the stack for the function to spill the 4 parameters, this is called
Shadow Space.
If the function parameters misalign the stack, extra bytes should be added to realign it.
Example
If the function takes 5 parameters, we would allocate 48 bytes (32 + 8 + 8)
- 32 bytes of shadow space
- 8 bytes for the 5th parameter, located at [RSP + 4 * 8]. (The 6th parameter would be [RSP + 5 * 8] and so on)
- 8 bytes to keep the stack aligned to a multiple of 16 bytes
Remember to remove the same number of bytes after the function returns.
WriteFile (hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped) would be
and RSP, 0FFFFFFFFFFFFFFF0h
sub RSP, 32 + 8 + 8
mov RCX, hFile
mov RDX, lpBuffer
mov R8, nNumberOfBytesToWrite
mov R9, lpNumberOfBytesWritten
mov qword [RSP + 4 * 8], lpOverlapped
call WriteFile
add RSP, 48
64 bit Windows structures
- Structures should start at a memory address wholly divisible by its largest member
- All members should be located at a memory address wholly divisible by its own length (naturally aligned).
Padding bytes are added after the preceding member
- A structures total length should be wholly divisible by its largest member. Padding bytes are added to the end
Example - MSG structure
- The largest member is 8 bytes
- The structure should start at a memory location divisible by 8, alignb 8 ensures this
- msg.message needs 4 bytes of end padding, so that msg.wParam (8 bytes) becomes naturally aligned
- These padding bytes bring the structure length to 44 bytes, which is not divisible by the length of its largest member
- 4 padding bytes are added to the end, bringing the total structure length to 48 bytes, which is divisible by 8
section .bss
alignb 8
msg.hwnd resq 1
msg.message resd 1
msg.Padding1 resd 1
msg.wParam resq 1
msg.lParam resq 1
msg.time resd 1
msg.py.x resd 1
msg.pt.y resd 1
msg.Padding2 resd 1
Example - PAINTSTRUCT struture
- The largest member is 8 bytes
- The structure should start at a memory location divisible by 8, alignb 8 ensures this
- All members are already naturally aligned, so no member padding bytes are necessary
- The structure length is 68 bytes, which is not divisible by the length of its largest member
- 4 padding bytes are added to the end, bringing the total structure length to 72 bytes, which is divisible by 8
section .bss
alignb 8
ps.hdc resq 1
ps.fErase resd 1
ps.rcPaint.left resd 1
ps.rcPaint.top resd 1
ps.rcPaint.right resd 1
ps.rcPaint.bottom resd 1
ps.Restore resd 1
ps.fIncUpdate resd 1
ps.rgbReserved resb 32
ps.Padding resd 1
Example - WNDCLASSEX struture
- The largest member is 8 bytes
- The structure should start at a memory location divisible by 8, alignb 8 ensures this
- All members are already naturally aligned, so no member padding bytes are necessary
- The total length is 80 bytes, which is divisible by the length of its largest member. So no padding is needed at the end
section .bss
alignb 8
wc.cbSize resd 1
wc.style resd 1
wc.lpfnWndProc resq 1
wc.cbClsExtra resd 1
wc.cbWndExtra resd 1
wc.hInstance resq 1
wc.hIcon resq 1
wc.hCursor resq 1
wc.hbrBackground resq 1
wc.lpszMenuName resq 1
wc.lpszClassName resq 1
wc.hIconSm resq 1