Skip to content

Commit 27bd934

Browse files
author
hackcatml
committed
Support comaring sections between two dumped files using r2pipe
1 parent 9fe59f0 commit 27bd934

File tree

6 files changed

+224
-112
lines changed

6 files changed

+224
-112
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ __pycache__
55
.idea
66
scripts/script_test
77
test
8+
/dump/*
9+
!/dump/dummyfile
810
ui_test.py
911
ui_test.ui
1012
ui_test2.ui

diff.py

Lines changed: 181 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from PyQt6.QtCore import Qt, pyqtSlot, QThread
55
from PyQt6.QtGui import QPalette
66
from PyQt6.QtWidgets import QFileDialog, QApplication
7+
import r2pipe
78

89
import diff_ui
910

@@ -25,9 +26,6 @@ def run(self) -> None:
2526
line = ''
2627
line_count = 0
2728

28-
dark_mode = self.is_dark_mode()
29-
default_color = "white" if dark_mode else "black"
30-
3129
for block_start, (block1, block2) in self.formatted_diffs.items():
3230
line_count += 1
3331
if line_count == 1:
@@ -68,7 +66,7 @@ class BinaryCompareWorker(QThread):
6866
binary_compare_finished_sig = QtCore.pyqtSignal(str)
6967

7068
"""Comparing two binary files"""
71-
def __init__(self, file1, file2):
69+
def __init__(self, file1, file2, sections):
7270
"""Get the files to compare and initialise message, offset and diff list.
7371
:param file1: a file
7472
:type file1: string
@@ -89,64 +87,80 @@ def __init__(self, file1, file2):
8987
self.file1_contents = None
9088
self.file2_contents = None
9189

90+
self.sections = sections
91+
9292
self.offset_differs = None
9393

94-
def read_file_contents(self, filename):
94+
def read_file_contents(self, filename, start, size):
9595
'''Read file contents into a list of bytes.'''
9696
with open(filename, 'rb') as file:
97-
return list(file.read())
97+
if start is not None:
98+
file.seek(start)
99+
try:
100+
return list(file.read(size))
101+
except Exception as e:
102+
return list()
103+
else:
104+
try:
105+
return list(file.read())
106+
except Exception as e:
107+
return list()
98108

99109
def run(self) -> None:
100110
"""Compare the two files
101-
:returns: Comparison result: True if similar, False if different.
102-
Set vars offset and message if there's a difference.
103-
"""
111+
:returns: Comparison result: True if similar, False if different.
112+
Set vars offset and message if there's a difference.
113+
"""
104114
self.message = None
105115
self.offset_differs = None
106116
offset = 0
107-
offset_diff = 0
108-
first = False
109117
if not os.path.isfile(self.file1) or not os.path.isfile(self.file2):
110118
self.message = "not found"
111119
return
112120
if os.path.getsize(self.file1) != os.path.getsize(self.file2):
113121
self.message = "size"
114122
self.binary_compare_finished_sig.emit("size")
115123
return
124+
self.binary_compare_finished_sig.emit("start")
116125
result = True
117-
f1 = open(self.file1, 'rb')
118-
f2 = open(self.file2, 'rb')
119-
loop = True
120-
while loop:
121-
buffer1 = f1.read(self._buffer_size)
122-
buffer2 = f2.read(self._buffer_size)
123-
if len(buffer1) == 0 or len(buffer2) == 0:
124-
loop = False
125-
for byte1, byte2 in zip(buffer1, buffer2):
126+
self.diff_list.clear()
127+
128+
# Compare each section
129+
for section in self.sections:
130+
start = section['start']
131+
size = section['size']
132+
133+
# Read section contents
134+
data1 = self.read_file_contents(self.file1, start, size)
135+
data2 = self.read_file_contents(self.file2, start, size)
136+
137+
if not data1 or not data2:
138+
self.message = "cannot read"
139+
self.binary_compare_finished_sig.emit(self.message)
140+
return
141+
142+
# Compare the contents byte by byte
143+
for offset, (byte1, byte2) in enumerate(zip(data1, data2), start=start if start is not None else 0):
126144
if byte1 != byte2:
127-
if first == False:
128-
first = True
129145
result = False
130146
self.diff_list.append((offset, byte1, byte2))
131-
offset += 1
132-
if not first:
133-
offset_diff += 1
134-
f1.close()
135-
f2.close()
136-
if not result:
137-
self.message = 'content'
138-
self.offset = hex(offset_diff)
139-
else:
140-
self.message = 'identical'
147+
148+
if not result:
149+
self.message = "content"
150+
self.offset = hex(offset)
151+
152+
# Sort the diff_list by offset in ascending order
153+
self.diff_list.sort(key=lambda x: x[0])
141154

142155
# Read file contents into lists after comparison
143-
self.file1_contents = self.read_file_contents(self.file1)
144-
self.file2_contents = self.read_file_contents(self.file2)
156+
self.file1_contents = self.read_file_contents(self.file1, None, None)
157+
self.file2_contents = self.read_file_contents(self.file2, None, None)
145158

146159
if result:
147-
self.binary_compare_finished_sig.emit("identical")
160+
self.message = "identical"
148161
else:
149-
self.binary_compare_finished_sig.emit("finished")
162+
self.message = "finished"
163+
self.binary_compare_finished_sig.emit(self.message)
150164

151165
def format_diff(self, block_size=16):
152166
"""Format the differences in blocks of specified size."""
@@ -172,14 +186,15 @@ def __init__(self, statusBar):
172186
super(DiffDialogClass, self).__init__(statusBar)
173187
self.diff_dialog = QtWidgets.QDialog()
174188
self.diff_dialog.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
175-
# self.diff_dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
176189
self.diff_dialog_ui = diff_ui.Ui_DiffDialog()
177190
self.diff_dialog_ui.setupUi(self.diff_dialog)
178191

179192
self.statusBar = statusBar
180193

181194
self.file1 = None
182195
self.file2 = None
196+
self.sections = None
197+
self.checked_sections = []
183198

184199
self.diff_dialog_ui.textEdit.file_dropped_sig.connect(self.file_dropped_sig_func)
185200
self.diff_dialog_ui.textEdit_2.file_dropped_sig.connect(self.file_dropped_sig_func)
@@ -190,46 +205,46 @@ def __init__(self, statusBar):
190205
self.diff_dialog_ui.doDiffBtn.setDisabled(True)
191206
self.diff_dialog_ui.doDiffBtn.clicked.connect(self.do_diff)
192207

193-
self.binary_diff_result_window = None
194-
self.binary_diff_result_ui = None
195208
self.binary_compare_worker = None
196209

210+
self.binary_diff_result_window = QtWidgets.QWidget()
211+
self.binary_diff_result_ui = diff_ui.CompareFilesWindow()
212+
self.binary_diff_result_ui.setupUi(self.binary_diff_result_window)
213+
self.binary_diff_result_ui.stopDiffBtn.clicked.connect(self.stop_diff)
214+
197215
self.process_diff_result_worker = None
198216
self.process_diff_result_sig_count = 0
199217

200218
self.diff_result = None
201219

202220
@pyqtSlot(list)
203-
def file_dropped_sig_func(self, dropped_file: list):
204-
if dropped_file[0] == "file1":
205-
self.file1 = dropped_file[1]
206-
elif dropped_file[0] == "file2":
207-
self.file2 = dropped_file[1]
208-
209-
if self.file1 and self.file2:
210-
self.diff_dialog_ui.doDiffBtn.setEnabled(True)
211-
212-
def select_file(self, file1or2):
213-
file, _ = QFileDialog.getOpenFileNames(self, caption="Select a file to compare", directory="./dump", initialFilter="All Files (*)")
214-
if file1or2 == "file1":
215-
self.file1 = "" if len(file) == 0 else file[0]
216-
if self.file1:
217-
self.diff_dialog_ui.textEdit.setText(self.file1)
218-
elif file1or2 == "file2":
219-
self.file2 = "" if len(file) == 0 else file[0]
220-
if self.file2:
221-
self.diff_dialog_ui.textEdit_2.setText(self.file2)
221+
def file_dropped_sig_func(self, sig: list):
222+
if sig[0] == "file1":
223+
self.file1 = sig[1]
224+
elif sig[0] == "file2":
225+
self.file2 = sig[1]
222226

223227
if self.file1 and self.file2:
224228
self.diff_dialog_ui.doDiffBtn.setEnabled(True)
225229

226230
@pyqtSlot(str)
227-
def binary_compare_finished_sig_func(self, is_finished: str):
228-
if is_finished == "size":
231+
def binary_compare_finished_sig_func(self, sig: str):
232+
if sig == "start":
233+
self.binary_diff_result_window.show()
234+
elif sig == "size":
229235
self.statusBar.showMessage("\tThe sizes of the two files are different", 5000)
230-
elif is_finished == "identical":
231-
self.statusBar.showMessage("\tTwo files are identical", 5000)
232-
elif is_finished == "finished":
236+
elif sig == "identical":
237+
if self.checked_sections and len(self.checked_sections) == 1 and self.checked_sections[0]['name'] != 'All':
238+
self.statusBar.showMessage(f"\t{self.checked_sections[0]['name']} is identical", 5000)
239+
elif self.checked_sections and len(self.checked_sections) > 1:
240+
self.statusBar.showMessage(f"\tSections are identical", 5000)
241+
else:
242+
self.statusBar.showMessage("\tTwo files are identical", 5000)
243+
elif sig == "cannot read":
244+
self.statusBar.showMessage("\tCannot read file contents", 5000)
245+
self.binary_diff_result_window.close()
246+
self.diff_dialog.show()
247+
elif sig == "finished":
233248
self.process_diff_result()
234249

235250
@pyqtSlot(str)
@@ -249,12 +264,99 @@ def process_diff_finished_sig_func(self):
249264
self.diff_result = self.binary_diff_result_ui.binaryDiffResultView.toPlainText()
250265
self.statusBar.showMessage("\tBinary diff is done!", 5000)
251266

252-
def process_diff_result(self):
253-
self.binary_diff_result_window = QtWidgets.QWidget()
254-
self.binary_diff_result_ui = diff_ui.CompareFilesWindow()
255-
self.binary_diff_result_ui.setupUi(self.binary_diff_result_window)
256-
self.binary_diff_result_ui.stopDiffBtn.clicked.connect(self.stop_diff)
267+
def section_checkbox(self, checkbox_name, state):
268+
if state == Qt.CheckState.Checked.value: # Check
269+
if checkbox_name == 'All':
270+
self.checked_sections.append({
271+
"start": None,
272+
"size": None,
273+
"name": checkbox_name
274+
})
275+
for checkbox in self.diff_dialog_ui.checkboxes:
276+
if checkbox.text() != 'All':
277+
checkbox.setChecked(False)
278+
checkbox.setEnabled(False)
279+
else:
280+
for section in self.sections:
281+
if section['name'] == checkbox_name:
282+
self.checked_sections.append(section)
283+
break
284+
else: # Uncheck
285+
if checkbox_name == 'All':
286+
self.checked_sections.clear()
287+
for checkbox in self.diff_dialog_ui.checkboxes:
288+
if checkbox.text() != 'All':
289+
checkbox.setEnabled(True)
290+
else:
291+
self.checked_sections = [section for section in self.checked_sections
292+
if section['name'] != checkbox_name]
293+
294+
def select_file(self, file1or2):
295+
file, _ = QFileDialog.getOpenFileNames(self, caption="Select a file to compare", directory="./dump", initialFilter="All Files (*)")
296+
if file1or2 == "file1":
297+
self.file1 = "" if len(file) == 0 else file[0]
298+
if self.file1:
299+
self.diff_dialog_ui.textEdit.setText(self.file1)
300+
elif file1or2 == "file2":
301+
self.file2 = "" if len(file) == 0 else file[0]
302+
if self.file2:
303+
self.diff_dialog_ui.textEdit_2.setText(self.file2)
257304

305+
if self.file1 and self.file2:
306+
self.diff_dialog_ui.doDiffBtn.setEnabled(True)
307+
while self.diff_dialog_ui.checkboxGridLayout.count():
308+
item = self.diff_dialog_ui.checkboxGridLayout.takeAt(0) # Remove the item at position 0
309+
widget = item.widget() # Get the widget associated with the item
310+
if widget:
311+
widget.deleteLater() # Schedule the widget for deletion
312+
313+
r2 = r2pipe.open(self.file1)
314+
sections_info = r2.cmdj("iSj")
315+
self.sections = [
316+
{
317+
"start": section["paddr"],
318+
"size": section["size"],
319+
"name": section["name"]
320+
}
321+
for section in sections_info
322+
]
323+
324+
section_found_count = 0
325+
max_columns = 4 # Limit the number of checkboxes per row
326+
row, col = 0, 0
327+
for section in self.sections:
328+
if section['name']:
329+
section_found_count += 1
330+
if section_found_count == 1:
331+
label = QtWidgets.QLabel("Sections:")
332+
self.diff_dialog_ui.checkboxGridLayout.addWidget(label, row, col)
333+
row += 1
334+
335+
# print(f"Name: {section['name']}, Start: {hex(section['start'])}, Size: {section['size']}")
336+
if section_found_count == 1:
337+
section_checkbox = QtWidgets.QCheckBox("All", self.diff_dialog)
338+
section_checkbox.setObjectName(f"checkbox_all")
339+
section_checkbox.setChecked(True)
340+
self.checked_sections.append({
341+
"start": None,
342+
"size": None,
343+
"name": "All"
344+
})
345+
else:
346+
section_checkbox = QtWidgets.QCheckBox(section['name'], self.diff_dialog)
347+
section_checkbox.setObjectName(f"checkbox_{section['name']}")
348+
section_checkbox.setChecked(False)
349+
section_checkbox.setEnabled(False)
350+
section_checkbox.stateChanged.connect(lambda state, cb=section_checkbox:
351+
self.section_checkbox(cb.text(), state))
352+
self.diff_dialog_ui.checkboxes.append(section_checkbox)
353+
self.diff_dialog_ui.checkboxGridLayout.addWidget(section_checkbox, row, col)
354+
col += 1
355+
if col >= max_columns: # Move to the next row if column limit is reached
356+
col = 0
357+
row += 1
358+
359+
def process_diff_result(self):
258360
formatted_diffs = self.binary_compare_worker.format_diff(16)
259361
self.binary_compare_worker.quit()
260362

@@ -265,17 +367,23 @@ def process_diff_result(self):
265367

266368
def stop_diff(self):
267369
if self.process_diff_result_worker is not None:
268-
self.process_diff_result_worker.process_diff_result_signal.disconnect(self.process_diff_result_sig_func)
269-
self.process_diff_result_worker.process_diff_finished_signal.disconnect(self.process_diff_finished_sig_func)
270-
self.process_diff_result_worker.terminate()
370+
try:
371+
self.process_diff_result_worker.process_diff_result_signal.disconnect(self.process_diff_result_sig_func)
372+
self.process_diff_result_worker.process_diff_finished_signal.disconnect(self.process_diff_finished_sig_func)
373+
except Exception as e:
374+
print(e)
375+
if self.process_diff_result_worker.isRunning():
376+
self.process_diff_result_worker.quit()
271377
self.diff_result = self.binary_diff_result_ui.binaryDiffResultView.toPlainText()
272378
self.statusBar.showMessage("\tBinary diff is done!", 5000)
273379

274380
def do_diff(self):
381+
if len(self.checked_sections) == 0:
382+
self.statusBar.showMessage("\tChoose sections to compare", 5000)
383+
return
275384
self.diff_dialog.close()
276-
277385
if self.file1 and self.file2:
278-
self.binary_compare_worker = BinaryCompareWorker(self.file1, self.file2)
386+
self.binary_compare_worker = BinaryCompareWorker(self.file1, self.file2, self.checked_sections)
279387
self.binary_compare_worker.binary_compare_finished_sig.connect(self.binary_compare_finished_sig_func)
280388
self.binary_compare_worker.start()
281389
else:

diff_ui.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ def setupUi(self, Dialog):
6161
self.doDiffBtn.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
6262
self.gridLayout.addWidget(self.doDiffBtn, 2, 0, 1, 2)
6363

64+
self.checkboxGridLayout = QtWidgets.QGridLayout()
65+
self.gridLayout.addLayout(self.checkboxGridLayout, 4, 0, 1, 2)
66+
self.checkboxes = []
67+
6468
self.retranslateUi(Dialog)
6569
QtCore.QMetaObject.connectSlotsByName(Dialog)
6670

0 commit comments

Comments
 (0)