NASM for x86 and x86-64

Last update: 2025-01-13 21:14:39 +0800 +0800
tags: Nasm | Assembly | X86
Available translations: 日本語 / 中文(简体) / 中文(繁體)

NASM translates Intel-syntax x86 assembly into a flat binary or an object file. A flat binary contains emitted bytes with no standard relocation or symbol metadata. An object file such as ELF, Mach-O, or COFF retains sections, symbols, and relocations so a linker can combine it with other objects and libraries.

Sections, Labels, and Symbols #

An object-file source normally separates executable code, initialized data, and zero-initialized storage:

nasm
global add_one
extern external_value

section .text
add_one:
    mov eax, edi
    add eax, 1
    ret

section .rodata
message: db "hello", 10
message_size equ $ - message

section .bss
scratch: resq 8

A label defines the current assembly position. GLOBAL exports a symbol to the linker, while EXTERN declares a symbol that another object must define. Local labels beginning with . are scoped to the preceding non-local label. Section names and attributes depend on the output format; .text, .rodata, .data, and .bss are ELF conventions rather than universal NASM keywords.

Flat binaries selected with -f bin have no linker to assign virtual addresses. ORG changes the origin used in address calculations but does not insert padding or load the bytes at that address. Boot sectors, firmware, and embedded images must establish their own layout and fill requirements.

Effective Addresses and Position Independence #

NASM places the destination operand first. Brackets denote a memory effective address:

nasm
mov rax, value          ; address/immediate determined by relocation rules
mov rbx, [value]        ; load from memory
mov ecx, [base + index*4 + 8]
lea rdx, [base + index*4 + 8]  ; calculate address; do not load

x86 addressing permits a base, an index scaled by 1, 2, 4, or 8, and a displacement, subject to mode-specific encoding rules. The size of a memory operand may need an explicit qualifier such as byte, dword, or qword when no register determines it.

BITS 16, BITS 32, and BITS 64 select the target instruction-encoding mode. They do not switch the processor at run time. Object formats set an appropriate default; -f bin defaults to 16-bit mode, so flat 32- or 64-bit code should state its mode.

In 64-bit mode, DEFAULT REL makes registerless memory references RIP-relative unless overridden by ABS; FS and GS overrides have special defaults documented by NASM. RIP-relative references can reach code or static data within ±2 GiB and are useful for position-independent code. Position independence also depends on the object format, relocation type, code model, symbol visibility, and linker. DEFAULT REL cannot by itself make arbitrary external-symbol or absolute-address uses position independent.

Defining, Reserving, and Aligning Data #

DB, DW, DD, DQ, DT, DO, DY, and DZ emit initialized values of increasing sizes. Common definitions include:

nasm
bytes:  db 0x7f, "NASM", 0
words:  dw 1, 2, 3
value:  dq 42
table:  times 16 dd 0

RESB, RESW, RESD, and RESQ reserve uninitialized storage in a section that supports it. EQU defines an assembly-time constant, and $ represents the current position. Reservation does not necessarily add bytes to an object file because a BSS-like section records a size rather than initialized contents.

ALIGN n advances to the requested boundary and can fill the gap according to its arguments and the active section. Alignment in an object also depends on section alignment and linker placement, so an aligned offset inside an under-aligned section is insufficient. Use SECTION ... align=n or the format-specific mechanism when the entire section needs a stronger guarantee.

Preprocessor Macros and Includes #

NASM runs a token-based preprocessor before assembly:

  • %define and %xdefine define single-line macros with different expansion timing.
  • %macro and %endmacro define multi-line macros; %1, %2, and so on name positional arguments.
  • %if, %ifdef, %elif, and %endif select source conditionally.
  • %include reads another source file, while -I adds include search paths.
  • %assign maintains a numeric preprocessor variable.
nasm
%ifndef WORD_SIZE
    %define WORD_SIZE 8
%endif

%macro save_pair 2
    push %1
    push %2
%endmacro

Macros generate source, not callable routines. They can duplicate code, capture names, or produce surprising diagnostics, so listings (-l file.lst) and disassembly are useful when inspecting their expansion. Include guards prevent multiple definitions when shared macro files are included more than once.

Assembling and Linking for an ABI #

The output format and the ABI are separate choices. On a 64-bit Linux System V target:

sh
nasm -f elf64 -g -F dwarf -o add_one.o add_one.asm
cc -o demo main.c add_one.o

On a 32-bit Linux target, use -f elf32 and a linker/compiler configured with the required 32-bit runtime, such as cc -m32. Windows x64 uses win64 and the Microsoft x64 calling convention; macOS uses macho64 and Mach-O symbol rules. Object files are not portable across those formats.

Hand-written functions must implement the target ABI’s argument registers, stack alignment, return values, preserved registers, unwind metadata, and symbol naming. A Linux syscall sequence follows the kernel syscall ABI rather than the user-space function ABI. Link with the compiler driver when C or C++ runtime startup, default libraries, position-independent-executable policy, or platform linker options are required.

NASM’s current manual defines directives and format-specific behavior. The operating-system ABI and processor manuals define the contracts outside the assembler.

Comments

Read or comment on GitHub (sign-in required to comment)