Skip to content

Latest commit

 

History

80 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AmmAsm - x86-64 Assembler

AmmAsm - Assembler that sucks less.

GitHub last commit Version Platform

License Status

Author: Ammar Najafli

AmmAsm is a handwritten x86-64 assembler designed for simplicity and clarity, educational and experimental purpose. 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. Successfully tested on Debian12, Kyronix, Windows(experimental).


What's New in v3.0.x

  1. Added new instructions: push2, pop2, push2p, pop2p, jmpabs, endbr64, CCMPcc(set), CTESTcc(set), CFCMOVcc(set), vpunpcklqdq

  2. Added Advanced APX support

  3. Added documentation for APX doc/APX/APX.md

  4. Added PE32+ output (experimental)


Advanced APX (Advance Performance Extension) Support

Starting from 3.0.0, AmmAsm includes a fully handwritten, Advance Performance Extension with zero external dependencies. It supports almost full of modern Intel APX architecture. For better documentation please see doc/APX/APX.md

Key APX Features

  • EGPRs: New r16-r31 registers
  • NDD: Non-destructive destination via EVEX prefix engine
  • NF: Status flags modification suppression
  • ZU: Zero Upper
  • CCMPcc / CTESTcc: Conditional CMP and TEST instructions
  • PUSH2 / POP2: Extended push/pop for two registers
  • JMPABS: Absolute 64-bit jump (jmp abs64)
  • REX2: Extended REX prefix
  • NDD-CMOVcc: Non-destructive destination for CMOVcc
  • Syntax Sugar: Exclusive AmmAsm syntax sugar
  • CFCMOVcc: Conditionally Faulting Conditional Move

Advanced AVX-512 Support (EVEX Prefix)

Starting from v2.4, AmmAsm includes a fully handwritten, high-performance EVEX prefix encoder with zero external dependencies. It supports the core features of the modern Intel/AMD AVX-512 architecture.

Key AVX-512 Features

  • 32 Vector Registers: Full access to ZMM0-ZMM31 (as well as XMM16-XMM31 and YMM16-YMM31).
  • Predicate Masking: Dedicated hardware masking with {k1}-{k7} register selection.
  • Zeroing Masking: Optional conditional zeroing via the {z} modifier.
  • Embedded Broadcast: Built-in {b} flag support for memory operands (e.g., DWORD BCST).
  • Compressed Displacement: Automatic scale matching (disp8 times N) based on data type, broadcast state, and vector size.
  • SAE Decorator: Support for reg-to-reg operations, mainly for ZMM but also supports for XMM if instruction is scalar

Syntax Example

Unlike standard assemblers, AmmAsm uses an explicit, clean, and bulletproof Key-Value syntax for memory operands to make handwritten assembly robust and easy to parse:

_start:
    ; 512-bit vector add with masking {k1}, zeroing {z}, SIB-addressing, 
    ; and embedded 1-to-16 DWORD broadcast {b} enabled!
    vaddps zmm20{k1}{z}, zmm10, [b=rbp, i=rcx, s=1, d=64]{b}
    vcvtneps2bf16 xmm15, yword [b=rax, i=rcx]{b}
    vcvtneps2bf16 xmm15, yword [b=rax, i=rcx] ; useing short form via VEX

also check tests/General/

Verification (objdump)

Code generated directly by AmmAsm and disassembled using standard Linux objdump -d -Mintel:

0000000000000000 <_start>:
   0:	62 e1 2c d9 58 64 0d 	vaddps zmm20{k1}{z},zmm10,DWORD BCST [rbp+rcx*1+0x40]
   7:	10 
   8:	62 72 7e 38 72 3c 08 	vcvtneps2bf16 xmm15,DWORD BCST [rax+rcx*1]{1to8}
   f:	c4 62 7e 72 3c 08    	{vex} vcvtneps2bf16 xmm15,YMMWORD PTR [rax+rcx*1]

Object File Support (ELF64 Relocatable)

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.

Supported Sections

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

Global and Extern Symbols

The global and extern directivies exports labels into the ELF symbol table.

global _start, strcmp
extern printf, __pthread_unregister_cancel_restore

Only object-file generation uses exported symbols. They have no effect when producing ET_EXEC/PIE.

Relocations

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.

Example

./aasm hello.asm -c hello.o
gcc hello.o -o hello
./hello

Features

  • Basic SSE/SSE2/AVX1/AVX2/AVX-512 support(VEX/EVEX full suport)
  • Advanced APX(Advanced Performance Extensions) support
  • 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 and vector 128/256/512 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, and jmpabs with absolute 64 bit jump
  • Two-pass linker - Built-in symbol resolution and relocation
  • Numeric literals - 0xDEADBEEF, 0b1010, 0o777, decimal, negative, 32 bit-float (IEE-754)
  • Align symbol: usage: align 16, 0x90
  • Hardware check for the presence of SIMD instructions via cpuid
  • ELF output - Generates valid Linux x86-64 ET_EXEC, PIE and OBJ(v2.0.0) binary
  • PE32+ output (experimental)

Expression features:

  • mov rax, msg+5 -> absolute address
  • jmp msg+10 -> relative jump with offset
  • lab: dq $-msg, msg+8, msg+16 -> data directives
  • add rax, $-_start -> arithmetic with current address
  • mov 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", 0

Backend 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 object-files -> linking via ld -> running binary

Demo


Pipeline Stages

1. Preprocess

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

2. Lexer (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'

3. Parser (PARSE)

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.

4. Code Generator (parseInst)

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
  • SSE/SSE2/AVX/AVX2/AVX512/APX

5. Linker (collect_labels + resolve_labels)

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

6. Compiler (compiler)

Orchestrates all passes and writes the final binary buffer.


Memory Addressing

Unlike 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 + 4

Building & Usage

# 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
./aasm input.asm -pe32+ -o a.out

# Run
chmod +x output && ./output
ld prog.o -o output && chmod +x output && ./output

Known Limitations

  • Limited instruction set - Only a subset of the x86-64 instruction set is currently implemented (look at ./insn.dat)
  • x87 FPU not implemented
  • No ah, bh, ch, dh registers (sorry)

Releases

Packages

Contributors

Languages