1. Write the source
The programmer writes assembly instructions, labels, directives, data definitions, and other NASM source elements in an assembly source file.
NASM • LINUX & 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
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 callsThis page connects those stages with practical debugging techniques.
← Return to the complete NASM guideBUILD PROCESS
NASM source code normally passes through multiple stages before it becomes a program that the operating system can execute.
The programmer writes assembly instructions, labels, directives, data definitions, and other NASM source elements in an assembly source file.
NASM translates the assembly source into machine-code instructions and produces an object file containing the assembled sections and related information.
A linker processes the object file and produces an executable with the required executable structure and symbol information.
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
./programThe 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
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.
| Register | Typical system-call role | Meaning |
|---|---|---|
| RAX | System-call number | Identifies the Linux system call being requested. |
| RDI | Argument 1 | Contains the first system-call argument. |
| RSI | Argument 2 | Contains the second system-call argument. |
| RDX | Argument 3 | Contains the third system-call argument. |
| R10 | Argument 4 | Used for the fourth system-call argument. |
| R8 | Argument 5 | Used for the fifth system-call argument. |
| R9 | Argument 6 | Used 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 simple exit operation demonstrates the relationship between registers and the syscall instruction.
section .text
global _start
_start:
mov rax, 60
mov rdi, 0
syscallIn 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
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
syscallThe 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 conceptsRETURN VALUES
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 errorThe 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
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 repeatable workflow is more effective than randomly changing instructions and rerunning the program.
Convert the NASM source file into an object file while checking for syntax and assembly errors.
Use a linker to combine the object file into an executable program with the required executable structure.
Execute the program normally and observe its output, behaviour, exit status, and any visible errors.
Load the executable into a debugger such as GDB when the program does not behave as expected.
Use breakpoints, stepping, register inspection, memory inspection, and disassembly to understand what the processor is doing.
Identify the point where the program state diverges from the intended behaviour, correct the source, and test again.
GDB
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 ./programOnce 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
A debugger becomes much more powerful when you know which parts of the processor state matter for the problem you are investigating.
Pause execution at a selected instruction so that the current processor state can be examined before execution continues.
Execute instructions one at a time to trace changes in registers, memory, flags, and control flow.
Examine general-purpose registers, the instruction pointer, stack pointer, and other processor state.
Examine the contents of memory at addresses referenced by registers, stack frames, pointers, or data structures.
View machine-code instructions as assembly and compare the executable with the original source.
Inspect stack contents and stack frames when investigating procedures, saved registers, return addresses, or corrupted execution.
BREAKPOINTS & STEPPING
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 correctA 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
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 locationRegister inspection becomes particularly powerful when combined with single-stepping because you can compare the state before and after individual instructions.
← Review NASM registers and instructionsMEMORY DEBUGGING
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:
Memory debugging connects directly to the addressing concepts discussed in the previous NASM guide.
Review NASM memory addressing and stack concepts →TROUBLESHOOTING
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.
Check instruction syntax, operand compatibility, labels, directives, register names, and other source-level errors reported by NASM.
Check object-file generation, linker commands, entry points, architecture assumptions, and external symbols.
Inspect control flow, return instructions, system-call arguments, stack state, and the instruction pointer.
Trace the data from its original source through registers and memory to determine where the value becomes incorrect.
Check PUSH/POP balance, stack-pointer changes, procedure prologues and epilogues, and any writes near the stack frame.
Verify the system-call number, argument registers, pointer values, buffer sizes, and return value.
PRACTICAL ANALYSIS
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
syscallIf 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
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
The four pages together provide a structured path from the fundamentals of NASM through registers, memory, system calls, and debugging.
Start with the main overview covering NASM, x86-64 architecture, registers, instructions, memory, stacks, Linux, and the learning path.
Open the NASM guide →Study RAX through R15, RIP, RFLAGS, data movement, arithmetic, comparisons, jumps, bitwise operations, and stack instructions.
Explore registers and instructions →Explore addresses, memory operands, addressing modes, arrays, LEA, RSP, RBP, PUSH, POP, CALL, RET, and stack frames.
Explore memory and addressing →ACADEMIC & TECHNICAL WORK
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
Tell us what you're researching, building, or trying to understand. We'll help you find the clearest ethical next move.