backend/docs/library-symbols-feature.md

471 lines
20 KiB
Markdown

# 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..."
}
```
1. `LibraryService.parseAndStore()` recebe o request
2. Busca um `LibrarySymbolParser` pelo `parserType` (pode ser um Map<String, LibrarySymbolParser> ou via qualifier annotation)
3. Cria `Library` entity e salva
4. Cria `LibraryAlias` entities para cada alias
5. Chama `parser.parse(rawContent, libraryId)``List<ParsedSymbol>`
6. Salva `LibrarySymbol` entities em batch (500 por vez)
7. Retorna `LibraryResponse` com 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:
1. Carrega `StaticImport` para o `analysisId`
2. Para cada import com `ordinal != 0`:
a. Busca `LibraryAlias` onde `alias ILIKE dll` (case-insensitive)
b. Se encontrado, busca `LibrarySymbol` por `(library_id, ordinal)`
c. Se encontrado, atualiza `StaticImport.librarySymbolId` e `resolvedName`
3. Loga: `Resolved 152/200 imports (76%) for analysis <id>`
4. Opcional: publica um evento `SymbolsResolvedEvent` para 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
```java
// 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`:
```java
// 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
```java
// 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
```java
// 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)
```java
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
```java
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:**
1. Divide o texto por blocos delimitados por `MODEND`
2. Para cada bloco:
- Extrai `symbolName` da linha `THEADR`
- Extrai `moduleName` do campo `Module Name:`
- Extrai `ordinal` do campo `ordinal:`
- Se não encontrar `ordinal:` ou o bloco não for `IMPDEF`, pula
- Classifica `symbolType`:
- Contém `::~``DESTRUCTOR`
- Contém `::` e termina em `()` sem `~``CONSTRUCTOR`
- Contém `::``METHOD`
- Sem `::``FUNCTION`
- Extrai `className` do trecho antes de `::`
- Extrai `parameters` dos parênteses após o nome do método
- Classifica `category` por 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
```
### 6.3 Registro de Parsers
```java
@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)