This commit is contained in:
Jason Tang
2026-03-18 20:02:06 -04:00
parent 63a80a76dc
commit b422b4cd78
6 changed files with 120 additions and 6 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
"plugin": {
"name": "IDAssist",
"entryPoint": "idassist_plugin.py",
"version": "1.2.0",
"version": "1.3.0",
"description": "AI-powered reverse engineering plugin with LLM analysis, semantic knowledge graphs, RAG search, and SymGraph collaboration",
"license": "MIT",
"urls": {
+17 -2
View File
@@ -590,6 +590,21 @@ class SymGraphController(QObject):
else:
self.view.set_binary_info("<no binary loaded>", None)
def _get_symbol_provenance(self, is_auto: bool, address: int, symbol_type: str) -> str:
"""Determine symbol provenance: decompiler, llm, or user."""
if is_auto:
return 'decompiler'
try:
from src.services.analysis_db_service import AnalysisDBService
binary_hash = self._get_sha256()
if binary_hash:
db = AnalysisDBService()
if db.is_llm_renamed(binary_hash, address, symbol_type):
return 'llm'
except Exception:
pass
return 'user'
def _get_sha256(self) -> Optional[str]:
"""Get SHA256 hash of the original binary."""
return get_binary_hash() or None
@@ -1006,7 +1021,7 @@ class SymGraphController(QObject):
'name': func_name,
'data_type': data_type,
'confidence': 0.5 if is_auto else 0.9,
'provenance': 'decompiler' if is_auto else 'user'
'provenance': self._get_symbol_provenance(is_auto, func.start_ea, 'function')
}
def _is_auto_generated_name(self, name: str) -> bool:
@@ -1221,7 +1236,7 @@ class SymGraphController(QObject):
'raw_content': node.raw_code,
'llm_summary': node.llm_summary,
'confidence': confidence,
'provenance': 'user' if node.user_edited else 'decompiler',
'provenance': 'user' if node.user_edited else ('llm' if node.llm_summary else 'decompiler'),
}
if node.security_flags:
+31 -3
View File
@@ -189,7 +189,7 @@ class ActionsService:
execute_on_main_thread(_do_rename)
if result_holder[0]:
# Sync rename to knowledge graph (best-effort)
# Sync rename to knowledge graph and record LLM provenance (best-effort)
try:
from .analysis_db_service import AnalysisDBService
from .graphrag.graph_store import GraphStore
@@ -199,10 +199,12 @@ class ActionsService:
bh_holder[0] = get_binary_hash()
execute_on_main_thread(_get_hash)
if bh_holder[0]:
gs = GraphStore(AnalysisDBService())
db = AnalysisDBService()
gs = GraphStore(db)
gs.update_node_name(bh_holder[0], function_address, new_name)
db.record_llm_rename(bh_holder[0], function_address, 'function', new_name)
except Exception as e:
log.log_warn(f"Graph name sync failed (non-fatal): {e}")
log.log_warn(f"Graph/provenance sync failed (non-fatal): {e}")
return ActionResult(
success=True,
@@ -262,6 +264,19 @@ class ActionsService:
execute_on_main_thread(_do_rename)
if result_holder[0]:
# Record LLM rename for provenance tracking (best-effort)
try:
from .analysis_db_service import AnalysisDBService
from src.ida_compat import get_binary_hash
bh_holder = [None]
def _get_hash():
bh_holder[0] = get_binary_hash()
execute_on_main_thread(_get_hash)
if bh_holder[0]:
AnalysisDBService().record_llm_rename(bh_holder[0], function_address, 'variable', new_name)
except Exception as e:
log.log_warn(f"Provenance record failed (non-fatal): {e}")
return ActionResult(
success=True,
message=f"Renamed variable from '{var_name}' to '{new_name}'",
@@ -320,6 +335,19 @@ class ActionsService:
execute_on_main_thread(_do_rename)
if result_holder[0]:
# Record LLM rename for provenance tracking (best-effort)
try:
from .analysis_db_service import AnalysisDBService
from src.ida_compat import get_binary_hash
bh_holder = [None]
def _get_hash():
bh_holder[0] = get_binary_hash()
execute_on_main_thread(_get_hash)
if bh_holder[0]:
AnalysisDBService().record_llm_rename(bh_holder[0], function_address, 'variable', new_name)
except Exception as e:
log.log_warn(f"Provenance record failed (non-fatal): {e}")
return ActionResult(
success=True,
message=f"Renamed stack variable '{var_name}' to '{new_name}'",
+31
View File
@@ -1826,6 +1826,37 @@ class AnalysisDBService:
log.log_error(f"Database cleanup failed: {e}")
return {"expired_contexts": 0, "old_chat_messages": 0}
def record_llm_rename(self, binary_id: str, address: int, symbol_type: str, new_name: str):
"""Record that a symbol was renamed by an LLM suggestion."""
try:
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute(
"INSERT OR REPLACE INTO llm_renames (binary_id, address, symbol_type, new_name, created_at) "
"VALUES (?, ?, ?, ?, ?)",
(binary_id, address, symbol_type, new_name, int(__import__('time').time()))
)
conn.commit()
conn.close()
except Exception as e:
log.log_error(f"Failed to record LLM rename: {e}")
def is_llm_renamed(self, binary_id: str, address: int, symbol_type: str) -> bool:
"""Check if a symbol at the given address was renamed by an LLM."""
try:
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute(
"SELECT COUNT(*) FROM llm_renames WHERE binary_id = ? AND address = ? AND symbol_type = ?",
(binary_id, address, symbol_type)
)
result = cursor.fetchone()[0] > 0
conn.close()
return result
except Exception as e:
log.log_error(f"Failed to check LLM rename: {e}")
return False
def vacuum_database(self) -> bool:
"""Optimize database by running VACUUM"""
try:
+28
View File
@@ -80,6 +80,7 @@ class DatabaseMigrations:
(4, DatabaseMigrations._migration_004_graphrag_tables),
(5, DatabaseMigrations._migration_005_graphrag_communities),
(6, DatabaseMigrations._migration_006_graphrag_fts),
(7, DatabaseMigrations._migration_007_llm_renames),
]
for version, migration_func in migrations:
@@ -494,6 +495,33 @@ class DatabaseMigrations:
log.log_warn(f"Migration 006 issue: {e}. FTS5 may be unavailable.")
return True
@staticmethod
def _migration_007_llm_renames(db_path: str) -> bool:
"""Migration 007: LLM renames tracking table"""
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS llm_renames (
binary_id TEXT NOT NULL,
address INTEGER NOT NULL,
symbol_type TEXT NOT NULL DEFAULT 'function',
new_name TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (binary_id, address, symbol_type)
)
''')
conn.commit()
conn.close()
log.log_info("Migration 007: llm_renames table created successfully")
return True
except Exception as e:
log.log_error(f"Migration 007 failed: {e}")
return False
class DatabaseCleanup:
"""Database cleanup utilities"""
+12
View File
@@ -351,6 +351,18 @@ def _rename_function(args: Dict) -> List['TextContent']:
if result_holder[2]:
return [TextContent(type="text", text=f"Error: {result_holder[2]}")]
if result_holder[0]:
# Record LLM rename for provenance tracking (best-effort)
try:
from .analysis_db_service import AnalysisDBService
from src.ida_compat import get_binary_hash
bh_holder = [None]
def _get_hash():
bh_holder[0] = get_binary_hash()
execute_on_main_thread(_get_hash)
if bh_holder[0]:
AnalysisDBService().record_llm_rename(bh_holder[0], ea, 'function', new_name)
except Exception:
pass
return [TextContent(type="text", text=f"Renamed '{result_holder[1]}' to '{new_name}'")]
else:
return [TextContent(type="text", text=f"Failed to rename function to '{new_name}'")]