一个可执行文件,打开方式居然是sqlite3——这不是段子,是开发者Farid Zakaria最近公开的项目SELF(Structured Executable & Linkable Format)。他把ELF可执行文件拆开,塞进SQLite数据库的多张表里,再靠一个C语言写的self-exec解释器,配合Linux的binfmt_misc机制,让内核像跑普通程序一样把这个"数据库"跑起来。
技术上这件事确实成立,但真正值得琢磨的不是"能不能做到",而是"为什么过去四十年没人做成过"。答案藏在启动延迟、安全模型,还有一个原文没提的自举悖论里。
巧思本身:把可执行文件拆成SQL表
SQLite文件格式在第68字节处留了一个4字节的application ID,本来是给不同应用标记私有用途的。SELF把这个值写成"SELF",然后按照公开的schema,把ELF的program header、section header、符号表这些结构一一拆进对应的数据库表。
想让这个文件真正跑起来,还得靠self-exec这个loader把表里的内容重新拼回内存里的可执行结构,再执行。配合binfmt_misc注册一条魔数匹配规则,内核碰到这类文件就会自动转手交给self-exec处理。
不是双料文件,是"以数据库为原生格式"
ELF文件头要求字节0是0x7f加ELF,SQLite文件头要求字节0是字符串SQLite format 3\0——两者天生冲突,不可能同时占住字节0。这意味着SELF根本不是那种一份文件同时合法满足两种格式的多态文件(像Cosmopolitan的APE那样),而是彻底选择SQLite作为原生存储格式,ELF的语义被完全拆散重组进表结构,靠loader单独重新拼装。
这一点容易被"application ID魔数"这个巧妙细节带偏。此前的sqlelf项目做过类似方向的探索,但只是把符号信息映射成可查询的表,并没有让整个可执行体真的以数据库形式存在。SELF往前走了一步,代价也随之而来。
代价清单:多花几毫秒,还丢了共享只读页
据相关报告,SELF原型的固定启动开销在5毫秒左右,strip后的二进制体积与等效ELF相比差异大约在1%以内——单看数字不算吓人。但用户态loader做不到内核原生ELF加载那种demand paging式的跨进程共享只读代码页。多个进程同时跑同一个SELF二进制,内存开销会比原生ELF更高,这对常驻服务、高频调用的CLI是实打实的短板。
安全模型的问题更隐蔽。可执行文件本身变成一份可写的SQLite数据库,意味着代码签名和完整性校验要覆盖的不再是一段静态字节流,而是一整套可被SQL语句改写的表结构。SQLite官方的安全指南建议,面对不可信来源的数据库文件应该关掉trusted schema、触发器、视图这类机制——这些警告本来是给"打开别人的.db文件"这种场景用的,现在要套用到"运行别人给你的可执行文件"这个风险等级更高的场景。self-exec这个loader,变成了整条信任链里最关键也最容易被忽视的一环。
- 风险.可执行文件变成可写数据库,签名和完整性校验要覆盖的对象整套变了
自举悖论:它现在的现实位置
self-exec这个用来解析SELF文件的loader,本身仍是一个普通的ELF程序——SELF要跑起来,离不开ELF先把loader跑起来。这意味着SELF目前没法真正自举,也就没法彻底取代ELF成为独立的底层格式,只能算是寄生在ELF之上的一层。
ELF四十年来一直是一份隐式数据库,SELF只是把它摆到台面上
Hacker News社区围绕更早的sqlelf项目讨论时,态度就已经相对克制:这类思路对二进制自省、可复现打包很有吸引力,但没人指望它在运行时性能上超过原生ELF。SELF大概会走上类似的路——NixOS这类把"可复现构建"当第一原则的社区,可能是最先愿意试它的地方,因为SQL可查询、可去重的特性和它们的理念本来就对得上。拿它去跑生产级的通用软件,现在还谈不上。
这条新闻真正有意思的地方,不是"有人把可执行文件做成了数据库",而是这件事顺手证明了一件更普遍的事:把底层执行格式做得结构化、可查询,和让它跑得快、兼容广,在可执行文件这个最保守的层面,目前还没找到两头都要的办法。
