Logo FazeCT
Advent of Code 2025 using x86 Assembly?

Advent of Code 2025 using x86 Assembly?

December 13, 2025
5 min read (1+ hour read total)
12 subposts
Table of Contents

Overview

Over the last two weeks, I participated in Advent of Code 2025 — a series of daily programming puzzles that get progressively harder. This year, the event consists of 12 challenges in total, each challenge has two parts (except the very last one) where the second part is more advanced than the previous one.

Usually, I spend my time reversing x86 binaries for CTFs. For Advent of Code 2025, I decided to write them instead. I challenged myself to solve all problems entirely in x86 Assembly, without the help of LLMs.

I will provide my thought process during the challenges, and explain how I constructed the algorithms using x86 Assembly in the sections below.

Disclaimer: Due to the large amount of challenges within one post, I will try my best to only briefly explain each x86 Assembly snippet, not in detail. Methodology of constructing the solutions will be more focused than the execution.

All of the solutions can be found here.

(link to the event: https://adventofcode.com/2025/)

Preparation

To better address the problems, here are some x86 Assembly snippets that can be reused in multiple challenges. Personally, I am more familiar with Intel syntax compared to AT&T syntax, hence I chose to program the solutions in Intel syntax and used NASM to compile.

Input reading

To get the input for the problem, we utilize chain of syscalls to read the input file, store the input into a null-initialized buffer and close the file.

Each line of input will be separated by an endline \n, and passed as argument to the solver function.

Some variables are also introduced in .bss section, in which input is for storing the challenge input, result is for storing the challenge result, and output is used for integer printing that will be explained in the next part.

templates/input_read.asm
section .data
    filename db "../problem/input.txt", 0
 
section .bss
    input resb 100000
    output resb 20
    result resq 1
 
section .text
    global _start
 
_start:
    ; Open input file 
    mov rax, 2
    mov rdi, filename 
    xor rsi, rsi
    xor rdx, rdx
    syscall
    mov rbx, rax
 
    ; Read input file 
    xor rax, rax
    mov rdi, rbx 
    mov rsi, input
    mov rdx, 100000
    syscall
 
    ; Close the file
    mov rax, 3
    mov rdi, rbx
    syscall
 
    ; Split the ranges and process them one by one
    mov r8, input
    mov r9, input 
    mov qword [result], 0
 
reader:
    movzx rax, byte [r9]
    test rax, rax 
    je process_last_line
 
    cmp rax, 10
    je process_line 
 
    inc r9 
    jmp reader
 
process_line:
    mov rdi, r8
    call solver 
 
    inc r9
    mov r8, r9
    jmp reader
 
process_last_line:
    cmp r8, r9
    je print_output
 
    mov rdi, r8
    call solver
 
print_output:
    ; Print result to stdout 
    mov rdi, qword [result] 
    call print_int
 
    ; Exit the program
    mov rax, 60
    xor rdi, rdi
    syscall

Integer printing

We generally can not directly print an integer from a register or a memory region. To resolve this, a helper is used to convert the integer to string and then print it to the screen. The default length of converted string is 20.

templates/print_int.asm
; ----------------------------------------------------------------------
; Component: Print Integer
; Args: rdi
 
print_int:
    mov rax, rdi
    mov rcx, output
    add rcx, 19
    mov byte [rcx], 10
    mov rbx, 10
 
.loop:
    dec rcx
    xor rdx, rdx
    div rbx
    add dl, '0'
    mov [rcx], dl
    test rax, rax
    jnz .loop
 
    ; Calculate length
    mov rdx, output
    add rdx, 20
    sub rdx, rcx
 
    ; Print
    mov rax, 1
    mov rdi, 1
    mov rsi, rcx
    syscall
    ret

ASCII to Integer (Atoi)

Another helper to convert strings of digits into integers is also being used a lot to solve the problems. Just a clarification, the function is terminated right away when any byte that is not in range [0-9] is detected.

templates/atoi.asm
; ----------------------------------------------------------------------
; Component: Atoi (String to Int) conversion
; Args: rdi
; Ret: rax = atoi(rdi)
; Terminate if any byte is not in range [0-9]
 
atoi:
    xor rax, rax
 
.convert:
    movzx rsi, byte [rdi]
    test rsi, rsi 
    je .atoi_done
 
    cmp rsi, 10
    je .atoi_done
 
    cmp rsi, '0'
    jl .atoi_done 
 
    cmp rsi, '9'
    jg .atoi_done 
 
    sub rsi, '0'
    imul rax, 10
    add rax, rsi 
 
    inc rdi 
    jmp .convert
 
.atoi_done:
    ret

Challenges

(yes I vibe-designed all of these thumbnails with Gemini…)

In a nutshell

The hardest challenge in my opinion is Day 10: Factory, Part 2. Along with that, parsing grids and graphs were also a nightmare using x86 Assembly.

Nonetheless, I really enjoyed solving all the challenges this year with x86 Assembly.