{
    "archive_path": "archive/1787686919.427829",
    "base_url": "fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database",
    "basename": "your-executable-is-a-sqlite-database",
    "bookmarked_date": "2026-08-25 19:41",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=fzakaria.com",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": "fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database.html"
    },
    "domain": "fzakaria.com",
    "extension": "",
    "hash": "YECM5P36PC6HV7CRBWTD",
    "history": {
        "archive_org": [],
        "dom": [],
        "favicon": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://www.google.com/s2/favicons?domain=fzakaria.com"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-08-25T19:46:07.739803+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1787686919.427829",
                "schema": "ArchiveResult",
                "start_ts": "2026-08-25T19:46:07.248593+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-08-25T19:46:08.049166+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1787686919.427829",
                "schema": "ArchiveResult",
                "start_ts": "2026-08-25T19:46:07.747857+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [],
        "media": [],
        "mercury": [
            {
                "cmd": [
                    "/app/node_modules/@postlight/parser/cli.js",
                    "https://fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database"
                ],
                "cmd_version": "1.0.0",
                "end_ts": "2026-08-25T19:46:16.552705+00:00",
                "index_texts": null,
                "output": "mercury",
                "pwd": "/data/archive/1787686919.427829",
                "schema": "ArchiveResult",
                "start_ts": "2026-08-25T19:46:12.327350+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/app/node_modules/readability-extractor/readability-extractor",
                    "/tmp/tmpk3xtascf",
                    "https://fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-08-25T19:46:12.320752+00:00",
                "index_texts": [
                    "I have been probably obsessed with two things in the last few years: Nix as a tool to explore\ninnovative ideas that require the capability to rebuild the world and replacing ELF with SQLite as an executable format. You might have noticed that these two ideas are well suited to each other.\n\nI explored the idea during my PhD thesis but found feedback from others unmotivating. \nRadical ideas are hard to sell, as you are working against the inertia of the established\nsolution.\n\n\n\nOne of the end results of that exploration was sqlelf,\na tool that lets you explore an ELF file declaratively using SQL.11I wrote a paper, arXiv:2405.03883,\nthat I failed to get published and a follow-up post on querying with it.\u00a0\nSELECT name FROM elf_symbols instead of fiddling with readelf and grep.\nIt was remarkably simple by leveraging virtual tables over the ELF: however I found it\nto be a refreshing improvement to explore the ELF file format. I knew however that\nthere is still something much bigger to be done.\n\nI never let the idea go and with the recent improvements with LLMs, I find it compelling to revisit these ideas to explore further. Specifically, can we replace ELF with SQLite as an executable format? \ud83e\udd14\n\nNot \u201ca database that describes an executable\u201d, but the actual file you chmod +x\nand run.\n\n$ file hello\nhello: SQLite 3.x database, application id 0x53454c46, user version 1\n\n$ ./hello\nHello, world!\n\n$ sqlite3 hello 'SELECT soname FROM ldd'\nlibc.so.6\n\n\nI developed a pretty fleshed out prototype. It is called SELF, the Structured Executable & Linkable Format, because I am unoriginal. It is on GitHub if you are interested. I\u2019m surprised about all the interesting things that fall out of this idea.\n\n\u00a7ELF is a database that refuses to admit it\n\nWorking through my PhD, I realized something that bugged me. ELF is already a database. It just implements many database primitives by hand, along with a surprising number of\ndata structures for performance, like a bloom filter for symbol lookup.\n\n\n\nIf you ever have to analyze or parse ELF, the kernel, ld.so, binutils, LIEF, goblin, readelf, you are re-implementing the same parser over and over again. Every producer re-implements the same serializer.\n\nThe format itself is incredibly terse, designed for a world where disk space and network\nbandwidth was at an extreme premium. Modifying the format is hard, you often have to zero out\nsections and add new ones since it is packed so tightly. There is also no self-describing schema. ELF itself is a very generic format that supports sections of data that by convention\nare interpreted in specific ways but the format does not enforce it.\n\nSQLite is the counter-example. They are a self-describing\nformat that is extremely stable. It is designed to be extended to support new features without breaking existing consumers and supporting a wide range of queries performantly.\n\nIf we were to replace ELF with SQLite, what would fall out and can all of the necessary information be represented in a SQLite database? The answer is yes, and it is surprisingly simple.\n\n\u00a7What falls away\n\nA SELF file needs two tables to run: self_meta is the ELF header as key/value\npairs and segments is the load image, one row per program header with the bytes\nin a BLOB:\n\nCREATE TABLE segments (\n  -- original phdr index\n  id      INTEGER PRIMARY KEY,\n  -- 'load' | 'tls' | 'stack' | 'relro'\n  type    TEXT NOT NULL,\n  -- original file offset\n  offset  INTEGER NOT NULL,\n  vaddr   INTEGER NOT NULL,\n  filesz  INTEGER NOT NULL,\n  memsz   INTEGER NOT NULL,\n  r INTEGER, w INTEGER, x INTEGER,\n  align   INTEGER NOT NULL DEFAULT 4096,\n  -- the segment bytes; NULL for pure BSS\n  content BLOB\n);\n\n\nA single table for the symbol table replaces many of the ELF sections and the .gnu.hash index. It is a single table with a single index:\n\nCREATE TABLE symbols (\n  id      INTEGER PRIMARY KEY,\n  name    TEXT NOT NULL,\n  -- 'GLIBC_2.2.5'\n  version TEXT,\n  value   INTEGER,\n  size    INTEGER,\n  -- 'func' | 'object' | 'tls' | ...\n  type    TEXT,\n  -- 'global' | 'weak' | 'local'\n  bind    TEXT,\n  defined  INTEGER NOT NULL,\n  exported INTEGER NOT NULL\n);\nCREATE INDEX idx_symbols_name ON symbols(name, version);\n\n\nOur capability to include an index is equivalent to .gnu.hash and .hash in ELF, but it is a proper b-tree index maintained by SQLite instead of a hand-rolled bloom filter.22.gnu.hash is a bloom filter plus bucket chains, laid out so\nld.so can reject a miss without touching the chain during symbol discovery.\u00a0\n\nSurprisingly a lot more falls out as well: .dynstr is gone, because name is TEXT and SQLite already interns strings, symbol versioning is a column, not the .gnu.version_r / .gnu.version_d contraption and there is no need for a strings table.\n\nOther tables exist as well for metadata which exist for tooling: sections,\nnotes, dynamic_entries. Delete them and the program still\nruns, which means strip(1) is a transaction:\n\n# ldd(1)\n$ sqlite3 hello 'SELECT soname FROM ldd' \nlibc.so.6\n\n# nm -D --undefined\n$ sqlite3 hello 'SELECT name,version FROM imports LIMIT 3'\n__libc_start_main|GLIBC_2.34\n_ITM_deregisterTMCloneTable|\nputs|GLIBC_2.2.5\n\n# readelf -l\n$ sqlite3 hello \\\n    \"SELECT type,vaddr,memsz,r,w,x FROM segments WHERE type='load'\"\nload|0|1744|1|0|0\nload|4096|361|1|0|1\nload|8192|312|1|0|0\nload|15768|640|1|1|0\n\n# strip(1)\n$ sqlite3 hello 'DELETE FROM sections; DELETE FROM notes; VACUUM;'\n# 57344 -> 49152 bytes\n\n# still runs,  the optional tables were optional\n$ ./hello\nHello, world!\n\n\nAll the tools that operate on ELF files for reading, reduce to queries over the database.\nAny tool that modifies an ELF file, like strip, can operate on the database within a transaction rather than performing fragile offset surgery: strip is a DELETE and VACUUM. patchelf is an UPDATE.\n\nAny information missing from the schema can be easily exposed via a view. For example, ldd is a query over the needed table, which is a join of the symbols table with the segments table to find the sonames of the libraries needed by the program.\n\nCREATE VIEW exports AS SELECT name, version, type, size FROM symbols WHERE exported = 1;\nCREATE VIEW imports AS SELECT name, version FROM symbols WHERE defined = 0;\nCREATE VIEW ldd     AS SELECT ord, soname FROM needed ORDER BY ord;\n\n\n\u00a7How does it work?\n\nSQLite reserves a 4-byte\napplication_id at byte\noffset 68 of its header, for exactly this purpose. We stamp it SELF, so an\nordinary SQLite database never matches:\n\n$ xxd -s 64 -l 8 hello\n00000040: 0000 0001 5345 4c46                      ....SELF\n\n\nWe can now leverage binfmt_misc, the subsystem that allows you to invoke any binary as if it were native. We need only to register\nthe magic to trigger on and an interpreter that will invoke our new file format.\n\nOn NixOS the registration is a few lines matching the SQLite magic at offset 0\nand SELF at 68:\n\nboot.binfmt.registrations.self = {\n  recognitionType = \"magic\";\n  offset = 0;\n  # bytes 0-15, 68-71\n  magicOrExtension = \"SQLite format 3\\\\x00\" + ... + \"SELF\";\n  # ignore the middle\n  mask = \"\\\\xff..\\\\x00..\\\\xff\";\n  interpreter = \"${self-exec}/bin/self-exec\";\n};\n\n\nFor now, I have a small tool elf2self that converts an ELF file into a SELF file. It is a simple postFixup hook you can opt into per package on NixOS. The tool reads the ELF, extracts the program headers and symbol table, and writes them into the SQLite database. We could look at extending gcc or ld to emit SELF directly, but for now this is a simple way to explore the idea.\n\n\n\n\n\nelf\n\nhello\n(ELF)\n\n\n\nconv\n\n\n\nelf2self\n\n\n\nelf->conv\n\n\n\n\n\nself\n\nhello\n(SQLite db)\n\n\n\nconv->self\n\n\n\n\n\nkrn\n\nexecve()\nbinfmt_misc\n\n\n\nself->krn\n\n\nmagic SELF@68\n\n\n\ninterp\n\nself-exec\n(interpreter)\n\n\n\nkrn->interp\n\n\n\n\n\nrun\n\nrunning\nprocess\n\n\n\ninterp->run\n\n\n\n\n\n\n\nself-exec is the interpreter. It is a small C program linked against libsqlite3.\nIts implementation is remarkably similar to that of ld.so but it fetches the program headers and symbol table from the database instead of reading them from the ELF file.\nIt maps the loadable segments into memory, relocates them, and jumps to the entry point.\n\n\n  Note\nself-exec has to stay an ELF file. An interpreter that also matches the\nregistration recurses straight into -ELOOP.\n\n\n\u00a7Dynamic linking\n\nRunning a static program was quick and easy but boring and unimaginative. The interesting part is dynamic linking, which is where the database shines.\n\nI explored two different ways to do dynamic linking. The first is to keep ld.so and just replace the lookup with a SQL query via glibc rtld-audit interface, to quickly iterate on the design. The second is to replace ld.so entirely with a new dynamic linker that does the entire lookup and binding in SQL.\n\nglibc\u2019s rtld-audit interface lets an audit library intercept every shared object lookup (la_objsearch) before any filesystem search happens, dlopen included. The audit library can then answer the question \u201cwhich library satisfies this symbol?\u201d with a SQL query instead of walking the RUNPATH and LD_LIBRARY_PATH. Stock ld.so maps and relocates it, so the full gamut of glibc features work: lazy PLT, IFUNCs, TLS and symbol versioning, while library storage are rows and library lookups are queries.\n\n# no ELF library anywhere on disk\n$ rm libgreet.so.1\n$ ./app\n./app: error while loading shared libraries: \n       libgreet.so.1: cannot open ...\n\n$ self scan --db system.db .\n$ SELF_SYSTEM_DB=system.db LD_AUDIT=libself-audit.so ./app\nHello, world, from a SQLite library!\n\n\nI was curious what a fully SQL dynamic linker would look like, so I prototyped one. It is called self-ld and it is a small C program that implements the dynamic linker entirely in SQL. It is a proof-of-concept, but it works. It maps every object\u2019s segments, publishes their exports, and for each relocation patches the GOT and jumps to the start.\n\nSELECT s.value + o.load_bias\nFROM   relocations r\nJOIN   symbols s ON r.symbol = s.id\nJOIN   objects o ON s.object = o.id\nWHERE  r.id = ?\nORDER BY o.load_order\nLIMIT  1;\n\n\n\u00a7Cost & Benchmark\n\nThe two things that often matter when replacing a well-established format are size and latency. How much bigger is a SELF file than an ELF file, and how much slower is it to run?\n\nSize. A SELF file carries SQLite\u2019s b-tree overhead and lands at roughly\ndouble the ELF.\n\n\n \n  \n   \n    \n    1980-01-01T00:00:00+00:00\n    image/svg+xml\n    \n     \n      Matplotlib v3.10.5, https://matplotlib.org/\n     \n    \n   \n  \n \n \n  \n \n \n  \n   \n  \n  \n   \n  \n  \n   \n    \n   \n   \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n   \n   \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n        \n       \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n       \n       \n       \n       \n       \n       \n      \n     \n    \n   \n   \n    \n   \n   \n    \n   \n   \n    \n   \n   \n    \n   \n   \n    \n    \n    \n    \n    \n    \n    \n    \n   \n   \n    \n     \n     \n      \n       \n       \n       \n       \n      \n      \n      \n      \n      \n     \n    \n   \n   \n    \n     \n     \n      \n      \n      \n      \n     \n    \n   \n   \n    \n     \n     \n      \n       \n      \n      \n      \n      \n      \n     \n    \n   \n   \n    \n     \n     \n      \n       \n      \n      \n      \n      \n      \n     \n    \n   \n   \n    \n   \n   \n    \n   \n  \n  \n   \n   \n    \n     \n     \n     \n     \n     \n     \n     \n     \n     \n     \n     \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n   \n  \n  \n   \n  \n  \n   \n  \n  \n   \n   \n    \n   \n   \n    \n   \n  \n  \n   \n   \n    \n     \n     \n     \n    \n    \n    \n    \n   \n  \n  \n   \n  \n  \n   \n   \n    \n   \n   \n    \n   \n  \n  \n   \n   \n    \n     \n    \n    \n    \n    \n    \n   \n  \n \n \n  \n   \n  \n  \n   \n  \n  \n   \n  \n \n\n\n\nSimilar to ELF binaries, most of that is recoverable, because the overhead is mostly the optional tables for debugging and tooling. Stripping them and deleting them is a transaction. A stripped coreutils SELF is 1,794,048 B against the ELF\u2019s 1,768,632 B, that is within 1%.\n\nWe will see though that there are interesting ways to amortise the overhead even more which I found very unique and interesting.\n\nLatency. I benchmarked various binaries from a 15 KiB hello to a 42 MiB gdb\nlinking 47 libraries:\n\n\n \n  \n   \n    \n    1980-01-01T00:00:00+00:00\n    image/svg+xml\n    \n     \n      Matplotlib v3.10.5, https://matplotlib.org/\n     \n    \n   \n  \n \n \n  \n \n \n  \n   \n  \n  \n   \n  \n  \n   \n    \n   \n   \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n        \n       \n       \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n       \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n       \n       \n       \n       \n      \n     \n    \n   \n   \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n   \n   \n    \n    \n    \n    \n    \n    \n    \n    \n   \n   \n    \n   \n   \n    \n   \n  \n  \n   \n   \n    \n     \n     \n     \n     \n     \n     \n     \n     \n     \n     \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n   \n  \n  \n   \n  \n  \n   \n  \n  \n   \n  \n  \n   \n   \n    \n   \n   \n    \n   \n  \n  \n   \n   \n    \n     \n     \n     \n    \n    \n    \n    \n   \n  \n  \n   \n  \n  \n   \n  \n  \n   \n   \n    \n   \n   \n    \n   \n  \n  \n   \n   \n    \n     \n    \n    \n    \n    \n    \n   \n  \n \n \n  \n   \n  \n  \n   \n  \n  \n   \n  \n \n\n\n\nThere is a fixed ~5 ms to open SQLite and start the interpreter, plus a copy proportional\nto the image. That copy is worse than it looks, because the b-tree pages are not mapped into memory. Two processes running the same SELF binary do not share text pages the way a normally-mmap\u2018d ELF does, because the bytes are copied out of the b-tree rather than mapped.33You might notice that curl (274 KiB, 27 libraries) starts slower than ELF git\n(4.6 MiB, 5 libraries). That is ld.so doing work proportional to the\nnumber of objects rather than the number of bytes, which I have\ncomplained about before.\u00a0\n\n\u00a7The system is a closure\n\nA SQLite database though need not merely be a single executable. It can be a closure, a single file that contains a program and all of its transitive dependencies. The ldd output of a program is ambiguous: it only lists the sonames of the libraries it needs, not the specific files that satisfy those needs. Nix improves upon this by explicitly resolving every edge to a specific store path via the use of RUNPATH.44I have written about RUNPATH on Nix before such as\nmaking it redundant or\nspeeding it up.\u00a0\n\nWe can do the same in SELF by storing the resolved path of each edge in the database:\n\nCREATE TABLE objects (id INTEGER PRIMARY KEY, path TEXT UNIQUE,\n                      soname TEXT, kind TEXT, is_root INTEGER);\nCREATE TABLE needs (\n  object_id     INTEGER REFERENCES objects(id),\n  ord           INTEGER NOT NULL,\n  soname        TEXT NOT NULL,\n  -- the FK that kills ambiguity\n  resolved_path TEXT REFERENCES objects(path)\n);\n\n\nself closure packs a binary and its transitive dependencies into one database\nwith those edges filled in. Shared library resolution stops being a guess and\nbecomes a foreign key and ldd becomes a JOIN \ud83e\udd2f:\n\n$ self closure \"$(readlink -f $(command -v ls))\" coreutils.db\nls + closure -> coreutils.db\n\n$ sqlite3 -column coreutils.db \\\n    \"SELECT n.soname, substr(n.resolved_path, 12, 20)\n     FROM needs n JOIN objects o ON o.id = n.object_id\n     WHERE o.is_root = 1\"\nlibgmp.so.10          rfabfsmwq02sn94mb3qg\nlibacl.so.1           x0zgiss9hdzcsll3cswg\nlibattr.so.1          08nfpyc4qhzdkc37nznv\nlibc.so.6             8kvxvr3pmsypxiypq4g8\n\n\nThis single database is a closure of the ls executable and its five libraries:\nsix objects, segment bytes and all, in one 4.8 MiB file. There is no soname ambiguity inside a closure, because a closure by\nconstruction contains exactly one provider per edge.\n\n\n\n\n\nls\n\nls\n(is_root)\n\n\n\nlibc\n\nlibc.so.6\n\n\n\nls->libc\n\n\n\n\n\ngmp\n\nlibgmp.so.10\n\n\n\nls->gmp\n\n\n\n\n\nacl\n\nlibacl.so.1\n\n\n\nls->acl\n\n\n\n\n\nattr\n\nlibattr.so.1\n\n\n\nls->attr\n\n\n\n\n\ngmp->libc\n\n\n\n\n\nacl->libc\n\n\n\n\n\nacl->attr\n\n\n\n\n\nattr->libc\n\n\n\n\n\n\n\n\u00a7How far does this go? One file, one userland\n\nI hope you\u2019ve been with me so far, because this is where it gets really interesting.\nWe can go even further and pack multiple closures into a single database.\n\n\n\nI pointed self closure at every ELF binary on this system\u2019s PATH: 723 executables,\nwhich pull in 400 distinct shared libraries. 1,123 objects, 346,386 symbols,\n3,808 dependency edges, all as one SQLite file.\n\nTurns out when you do that, the database is much smaller than you would expect.\n\n\n \n  \n   \n    \n    1980-01-01T00:00:00+00:00\n    image/svg+xml\n    \n     \n      Matplotlib v3.10.5, https://matplotlib.org/\n     \n    \n   \n  \n \n \n  \n \n \n  \n   \n  \n  \n   \n  \n  \n   \n    \n   \n   \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n    \n   \n   \n    \n     \n      \n     \n     \n      \n      \n       \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n        \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n      \n      \n      \n       \n        \n        \n        \n        \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n        \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n      \n      \n      \n       \n       \n       \n       \n       \n       \n       \n       \n      \n     \n    \n    \n     \n      \n     \n     \n      \n      \n       \n      \n     \n     \n      \n      \n       \n        \n        \n        \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n      \n      \n      \n       \n        \n        \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n       \n      \n     \n    \n   \n   \n    \n    \n    \n   \n   \n    \n     \n     \n      \n       \n       \n       \n      \n      \n      \n      \n      \n      \n      \n      \n      \n      \n     \n    \n   \n   \n    \n     \n     \n      \n       \n       \n      \n      \n      \n      \n      \n      \n      \n      \n      \n      \n     \n    \n   \n   \n    \n     \n     \n      \n       \n       \n      \n      \n      \n      \n      \n      \n      \n      \n      \n      \n     \n    \n   \n   \n    \n   \n   \n    \n   \n  \n  \n   \n   \n    \n     \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n    \n   \n  \n \n \n  \n   \n  \n \n\n\n\n611.9 MiB of database against 644.4 MiB of ELF files. The whole userland,\nas one queryable file, is smaller than the files it came from. The b-tree cost that doubled a single hello amortises to nearly nothing across 1,123 objects and is roughly 6% over\nthe actual program bytes.\n\nThe libraries and closure are shared across the executables very similar to how Nix might share them across multiple closures, if the store-path was the same. If every root shipped its own private closure (i.e. the AppImage model), the same 723 programs would come to\n5.53 GiB but the deduplication of libraries and symbols falls out naturally from the database schema.\n\n$ sqlite3 userland.db \\\n    'SELECT count(DISTINCT soname), count(*)\n     FROM objects WHERE soname IS NOT NULL'\n345|399\n\n$ sqlite3 -column userland.db \\\n    'SELECT soname, count(*) FROM objects\n     WHERE soname IS NOT NULL\n     GROUP BY soname HAVING count(*) > 1\n     ORDER BY 2 DESC LIMIT 4'\nlibsystemd.so.0   3\nlibpthread.so.0   3\nlibgcc_s.so.1     3\nlibc.so.6         3\n\n$ sqlite3 userland.db \\\n    \"SELECT count(*)\n    FROM needs\n    WHERE resolved_path IS NULL AND soname NOT LIKE 'ld-%'\"\n4\n\n\nMany common idioms we use in ELF immediately fall out of the database. For example, LD_PRELOAD is a row in a table rather than an environment variable. The preload table is a list of objects to map last, so their exports win. This means that turning LD_PRELOAD on and off is a transaction.\n\n$ ./app.self; echo $?\n13\n\n$ sqlite3 system.db \"BEGIN;\n    CREATE TABLE preload(ord INTEGER PRIMARY KEY, path TEXT);\n    INSERT INTO preload VALUES (0, 'libmul.so.1.self');\n  COMMIT;\"\n\n# same binary, no env var, no relink\n$ ./app.self; echo $?\n42\n\n$ sqlite3 system.db 'DELETE FROM preload;'\n$ ./app.self; echo $?\n13\n\n\nWe were able to accomplish an atomic LD_PRELOAD across a whole userland\nin one file, \u201cinterpose a tracing malloc everywhere, then ROLLBACK\u201d is a\nsingle transaction. \ud83d\ude08\n\n\u00a7Where it stands\n\nThe format is done and round-trips between ELF and SELF losslessly. The tooling is done and can query, modify, and pack closures.  Lookup through SQL works on unmodified glibc programs perfectly and the native-SQL loader works enough to explore it as a possibility for ideas.\n\nThe whole thing is at fzakaria/selfdb.\nnix run .#self-vm boots a NixOS VM where hello is a SQLite database. \ud83d\ude4c\n\nNix lets us explore radical ideas like this. We can rebuild the world down to the Linux kernel if needed. We need not be constrained by the existing decisions and constraints of the past. We can explore new ideas and see what falls out. I hope you find this idea as interesting as I do."
                ],
                "output": "readability",
                "pwd": "/data/archive/1787686919.427829",
                "schema": "ArchiveResult",
                "start_ts": "2026-08-25T19:46:09.619551+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-08-25T19:46:09.615285+00:00",
                "index_texts": null,
                "output": "Your executable is a SQLite database | Farid Zakaria\u2019s Blog",
                "pwd": "/data/archive/1787686919.427829",
                "schema": "ArchiveResult",
                "start_ts": "2026-08-25T19:46:09.549142+00:00",
                "status": "succeeded"
            }
        ],
        "wget": [
            {
                "cmd": [
                    "wget",
                    "--no-verbose",
                    "--adjust-extension",
                    "--convert-links",
                    "--force-directories",
                    "--backup-converted",
                    "--span-hosts",
                    "--no-parent",
                    "-e",
                    "robots=off",
                    "--timeout=60",
                    "--restrict-file-names=windows",
                    "--warc-file=/data/archive/1787686919.427829/warc/1787687168",
                    "--page-requisites",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) wget/GNU Wget 1.21.3",
                    "--compression=auto",
                    "https://fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database"
                ],
                "cmd_version": "GNU Wget 1.21.3",
                "end_ts": "2026-08-25T19:46:09.544022+00:00",
                "index_texts": null,
                "output": "fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database.html",
                "pwd": "/data/archive/1787686919.427829",
                "schema": "ArchiveResult",
                "start_ts": "2026-08-25T19:46:08.055997+00:00",
                "status": "succeeded"
            }
        ]
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": null,
        "dom": null,
        "favicon": "favicon.ico",
        "git": null,
        "media": null,
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Your executable is a SQLite database | Farid Zakaria\u2019s Blog",
        "warc": null,
        "wget": "fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database.html"
    },
    "link_dir": "/data/archive/1787686919.427829",
    "newest_archive_date": "2026-08-25T19:46:12.327350+00:00",
    "num_failures": 0,
    "num_outputs": 6,
    "oldest_archive_date": "2026-08-25T19:46:07.248593+00:00",
    "path": "/2026/08/23/your-executable-is-a-sqlite-database",
    "schema": "Link",
    "scheme": "https",
    "snapshot_id": "0479e37a-a5e5-446f-bb8f-f1a3a8012aa1",
    "sources": [
        "/data/sources/1787686919-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1787686919.427829",
    "title": "Your executable is a SQLite database | Farid Zakaria\u2019s Blog",
    "updated": "2026-08-25T19:46:07.248313+00:00",
    "updated_date": "2026-08-25 19:46",
    "url": "https://fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database"
}