Assembly Language Fundamentals
itProgramming languages
Assembly Language Fundamentals
Assembly language gives readable names to machine instructions, registers, and addresses. An assembler translates those statements into machine code for one target architecture. The processor executes the encoded instructions, not the source text.
That distinction sets the boundary for this course. Assembly language is not one portable language. Each instruction set architecture, or ISA, defines its own registers, instruction encodings, operations, and execution rules. Assemblers then add their own syntax, directives, and conveniences around that ISA. Even x86 tools commonly offer both AT&T and Intel syntax.
You use assembly language when the machine-level contract matters. Common reasons include reading compiler output, debugging without source, implementing startup code, examining security failures, writing architecture-specific routines, and understanding operating-system or embedded code. Most application code stays in a higher-level language because assembly is architecture-specific and exposes details that compilers normally manage.
The machine-state model
A processor repeatedly fetches an instruction at the program counter, decodes it, executes it, and advances or replaces the program counter. The visible state includes registers, memory, and status information.
Registers are small storage locations named by the ISA. General-purpose registers commonly hold integers, addresses, and intermediate values. An architecture can also define dedicated floating-point, vector, status, stack-pointer, or link registers. The names and exact roles differ by architecture.
Memory is a byte-addressed space on the common architectures covered by the primary sources. A load copies data from memory into a register. A store copies data from a register into memory. Arithmetic instructions usually work on register values. Branch instructions change control flow by replacing the next instruction address.
The stack is a memory region managed by software convention. A stack pointer identifies its current boundary. Procedure-call standards define how functions use it for return information, saved registers, local storage, and spilled values. The stack is not a magic container supplied by assembly syntax.
Reading a statement
An assembly source statement can define a label, issue an assembler directive, or name an instruction. GNU assembler documentation distinguishes these cases: labels define symbols, directives guide the assembler, and instruction statements encode machine instructions.
loop:
add r0, r0, r1
branch-if-not-zero loop
This is architecture-neutral pseudocode, not valid input for a specific assembler. It shows the transferable shape:
loopis a label that names a location.addis a mnemonic for an arithmetic operation.r0,r1, andloopare operands.- the branch changes the next instruction address when its condition is true.
Real syntax can reverse operand order, prefix register names, encode operand width in the mnemonic, or use different branch conditions. Read the assembler manual and the architecture manual together. The assembler manual defines accepted source syntax. The architecture manual defines what each instruction does.
Instructions, directives, and data
An instruction requests a processor operation. Common groups include data movement, integer arithmetic, bitwise logic, shifts, comparisons, branches, calls, returns, and floating-point or vector work.
A directive controls translation rather than processor execution. Directives can select a section, define data, align the next item, export a symbol, or attach metadata. GNU assembler directives commonly begin with a period, but that spelling is a tool convention rather than an ISA rule.
Operand forms describe where values come from. An immediate operand places a constant in the instruction. A register operand names processor state. A memory operand calculates an effective address from some architecture-specific combination of registers, offsets, scales, or literal locations.
Data width matters. An operation may interpret only part of a register or may load one, two, four, or eight bytes. Signed and unsigned operations can treat the same bit pattern differently. Byte order, or endianness, determines how a multi-byte value is arranged at consecutive memory addresses. You must know the target architecture and selected execution mode before interpreting raw bytes.
Comparisons and control flow
Straight-line code advances through neighboring instruction addresses. A branch selects another address. An unconditional branch always transfers control. A conditional branch transfers control only when its condition is satisfied.
Architectures represent conditions differently. Some instructions update status flags. Other architectures compare registers directly in a branch or write the comparison result into a register. The reusable idea is data-dependent control flow, not one specific flag register.
Loops and decisions are built from labels, comparisons, and branches. Function calls add a return path. An ISA defines the low-level call and return mechanisms, while an application binary interface, or ABI, assigns roles to registers and defines the function-call contract.
Calls are contracts
Separately assembled functions interoperate only when they agree on an ABI. The ABI defines where arguments and return values go, which registers a caller may expect to survive, how the stack is aligned, and how external symbols are named.
A caller-saved register may be changed by the called function. The caller preserves it when the current value is still needed. A callee-saved register must be restored by the called function before return when that function uses it. These categories are ABI rules, not meanings inherent in the physical registers.
Violating the call contract can corrupt values far from the faulty instruction. When you inspect a function, identify its ABI before interpreting its setup and cleanup code.
From source to execution
Assembly source does not normally become a running process in one step.
source → assembler → relocatable object → linker → executable or shared object
The assembler emits encoded instructions, data, sections, symbols, and relocation records. A symbol names a location or value. A relocation records a place that needs adjustment when a final address becomes known.
The linker combines object files and libraries. It resolves symbol references, relocates data, and maps input sections into an output file. A loader then maps an executable and its required shared objects into a process before execution begins.
This pipeline explains why an address printed in a source listing may not be the final runtime address. It also explains why a call to an external function can remain unresolved in a relocatable object.
Inspection before modification
Disassembly translates machine-code bytes back into instruction mnemonics. It is an interpretation based on a selected architecture, execution mode, and syntax. It cannot restore comments, original labels, type information, or the exact source structure that produced the bytes.
GNU objdump can display sections, symbols, relocations, raw contents, and disassembly. GDB can disassemble an address range, inspect registers, examine memory, and execute one machine instruction at a time. Start with read-only inspection. A wrong architecture or a mistaken instruction boundary can make valid data look like plausible code.
Compiler output is a strong study aid. Compile a small function with debug information, inspect the object file, and connect each source operation to the generated instructions. Optimization can reorder, combine, remove, or inline operations, so optimized machine code may not preserve a one-to-one source mapping.
Limits and next steps
This course gives you the transferable model. It does not teach one architecture's full instruction set or one platform's complete ABI. Choose one target next. Learn its registers, data types, addressing forms, base instructions, assembler syntax, object format, and procedure-call standard as one connected system.
Then trace small compiler-generated functions in a debugger. Move from arithmetic to branches, loops, memory access, and calls. Only after that should you write routines that cross an ABI boundary or manipulate process state directly.
