20 KiB
Library Symbols — Feature Design Document
1. Visão Geral
Sistema de importação e resolução de símbolos de bibliotecas externas (ex: Borland OWL, MFC, Qt, .NET BCL). O parser consome dumps de símbolos (formato TurboDump inicialmente), armazena-os como referência autoritativa, e cruza automaticamente com os resultados de análise estática para resolver imports opacos (ordinais) em nomes semânticos.
A informação resolvida é injetada no pipeline de embeddings (pgvector) e na construção de contexto do chat RAG, melhorando dramaticamente a qualidade das respostas do LLM.
Fase atual (1): Parser TurboDump + integração completa com AI/RAG. Sem sessões de chat temático (Fase 2 futura).
2. Arquitetura
┌──────────────────────────────────────────────────────────────────┐
│ LIBRARY UPLOAD FLOW │
│ │
│ POST /libraries/upload │
│ │ {parserType, rawContent, libraryName, aliases[]} │
│ ▼ │
│ LibraryService.parseAndStore() │
│ ├─ LibrarySymbolParser (interface) │
│ │ └─ TurboDumpParser ← implementação atual │
│ │ └─ DefFileParser ← futuro │
│ │ └─ MapFileParser ← futuro │
│ ├─ Cria Library entity │
│ ├─ Cria LibraryAlias entities (mapeamento DLL ↔ library) │
│ ├─ Chama parser.parse() → List<ParsedSymbol> │
│ └─ Salva LibrarySymbol entities em lote │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ SYMBOL RESOLUTION FLOW │
│ │
│ StaticAnalysisCompletedEvent (disparado após IDA/Ghidra) │
│ ▼ │
│ SymbolResolutionService.resolveImports(analysisId) │
│ ├─ Carrega todos os StaticImport do analysis │
│ ├─ Para cada import com ordinal != 0: │
│ │ ├─ Busca LibraryAlias pelo campo dll do import │
│ │ ├─ Se encontrado: busca LibrarySymbol por (library, ordinal)│
│ │ └─ Se encontrado: atualiza StaticImport.resolvedName │
│ │ e StaticImport.librarySymbolId │
│ └─ Loga estatísticas: quantos imports resolvidos │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ AI / RAG ENRICHMENT │
│ │
│ EmbeddingChunkBuilder │
│ ├─ buildImportChunks(): inclui resolvedName + className │
│ ├─ buildMetadataChunk(): inclui Library Diagnostics section │
│ └─ buildFunctionChunkContent(): callees resolvidos via OWL │
│ │
│ ChatContextBuilder │
│ ├─ buildSystemPrompt(): inclui [LIBRARY CONTEXT] seção │
│ └─ detectFunctionReferences(): busca por classes/métodos OWL │
└──────────────────────────────────────────────────────────────────┘
3. Modelo de Dados
3.1 Tabelas Novas
library
| Coluna | Tipo | Descrição |
|---|---|---|
id |
UUID PK | Identificador |
name |
VARCHAR(255) NOT NULL | Nome da biblioteca (ex: "Borland OWL 2.52") |
description |
TEXT | Descrição opcional (ex: "Object Windows Library for Borland C++") |
created_at |
TIMESTAMPTZ NOT NULL | Data de criação |
library_alias
| Coluna | Tipo | Descrição |
|---|---|---|
id |
UUID PK | Identificador |
library_id |
UUID FK → library(id) ON DELETE CASCADE | Biblioteca pai |
alias |
VARCHAR(255) NOT NULL | Nome do DLL/arquivo no import table (ex: "OWLWI", "owl252.dll") |
UNIQUE(library_id, alias) |
Um alias pertence a uma única biblioteca |
library_symbol
| Coluna | Tipo | Descrição |
|---|---|---|
id |
UUID PK | Identificador |
library_id |
UUID FK → library(id) ON DELETE CASCADE | Biblioteca pai |
module_name |
VARCHAR(255) NOT NULL | Módulo original (ex: "Owl252" do TurboDump) |
class_name |
VARCHAR(500) | Classe C++ (nulo para funções livres) |
symbol_name |
VARCHAR(500) NOT NULL | Nome completo do símbolo (ex: "TButtonGadget::Paint(TDC*)") |
ordinal |
INTEGER NOT NULL | Número ordinal de importação |
symbol_type |
VARCHAR(50) | Tipo: CONSTRUCTOR, DESTRUCTOR, METHOD, FUNCTION, STATIC_MEMBER |
parameters |
VARCHAR(1000) | Assinatura dos parâmetros (extraído do nome) |
category |
VARCHAR(100) | Categoria (ex: "UI/Gadget", "Core/Application", "Graphics/Bitmap") |
created_at |
TIMESTAMPTZ NOT NULL | Data de criação |
Índices: idx_library_symbol_library_ordinal ON library_symbol (library_id, ordinal)
idx_library_symbol_class ON library_symbol (class_name) WHERE class_name IS NOT NULL
3.2 Tabela Alterada
static_import (adições)
| Coluna | Tipo | Descrição |
|---|---|---|
library_symbol_id |
UUID FK → library_symbol(id) ON SET NULL | Símbolo de biblioteca que corresponde a este import |
resolved_name |
VARCHAR(500) | Nome resolvido (ex: "TButtonGadget::Paint(TDC*)") |
Índice: idx_static_import_library_symbol ON static_import (library_symbol_id) WHERE library_symbol_id IS NOT NULL
4. Fluxo de Dados Detalhado
4.1 Upload da Biblioteca
POST /libraries/upload
Content-Type: application/json
{
"name": "Borland OWL 2.52",
"description": "Object Windows Library for Borland C++ 5.0",
"parserType": "TURBO_DUMP",
"aliases": ["OWLWI", "owl252.dll", "Owl252"],
"rawContent": "Turbo Dump Version 5.0.1..."
}
LibraryService.parseAndStore()recebe o request- Busca um
LibrarySymbolParserpeloparserType(pode ser um Map<String, LibrarySymbolParser> ou via qualifier annotation) - Cria
Libraryentity e salva - Cria
LibraryAliasentities para cada alias - Chama
parser.parse(rawContent, libraryId)→List<ParsedSymbol> - Salva
LibrarySymbolentities em batch (500 por vez) - Retorna
LibraryResponsecom contagem de símbolos
4.2 Resolução de Import Symbols
Após a análise IDA/Ghidra completar, o StaticAnalysisCompletedEvent é publicado. O listener SymbolResolutionListener (ou SymbolResolutionService) escuta o evento:
- Carrega
StaticImportpara oanalysisId - Para cada import com
ordinal != 0: a. BuscaLibraryAliasondealias ILIKE dll(case-insensitive) b. Se encontrado, buscaLibrarySymbolpor(library_id, ordinal)c. Se encontrado, atualizaStaticImport.librarySymbolIderesolvedName - Loga:
Resolved 152/200 imports (76%) for analysis <id> - Opcional: publica um evento
SymbolsResolvedEventpara notificar downstream
Alternativamente, se o listener não for viável (timing), o StaticAnalysisHandler chama symbolResolutionService.resolveImports(analysisId) no final do process().
4.3 Enriquecimento de Embedding Chunks
Import Chunks
// Antes
"[IMPORT] OWLWI.ord_75 @ 0x00405A00"
// Depois (com library symbol resolvido)
"[IMPORT] OWLWI.TButtonGadget::Paint(TDC*) @ 0x00405A00 " +
" Library: Borland OWL 2.52 | Class: TButtonGadget | Method: Paint"
Metadata Chunk — Nova seção [LIBRARY DIAGNOSTICS]
[LIBRARY DIAGNOSTICS]
Framework: Borland OWL 2.52 (Object Windows Library)
Detected classes (15): TApplication, TFrameWindow, TButtonGadget, TBitmap,
TControlBar, TChooseColorDialog, TChooseFontDialog, TClipboard, TBrush,
TDC, TCursor, TBitSet, TCharSet, TCommonDialog, TControlGadget
Category distribution: UI/Gadget (6), Core/Application (3), Graphics (4),
UI/Window (2)
Architecture: Event-driven GUI, message dispatch via TEventHandler::Find()
Function Chunks
No buildFunctionChunkContent(), ao listar callees, se o callee for um import com resolvedName:
// Antes
"Callees: sub_401000, ordinal_75, ordinal_67"
// Depois
"Callees: sub_401000, TButtonGadget::Paint [OWL/ord75], TButtonGadget::TButtonGadget [OWL/ord67]"
4.4 Enriquecimento do System Prompt
// Antes
String.format("""
[BINARY OVERVIEW]
Filename: %s
Format: %s
Architecture: %s
Compiler: %s
""", ...);
// Depois
String.format("""
[BINARY OVERVIEW]
Filename: %s
Format: %s
Architecture: %s
Compiler: Borland C++
[LIBRARY CONTEXT]
This binary links against Borland OWL 2.52 (Object Windows Library), a
C++ framework for Windows GUI applications. Key classes detected:
%s
OWL uses a message-driven architecture similar to MFC. Windows messages
are routed through TEventHandler response tables. Common patterns:
- TFrameWindow is the main application window
- TButtonGadget handles button controls with Paint() for WM_PAINT
- Dialogs extend TCommonDialog via TChooseColorDialog/TChooseFontDialog
- Graphics use TDC (Device Context wrapper) with TBrush/TPen/TBitmap
When analyzing functions that call OWL imports, consider the OWL class
hierarchy and event dispatch model to infer the function's role.
""", ..., libraryDiagnostics);
5. API REST
5.1 Endpoints
| Método | Path | Descrição |
|---|---|---|
POST |
/libraries/upload |
Upload de um dump de símbolos de biblioteca |
GET |
/libraries |
Listar todas as bibliotecas carregadas |
GET |
/libraries/{id} |
Detalhes de uma biblioteca |
DELETE |
/libraries/{id} |
Remover uma biblioteca e seus símbolos |
GET |
/libraries/{id}/symbols |
Listar símbolos (com paginação, filtro por nome/ordinal) |
POST |
/analysis/{analysisId}/resolve-symbols |
Forçar resolução de símbolos (debug) |
5.2 DTOs
// Request
public record LibraryUploadRequest(
@NotBlank String name,
String description,
@NotBlank String parserType,
@NotEmpty List<String> aliases,
@NotBlank String rawContent
) {}
// Response
public record LibraryResponse(
UUID id, String name, String description, int symbolCount,
int aliasCount, Instant createdAt
) {
public static LibraryResponse from(Library lib) { ... }
}
public record LibrarySymbolResponse(
UUID id, String moduleName, String className, String symbolName,
int ordinal, String symbolType, String parameters, String category
) {
public static LibrarySymbolResponse from(LibrarySymbol sym) { ... }
}
5.3 ImportResponse (estendido)
public record ImportResponse(
UUID id, String dll, String name, String address, int ordinal,
// Novos campos opcionais:
String resolvedName, // nulo se não resolvido
String libraryName, // nulo se não resolvido
String className, // nulo se não for método de classe
UUID librarySymbolId // nulo se não resolvido
) {
public static ImportResponse from(StaticImport imp) {
var sym = imp.getLibrarySymbol();
return new ImportResponse(
imp.getId(), imp.getDll(), imp.getName(), imp.getAddress(),
imp.getOrdinal(),
imp.getResolvedName(),
Objects.nonNull(sym) ? sym.getLibrary().getName() : null,
Objects.nonNull(sym) ? sym.getClassName() : null,
imp.getLibrarySymbolId()
);
}
}
6. Parser Architecture
6.1 Interface
package ai.decompile.analysis.service.parser;
import java.util.List;
public interface LibrarySymbolParser {
/** Identificador único do formato (ex: "TURBO_DUMP", "DEF_FILE") */
String getFormatName();
/** Valida se o conteúdo é compatível com este parser */
boolean canParse(String rawContent);
/** Parseia o conteúdo e retorna símbolos para association com libraryId */
List<ParsedSymbol> parse(String rawContent);
}
/** Registro intermediário entre parser e serviço */
public record ParsedSymbol(
String moduleName,
String className,
String symbolName,
int ordinal,
String symbolType,
String parameters,
String category
) {}
6.2 TurboDumpParser
Implementação concreta para o formato do utilitário TDUMP.EXE da Borland:
Formato de entrada:
000020 THEADR TAppDictionary::TAppDictionary()
00003E COMENT Purge: Yes, List: Yes, Class: 160 (0A0h)
Dynamic link import (IMPDEF)
Imported by: ordinal
Internal Name: TAppDictionary::TAppDictionary()
Module Name: Owl252
ordinal: 2
000069 MODEND
Lógica de parsing:
- Divide o texto por blocos delimitados por
MODEND - Para cada bloco:
- Extrai
symbolNameda linhaTHEADR - Extrai
moduleNamedo campoModule Name: - Extrai
ordinaldo campoordinal: - Se não encontrar
ordinal:ou o bloco não forIMPDEF, pula - Classifica
symbolType:- Contém
::~→DESTRUCTOR - Contém
::e termina em()sem~→CONSTRUCTOR - Contém
::→METHOD - Sem
::→FUNCTION
- Contém
- Extrai
classNamedo trecho antes de:: - Extrai
parametersdos parênteses após o nome do método - Classifica
categorypor prefixo de classe:TApp, TModule, TStatus, TXCompatibility → Core/Application TFrame, TWindow, TDialog, TClipboard → UI/Window TButton, TGadget, TControl, TSlider → UI/Control TBitmap, TPalette, TDib, TIcon, TDC → Graphics TBrush, TPen, TFont → Graphics/Drawing TColor → Graphics/Color TCursor → UI/Cursor TBitSet, TCharSet → Core/DataTypes default → Core/Other
- Extrai
6.3 Registro de Parsers
@Component
@RequiredArgsConstructor
public class LibrarySymbolParserRegistry {
// Spring injeta todos os beans LibrarySymbolParser
private final Map<String, LibrarySymbolParser> parsers;
// Construtor monta o mapa formatName → parser
public LibrarySymbolParserRegistry(List<LibrarySymbolParser> parserList) {
this.parsers = parserList.stream()
.collect(Collectors.toMap(
LibrarySymbolParser::getFormatName,
Function.identity()));
}
public LibrarySymbolParser get(String formatName) {
var parser = parsers.get(formatName);
if (Objects.isNull(parser)) {
throw new IllegalArgumentException(
"No parser found for format '" + formatName + "'. " +
"Available: " + parsers.keySet());
}
return parser;
}
public List<String> getAvailableFormats() {
return List.copyOf(parsers.keySet());
}
}
7. Estrutura de Código
src/main/java/ai/decompile/analysis/
├── model/
│ ├── entity/
│ │ ├── Library.java (novo)
│ │ ├── LibraryAlias.java (novo)
│ │ ├── LibrarySymbol.java (novo)
│ │ └── StaticImport.java (modificado: +resolvedName, +librarySymbolId, +librarySymbol)
│ ├── repository/
│ │ ├── LibraryRepository.java (novo)
│ │ ├── LibraryAliasRepository.java (novo)
│ │ └── LibrarySymbolRepository.java (novo)
│ └── dto/
│ ├── LibraryResponse.java (novo)
│ ├── LibrarySymbolResponse.java (novo)
│ ├── LibraryUploadRequest.java (novo)
│ └── ImportResponse.java (modificado)
├── service/
│ ├── parser/
│ │ ├── LibrarySymbolParser.java (novo - interface)
│ │ ├── ParsedSymbol.java (novo - record)
│ │ ├── LibrarySymbolParserRegistry.java (novo)
│ │ └── TurboDumpParser.java (novo)
│ ├── LibraryService.java (novo)
│ ├── SymbolResolutionService.java (novo)
│ └── AnalysisService.java (modificado: chama resolution após saveResult)
├── event/
│ └── SymbolsResolvedEvent.java (novo, opcional)
└── controller/
└── LibraryController.java (novo)
src/main/resources/db/migration/
├── V021__create_library_tables.sql (novo)
└── V022__alter_static_import_library.sql (novo)
src/main/java/ai/decompile/ai/service/
├── EmbeddingChunkBuilder.java (modificado: enriquecido com library info)
└── ChatContextBuilder.java (modificado: system prompt + context enriquecido)
8. Casos de Teste
8.1 TurboDumpParser
| Teste | Entrada | Esperado |
|---|---|---|
| Construtor de classe | TAppDictionary::TAppDictionary() |
type=CONSTRUCTOR, class=TAppDictionary |
| Destrutor | TBitmap::~TBitmap() |
type=DESTRUCTOR, class=TBitmap |
| Método com parâmetros | TDC::SelectObject(const TPen*) |
type=METHOD, class=TDC, params=const TPen* |
| Método sem parâmetros | TBitSet::IsEmpty() |
type=METHOD, params=null |
| Função livre | NColors(unsigned short) |
type=FUNCTION, className=null |
| Bloco sem ordinal | Qualquer não-IMPDEF | Ignorado (não retorna símbolo) |
| Parser chamado 2x | Conteúdo com 1854 IMPDEF | 1854 símbolos retornados |
8.2 SymbolResolutionService
| Teste | Setup | Esperado |
|---|---|---|
| Match por ordinal + alias | Import(dll="OWLWI", ordinal=75), LibrarySymbol(ordinal=75, library=L1), Alias("OWLWI"→L1) | resolvedName = "TButtonGadget::Paint(TDC*)" |
| Ordinal zero (nomeado) | Import(ordinal=0) | Não resolvido (pula) |
| Alias não encontrado | Import(dll="UNKNOWN", ordinal=5) | Não resolvido |
| Ordinal não encontrado | Import(dll="OWLWI", ordinal=9999) | Não resolvido |
| Sem bibliotecas carregadas | Nenhuma Library no banco | Nenhum import resolvido |
| Case insensitive alias | Import(dll="owlwi"), Alias("OWLWI") | Resolvido |
8.3 EmbeddingChunkBuilder (enriquecido)
| Teste | Setup | Esperado |
|---|---|---|
| Import com resolvedName | StaticImport com resolvedName="TDC::Paint(TDC*)" | Chunk content contém o resolvedName |
| Metadata com library diagnostics | Library + LibrarySymbol carregados | Seção [LIBRARY DIAGNOSTICS] presente |
| Function callees resolvidos | Function com xrefs para imports resolvidos | Callee list mostra class::method [OWL] |
9. Rollout e Rollback
- Nova feature, sem quebra de compatibilidade: migration adiciona colunas nullable ao
static_import - Sem bibliotecas carregadas: comportamento idêntico ao atual (resolvedName = null, nenhum chunk afetado)
- Rollback: reverter migrations V021 e V022, remover código novo (as APIs novas simplesmente não existiriam mais)