feat(ai): suggestPseudocode tool, simplified prompt, DecompiledResult.found, and address sort

This commit is contained in:
Rodrigo Verdiani 2026-07-04 18:40:21 -03:00
parent 6ad98e9675
commit 1b1da09ff3
3 changed files with 73 additions and 54 deletions

View File

@ -60,60 +60,31 @@ public class ChatContextBuilder {
getFunctionCallGraph (or getFunctionAssembly + getCallers + getCallees) getFunctionCallGraph (or getFunctionAssembly + getCallers + getCallees)
to examine the function AND the functions it calls. A thorough analysis to examine the function AND the functions it calls. A thorough analysis
includes understanding what callees do and how they relate to the caller. includes understanding what callees do and how they relate to the caller.
Never just read one function in isolation and suggest analyzing callees Never just read one function in isolation analyze callees now.
"later" analyze them now as part of your response.
PRIORITY RULE: When the user asks for a specific mutation (rename, comment, [MUTATION TOOLS MANDATORY]
signature, flag), use that tool first. Do NOT respond with a different mutation When your analysis reveals findings deserving a rename, comment, signature,
type instead (e.g., don't suggest renames when the user asked for comments). flag, or label change, you MUST call the corresponding tool IMMEDIATELY.
This rule is about mutation tool selection ONLY it does NOT limit your A suggestion described only in text will NOT be reflected in the UI and is
analysis. Always do a thorough analysis including callees and callers. USELESS the user needs the tool call to see and approve it.
[FUNCTION RENAME] For proactive suggestions (requires user approval), use the suggest* tools:
Use renameFunction when the user explicitly asks to rename a specific function suggestRenameFunction, suggestAddComment, suggestUpdateSignature,
(applies immediately). Use suggestRenameFunction when you independently identify suggestPseudocode, suggestRenameLabel, suggestRenameGlobalVar,
a function's purpose and want to suggest a better name (requires user approval). suggestRenameStruct
Proactively suggest renames whenever you identify a function's purpose with high
confidence. For example:
- A function that decrypts data with RC4 CALL suggestRenameFunction("DecryptRC4")
- A function that parses HTTP headers CALL suggestRenameFunction("ParseHttpHeaders")
- A function that validates a checksum CALL suggestRenameFunction("ValidateChecksum")
- A function that is the WinMain entry point CALL suggestRenameFunction("WinMain")
CRITICAL: When you decide a function should be renamed, you MUST call the
suggestRenameFunction tool with the function ID and new name. NEVER just
describe the rename in your text response without calling the tool.
Always call the tool, then briefly explain your reasoning afterward.
[FUNCTION SIGNATURE] For changes the user explicitly asked for (applied immediately), use:
Use updateSignature (apply) or suggestUpdateSignature (pending approval) renameFunction, addComment, updateSignature,
to set a function's type signature. Infer parameter types and return type addFlag, removeFlag, renameLabel,
from assembly analysis. Example: 'int DecryptRC4(int keyLen, const uint8_t* key)'. renameGlobalVar, renameStruct
[FUNCTION COMMENTS] Rules:
Use addComment (apply) or suggestAddComment (pending approval) to document 1. Call mutation tools BEFORE writing your analysis text.
analysis findings. You can attach comments to specific addresses 2. If you have 3 suggestions, make 3 tool calls not 1 text block.
(e.g., address="0x5EB5") to document individual instructions inline. 3. For pseudocode: only suggestPseudocode when getFunctionDecompiled returned
Omit address to attach to the function. The tool appends automatically. found=false and you are confident in the assembly logic.
CRITICAL: Pass ONLY the new comment text never include previous comments. 4. When the user explicitly asks for a specific change type (e.g. "add a comment"),
CRITICAL: When the user asks to add comments, call addComment/suggestAddComment. use that tool first. Do not respond with a different mutation type instead.
Do NOT respond with renames or signature changes unless the user also asked for those.
[FUNCTION FLAGS]
Use addFlag and removeFlag to categorize functions.
Valid flags: CRYPTO, NETWORK, FILE_IO, ANTI_DEBUG, ENTRY_POINT,
CALLBACK, MEMORY, PROCESS, REGISTRY, UI.
[GLOBAL VARIABLES]
Use searchGlobalVars to find data label IDs, then renameGlobalVar (apply)
or suggestRenameGlobalVar (pending approval).
[LABELS]
Use searchLabels to find label IDs within a function, then renameLabel (apply)
or suggestRenameLabel (pending approval).
[STRUCTS]
Use searchStructs to find struct IDs, then renameStruct (apply)
or suggestRenameStruct (pending approval).
%s %s
[BINARY OVERVIEW] [BINARY OVERVIEW]
Filename: %s Filename: %s

View File

@ -2,6 +2,7 @@ package ai.decompile.ai.service.tools;
import ai.decompile.analysis.model.repository.StaticFunctionRepository; import ai.decompile.analysis.model.repository.StaticFunctionRepository;
import ai.decompile.analysis.service.MutationService; import ai.decompile.analysis.service.MutationService;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.Tool;
@ -217,6 +218,48 @@ public class FunctionMutationTools {
mutationService.suggestComment(funcId, comment, address, "AI", sessionId)); mutationService.suggestComment(funcId, comment, address, "AI", sessionId));
} }
@Tool(
description =
"""
Suggest C-like pseudocode for a function that currently has no decompiled code.
CRITICAL: Only call this when getFunctionDecompiled returns found=false and you
have thoroughly analyzed the function's assembly (call getFunctionAssembly first).
Be conservative only suggest pseudocode when the assembly logic is clear and
you can produce accurate, idiomatic C code. Do NOT guess or fabricate logic.
The pseudocode must faithfully reproduce the exact logic shown in the assembly,
using proper C syntax (control flow, variable names derived from stack offsets,
function calls using resolved names when available).
Provide one line of code per array element. Do NOT combine multiple statements
into a single string element. A comment "Pseudocode generated by AI" will be
automatically added.
""")
public String suggestPseudocode(
@ToolParam(description = "The function ID (UUID from searchFunctions result)")
String functionId,
@ToolParam(
description =
"Array of C-like pseudocode lines. Each element is exactly ONE line of code. "
+ "Do NOT put multiple statements in one element. "
+ "Do not include markdown code fences.")
List<String> pseudocodeLines) {
if (pseudocodeLines == null || pseudocodeLines.isEmpty()) {
return error(functionId, "Pseudocode must not be empty");
}
var pseudocode = String.join("\n", pseudocodeLines);
var funcId = UUID.fromString(functionId);
if (functionRepository.findById(funcId).isEmpty()) {
return error(functionId, "Function not found: " + functionId);
}
mutationService.applyComment(funcId, "Pseudocode generated by AI", "", "AI");
var sessionId = toolContext.getSessionId();
return helper.formatResult(
mutationService.suggest("FUNCTION", funcId, "decompiledCode", pseudocode, "AI", sessionId));
}
@Tool( @Tool(
description = description =
""" """

View File

@ -19,6 +19,7 @@ import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -155,16 +156,18 @@ public class FunctionTools {
var func = functionRepository.findById(UUID.fromString(functionId)).orElse(null); var func = functionRepository.findById(UUID.fromString(functionId)).orElse(null);
if (Objects.isNull(func)) { if (Objects.isNull(func)) {
return new DecompiledResult("unknown", "unknown", ""); return new DecompiledResult("unknown", "unknown", "", false);
} }
var renameMap = mutationService.buildRenameMap(func.getAnalysis().getId()); var renameMap = mutationService.buildRenameMap(func.getAnalysis().getId());
var code = func.getDecompiledCode(); var code = func.getDecompiledCode();
var hasDecompiled = Objects.nonNull(code) && !code.isBlank();
return new DecompiledResult( return new DecompiledResult(
func.getName(), func.getName(),
func.getAddress(), func.getAddress(),
AnalysisService.resolveNames(Objects.nonNull(code) ? code : "", renameMap)); hasDecompiled ? AnalysisService.resolveNames(code, renameMap) : "",
hasDecompiled);
} }
@Tool( @Tool(
@ -291,7 +294,8 @@ public class FunctionTools {
var page = var page =
functionRepository.findByAnalysisId( functionRepository.findByAnalysisId(
analysis.getId(), PageRequest.of(0, MAX_LIST_FUNCTIONS)); analysis.getId(),
PageRequest.of(0, MAX_LIST_FUNCTIONS, Sort.by("address").ascending()));
return page.getContent().stream().map(this::toSummary).toList(); return page.getContent().stream().map(this::toSummary).toList();
} }
@ -345,7 +349,8 @@ public class FunctionTools {
public record FunctionSummary( public record FunctionSummary(
UUID id, String name, String address, int functionSize, String signature) {} UUID id, String name, String address, int functionSize, String signature) {}
public record DecompiledResult(String name, String address, String decompiledCode) {} public record DecompiledResult(
String name, String address, String decompiledCode, boolean found) {}
public record FunctionAssembly(String name, String address, String assembly) {} public record FunctionAssembly(String name, String address, String assembly) {}
} }