From b422b4cd78cc350a0607797f8ac33c1de7c0e810 Mon Sep 17 00:00:00 2001 From: Jason Tang Date: Wed, 18 Mar 2026 20:02:06 -0400 Subject: [PATCH] Updates. --- ida-plugin.json | 2 +- src/controllers/symgraph_controller.py | 19 ++++++++++++-- src/services/actions_service.py | 34 +++++++++++++++++++++++--- src/services/analysis_db_service.py | 31 +++++++++++++++++++++++ src/services/db_migrations.py | 28 +++++++++++++++++++++ src/services/internal_tools.py | 12 +++++++++ 6 files changed, 120 insertions(+), 6 deletions(-) diff --git a/ida-plugin.json b/ida-plugin.json index e9b8731..86629cb 100644 --- a/ida-plugin.json +++ b/ida-plugin.json @@ -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": { diff --git a/src/controllers/symgraph_controller.py b/src/controllers/symgraph_controller.py index 43bfae1..348bc88 100644 --- a/src/controllers/symgraph_controller.py +++ b/src/controllers/symgraph_controller.py @@ -590,6 +590,21 @@ class SymGraphController(QObject): else: self.view.set_binary_info("", 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: diff --git a/src/services/actions_service.py b/src/services/actions_service.py index 4afe46c..b9d2b44 100644 --- a/src/services/actions_service.py +++ b/src/services/actions_service.py @@ -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}'", diff --git a/src/services/analysis_db_service.py b/src/services/analysis_db_service.py index b65a8fa..a8f8d7a 100644 --- a/src/services/analysis_db_service.py +++ b/src/services/analysis_db_service.py @@ -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: diff --git a/src/services/db_migrations.py b/src/services/db_migrations.py index 6736850..ee9b297 100644 --- a/src/services/db_migrations.py +++ b/src/services/db_migrations.py @@ -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""" diff --git a/src/services/internal_tools.py b/src/services/internal_tools.py index 97fb796..2f598d8 100644 --- a/src/services/internal_tools.py +++ b/src/services/internal_tools.py @@ -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}'")]