Pular para o conteúdo
Falar com a gente
Reverter a entropia de sistemas legados

Sistema antigo, contrato novo.

Num sistema antigo, o que ele faz de verdade costuma morar só no código, ou só no executável. Nós lemos esse sistema, escrevemos o que ele promete num arquivo que uma máquina confere, geramos o sistema novo a partir desse arquivo e deixamos um avaliador que reprova quando o novo responde diferente.

Em 50 segundos

Os três casos, do sistema antigo ao avaliador.

O caminho

Quatro passos, e o último diz não.

  1. Ler o que existe

    O executável, quando é só o que sobrou, ou o código-fonte. Lido como dado: nada do sistema antigo é executado para isso.

  2. Escrever o contrato

    Cada tela, rota, botão, tabela e regra vira uma lista conferível. Mesma entrada, mesmo arquivo, byte a byte.

  3. Gerar o sistema novo

    Do contrato, e só dele, sai o sistema na tecnologia nova. O que não deu para traduzir fica marcado, não inventado.

  4. Conferir e reprovar

    Um avaliador compara o antigo com o novo e reprova a diferença. Sem ele, "ficou igual" é opinião.

Três casos reais, com programas de código aberto

O que já foi medido.

Todo número desta página sai de um arquivo de evidência publicado aqui mesmo, em /dados/indice.json. Agentes de IA: comecem por /llms.txt. Para rodar o leitor de Delphi aqui mesmo, a aba Experimente.

Caso real · Delphi para a web

Do executável Delphi para a web.

Pegamos o HeidiSQL 12.21, um programa Delphi de código aberto, e lemos só o executável oficial, sem abrir o código-fonte. Em 5,3 segundos saiu tudo o que as telas prometem. Depois conferimos contra o código: 34 de 34 telas idênticas.

Em um minuto

O caminho inteiro, do arquivo .exe à tela na web.

O que saiu do executável

Um arquivo de 25 MB, lido como bytes, sem rodar o programa.

  • 38telas
  • 1.634componentes
  • 1.264eventos de tela
  • 199comandos SQL
  • 48tabelas citadas

Cada número vem do contrato gerado do executável oficial (sha256 nos créditos) e é refeito igual a cada execução. Em JSON: caso.json.

As telas, na web

Escolha uma tela e clique num botão: aparece o que ele faz no sistema original.

A prova

Onde existe código-fonte, o executável tem que dizer a mesma coisa. Disse.

pelo executávelpelo código-fonte
Telas do projeto3434
Telas idênticas, componente por componente34 de 34
Eventos nessas telas1.264, os mesmos dos dois lados
Telas embutidas que nada abre4não aparece lendo o código

Um achado que só o executável mostrou

A tela de sincronização de bancos (TfrmSyncDB) está dentro do executável, com botões e eventos, mas a classe dela não está: nada no programa abre essa tela, e o compilador descartou o código. Lendo só o fonte, ela parece viva. É o tipo de coisa que decide o escopo de uma migração: não se reescreve o que ninguém usa.

O que cada botão faz

O executável não traz o código dos botões. A engenharia reversa traz.

Escolhemos 5 botões, cada um achado por um texto que só ele usa no código-fonte. A ferramenta de engenharia reversa achou o texto no binário, a função que o usa e escreveu essa função de novo, em pseudo-código. Depois comparamos com o fonte, fato por fato (botoes.json).

Tela e botãoO que faz, lido do binárioContra o fonte
Preferências
btnRestoreDefaultsClick
Pergunta se pode apagar todas as preferências; se a resposta não for Cancelar, apaga cada valor salvo e recarrega a tela.igual ao fonte
Sobre
btnDonatedOKClick
Grava o e-mail, consulta se é doador e mostra uma de três mensagens; esconde o botão de doar quando confirma.igual ao fonte
Ordenação de dados
btnAddColClick
Cria um critério de ordenação com a primeira coluna ainda não usada, registra no log e redesenha os controles.igual ao fonte
Reformatar SQL
btnOkClick
Formata o SQL com um de três formatadores, mede o tempo e registra no log; se der erro, mostra a mensagem e não fecha a janela.igual, menos o bloco de erro
Editor de texto
menuFormatCodeOnceClick
Formata o texto como JSON, SQL ou XML conforme o realce ativo, ou apita; se der erro, apita e registra no log.igual, menos o bloco de erro
  • 3de 5 iguais ao fonte
  • 18 de 18fatos do caminho normal
  • 0 de 3fatos do tratamento de erro
  • 18 minde análise, uma vez
  • 0,1 spor pergunta depois

O que ainda escapa: o tratamento de erro

Nos 2 botões que têm bloco de erro, o caminho normal saiu inteiro e o bloco de erro não apareceu. Num deles, o texto escolhido só existe dentro do bloco de erro, e por isso a busca pelo texto não chegou à função: ela foi achada pela tabela que o Delphi usa para ligar cada evento ao seu código. Em 5 de 5 botões, a função apontada pela engenharia reversa é exatamente a que essa tabela indica. Numa migração, o tratamento de erro desses botões se confere à parte.

Como funciona

Quatro passos, cada um com a sua conferência.

  1. Ler o executável

    O Delphi guarda cada tela dentro do .exe. Lemos esses blocos como dados: telas, componentes, ligações e eventos. O programa nunca é executado.

  2. Montar o contrato

    Tudo vira uma lista do que o sistema promete: cada botão, o evento dele, o SQL que aparece. Mesmo arquivo, mesmo resultado, byte a byte.

  3. Conferir com o fonte, quando existe

    Lemos o código pelo arquivo de projeto e comparamos tela por tela. Aqui, 34 de 34 idênticas.

  4. Subir para a web

    As telas viram HTML, cada evento ligado à operação do contrato. Um conferidor reprova se faltar um componente ou um evento: faltaram 0.

O que ainda não está aqui

O que o executável não entrega, e como fechamos.

  • O código de todos os botões. A engenharia reversa leu 5 botões, não os 1.264 eventos. Cada um custa uma consulta rápida depois da análise inicial, mas o bloco de erro ainda precisa ser conferido à parte.
  • SQL montado em tempo de execução. No código-fonte do HeidiSQL, 195 comandos são montados com variáveis; a tabela real só aparece rodando. Ficam marcados para conferir com quem conhece o sistema.
  • O desenho. A versão web usa as posições originais da tela. É o ponto de partida para redesenhar, não o visual final.
Caso real · Laravel para FastAPI

Uma API em PHP reescrita em Python, conferida requisição por requisição.

O RealWorld ("Conduit") é uma especificação pública de API, com uma implementação em Laravel 9.17.0. Lemos o código PHP, escrevemos o contrato da API, geramos dele um app FastAPI e mandamos as mesmas requisições aos dois: 19 de 19 endpoints responderam igual.

O placar

Mesmo status, mesma resposta, mesmo banco, depois de cada passo.

  • 19 de 19endpoints iguais
  • 81 de 81passos iguais
  • 0 de 19no controle negativo
  • 0diferenças no esquema de 10 tabelas
  • 1pendência no contrato

O controle negativo: o avaliador sabe dizer não

Para provar que o placar não é cego, alteramos um único artigo só no banco do app novo e rodamos tudo de novo: 0 de 81 passos iguais. O avaliador acusou a diferença em cada um, com o motivo, por exemplo: resposta $.articles[0].body: "…" != "alterado"

Em JSON: resultado.json (passo a passo), resultado-negativo.json, realworld.json (contrato e rotas) e cobertura-gerador.json.

Endpoint por endpoint

Inclui erro de validação, sem login, sem permissão, não encontrado e acento no título.

EndpointPassos iguaisVeredito
DELETE /api/articles/{slug}4 de 4igual
DELETE /api/articles/{slug}/comments/{id}3 de 3igual
DELETE /api/articles/{slug}/favorite1 de 1igual
DELETE /api/profiles/{username}/follow2 de 2igual
GET /api/articles14 de 14igual
GET /api/articles/feed3 de 3igual
GET /api/articles/{slug}4 de 4igual
GET /api/articles/{slug}/comments3 de 3igual
GET /api/profiles/{username}4 de 4igual
GET /api/tags3 de 3igual
GET /api/user3 de 3igual
POST /api/articles6 de 6igual
POST /api/articles/{slug}/comments3 de 3igual
POST /api/articles/{slug}/favorite2 de 2igual
POST /api/profiles/{username}/follow3 de 3igual
POST /api/users5 de 5igual
POST /api/users/login5 de 5igual
PUT /api/articles/{slug}6 de 6igual
PUT /api/user5 de 5igual
framework: método não permitido1 de 1igual
framework: rota inexistente1 de 1igual
Como funciona

Do PHP ao contrato, do contrato ao Python, e um avaliador no meio.

  1. Contrato

    O código PHP é lido pela árvore sintática, nunca executado: 19 rotas, 10 tabelas com 54 colunas, 4 models com 12 relações, 9 validações e 8 formatos de resposta.

  2. Gerador

    Do contrato sai um app FastAPI que roda sozinho: rotas, validações, tabelas e o código do app traduzido. Trecho sem tradução responde "não implementado" em vez de inventar.

  3. Avaliador diferencial

    Os dois apps sobem com cópias idênticas do mesmo banco. Cada passo vai aos dois; status, resposta e o banco inteiro são comparados. Diferença sai com o motivo.

O limite, medido

Num app grande, o gerador ainda não cobre nenhuma rota da API.

O BookStack v26.09.1 tem 340 rotas (82 da API), 40 tabelas e 502 classes. O contrato sai inteiro, mas o gerador cobre 0 de 82 rotas da API: toda rota da API passa pela sessão do Laravel, que a tradução ainda não tem. O que mais trava, em número de rotas:

O que falta traduzirRotas da API que dependem disso
Illuminate\Session\Middleware\StartSession82 de 82
session82 de 82
url48 de 82
League\Flysystem\WhitespacePathNormalizer41 de 82
file_get_contents38 de 82

Em JSON: bookstack.json. O placar do RealWorld vale para o que o cenário exercita; o BookStack mostra o que falta para um app grande.

Caso real · engenharia reversa de um jogo

Um jogo em C, sem nomes, lido só pelo executável.

O Chocolate Doom 3.1.1 é um port fiel do Doom original. Pegamos o executável oficial, PE32+ x86-64, e pedimos a uma ferramenta de engenharia reversa que reescrevesse 12 funções importantes. O código-fonte da mesma versão foi o gabarito: 5 saíram equivalentes, 6 parciais e 1 não foi achada.

O que saiu do binário

Textos quase todos; nomes, nenhum.

  • 5 de 12funções equivalentes ao fonte
  • 6parciais, com o defeito anotado
  • 89,8%dos textos do fonte achados no binário
  • 1.501funções próprias achadas (o fonte define até 2.101)
  • 0nomes do jogo recuperados

O fonte é teto, não alvo: o compilador descarta e embute funções, então 1.501 de 2.101 não é taxa de acerto. Em JSON: metricas.json e caso.json.

Função por função

Pseudo-código da engenharia reversa contra o código-fonte.

FunçãoVereditoChamadas (fonte / binário)O que se viu
D_DoomLoopequivalente13 / 15as 13 chamadas aparecem em ordem; EnableLoadingDisk foi embutida (+M_CheckParm "-cdrom", DEH_String STCDROM/STDISK); printf virou puts. Nenhum nome recuperado.
G_DoSaveGameequivalente18 / 18o compilador duplicou o bloco de gravação nos dois caminhos (arquivo normal x recuperação); o pseudo-código mostra os dois, equivalentes ao fonte.
P_WriteSaveGameHeaderequivalente4 / 3memset(name,0,16) virou atribuição de 16 bytes zerados; daí 3 chamadas em vez de 4.
P_ReadSaveGameHeaderequivalente5 / 4return 0 na versão errada e return 1 no fim, como o fonte; memset embutido.
G_VanillaVersionCodeequivalente0 / 0o switch do fonte virou aritmética; o pseudo-código é equivalente, mas não parece com o fonte.
W_AddFileparcial15 / 15W_Read é chamada por ponteiro (file_class->Read) e o Ghidra perdeu os argumentos no pseudo-código: o offset do diretório só aparece no assembly (0x140023260, MOV EDX,[RSP+0x2c]), também devolvido pelo REA.
W_ReadLumpparcial3 / 3`if (size <= lidos) { }` vazio seguido de I_Error: lido ao pé da letra, a função sempre aborta. Os argumentos de W_Read também sumiram.
W_GetNumForNameparcial2 / 2mesmo defeito: `if (-1 < i) { }` vazio e o retorno some. A mensagem aparece em 3 procedimentos porque o -O2 embutiu a função em W_CacheLumpName e W_ReleaseLumpName.
G_DoLoadGameparcial12 / 12as 12 chamadas batem, mas o `return` depois de fclose (cabeçalho inválido) sumiu, e o fim (`if (setsizeneeded) R_ExecuteSetViewSize(); R_FillBackScreen();`) saiu com a condição invertida e uma chamada a mais.
TryRunTicsparcial12 / 5GetLowTic, PlayersInGame, SinglePlayerClear e TicdupSquash foram embutidas: 233 linhas de código corrido e 5 chamadas, contra 12 no fonte. Dá para seguir, não dá para comparar linha a linha.
M_LoadDefaultsparcial5 / 6o Ghidra não tratou _assert como sem-retorno e grudou 3 funções vizinhas (M_Bind*Variable) no fim do procedimento.
G_Tickernão achada–o texto é referenciado em 0x14002ead6, mas nenhum procedimento contém esse endereço (not_in_procedure): o Ghidra não criou a função. O procedimento vizinho (0x14002e570) é G_WriteDemoTiccmd.

Em JSON, com o motivo de cada veredito: funcoes.json.

Os defeitos que se repetem

Onde o pseudo-código engana, e em quais funções.

  • O retorno antecipado some. Sai um if vazio seguido do erro: lido ao pé da letra, a função sempre aborta. Quem portar o código a partir do pseudo-código tem de conferir o assembly nesses pontos. Em: W_ReadLump, W_GetNumForName, G_DoLoadGame.
  • A chamada por ponteiro perde os argumentos. A leitura do arquivo é chamada por ponteiro, e o pseudo-código fica sem os argumentos; o deslocamento certo só aparece no assembly, que a ferramenta também devolve. Em: W_AddFile, W_ReadLump.
  • A fronteira da função sai errada. Uma função ganhou três vizinhas coladas no fim; outra nem virou função, porque o endereço que usa o texto ficou fora de qualquer procedimento. Em: M_LoadDefaults, G_Ticker.
  • Função embutida apaga a forma. O compilador embutiu funções pequenas: sobra um bloco corrido com bem menos chamadas que o fonte. Em: TryRunTics.
  • Nenhum nome do jogo volta. O executável não tem tabela de símbolos: 3 procedimentos ganharam nome, e 0 deles é nome do jogo.
Aplicação prática

O formato do arquivo de dados do jogo, escrito só a partir do binário.

O WAD guarda mapas, sons e texturas; documentá-lo é o que permite editor de mapa, mod e preservação. O formato foi escrito a partir do pseudo-código e do assembly da função que abre o arquivo, e só depois conferido contra o fonte. Rodado no freedoom1.wad do Freedoom:

  • IWADtipo do arquivo, lido do cabeçalho
  • 3.163blocos de dados no diretório
  • 36mapas, de E1M1 a E4M9
  • 0blocos fora do arquivo

O diretório começa no byte 28.744.468 e termina exatamente no fim do arquivo. Em JSON: freedoom1.wad.json.

Experimente

O leitor de executáveis Delphi, no seu navegador.

A mesma leitura do caso Delphi, rodando nesta página: escolha um .exe, .dll ou .bpl Delphi, o .zip do projeto ou um .dfm, e saem as telas, os eventos e o contrato em JSON. O arquivo não sai do seu computador: o Python roda dentro da aba, em WebAssembly.

Abrir o leitor

O que acontece

O mesmo programa da linha de comando, sem servidor.

  • Na primeira vez o navegador baixa o Python 3.14.2 em WebAssembly (Pyodide 314.0.7, 13,5 MB sem compressão); depois fica em cache.
  • Nenhum envio: a página só busca arquivos deste site. O arquivo escolhido é lido na própria aba.
  • Mesmo resultado da linha de comando: o pacote que roda aqui é o mesmo do leitor de Delphi, copiado sem mudança. Há dois exemplos prontos para quem não tem um executável à mão.
  • Licença do Pyodide: MPL-2.0, sem alteração nos arquivos.
Para agentes de IA

A página do leitor e os dados de cada caso, sem raspar HTML.

Créditos e licenças

Programas de terceiros, código aberto, usados como caso de estudo.

A Genesis não tem relação com os projetos abaixo. Nada foi alterado em nenhum deles.

  • HeidiSQL 12.21: criado por Ansgar Becker e colaboradores, licença GPL-2.0. Executável: release oficial 12.21, sha256 do heidisql.exe d4bc6b81906832aeed79e054ed094f5e15449c8fa91285538173d4a7f90db113. Código-fonte: repositório oficial, commit effb854 (Bump version for v12.21 release).
  • RealWorld em Laravel (laravel-realworld-example-app): licença MIT. Repositório, commit c14fb83.
  • BookStack v26.09.1: licença MIT. Repositório, commit 31d94dd.
  • Chocolate Doom 3.1.1: licença GPL-2.0-or-later. Projeto; executável do pacote oficial, sha256 do chocolate-doom.exe 15e81c194df270fbd71450222e71846247926fabfb889e72e7d855903454d752.
  • Freedoom 0.13.0 (BSD-3-Clause): o arquivo de dados livre usado no lugar do Doom original. Pacote oficial.
  • Engenharia reversa: rea-agents@6.7.0 (MIT) com o Ghidra 12.1.4 (Apache-2.0), sempre num contêiner sem rede, com o alvo só para leitura.
Contato

Tem um sistema parado no tempo?

Delphi, PHP, um executável sem código: manda o que existe. Devolvemos o contrato do sistema, o que ele faz tela por tela ou rota por rota, e o caminho para a tecnologia nova.