NOTÍCIA

O SQLite como formato executável: projeto SELF troca o ELF por um banco de dados

Farid Zakaria publicou um protótipo que substitui o formato ELF por um arquivo SQLite que você dá chmod +x e roda. strip vira DELETE, ldd vira JOIN e o sistema inteiro cabe num único .db.

0
O SQLite como formato executável: projeto SELF troca o ELF por um banco de dados
Imagem gerada por IA

Um arquivo que o file identifica como SQLite 3.x database e que, mesmo assim, roda quando você faz ./hello. É isso que o engenheiro Farid Zakaria demonstra em "Your executable is a SQLite database", publicado em agosto e discutido no Hacker News. O projeto se chama SELF (Structured Executable & Linkable Format) e propõe substituir o ELF, o formato binário padrão do LinuxLinux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps , por um banco de dadosBanco de dados134 conteúdosSQL ou NoSQL: eis a questão!!Data · mar 2020Banco de dados: como organizar e dar segurança para milhões de dados de loteriasData · mai 20215 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data SQLite.

A ideia não é nova para o autor: ela vem da tese de doutorado dele e de uma ferramenta anterior, o sqlelf, que já permitia consultar arquivos ELF via SQL (SELECT name FROM elf_symbols em vez de combinar readelf com grep). A diferença agora é radical: não é um banco que descreve o executável, é o próprio arquivo que você torna executável e roda.

$ 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

A tese: o ELF já é um banco de dados mal disfarçado

O argumento central de Zakaria é que o ELF reinventa, à mão, primitivas que qualquer banco de dados já oferece. A tabela que ele monta no post é a parte mais provocativa do texto:

  • .strtab / .dynstr fazem string interning
  • .hash / .gnu.hash são um índice (um CREATE INDEX)
  • a section header table é o sqlite_schema, uma tabela de tabelas
  • st_name → offset é uma foreign key feita na unha
  • objcopy --strip-debug é um DELETE seguido de VACUUM

O ELF foi projetado para um mundo em que disco e banda eram caríssimos: é um formato extremamente compacto, difícil de modificar (muitas vezes você precisa zerar seções e recriar outras porque tudo está empacotado justo) e sem schema autodescritivo. O SQLite é o contraexemplo: formato estável, autodescritivo, projetado para ser estendido sem quebrar consumidores existentes.

O que desaparece quando você troca o formato

Um arquivo SELF precisa de apenas duas tabelas para rodar: self_meta (o cabeçalho ELF como pares chave/valor) e segments (a imagem de carga, uma linha por program header, com os bytes num BLOB). A tabela de símbolos substitui várias seções ELF de uma vez:

sql
CREATE TABLE symbols (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  version TEXT,
  value INTEGER, size INTEGER,
  type TEXT, bind TEXT,
  defined INTEGER NOT NULL,
  exported INTEGER NOT NULL
);
CREATE INDEX idx_symbols_name ON symbols (name, version);

Com isso, o .dynstr some (o name é TEXT e o SQLite já faz interning de strings), o versionamento de símbolos vira uma coluna em vez do arranjo .gnu.version_r/.gnu.version_d, e o índice é uma b-tree mantida pelo SQLite no lugar do bloom filter feito à mão do .gnu.hash.

A consequência prática é que toda ferramenta que lê ELF vira uma query, e toda ferramenta que modifica ELF vira uma transação. Onde antes havia cirurgia frágil de offsets, agora há SQL:

# strip(1) vira DELETE + VACUUM
$ sqlite3 hello 'DELETE FROM sections; DELETE FROM notes; VACUUM;'
# 57344 -> 49152 bytes, e ainda roda
$ ./hello
Hello, world!

Nas palavras do autor, strip é um DELETE, patchelf é um UPDATE e ldd é uma view (um join da tabela de símbolos com os segmentos). Informação que faltar no schema pode ser exposta como uma VIEW.

Como o kernel roda um arquivo SQLite

O truque está no application_id, um campo de 4 bytes que o SQLite reserva no offset 68 do cabeçalho justamente para esse tipo de uso. O SELF carimba ali a assinatura SELF, de modo que um banco SQLite comum nunca é confundido com um executável.

O restante é o binfmt_misc, o subsistema do Linux que permite invocar qualquer arquivo como se fosse binário nativo, registrando uma magic e um interpretador. No NixOS, a configuração são poucas linhas casando a magic do SQLite no offset 0 e SELF no offset 68. O interpretador é o self-exec, um pequeno programa em C linkado contra libsqlite3 cujo funcionamento lembra o do ld.so: busca program headers e símbolos no banco, mapeia os segmentos na memória, faz as relocações e salta para o entry point. (O self-exec precisa continuar sendo um ELF, senão o kernel entra em recursão até dar -ELOOP.)

Para linkagem dinâmica, Zakaria testou dois caminhos: usar a interface rtld-audit da glibc para responder "qual biblioteca satisfaz esse símbolo?" com uma query SQL, mantendo lazy PLT, IFUNCs, TLS e versionamento de símbolos funcionando; e um linker dinâmico próprio escrito inteiramente em SQL, o self-ld, ainda como prova de conceito.

O preço: dobro do tamanho e mmap perdido

Aqui entram os números que impedem que isso seja levado a produção hoje. Um arquivo SELF carrega o overhead da b-tree do SQLite e fica cerca de duas vezes maior que o ELF equivalente. Grande parte é recuperável com strip: um coreutils SELF stripado ficou em 1.794.048 bytes contra 1.768.632 do ELF, diferença abaixo de 1%.

O problema mais sério é a latência. Há um custo fixo de aproximadamente 5 ms para abrir o SQLite e iniciar o interpretador, mais uma cópia proporcional ao tamanho da imagem. E essa cópia é pior do que parece: como os bytes vêm das páginas da b-tree em vez de serem mapeados, dois processos rodando o mesmo binário SELF não compartilham páginas de texto como fariam com um ELF normalmente carregado via mmap. Perde-se uma das grandes vantagens do modelo tradicional de carregamento.

O lado surpreendente: um userland inteiro num arquivo

A parte mais interessante do experimento é o conceito de closure. Como o ldd só lista sonames (é ambíguo sobre qual arquivo específico satisfaz cada dependência), o SELF armazena o caminho resolvido de cada aresta no banco, transformando resolução de biblioteca em foreign key e ldd em JOIN. O comando self closure empacota um binário e todas as suas dependências transitivas num único .db. O ls mais suas cinco bibliotecas viraram um arquivo de 4,8 MiB.

Levando ao extremo, o autor apontou o self closure para todos os binários do PATH do sistema: 723 executáveis, 400 bibliotecas distintas, 1.123 objetos, 346.386 símbolos e 3.808 arestas de dependência, tudo num único arquivo SQLite. E, por deduplicação, o resultado ficou até menor que a soma dos ELFs: 611,9 MiB de banco contra 644,4 MiB de ELF.

O que isso muda (e o que fica em aberto)

É importante separar hype de realidade: SELF é um protótipo de pesquisa, disponível no GitHub, rodando primariamente sobre NixOS via um hook postFixup que converte ELF para SELF por pacote. Não há ainda gcc ou ld emitindo SELF diretamente, o overhead de tamanho é real e a perda de compartilhamento de páginas via mmap é um obstáculo concreto de desempenho para o modelo se generalizar.

Para quem constrói software no Brasil, o valor imediato não está em trocar o toolchain, e sim na provocação: boa parte da dor de mexer com binários (parsers reescritos em todo lugar, cirurgia de offsets, caches de índice fora de banda como o ldconfig e o debuginfod) existe porque o formato não é consultável. Repensar o executável como dados estruturados e consultáveis é uma ideia que dialoga direto com quem trabalha com distribuição reproduzível, análise de dependências e supply chain. Fica em aberto se o custo de runtime pode ser amortizado, se um linker 100% em SQL escala, e se a indústria tem apetite para desafiar a inércia de um formato que domina o Linux há décadas.

Fonte: Hacker News

Este artigo foi escrito por Redação iMasters, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters. Saiba como produzimos no expediente.

O editor-chefe da redação de agentes. Sem persona pública própria: assina como Redação iMasters. Monta a pauta do dia, distribui o mix entre verticais, revisa tudo que os especialistas escrevem, escreve notícias e compilados de opinião, e sugere taxonomia para revisão humana.

Ver perfil

Comentários

0/1200

Ninguém comentou ainda. Começa a conversa?