可查询的可执行文件
Queryable Executables

原始链接: https://fzakaria.com/2026/08/24/actually-queryable-executables

作者介绍了 **SELF**,这是一种实验性文件格式,将可执行程序直接作为 SQLite 数据库。通过使用 `binfmt_misc`,内核会触发一个解释器,将二进制文件不仅视为代码,还视为一个可查询的数据存储。 其突破之处在于,运行中的程序可以通过 SQL 与自身文件进行交互,包括读取片段、托管网页内容以及记录用户活动。由于可执行文件本身就是 SQLite 文件,它支持 ACID 事务,使得应用程序无需重启即可实时修改自身的代码或数据。例如,一个 Web 服务器可以将路由、访问日志和程序状态存储在提供网页服务的同一个二进制文件中。 这种方法简化了部署,将版本更新转化为数据迁移,并允许开发者使用标准的 SQL 工具进行审计、搜索或调试。受 Redbean“单文件”理念的启发,作者提出了一种“可查询可执行文件”(Actually Queryable Executable),将二进制文件、文件系统和数据库三者合而为一。这一模型表明,当程序将自身架构视为数据库时,围绕应用程序状态的许多传统且复杂的架构将变得不再必要。

这篇 Hacker News 的讨论探讨了一个实验性概念:将 **SQLite 数据库作为可自我修改、可查询的可执行文件**。 通过将程序逻辑(二进制代码)和运行时状态嵌入到单个 SQLite 文件中,开发者可以将应用程序视为关系型数据库。用户们讨论了这种方法“疯狂科学家”般的特质,并将其与 Lisp 机器、Smalltalk 镜像以及 AS/400 操作系统等历史上数据与代码统一的概念进行了对比。 **核心观点包括:** * **优势:** 这种格式支持可移植、事务性的应用状态,有望简化部署和实现“密闭”的软件分发。 * **安全顾虑:** 许多评论者警告称,让二进制文件具备“自写入”能力会显著增加安全漏洞风险,例如将 SQL 注入漏洞转化为远程代码执行(RCE)。 * **不同视角:** 一些人支持传统的架构分离,偏好带有外部数据目录的不可变二进制文件(如 Nix/容器)。另一些人则建议将此架构用于特定领域,例如可查询的二进制分析(ELF 头)或嵌入式应用逻辑。 总体而言,虽然用户认为该项目是一个出色且极具创意的技术黑客行为,但大多数人认为它更适合作为研究或特定细分领域的工具,而非通用的互联网软件。
相关文章

原文

I was pleasantly surprised and happy to see that my article ‘Your executable is a SQLite database’ resonated with people. It is a format I have been thinking about for a while, and the idea seems to have struck a chord with others.

meme of Danny from Ted Lasso saying sqlite is life

A quick recap: SELF, a format where the program is a SQLite database. We can use binfmt_misc to trigger a custom interpreter that maps the rows in the segments table and jumps to the entry point, and a whole class of binary tooling collapses into SQL.

What keeps surprising me is how having the file format be a SQLite database keeps collapsing everything into SQL. One idea that was immediately evident to myself and others through comments: If the executable is a database, and a database is something you can write to, can the running program use it to also store its state? 🤔

Yes! 🤯 We can collapse not only a complete distribution but all the state for every application into a single file, alleviating the need for /var/ or /tmp/ or /home/ or any other filesystem. The program can store its own state in the same file it is running from, and it can do so transactionally.

self-httpd is a proof-of-concept webserver that does exactly that. It is a single file program executed from a database. The file contains the program, the website, the routes and all the visitor logs. All state is updated in the same SQLite file as the program itself.

# Our server is a single file, and it is a SQLite database
$ file server
server: SQLite 3.x database, application id 1397050438, ...

$ ./server --journal wal 8080
self-httpd: serving 3 routes out of /srv/self/server
self-httpd: listening on http://0.0.0.0:8080 with 4 workers

$ curl -s localhost:8080 | head -1
<!doctype html>

# nobody has pressed the button on that page yet
$ sqlite3 server 'SELECT count(*) FROM presses'
0

$ curl -s -X POST -d press localhost:8080/api/press
{"presses":1,"button":"press"}

# the application data is inside the same database
$ sqlite3 server 'SELECT id, at, button FROM presses'
1|2026-08-25 03:11:28|press

# so was the GET that fetched the page in the first place
$ sqlite3 server 'SELECT count(*) AS n, path
                  FROM visits GROUP BY path'
1|/
1|/api/press

This web-server is live at https://selfdb.exe.xyz.11If the site is not working for you, sorry. I deployed it on their smallest tier. I included a screenshot of the site just in case for posterity!  It is one file, a SQLite database, and it is also the server. It is the website, it is the program, and it is the visitor log and state.

Screenshot of selfdb.exe.xyz. The heading reads "This page is a row in the
executable that served it." Below it a console block shows `file server`
reporting a SQLite database with application id 1397050438, and `xxd` showing
the bytes "....SELF" at offset 68. Under the heading "What is in it, right
now" is a grid of live counters read out of the file while it answered the
request: 13 segments, 179 symbols, 105 relocations, 2 needed libraries, 3
routes, 12 tables, 103 visits recorded, 24 presses
recorded.

I have a lot of admiration for the work of Justine Tunney, whose prior art redbean: a webserver in a single file, built as an Actually Portable Executable with a self-extracting ZIP archive, inspired the idea.

SELF is many ways is less brilliant. It relies on simpler tools to achieve something very similar but I’m amazed how much collapses into a single domain: SQL.

Whereas, redbean needs to include an archive format (ZIP), the database itself is the container. Redbean provides Lua hooks to manipulate the responses, whereas the equivalent in SELF is a new row in a handlers table.

INSERT INTO handlers VALUES
  ('/api/busiest', 'SELECT path, count(*)
                    FROM visits GROUP BY path
                    ORDER BY 2 DESC LIMIT 5');

If redbean is an Actually Portable Executable, this is an Actually Queryable Executable. One of them runs anywhere, the other one you can SELECT from.

How does the process get access to itself? 🤔

For now, you cannot use /proc/self/exe.22Funny enough, the VFS Linux maintainer recently landed support for transparent binfmt_misc in the kernel, which would make /proc/self/exe point to the original file. I wrote about it here When binfmt_misc matches, the kernel does not execve your file at all , it execs the interpreter, and hands it the path:

self-exec passes argv + 1 through to the program, so the program’s argv[0] is the path to the executable itself. The interpreter also releases its SQLite connection before jumping to the entry point, so the program can open its own file and query it.

int main(int argc, char **argv) {
	sqlite3 *db;
	/* the file the kernel just executed */
	sqlite3_open(argv[0], &db);
	...
}

This is pretty unrestricted and magical. You can read your own segment table or a new table next to it. The writes persist across invocations. ✨

The web-server for our example is three tables: routes, visits and presses. We will record every visitor and every button press.

-- the content, added to the executable
-- after it is compiled and linked
CREATE TABLE routes  (path TEXT PRIMARY KEY,
                      mime TEXT, body BLOB);
-- what the site collects, written back 
-- into the executable while it runs
CREATE TABLE visits  (id INTEGER PRIMARY KEY, at TEXT,
                      ua TEXT, path TEXT);
CREATE TABLE presses (id INTEGER PRIMARY KEY,
                      at TEXT, button TEXT);

Building the application feels very unremarkable and familiar. We execute DDL to create the application schema and INSERT the website.

# an ordinary ELF for now
$ cc -O2 server.c -o server.elf $(pkg-config --libs sqlite3)
# the same program, as rows
$ elf2self server.elf server
$ sqlite3 server < site/schema.sql
$ sqlite3 server "INSERT INTO routes VALUES
                    ('/index.html', 'text/html',
                     readfile('site/index.html'))"

The asset pipeline looks like a “normal webserver” until you realize it’s querying itself with SQL for the content. Oh, and “itself” is a SQLite database.

cluster_file server — the same file! req GET / proc running server req->proc krn execve() binfmt_misc se self-exec krn->se se->proc map, jump seg segments (the program) se->seg SELECT content rsp 200 OK proc->rsp rt routes (the website) proc->rt SELECT body vis visits (the log) proc->vis INSERT

The page at https://selfdb.exe.xyz shows a lot of fun additional information besides the visitor log and button presses. I included segments, symbols and relocations. Those are not baked in at built time, they are queried from itself while running.

Once you have the capability to do ACID transactions, interesting things become possible. The webserver can edit its own content while it is running, and the edits are transactional. The UPDATE is committed to the same file as the program, and a ROLLBACK undoes it.

# change the running site. no restart, no reload, no deploy
$ sqlite3 server "UPDATE routes SET body = readfile('new.html')
                  WHERE path = '/index.html'"
$ curl -s localhost:8080
<!doctype html><h1>edited in place</h1>

Since the file format is SQLite we can also take advantage of the cornicopea of tooling that exists. sqldiff will tell you exactly what a “deploy did”, this can let us audit and identify changes between two versions of the same program.

$ sqldiff --summary yesterday.server server
routes:      1 changes, 0 inserts, 0 deletes, 2 unchanged
segments:    0 changes, 0 inserts, 0 deletes, 13 unchanged
symbols:     0 changes, 0 inserts, 0 deletes, 174 unchanged
relocations: 0 changes, 0 inserts, 0 deletes, 99 unchanged

What about full-text search? FTS5 is a CREATE VIRTUAL TABLE away, so a webserver can index its own pages, inside itself, and still be a webserver afterwards:

$ sqlite3 server "CREATE VIRTUAL TABLE search USING fts5(path, body);
                  INSERT INTO search SELECT path, body FROM routes
                    WHERE mime LIKE 'text/%'"

$ sqlite3 server "SELECT path, snippet(search, 1, '[', ']', '...', 6)
                  FROM search WHERE search MATCH 'transaction'"
/index.html|...Editing is a [transaction].</h2>

# still runs. it just knows about itself now
$ ./server 8080

None of that is machinery I wrote. It is machinery SQLite already has, that a program inherits for free by being a database.

All the rage was static site generators, but the future is an actually queryable executable.

I am really enjoying the simplicity that seems to be popular and heralded by products like exe.dev. People often yearn to go back to the “good old days” of scp and ssh to deploy a single file, and SELF is a format that makes that possible again, but better! Rather than just shipping an archive of PHP, we ship the whole system or application closure down to the libc.

How would we make a deployment if the data and code is intertwined?

We can think of a redeploy as a data migration, and the migration is two INSERT ... SELECT, because the program and its data are the same file!

-- the running deployment
ATTACH '/srv/self/server' AS old;
INSERT INTO visits  (at, ua, path)
  SELECT at, ua, path FROM old.visits;
INSERT INTO presses (at, button)
  SELECT at, button FROM old.presses;

Swap the file, restart, and the visitor log survives the new build. You can even do this for the program itself in reverse. The segments table is just like any other table. 😈

https://selfdb.exe.xyz has a button on it. Pressing it is an INSERT into the executable that served you the page

The code is at fzakaria/selfdb if you are curious. It is probably a bit half-baked, and definitely AI assisted, but that’s OK with me. I wanted to explore this idea and see if it was feasible and what might be possible.

I think I only scratched the surface of some of the fun possibilities. I am curious to see what others might do with it, and I would love to see a few more examples of “actually queryable executables” in the wild.33One idea a friend suggested was discovery over multicase DNS to spread program updates via transactions. 

Turns out that when we re-envision what we considered to be simply a byte layout specification was actually better off being a database, a lot of machinery we have been using for decades simply stops being necessary. The program is the database, and the database is the program.

“Never, ever underestimate the importance of having fun”

– Randy Pausch

联系我们 contact @ memedata.com