NASM • LINUX & DEBUGGING

NASM on Linux: System Calls, Linking and Debugging.

Learn how x86-64 NASM programs interact with Linux through system calls, how source code becomes an executable, and how debugging tools can reveal what is actually happening inside a low-level program.

FROM CODE TO EXECUTION

Assembly becomes useful when it can interact with the operating system.

Writing individual instructions is only one part of low-level programming. A useful program must eventually interact with the operating system, produce an executable, and behave correctly when it runs.

A NASM source file is not itself a finished executable program. It must first be assembled into an object file and then linked into an executable. Once executed, the program runs in a particular operating-system environment and may request services from the Linux kernel.

Understanding this complete path makes assembly programming much easier to reason about:

NASM source
     ↓
Object file
     ↓
Linker
     ↓
Executable
     ↓
Process
     ↓
Linux system calls

This page connects those stages with practical debugging techniques.

← Return to the complete NASM guide

BUILD PROCESS

From a .asm file to an executable.

NASM source code normally passes through multiple stages before it becomes a program that the operating system can execute.

1. Write the source

The programmer writes assembly instructions, labels, directives, data definitions, and other NASM source elements in an assembly source file.

2. Assemble

NASM translates the assembly source into machine-code instructions and produces an object file containing the assembled sections and related information.

3. Link

A linker processes the object file and produces an executable with the required executable structure and symbol information.

4. Execute

The operating system loads the executable into a process and begins execution at its appropriate entry point.

A common educational workflow on Linux looks conceptually like this:

nasm -f elf64 program.asm -o program.o
ld program.o -o program

./program

The exact build command can vary depending on the operating system, linker, libraries, executable format, and whether the program uses external runtime components.

LINUX SYSTEM CALLS

How a NASM program requests services from Linux.

A system call provides a controlled interface through which a user-space program can request functionality from the operating-system kernel.

A Linux x86-64 program cannot simply perform every privileged operation directly. Instead, it can request operating-system services through defined system-call interfaces.

For the x86-64 Linux system-call convention, the system-call number is placed in RAX and arguments are passed through designated registers.

RegisterTypical system-call roleMeaning
RAXSystem-call numberIdentifies the Linux system call being requested.
RDIArgument 1Contains the first system-call argument.
RSIArgument 2Contains the second system-call argument.
RDXArgument 3Contains the third system-call argument.
R10Argument 4Used for the fourth system-call argument.
R8Argument 5Used for the fifth system-call argument.
R9Argument 6Used for the sixth system-call argument.

The exact system-call number and argument requirements depend on the particular system call being requested.

PRACTICAL EXAMPLE

A minimal Linux exit system call.

A simple exit operation demonstrates the relationship between registers and the syscall instruction.

section .text
    global _start

_start:
    mov rax, 60
    mov rdi, 0
    syscall

In this example, RAX contains the Linux x86-64 system-call number for exit, while RDI contains the exit status argument. The syscall instruction transfers control to the operating-system system-call mechanism.

The important concept is the interface: the processor executes the SYSCALL instruction, while Linux interprets the register state according to its x86-64 system-call convention.

SYSTEM CALL EXAMPLE

Writing data through a Linux system call.

System calls become more useful when a program passes pointers and sizes to the operating system.

section .data
    message db "Hello from NASM!", 10
    length equ $ - message

section .text
    global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, length
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall

The example demonstrates an important combination of concepts. The program has data in a separate section, calculates its length, places the required values into registers, requests the Linux write operation, and then exits.

Notice that RSI contains the address of the message rather than the message itself. This connects directly to the memory addressing concepts covered in the previous guide.

← Review NASM memory addressing and stack concepts

RETURN VALUES

System calls return information too.

A system call is not simply a one-way request. Linux returns a result through the processor registers, allowing the program to determine whether an operation succeeded or failed.

The return value of a Linux x86-64 system call is conventionally returned in RAX. Programs can therefore inspect RAX after the SYSCALL instruction and make subsequent decisions based on the result.

syscall

; RAX now contains the system-call result

cmp rax, 0
jl  error

The exact meaning of the returned value depends on the system call. Understanding return values is essential when writing reliable low-level programs and when debugging unexpected behaviour.

DEBUGGING NASM

When the program is wrong, inspect the processor state.

Assembly debugging becomes much more systematic when you stop treating the program as a black box and instead examine registers, memory, control flow, and stack state.

In a high-level language, a debugger can show variables, source-level expressions, function calls, and other abstractions that are familiar to the programmer. In assembly, many of those abstractions are much closer to the underlying processor.

This makes debugging both more challenging and more informative. You can observe the exact registers and memory locations being modified as instructions execute.

A useful debugging question is not simply: "Why did the program fail?"

Instead ask: "At which instruction did the processor state stop matching what I expected?"

DEBUGGING WORKFLOW

A systematic way to debug assembly programs.

A repeatable workflow is more effective than randomly changing instructions and rerunning the program.

Assemble the source

Convert the NASM source file into an object file while checking for syntax and assembly errors.

Link the object

Use a linker to combine the object file into an executable program with the required executable structure.

Run the program

Execute the program normally and observe its output, behaviour, exit status, and any visible errors.

Start the debugger

Load the executable into a debugger such as GDB when the program does not behave as expected.

Inspect execution

Use breakpoints, stepping, register inspection, memory inspection, and disassembly to understand what the processor is doing.

Trace and fix

Identify the point where the program state diverges from the intended behaviour, correct the source, and test again.

GDB

Using GDB to inspect x86-64 execution.

The GNU Debugger can be used to pause a program, step through instructions, inspect registers and memory, and examine the execution state.

When debugging an assembly program, it is useful to build the executable with debugging information where appropriate. The debugger can then provide more useful source-level context while still allowing the underlying machine state to be inspected.

gdb ./program

Once inside GDB, commands can be used to establish breakpoints, execute instructions step by step, inspect registers, examine memory, and inspect the current instruction.

The exact commands and debugging workflow depend on the executable and how it was assembled and linked, but the central principle remains the same: pause execution, inspect state, advance execution, and compare the observed state with the expected state.

DEBUGGING TECHNIQUES

The most useful things to inspect.

A debugger becomes much more powerful when you know which parts of the processor state matter for the problem you are investigating.

Breakpoints

Pause execution at a selected instruction so that the current processor state can be examined before execution continues.

Single-stepping

Execute instructions one at a time to trace changes in registers, memory, flags, and control flow.

Register inspection

Examine general-purpose registers, the instruction pointer, stack pointer, and other processor state.

Memory inspection

Examine the contents of memory at addresses referenced by registers, stack frames, pointers, or data structures.

Disassembly

View machine-code instructions as assembly and compare the executable with the original source.

Stack analysis

Inspect stack contents and stack frames when investigating procedures, saved registers, return addresses, or corrupted execution.

BREAKPOINTS & STEPPING

Stop execution at the point where things matter.

A breakpoint allows the debugger to pause execution before a particular instruction runs, making it possible to inspect the current state.

Suppose a register is expected to contain a particular value before an arithmetic operation. A breakpoint placed before that instruction allows you to inspect the register and determine whether the problem occurred earlier or during the operation itself.

Single-stepping then allows the next instruction to execute while you observe how the processor state changes.

mov rax, 10
add rax, 20
cmp rax, 30
je  correct

A debugger can help determine whether RAX contained the expected value before ADD, whether ADD produced the expected result, whether CMP affected the expected flags, and whether the conditional jump behaved accordingly.

REGISTER INSPECTION

Registers provide a snapshot of program state.

When an assembly program behaves incorrectly, examining the registers often reveals exactly where the unexpected state originated.

Important registers to inspect may include RAX through R15, RSP, RBP, RIP, and relevant processor flags. Which registers matter most depends on the instruction or procedure being investigated.

RAX  = current calculation / result
RSP  = current stack position
RBP  = stack-frame reference, when used
RIP  = current instruction location

Register inspection becomes particularly powerful when combined with single-stepping because you can compare the state before and after individual instructions.

← Review NASM registers and instructions

MEMORY DEBUGGING

Inspecting memory when registers are not enough.

Many assembly bugs are caused by incorrect pointers, offsets, buffer sizes, or overwritten memory.

If a register contains a pointer, inspecting the register alone may not explain the problem. You may also need to inspect the memory at that address and determine whether the expected data is actually present.

This is particularly important when debugging:

  • Arrays and buffers
  • Strings
  • Stack variables
  • Pointers
  • Structures and data layouts
  • System-call arguments

Memory debugging connects directly to the addressing concepts discussed in the previous NASM guide.

Review NASM memory addressing and stack concepts →

TROUBLESHOOTING

Common NASM and Linux problems.

A structured troubleshooting process can quickly narrow down whether a problem originates in the source, build process, control flow, memory state, stack, or operating-system interface.

The program does not assemble

Check instruction syntax, operand compatibility, labels, directives, register names, and other source-level errors reported by NASM.

The executable does not link

Check object-file generation, linker commands, entry points, architecture assumptions, and external symbols.

The program exits unexpectedly

Inspect control flow, return instructions, system-call arguments, stack state, and the instruction pointer.

The output is incorrect

Trace the data from its original source through registers and memory to determine where the value becomes incorrect.

The stack is corrupted

Check PUSH/POP balance, stack-pointer changes, procedure prologues and epilogues, and any writes near the stack frame.

A system call behaves unexpectedly

Verify the system-call number, argument registers, pointer values, buffer sizes, and return value.

PRACTICAL ANALYSIS

Trace the program instead of guessing.

The most reliable way to debug assembly is to establish what the program should do, then trace execution until the actual state differs from the expected state.

mov rax, 10
mov rbx, 20

add rax, rbx

cmp rax, 30
jne error

mov rdi, 0
jmp done

error:
    mov rdi, 1

done:
    mov rax, 60
    syscall

If this program unexpectedly follows the error path, the debugging process should examine the values in RAX and RBX, verify the result of ADD, inspect the comparison, and determine why the conditional branch did not behave as expected.

This method scales to much larger programs. Instead of changing several instructions at once, isolate the point where the program's actual state first differs from the expected state.

CYBERSECURITY & REVERSE ENGINEERING

Why NASM knowledge matters beyond programming.

Understanding assembly, memory, registers, system calls, and debugging provides an important foundation for analysing compiled software.

Security professionals and researchers frequently need to understand what compiled programs are doing at the machine-code level. Assembly knowledge helps make processor behaviour, function calls, memory access, control flow, and operating system interactions more visible.

The same skills introduced in this NASM cluster can therefore support legitimate work in areas such as reverse engineering, vulnerability research, binary analysis, secure software development, and incident investigation.

The emphasis should always remain on authorised analysis, controlled environments, research, education, and defensive security work.

Explore our cybersecurity technical support →

COMPLETE THE NASM CLUSTER

Continue exploring NASM and x86-64 programming.

The four pages together provide a structured path from the fundamentals of NASM through registers, memory, system calls, and debugging.

NASM Complete Guide

Start with the main overview covering NASM, x86-64 architecture, registers, instructions, memory, stacks, Linux, and the learning path.

Open the NASM guide →

Memory & Addressing

Explore addresses, memory operands, addressing modes, arrays, LEA, RSP, RBP, PUSH, POP, CALL, RET, and stack frames.

Explore memory and addressing →

ACADEMIC & TECHNICAL WORK

NASM is a practical gateway into low-level computing.

System calls and debugging bring together many of the concepts studied throughout the NASM cluster.

Academic projects involving assembly may require students to implement programs, explain system interactions, analyse processor state, document debugging results, or demonstrate how source instructions translate into observable execution behaviour.

Understanding the complete path from source code to executable program, system calls, registers, memory, stack state, and debugging makes it easier to explain technical decisions and defend project work.

ProjectAssignments provides structured technical and academic guidance for complex computing work, with an emphasis on understanding the concepts and reasoning behind the work.

Explore our technical academic services →

Let's make your work clearer

Bring us the difficult part.

Tell us what you're researching, building, or trying to understand. We'll help you find the clearest ethical next move.

Get Guidance
Chat with us on WhatsApp