backend/docs/library-symbols-feature.md

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..."
}
  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

// 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:

  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

@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)