Learn x86-64 Assembly by Writing a GUI from Scratch This article by Philippe Gaultier demonstrates how to write a minimalist GUI program in pure x86-64 assembly targeting Linux/X11. The goal is to build a small (around 1 KiB stripped) GUI application from the ground up without libraries, exploring system calls and the X11 protocol. --- Overview & Motivation Assembly is often seen as only for toys or small, optimized functions. What if you wrote a complete GUI program in assembly? Result: a black window with "Hello, world!" text, around 600 lines assembly, 1 KiB size. --- Tools and Setup Uses nasm assembler (Intel syntax), outputs ELF64. Targets Linux using Linux syscall ABI. Uses the X11 protocol directly over a Unix domain socket /tmp/.X11-unix/X0. Compatible with XWayland or possibly macOS XQuartz with some flags. --- X11 Basics X11 server manages windows and rendering. Clients connect via sockets, send commands, receive events. No external libraries used (libX11 or libxcb usually used). Protocol documentation: https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html --- Starting the Assembly Program Entry point start defined globally. Use system calls directly: exit(60), socket(41), connect(42), etc. Linux x64 syscall arguments via registers (rax=syscall, rdi, rsi, rdx...). Initial minimal program just exits cleanly with code 0. --- Stack Primer Stack grows downward, use rsp to manage it. Standard function prolog/epilog: Maintain 16-byte alignment before call. Example of printing "hello" using stack bytes and write(1) syscall. --- Creating & Connecting Socket Open Unix domain socket: socket(AFUNIX, SOCKSTREAM, 0). Prepare sockaddrun struct on stack with AFUNIX + path /tmp/.X11-unix/X0. Connect to the X11 server using connect syscall. Inline functions with standard prolog/epilog to modularize. --- Handshake With X11 Server Compose and send connection handshake message (12 bytes) with: Byte order: 'l' for little endian. Major version: 11. Read server response: First 8 bytes: check success = 1. Then read the full 14 KiB server info into stack buffer. Extract global IDs from response: idbase, idmask, rootvisualid, window root id. --- Resource ID Generation IDs created client-side as idmask & id | id_base. Global ID counter incremented for each new resource. --- Opening Font Send OpenFont request to X11 server with font name "fixed". Uses packed message including font id and name with padding. Font must be opened before drawing text. --- Creating Graphical Context (GC) Create a graphical context resource (CreateGC request). Includes settings like foreground & background colors, font. Complex packet built on stack with flags and IDs. --- Creating and Mapping the Window Create window specifying: Window resource id. Parent: root window. Coordinates and dimensions (x, y, w, h). Visual id, event masks (key, exposure). Send MapWindow request to display the window. Result is a black window visible on screen. --- Polling & Event Loop Set socket non-blocking via fcntl syscall. Use poll syscall with POLLIN to wait for server events. Read event messages (32 bytes) via read syscall. Wait for Expose event to know when window is visible. On exposure, proceed to draw text. --- Drawing Text Build ImageText8 request dynamically for arbitrary text length. Calculate padding to 4-byte alignment. Send text with window id, GC id, and packed x/y coordinates. Displays "Hello, world!" inside the window. --- Results & Summary Final executable sizes: With debug info: ~10 KiB. Stripped: ~8 KiB. Stripped + --omagic: ~1 KiB. Pure