Farid Zakaria published something yesterday that I haven't stopped thinking about: "Your executable is a SQLite database". It's not a metaphor. He built SELF (Structured Executable & Linkable Format) — a prototype that replaces ELF with SQLite as the executable format.
Not "a database of executables." The file you chmod +x and run is a SQLite database.
$ file hello
hello: SQLite 3.x database, application id 0x53454c46, user version 1
$ ./hello
Hello, world!
$ sqlite3 hello 'SELECT soname FROM ldd'
libc.so.6
This is one of those ideas that sounds insane until you sit with it. Then it starts to feel inevitable. Here's why.
ELF is a database that won't admit it
Zakaria's core insight, developed during a PhD thesis that "failed to get published": ELF already implements every database primitive, just badly.
| ELF mechanism | What it really is |
|---|---|
.strtab / .dynstr | String interning |
.hash / .gnu.hash | An index (CREATE INDEX) |
| Section header table | sqlite_schema — table of tables |
st_name → offset into .strtab | A foreign key, done by hand |
sh_offset / sh_size | Record layout of a b-tree page |
objcopy --strip-debug | DELETE + VACUUM |
Every consumer of ELF — the kernel, ld.so, binutils, LIEF, goblin, readelf — reimplements the same parser. Every producer reimplements the same serializer. The format is packed tight for a 1980s disk budget, which makes modifying it an exercise in offset surgery. Want to add a section? Zero out the old one and pray nothing breaks.
SQLite is the anti-ELF: self-describing, stable, extensible without breaking consumers, and queryable with a language that's been standard for 50 years.
What falls out
A SELF file needs exactly two tables to run:
CREATE TABLE segments (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL,
offset INTEGER NOT NULL,
vaddr INTEGER NOT NULL,
filesz INTEGER NOT NULL,
memsz INTEGER NOT NULL,
r INTEGER, w INTEGER, x INTEGER,
align INTEGER NOT NULL DEFAULT 4096,
content BLOB -- the segment bytes; NULL for pure BSS
);
CREATE TABLE symbols (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
version TEXT,
value INTEGER, size INTEGER,
type TEXT, -- 'func' | 'object' | 'tls' | ...
bind TEXT, -- 'global' | 'weak' | 'local'
defined INTEGER NOT NULL,
exported INTEGER NOT NULL
);
CREATE INDEX idx_symbols_name ON symbols(name, version);
That's it. Everything else — .dynstr, .gnu.hash, .dynsym, .hash — is gone, replaced by SQLite internals. Consider what this means for every tool in the chain:
# ldd(1) is a simple query
$ sqlite3 hello 'SELECT soname FROM ldd'
libc.so.6
# nm -D --undefined
$ sqlite3 hello 'SELECT name, version FROM imports LIMIT 3'
__libc_start_main|GLIBC_2.34
_ITM_deregisterTMCloneTable|
puts|GLIBC_2.2.5
# readelf -l
$ sqlite3 hello \
"SELECT type, vaddr, memsz, r, w, x FROM segments WHERE type='load'"
load|0|1744|1|0|0
load|4096|361|1|0|1
load|8192|312|1|0|0
load|15768|640|1|1|0
# strip(1) is DELETE + VACUUM
$ sqlite3 hello 'DELETE FROM sections; DELETE FROM notes; VACUUM;'
# 57344 → 49152 bytes — still runs
$ ./hello
Hello, world!
# patchelf is an UPDATE statement
$ sqlite3 hello 'UPDATE segments SET content = ... WHERE id = ?'
The implications compound. strip is a transaction — atomic, reversible, safe. patchelf is UPDATE ... WHERE ... — no fragile byte offsets, no risk of corrupting the binary. Debug symbols are a table you can query, not a separate section you have to parse. Symbol versioning is a column, not the .gnu.version_r / .gnu.version_d contraption.
How it actually runs
This isn't theoretical. The magic is in two parts:
1. Application ID. SQLite reserves a 4-byte application_id at byte offset 68 of its header. Zakaria stamps it SELF (0x53454c46) so the file is both a valid SQLite database and a recognizable binary format.
2. binfmt_misc. Linux's binfmt_misc subsystem matches on the SQLite magic bytes at offset 0 plus the SELF stamp at offset 68. When matched, it invokes a tiny interpreter — a ~5KB Rust binary using rusqlite — that opens the database, loads the segments, and jumps to the entry point.
graph TD
A[ELF binary] -->|elf2self converter| B[SELF file]
B -->|chmod +x ./hello| C[binfmt_misc matches SELF magic]
C --> D[self-exec interpreter ~5KB Rust]
D -->|sqlite3 hello 'SELECT * FROM segments'| E[Load segments into memory]
E --> F[Jump to entry point]
F --> G[Program runs normally]
B -->|sqlite3 hello 'SELECT name FROM ldd'| H[Query deps with SQL]
B -->|sqlite3 hello 'DELETE FROM sections; VACUUM;'| I[strip with SQL]
Zakaria has it running on NixOS today. The conversion is a postFixup hook: elf2self reads the ELF, extracts program headers and symbol tables, and writes them into SQLite. GCC or ld could emit SELF directly with a patch.
The fun part: what else falls out
The post keeps going in ways that reveal how much this unlocks:
Compression. SQLite supports virtual tables. Zakaria built a .zip virtual table that mounts a zip file inside the database. This makes compressed debug symbols transparent: you can store DWARF data compressed, and queries against it decompress on the fly via the virtual table interface. gdb doesn't need to know. It just reads from the database.
Nix store integration. SQLite supports content-addressed storage via its rowid. A Nix store path becomes a query: SELECT * FROM store WHERE hash = 'sha256-...'. The hash is the primary key. No symlink farms. No hash collisions. The store is a database.
Memory safety. Every tool that parses ELF is a potential exploit surface. The kernel's ELF loader, ld.so, readelf, objcopy — each one reimplements the same fragile format parser. Squashing bugs in these is a full-time job for teams at Red Hat and Intel. With SELF, the parser is SQLite, maintained by a team that's been doing it for 25 years. The attack surface goes from "everyone's hand-rolled parser" to "one battle-tested B-tree library."
Why this matters now
This isn't the first "replace ELF" proposal. It won't be the last. But the timing is interesting for three reasons:
LLMs change the tooling calculus. Zakaria explicitly notes that recent LLM improvements made him revisit this idea. The ability to generate and manipulate structured data is exactly what agents are good at. An agent can generate a SELF file by writing SQL, not by wrestling with ELF struct layout.
SQLite isn't going anywhere. It's the most deployed piece of software on the planet. Every phone, browser, car, and fridge ships it. Its format stability guarantee (one file format, compatible since 2004) makes ELF's ad-hoc evolution look amateurish.
The security surface is real. Every ELF parsing CVE (and there are many) is a reminder that the format was designed before buffer overflows were a concern. SQLite's fuzz testing is legendary — the project has 10x more test code than implementation code. Replacing hand-rolled parsers with a proven database engine is a security win that compounds across every tool in the chain.
Bottom line
I don't know if SELF will replace ELF. Three decades of inertia is a lot to overcome, and the performance constraints that justified ELF's terseness no longer apply. But the idea — that an executable should be a queryable format, not just a loadable one — is too good to ignore.
Read the full post. Clone the repo. Try converting a binary. The experience of running sqlite3 hello 'SELECT * FROM symbols WHERE exported = 1' on your own compiled program is surprisingly satisfying. It shouldn't work. It does.
- Your executable is a SQLite database — Farid Zakaria, Aug 23, 2026
- SELF — Structured Executable & Linkable Format — GitHub
- sqlelf: Query ELF files with SQL — arXiv, May 2024
- SQLite Testing — SQLite Consortium