Skip to content

Commit 7f91d53

Browse files
committed
feat:ai decompiler
1 parent 3167603 commit 7f91d53

9 files changed

Lines changed: 322 additions & 507 deletions

File tree

revengai/features/ai_decompiler/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from binaryninja import PluginCommand, log_info, BinaryView, log_error
2-
from PySide6.QtWidgets import QWidget, QVBoxLayout, QTabWidget, QLabel, QDockWidget
2+
from PySide6.QtWidgets import QDockWidget
33
from .ai_decompiler import AIDecompiler
44
from binaryninjaui import UIContext
55
from PySide6.QtCore import Qt
@@ -60,7 +60,10 @@ def show_ai_decompiler_dialog(self, bv: BinaryView, func):
6060

6161
def on_dock_closed(self, visible):
6262
if not visible:
63+
log_info("RevEng.AI | AI Decompiler Dock closed")
6364
self.dock_widget = None
65+
self.widget = None
66+
self.ai_decompiler.stop_address_tracking()
6467

6568
def is_valid(self, bv: BinaryView, func):
6669
return self.config.is_configured == True

revengai/features/ai_decompiler/ai_decompiler.py

Lines changed: 22 additions & 284 deletions
Large diffs are not rendered by default.

revengai/features/ai_decompiler/ai_decompiler_dialog.py

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,102 @@
11
from binaryninja import log_error, log_info
2-
from PySide6.QtWidgets import (QDockWidget, QVBoxLayout, QHBoxLayout,
3-
QPushButton, QLabel, QCheckBox, QWidget, QTabWidget, QPlainTextEdit)
4-
from PySide6.QtCore import Qt
5-
from PySide6.QtGui import QPixmap
6-
from PySide6.QtWidgets import QWidget
2+
from PySide6.QtWidgets import QVBoxLayout, QCheckBox, QWidget, QTabWidget, QPlainTextEdit
73
from PySide6.QtCore import QCoreApplication
8-
from PySide6.QtWidgets import QMessageBox
9-
from PySide6.QtWidgets import QProgressBar
10-
from revengai.utils import create_progress_dialog
11-
from .c_highlighter import CHighlighter
12-
from revengai.utils.data_thread import DataThread
13-
from revengai.utils import get_function_by_addr
14-
from revengai.utils.periodic_check import PeriodicChecker
15-
import os
4+
from revengai.utils import create_progress_dialog, get_function_by_addr
5+
from revengai.utils.c_highlighter import CHighlighter
166

177
class AIDecompilerDialog(QWidget):
188
def __init__(self, config, ai_decompiler, bv, func):
199
super().__init__()
2010
self.config = config
2111
self.ai_decompiler = ai_decompiler
12+
self.ai_decompiler.dialog = self
2213
self.bv = bv
2314
self.func = func
2415
self.tabs = QTabWidget()
2516
self.number_of_clicks = 0
26-
self.init_ui(bv, func)
17+
self.initial_setup_done = False
18+
self.init_ui()
2719

28-
def init_ui(self, bv, func):
20+
def init_ui(self):
2921
self.setWindowTitle("RevEng.AI: AI Decompiler")
3022
layout = QVBoxLayout()
3123
self.setLayout(layout)
3224
layout.addWidget(self.tabs)
33-
self.pre_tab_setup(bv, func)
25+
self.address_tracking_checkbox = QCheckBox("Address Tracking")
26+
self.address_tracking_checkbox.setChecked(True)
27+
layout.addWidget(self.address_tracking_checkbox)
28+
self.address_tracking_checkbox.stateChanged.connect(self.toggle_address_tracking)
3429
self.tabs.tabCloseRequested.connect(self.close_tab)
3530

31+
def showEvent(self, event):
32+
super().showEvent(event)
33+
if not self.initial_setup_done:
34+
self.initial_setup_done = True
35+
QCoreApplication.processEvents()
36+
self.pre_tab_setup(self.bv, self.func)
37+
38+
def toggle_address_tracking(self, state):
39+
log_info(f"RevEng.AI | Address tracking checkbox state changed to {state}")
40+
if state == 2:
41+
log_info(f"RevEng.AI | Starting address tracking")
42+
self.ai_decompiler.start_address_tracking()
43+
else:
44+
log_info(f"RevEng.AI | Stopping address tracking")
45+
self.ai_decompiler.stop_address_tracking()
46+
3647
def pre_tab_setup(self, bv, func):
37-
function = get_function_by_addr(bv, func)
38-
tab_name = str(f"0x{function.start:x}")
39-
log_info(f"RevEng.AI | Given address 0x{func:x} is function: {function.name} at 0x{function.start:x}")
40-
for i in range(self.tabs.count()):
41-
if self.tabs.tabText(i) == tab_name:
42-
log_info(f"RevEng.AI | Tab {tab_name} already exists, switching to it.")
43-
return
44-
45-
log_info(f"RevEng.AI | Adding tab {tab_name}")
46-
tab = QWidget()
47-
layout = QVBoxLayout()
48+
try:
49+
progress_dialog = create_progress_dialog(self, "RevEng.AI", "Setting up AI Decompiler...")
50+
progress_dialog.show()
51+
QCoreApplication.processEvents()
4852

49-
editor = QPlainTextEdit()
50-
editor.setPlainText("Starting AI Decompiler...")
51-
editor.setReadOnly(True)
53+
function = get_function_by_addr(bv, func)
54+
tab_name = str(f"0x{function.start:x}")
55+
log_info(f"RevEng.AI | Given address 0x{func:x} is function: {function.name} at 0x{function.start:x}")
56+
for i in range(self.tabs.count()):
57+
if self.tabs.tabText(i) == tab_name:
58+
log_info(f"RevEng.AI | Tab {tab_name} already exists, switching to it.")
59+
self.tabs.setCurrentIndex(i)
60+
progress_dialog.close()
61+
return
62+
63+
log_info(f"RevEng.AI | Adding tab {tab_name}")
64+
tab = QWidget()
65+
layout = QVBoxLayout()
5266

53-
#CHighlighter(editor.document())
54-
editor.show()
55-
layout.addWidget(editor)
56-
tab.setLayout(layout)
57-
58-
index = self.tabs.addTab(tab, tab_name)
59-
self.tabs.setCurrentIndex(index)
67+
editor = QPlainTextEdit()
68+
editor.setPlainText("Starting AI Decompiler...")
69+
editor.setReadOnly(True)
6070

61-
if self.tabs.count() > 1:
62-
self.tabs.setTabsClosable(True)
71+
editor.show()
72+
QCoreApplication.processEvents()
6373

64-
options = {
65-
"editor": editor,
66-
"tab_name": tab_name,
67-
"function": function,
68-
"callback": self.edit_editor
69-
}
70-
self.ai_decompiler.start_ai_decompiler(self.bv, options)
74+
layout.addWidget(editor)
75+
tab.setLayout(layout)
76+
77+
index = self.tabs.addTab(tab, tab_name)
78+
self.tabs.setCurrentIndex(index)
79+
80+
if self.tabs.count() > 1:
81+
self.tabs.setTabsClosable(True)
82+
83+
options = {
84+
"editor": editor,
85+
"tab_name": tab_name,
86+
"function": function,
87+
"callback": self.edit_editor
88+
}
89+
90+
self.ai_decompiler.start_ai_decompiler(self.bv, options)
91+
progress_dialog.close()
92+
except Exception as e:
93+
if 'progress_dialog' in locals():
94+
progress_dialog.close()
95+
log_error(f"RevEng.AI | Error during pre_tab_setup: {str(e)}")
7196

7297
def close_tab(self, index):
7398
log_info(f"RevEng.AI | Closing tab {index} of {self.tabs.count()} tabs")
7499

75-
# Stop AI decompiler for this tab if it's running
76100
try:
77101
self.ai_decompiler.stop_ai_decompiler()
78102
except Exception as e:
@@ -83,10 +107,9 @@ def close_tab(self, index):
83107
self.tabs.setTabsClosable(False)
84108

85109
def closeEvent(self, event):
86-
"""Handle dialog close event"""
87110
try:
88-
# Stop any running AI decompiler
89111
if hasattr(self, 'ai_decompiler'):
112+
log_info(f"RevEng.AI | Stopping AI decompiler")
90113
self.ai_decompiler.stop_ai_decompiler()
91114
except Exception as e:
92115
log_error(f"RevEng.AI | Error during cleanup: {str(e)}")

revengai/features/match_current_function/match_current_function_dialog.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,18 @@ def init_ui(self):
2626
self.resize(1200, 800)
2727

2828
main_layout = QVBoxLayout()
29-
3029
self.tab_widget = QTabWidget()
31-
32-
# Footer layout
3330
footer_layout = self.create_footer_layout()
3431

35-
# Search tab
3632
self.search_tab = SearchTab(self.match_current_function, self.bv, self.status_label)
3733
self.tab_widget.addTab(self.search_tab, "Search")
3834

39-
# Results tab
4035
self.results_tab = ResultTab(self.match_current_function, self.bv, self.status_label)
4136
self.tab_widget.addTab(self.results_tab, "Results")
4237

4338
main_layout.addWidget(self.tab_widget)
44-
4539
main_layout.addLayout(footer_layout)
4640

47-
# Status bar
48-
#self.status_label = QLabel("Ready")
49-
#main_layout.addWidget(self.status_label)
50-
5141
self.setLayout(main_layout)
5242

5343
def create_footer_layout(self):
@@ -174,10 +164,8 @@ def start_matching(self):
174164
self.match_thread.start()
175165

176166
def start_renaming(self):
177-
"""Start the current function renaming process"""
178167
log_info("RevEng.AI | Starting current function renaming process")
179168

180-
# Check if a result is selected
181169
if not self.results_tab.selected_result:
182170
QMessageBox.warning(
183171
self,
@@ -187,13 +175,11 @@ def start_renaming(self):
187175
)
188176
return
189177

190-
# Create and show progress dialog
191178
self.progress = create_progress_dialog(self, "RevEng.AI Rename Selected Function", "Renaming selected function...")
192179
self.progress.show()
193180
QCoreApplication.processEvents()
194181
self.status_label.setText("Renaming selected function...")
195182

196-
# Create and start matching thread - pass single result as list for compatibility
197183
self.rename_thread = DataThread(
198184
self.match_current_function.rename_function,
199185
self.bv,
@@ -205,8 +191,6 @@ def start_renaming(self):
205191
def start_fetching_data_types(self):
206192
log_info("RevEng.AI | Starting current function data type fetching process")
207193
try:
208-
209-
# Create and show progress dialog
210194
self.progress = create_cancellable_progress_dialog(self, "RevEng.AI Fetch Data Types", "Fetching data types...", self.match_current_function.cancel)
211195
self.progress.show()
212196
QCoreApplication.processEvents()
@@ -326,8 +310,3 @@ def on_fetching_data_types_finished(self, success, data):
326310
f"Failed to fetch data types:\n{data}",
327311
QMessageBox.Ok
328312
)
329-
330-
"""
331-
def closeEvent(self, event):
332-
self.accept()
333-
"""

revengai/utils/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
from .progress_dialog import create_progress_dialog, create_cancellable_progress_dialog
44
from .utils import rename_function, parse_date, get_function_by_addr, get_function_id_by_addr
55
from .datatypes import apply_data_types
6+
from .c_highlighter import CHighlighter
67

7-
8-
__all__ = ['PeriodicChecker', 'BaseAuthFeature', 'create_progress_dialog', 'create_cancellable_progress_dialog', 'rename_function', 'parse_date', 'apply_data_types', 'get_function_by_addr', 'get_function_id_by_addr']
8+
__all__ = ['PeriodicChecker', 'BaseAuthFeature', 'create_progress_dialog', 'create_cancellable_progress_dialog', 'rename_function', 'parse_date', 'apply_data_types', 'get_function_by_addr', 'get_function_id_by_addr', 'CHighlighter']
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from binaryninja import BinaryView, log_info, log_error, Symbol, SymbolType, interaction
2+
from binaryninja.interaction import InteractionHandler
3+
from reait.api import RE_authentication, RE_search, RE_nearest_symbols_batch, RE_analyze_functions, RE_name_score, RE_functions_data_types, RE_functions_data_types_poll, RE_get_analysis_id_from_binary_id, RE_get_functions_from_analysis, RE_poll_ai_decompilation, RE_begin_ai_decompilation
4+
from concurrent.futures import ThreadPoolExecutor, as_completed
5+
from typing import List, Dict, Tuple, Optional, Callable
6+
import math
7+
from revengai.utils.datatypes import apply_data_types as apply_data_types_util
8+
import time
9+
from revengai.utils import rename_function as rename_function_util, get_function_id_by_addr as get_function_id_by_addr_util
10+
from libbs.api import DecompilerInterface
11+
from libbs.decompilers.binja.interface import BinjaInterface
12+
from libbs.artifacts import _art_from_dict
13+
from libbs.artifacts import (
14+
Function,
15+
FunctionArgument,
16+
GlobalVariable,
17+
Enum,
18+
Struct,
19+
Typedef,
20+
)
21+
from revengai.utils.periodic_check import PeriodicChecker
22+
from PySide6.QtWidgets import QPlainTextEdit
23+
from binaryninja import BinaryView
24+
from binaryninjaui import UIContext, UIContextNotification
25+
from PySide6.QtCore import QTimer
26+
27+
class AddressChangeMonitor(UIContextNotification):
28+
"""
29+
Monitors address changes in Binary Ninja's UI and calls a callback function
30+
when the user navigates to a new address in the decompiler view.
31+
"""
32+
33+
def __init__(self, callback: Optional[Callable] = None):
34+
"""
35+
Initialize the address change monitor.
36+
37+
Args:
38+
callback: Optional callback function to call when address changes.
39+
Should accept parameters: (context, view, address)
40+
"""
41+
super().__init__()
42+
self.callback = callback
43+
self._registered = False
44+
self._last_address = None
45+
46+
# Register for notifications
47+
self.register()
48+
49+
def register(self):
50+
"""Register this notification with the UI context"""
51+
if not self._registered:
52+
UIContext.registerNotification(self)
53+
self._registered = True
54+
log_info("RevEng.AI | AddressChangeMonitor registered for notifications")
55+
56+
def unregister(self):
57+
"""Unregister this notification from the UI context"""
58+
if self._registered:
59+
UIContext.unregisterNotification(self)
60+
self._registered = False
61+
log_info("RevEng.AI | AddressChangeMonitor unregistered from notifications")
62+
63+
def set_callback(self, callback: Callable):
64+
"""Set or update the callback function"""
65+
self.callback = callback
66+
log_info("RevEng.AI | AddressChangeMonitor callback updated")
67+
68+
def OnAddressChange(self, context, view, frame, addr):
69+
"""Called when the user navigates to a new address"""
70+
try:
71+
# Extract the address from the ViewLocation object
72+
current_addr = None
73+
74+
# Handle ViewLocation object
75+
if hasattr(addr, 'getOffset'):
76+
current_addr = addr.getOffset()
77+
elif hasattr(addr, 'addr'):
78+
current_addr = addr.addr
79+
elif hasattr(addr, 'address'):
80+
current_addr = addr.address
81+
else:
82+
current_addr = addr
83+
84+
85+
# Avoid duplicate notifications for the same address
86+
if current_addr == self._last_address:
87+
return
88+
89+
self._last_address = current_addr
90+
91+
if self.callback:
92+
try:
93+
self.callback(context, view, current_addr, "address_changed")
94+
except Exception as e:
95+
log_error(f"RevEng.AI | Error in address change callback: {str(e)}")
96+
except Exception as e:
97+
log_error(f"RevEng.AI | Error processing address change: {str(e)}")
98+
log_info(f"RevEng.AI | OnAddressChange called with addr type: {type(addr)}")
File renamed without changes.

0 commit comments

Comments
 (0)