x86 32-bit Assembly with NASM and GNU as

Assemblers translate source notation into object-file sections, symbols, instruction bytes, and relocations. NASM and GNU as can both produce ELF32 for IA-32, but their default syntaxes differ. This lesson keeps the boundary deliberately safe: assemble and inspect small source files you wrote. It does not require a 32-bit runtime, does not invoke a system-call interface, and does not ask you to execute a binary. Linking and running a 32-bit program requires a deliberately configured compatible environment; object-only work is enough to learn syntax and relocation basics.

Two source languages for the same architecture

NASM commonly uses Intel syntax. Operands are written destination first, registers have no prefix, immediate constants have no marker, and brackets denote memory. GNU as defaults to AT&T syntax on many targets: operands are source first, registers begin with %, immediates begin with $, and parentheses express memory addressing. GNU tools can sometimes select Intel syntax with directives, but this article compares their conventional forms so a reader can recognize published examples accurately.

MeaningNASM / Intel formGAS / AT&T form
copy immediate 7 to EAXmov eax, 7movl $7, %eax
add EBX to EAXadd eax, ebxaddl %ebx, %eax
load at EBP minus fourmov eax, [ebp-4]movl -4(%ebp), %eax
address base + index × 4 + 8[ebx+esi*4+8]8(%ebx,%esi,4)
returnretret

The l suffix in familiar AT&T examples means a 32-bit “long” operand. Other suffixes include b, w, and q. Instruction suffix requirements vary with the instruction and available operand information; do not mechanically add one where GAS already infers the size. Intel syntax often derives memory width from the register operand, but an immediate-to-memory operation can need an explicit qualifier such as dword [value] in NASM.

Equivalent, object-only sources

Each file below exports a pure integer function. It has no entry point and no external reference. The stated cdecl-style interface is part of the example: two 32-bit arguments are on the incoming stack, EAX returns their sum, and the function does not modify the commonly callee-saved EBX, ESI, EDI, or EBP.

; add_nasm.asm
BITS 32
section .text
global add_words
add_words:
    mov eax, [esp+4]      ; first argument after return address
    add eax, [esp+8]      ; second argument
    ret

nasm -f elf32 add_nasm.asm -o add_nasm.o
# add_gas.s
    .text
    .globl add_words
    .type add_words, @function
add_words:
    movl 4(%esp), %eax
    addl 8(%esp), %eax
    ret
    .size add_words, .-add_words

as --32 add_gas.s -o add_gas.o

Both commands emit an ELF32 relocatable object. They neither select a C library nor provide startup code, so they avoid assuming that a host has multilib runtime support. Check rather than assume the result:

readelf -h -S -s add_nasm.o
readelf -h -S -s add_gas.o
objdump -dr -Mintel add_nasm.o
objdump -dr -Mintel add_gas.o

readelf -h should identify ELF32 and the Intel 80386 machine for a normal i386 ELF target. The section table shows .text; the symbol table should include the global function. objdump -dr displays instructions and any relocation records together. The -Mintel option changes objdump’s display notation, not the bytes generated by GAS. Compare structure and meaning rather than expecting byte-for-byte identity after changing assembler version or source layout.

Labels, sections, and relocations

A label names a position. NASM’s global and GAS’s .globl make a symbol visible to a linker. ELF sections separate code, initialized data, read-only data, and metadata; common names include .text, .data, .rodata, and .bss. The assembler can resolve a branch to a label in the same section, but a reference to another object often needs a relocation. A relocation is structured linker information, not an error and not a final address.

; NASM relocation demonstration: object only
BITS 32
section .text
global call_helper
extern helper
call_helper:
    call helper
    ret

nasm -f elf32 reloc.asm -o reloc.o
readelf -r reloc.o

The unresolved helper is intentional. Inspecting the relocation is safe and illustrates why disassembly of a relocatable object does not always show a final call destination. Do not invent a target, download an object of unknown origin, or execute it. In a real project, the linker resolves the symbol from an authorized companion object or library built for the same ABI.

Addresses, directives, and sizes

Both syntaxes can express ModR/M and SIB addressing without requiring hand-written machine bytes. NASM’s [base + index*scale + displacement] and AT&T’s displacement(base,index,scale) describe the same conceptual effective address. A memory operand actually reads or writes memory for instructions such as mov; lea uses that syntax to calculate the address only. The architecture stores multi-byte integers little-endian, but source constants remain conventionally written most-significant digit first.

Use directives for data instead of guessing encodings. NASM offers db, dw, and dd; GAS offers .byte, .word, and .long. A named datum can be made local to its object or exported only when it is a real interface. Alignment directives and zero-filled storage have assembler-specific spelling, so consult the relevant manual rather than copy a directive between dialects.

ABI discipline in source

Assembler syntax does not establish a calling convention. A function intended for 32-bit cdecl must preserve the nonvolatile registers promised by its ABI and leave ESP balanced. Standard calling-convention names also need platform qualification: 32-bit stdcall typically has the callee discard fixed stack arguments, while fastcall is compiler-specific and may use ECX and EDX. Floating point further complicates an interface: x87 and SSE/XMM use depends on ABI and target settings. See stack frames and calling conventions before exporting a function.

Linux int 0x80 appears in historical 32-bit Linux examples, but it is an operating-system-specific system-call entry convention, not an x86 instruction-set calling convention and not portable to other systems. Its number assignments and register rules are kernel ABI details. This object-only lesson intentionally has no such code; use current, documented platform APIs for normal applications and consult the kernel’s ABI documentation only when working on authorized platform-specific low-level software.

Diagnostics and comparison

Errors are useful evidence. “Invalid combination of opcode and operands” usually means an illegal operand-size or memory-to-memory combination, not that an arbitrary alternative byte should be tried. “Relocation truncated” can signal an incompatible target or model. First verify BITS 32 or as --32, ELF32 output, the selected syntax, and symbol spelling. Then reduce the source to a documented instruction and consult the assembler manual. Do not solve build errors by disabling checks or by running a partially understood executable.

  1. Assemble each pure-function source above and compare its ELF header, sections, and symbol table.
  2. Replace the addition with lea that computes a + b*4. State the preconditions and verify only the object-file disassembly.
  3. Add a local label and a conditional branch that returns either 0 or 1. Explain whether the branch target needs a relocation.
  4. Find the exact GAS version’s manual entry for .type and record why it is metadata rather than an instruction.

A repeatable source-review workflow

Keep one dialect per source file and state it in a header comment. Name the requested output format explicitly—elf32 for NASM or --32 for GAS—rather than trusting a host default. After assembling, inspect the ELF header before looking at instructions: it establishes class and machine target. Next inspect section and symbol tables, then relocation records, and only then use a disassembly to relate labels to instructions. This order makes it clear whether a surprising result is an assembler syntax issue, an object-format issue, or an ABI issue.

GAS directives beginning with a period are assembler instructions to create metadata or data, not CPU instructions. NASM directives serve the same broad purpose but do not always share spelling or semantics. Likewise, comments differ by syntax and context. The manuals linked below are the canonical source for the installed version. A build script should record tool versions and target options so another authorized developer can reproduce the object instead of guessing which dialect produced it.

Do not confuse objdump presentation with source provenance. A GAS-produced object can be displayed with Intel operands, and a NASM-produced object can be displayed in an AT&T-oriented environment. Compare addresses, relocations, and symbol boundaries with the source you own. Where an immediate or displacement is emitted, remember little-endian storage affects the byte order displayed by a hex dump, while the disassembler reconstructs the numeric value for humans.

For register names, protected-mode context, and encoding concepts, read x86 32-bit registers, modes, and encoding. The broader x86 assembly overview keeps IA-32 and x86-64 terminology distinct.

References