Author: Ammar Najafli
AmmAsm - Assembler that sucks less
AmmAsm is a handwritten x86-64 assembler designed for simplicity and clarity. It compiles assembly code directly to machine code and produces ELF executables, PIE binaries (Position-Independent Executables), and relocatable object files for Linux x86-64,primarily tested on Debian GNU/Linux 12 (bookworm) x86-64.
-
New macro system. Look doc/macro.txt in order to know it syntax
-
Added bss Section support for ET_REL
-
-Eflag, that runs the preprocessor only and generate .i
Starting from v2.0.0, AmmAsm can generate valid ELF64 relocatable object files (.o) in addition to executables.
Generated object files are compatible with the standard Linux toolchain and can be linked using ld, gcc, or other GNU binutils-compatible linkers.
This allows AmmAsm to participate in normal C/C++ build pipelines instead of being limited to standalone executable generation.
Generated object files contain:
.text- executable code.data- initialized data.symtab- symbol table.strtab- symbol string table.shstrtab- section-name string table.rela.text- relocation records.note.GNU-stack- marks stack as non-executable
The global and extern directivies exports labels into the ELF symbol table.
global _start, strcmp
extern printf, __pthread_unregister_cancel_restoreOnly object-file generation uses exported symbols. They have no effect when producing ET_EXEC.
References that cannot be resolved during assembly automatically generate relocation entries.
Currently supported relocations include:
- RIP-relative label references
- External/global symbols
- Symbol references requiring linker resolution
Relocations are emitted into .rela.text and are resolved later by ld, gcc, or compatible ELF linkers.
./aasm hello.asm -c hello.o
gcc hello.o -o hello
./helloThe repository contains bootstrap/strcmp.asm, which is assembled into strcmp.o and linked together with Aasm.c during the build process.
This demonstrates interoperability between AmmAsm-generated object files and ordinary C programs.
- Macro system(v2.2.0)
- Compatible with GNU ld and GCC object-file linking
- Direct x86-64 encoding - No NASM/GAS dependencies
- Multiple operand sizes - 8/16/32/64-bit registers and immediates
- Memory addressing - Full SIB/ModRM support with explicit key-value syntax
- RIP-relative addressing - Automatic for label bases (v1.6)
- Label support - Global and local labels with two-pass symbol resolution
- Inline literals - Embed strings and data directly in .text
- Control flow - jmp, call, conditional jumps with relative addressing
- Two-pass linker - Built-in symbol resolution and relocation
- Numeric literals - 0xDEADBEEF, 0b1010, 0o777, decimal, negative
- ELF output - Generates valid Linux x86-64 ET_EXEC, PIE and OBJ(v2.0.0) binary
Expression features:
mov rax, msg+5-> absolute addressjmp msg+10-> relative jump with offsetlab: dq $-msg, msg+8, msg+16-> data directivesadd rax, $-_start-> arithmetic with current addressmov rax, (((((10 * 2) << 2) + $) & 0xFF) | 0x100) - label-> mixed all
Expression examples:
_start:
mov rax, msg ; address of msg (0x401000)
mov rbx, $-_start ; length from _start to current
mov rcx, msg+5 ; address of 'W' in "Hello World"
mov rdx, msg+8 ; address of 'r' in "World"
.tmp: dq $-msg, msg+5, msg+8, 0xdeadbeef
jmp _start
msg: db "Hello World", 0Backend Refactoring
- resolve_expr() - New expression evaluator that supports:
- Label resolution (msg)
- Current address ($)
- Character literals ('A')
- Arithmetic (+, -, *, /, <<, >>, &, |, ^)
- Parentheses for grouping
- Mixed expressions with labels and constants
Assembling x86-64 code → generating machine code → running binary
Parse all macro and replace them in called place.
- Parse macro body, store it args, content in memory.
- Parses all macros and expands them where they are called.
- Recursively expands nested macros.
- After finishing, creates file.asm.i file and gives this file to Lexer
Converts source text to a flat token stream.
- Recognizes instructions, registers (rax), literals, labels, directives
- Comments: //, ;, /* ... */
- Number bases: hex (0x), binary (0b), octal (0o), decimal
- Label scoping: global label:, local .label: (scoped to last global)
- Character literals: 'A', '\n', '\0'
Builds the Abstract Syntax Tree.
- Validates operand combinations per instruction
- Resolves operand types: O_REG8/16/32/64, O_IMM, O_MEM, O_CHAR, O_EXPR
- Produces typed AST nodes: AST_INS, AST_LABEL, AST_U8/16/32/64, etc.
Emits x86-64 machine code per AST node.
- REX prefix construction
- ModR/M and SIB encoding via encode_inst_rm_rm()
- Displacement and immediate encoding (little-endian)
- Placeholder bytes (0x00000000) for unresolved label references
Two-pass symbol resolution.
- Pass 1 - Walks AST, assigns vaddr to each AST_LABEL (base 0x401000 or 0x1000(PIE))
- Pass 2 - Patches placeholders:
- MOV r64, label -> absolute 64-bit address (8 bytes at mc[2])
- JMP/CALL/JCC label -> rel32 = target - (current_pc + inst_size)
- RIP-relative -> disp32 = target - (current_pc + inst_size) + user_disp
Orchestrates all passes and writes the final binary buffer.
mov rax, 42 ; 64-bit
mov eax, 42 ; 32-bit
mov ax, 42 ; 16-bit
mov al, 42 ; 8-bit
mov r8, r15 ; Extended regs r8-r15mov rax, 42 ; Decimal
mov rax, 0xff ; Hexadecimal
mov rax, 0b1010 ; Binary
mov rax, 0o777 ; Octal
mov rax, -10 ; Negative
mov al, 'A' ; Character literalUnlike NASM, AmmAsm uses an explicit key-value format inside [...]:
| Key | Meaning | Example |
|---|---|---|
| b=REG | Base register | b=rbx |
| i=REG | Index register | i=rcx |
| s=N | Scale (1/2/4/8) | s=4 |
| d=N | Displacement | d=0x10 |
mov rax, [b=rbx] ; [rbx]
mov rax, [b=rbx, d=16] ; [rbx + 16]
mov rax, [b=rbx, i=rcx, s=8] ; [rbx + rcx*8]
mov rax, [b=rbx, i=rcx, s=8, d=0x10] ; [rbx + rcx*8 + 16]
mov [b=rsp, d=8], rax ; store to [rsp+8]
mov rax, [b=msg] ; load from msg
mov rax, [b=msg, d=4] ; msg + 4msg: db "Hello, World!", 0x0A, 0
bytes: db 0x01, 0x02, 0x03, 'A', 'B'
words: dw 100, 200, 300, 0x1234
dwords: dd 0xDEADBEEF, 1000000
qwords: dq 0x123456789ABCDEF0, 0cmp rax, 42 ; rax vs immediate (64-bit)
cmp eax, ebx ; register vs register (32-bit)
cmp al, 'A' ; 8-bit with char literalcmp rax, 0
je done ; jump if equal
jne loop ; jump if not equal
jl less ; jump if less (signed)
jg greater ; jump if greater (signed)
jb below ; jump if below (unsigned)
ja above ; jump if above (unsigned)jmp label ; relative jump (E9 rel32)
jmp rax ; indirect jump (FF /4)
call func ; relative call (E8 rel32)
call rbx ; indirect call (FF /2)mov rax, msg ; load virtual address of 'msg' into rax(Does not work in PIE)# Build
./build.sh
# Compile assembly
./aasm input.asm
./aasm input.asm -o output
./aasm -pie input.asm -o prog
./aasm input.asm -c prog.o -d
./aasm input.asm -c prog.o -E
# Run
chmod +x output && ./output
ld prog.o -o output && chmod +x output && ./output- Limited instruction set - Only a subset of the x86-64 instruction set is currently implemented (look at ./src/instructions.c)
- No floating-point (FPU/SSE)
