From 98e370afb67494c66e1a6a97423a4911b49d34d2 Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Tue, 3 Oct 2023 20:08:14 +0300
Subject: [PATCH 1/8] [WIP] Added basic qemu tracer for ARM32
---
tracers/qemu/arm32/.clang-format | 111 +++++++++++++++
tracers/qemu/arm32/README.md | 42 ++++++
tracers/qemu/arm32/tenet.c | 203 ++++++++++++++++++++++++++++
tracers/qemu/{ => x86-32}/README.md | 0
tracers/qemu/{ => x86-32}/tenet.c | 0
5 files changed, 356 insertions(+)
create mode 100644 tracers/qemu/arm32/.clang-format
create mode 100644 tracers/qemu/arm32/README.md
create mode 100644 tracers/qemu/arm32/tenet.c
rename tracers/qemu/{ => x86-32}/README.md (100%)
rename tracers/qemu/{ => x86-32}/tenet.c (100%)
diff --git a/tracers/qemu/arm32/.clang-format b/tracers/qemu/arm32/.clang-format
new file mode 100644
index 0000000..b3dcb35
--- /dev/null
+++ b/tracers/qemu/arm32/.clang-format
@@ -0,0 +1,111 @@
+---
+Language: Cpp
+# BasedOnStyle: Mozilla
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterControlStatement: Always
+ AfterClass: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: false
+ BeforeElse: true
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Mozilla
+BreakBeforeInheritanceComma: true
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 100
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: true
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: All
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: false
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 600
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 9999
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeInheritanceColon: true
+SpacesInParentheses: false
+SpaceBeforeCpp11BracedList: false
+SpacesInSquareBrackets: false
+IndentAccessModifiers: false
+Standard: Cpp11
+TabWidth: 8
+UseTab: Never
+...
+
diff --git a/tracers/qemu/arm32/README.md b/tracers/qemu/arm32/README.md
new file mode 100644
index 0000000..0244e22
--- /dev/null
+++ b/tracers/qemu/arm32/README.md
@@ -0,0 +1,42 @@
+# QEMU Tracer
+
+In this folder is a sample arm32 QEMU-based Tenet tracer built on the QEMU plugin API + some black magic. This tracer is mostly based on the x86-32 version: [README.md](../x86-32/README.md).
+
+Tested on QEMU v5.0.0 (fdd76fecdde1ad444ff4deb7f1c4f7e4a1ef97d6).
+Configure cmd: `CC=gcc-9 ../configure --enable-plugins --disable-strip --enable-linux-user --target-list=arm-softmmu,arm-linux-user`
+Make cmd: `make -j8`
+
+## Usage
+
+This QEMU plugin can be used to trace [XEMU](https://github.com/mborgerson/xemu), and maybe some other x86 based QEMU projects (with some adaptations). Please note, XEMU needs to be built with `--enable-plugins` (add it to `build.sh`) to use the provided plugin.
+
+Example usage:
+
+```
+~/xemu/dist/xemu -plugin ~/xemu/tests/plugin/libtenet.so
+```
+
+This will start the system and generate a `trace.log` file. Since there is no filtering of any sort, I would recommend skipping the startup animation, or modifying the plugin to trace specific areas of interest. Otherwise you will get a raw, 'full-system' trace.
+
+## Compilation
+
+QEMU's native plugin API does not provide access to guest registers or memory making typical instrumentation... difficult. This tracer demonstrates how to use some ugly hacks (eg, hardcoding offsets off the opaque CPU handle) to workaround these limitations.
+
+### Finding magic offsets
+
+1. Place this line near the end of `~/xemu/target/i386/cpu.h`
+
+ ```c
+ char __foo[] = {[offsetof(X86CPU, env)] = ""};
+ ```
+
+2. Attempt to build XEMU/QEMU and let the compiler explode, the error message will print the magic offset value we will need to hardcode into the QEMU plugin / tracer.
+3. Remove / comment out the line added in Step 1
+
+### Building libtenet
+
+1. Place `tenet.c` in `~/xemu/tests/plugin/`
+2. Add `NAMES += tenet` to the `Makefile` in this directory
+3. Modify `tenet.c`, and replace `34928` with the magic offset found using the above steps
+4. Run `make`
+5. There should be a resulting libtenet.so in this plugins directory
diff --git a/tracers/qemu/arm32/tenet.c b/tracers/qemu/arm32/tenet.c
new file mode 100644
index 0000000..54e7306
--- /dev/null
+++ b/tracers/qemu/arm32/tenet.c
@@ -0,0 +1,203 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#define NUM_REG 16
+
+enum reg
+{
+ // GPR regs
+ R0 = 0,
+ R1 = 1,
+ R2 = 2,
+ R3 = 3,
+ R4 = 4,
+ R5 = 5,
+ R6 = 6,
+ R7 = 7,
+ R8 = 8,
+ R9 = 9,
+ R10 = 10,
+ R11 = 11,
+ R12 = 12,
+
+ // Special purpose regs
+ SP = 13,
+ LR = 14,
+ PC = 15,
+};
+
+const char* reg_name[NUM_REG] = { "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7",
+ "R8", "R9", "R10", "R11", "R12", "SP", "LR", "PC" };
+
+char reg_scratch[2048] = {};
+char mem_scratch[2048] = {};
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+void* qemu_get_cpu(int index);
+
+// void *qemu_ram_ptr_length(void * ram_block, uint64_t addr, uint64_t * size,
+// bool lock);
+void* qemu_map_ram_ptr(void* ram_block, uint64_t addr);
+void cpu_physical_memory_rw(uint64_t hwaddr, uint8_t* buf, uint64_t len, int is_write);
+int32_t cpu_memory_rw_debug(void* cpu, uint32_t addr, void* ptr, uint32_t len, int is_write);
+
+uint32_t* get_cpu_regs(void);
+uint32_t* get_cpu_regs(void)
+{
+ uint8_t* cpu = qemu_get_cpu(0);
+ return (uint32_t*)(cpu + 33488);
+}
+
+FILE* g_out = NULL;
+
+uint32_t* g_cpu = NULL;
+uint32_t g_cpu_prev[NUM_REG] = {};
+
+typedef struct mem_entry
+{
+ qemu_plugin_meminfo_t info;
+ uint64_t virt_addr;
+ uint64_t ram_addr;
+} mem_entry;
+
+mem_entry g_mem_log[2048] = {};
+size_t g_mem_log_count = 0;
+
+static void vcpu_insn_exec(unsigned int cpu_index, void* udata)
+{
+ int length = 0;
+
+ if (g_cpu[R0] != g_cpu_prev[R0])
+ length += sprintf(reg_scratch + length, "R0=%X,", g_cpu[R0]);
+ if (g_cpu[R1] != g_cpu_prev[R1])
+ length += sprintf(reg_scratch + length, "R1=%X,", g_cpu[R1]);
+ if (g_cpu[R2] != g_cpu_prev[R2])
+ length += sprintf(reg_scratch + length, "R2=%X,", g_cpu[R2]);
+ if (g_cpu[R3] != g_cpu_prev[R3])
+ length += sprintf(reg_scratch + length, "R3=%X,", g_cpu[R3]);
+ if (g_cpu[R4] != g_cpu_prev[R4])
+ length += sprintf(reg_scratch + length, "R4=%X,", g_cpu[R4]);
+ if (g_cpu[R5] != g_cpu_prev[R5])
+ length += sprintf(reg_scratch + length, "R5=%X,", g_cpu[R5]);
+ if (g_cpu[R6] != g_cpu_prev[R6])
+ length += sprintf(reg_scratch + length, "R6=%X,", g_cpu[R6]);
+ if (g_cpu[R7] != g_cpu_prev[R7])
+ length += sprintf(reg_scratch + length, "R7=%X,", g_cpu[R7]);
+ if (g_cpu[R8] != g_cpu_prev[R8])
+ length += sprintf(reg_scratch + length, "R8=%X,", g_cpu[R8]);
+ if (g_cpu[R9] != g_cpu_prev[R9])
+ length += sprintf(reg_scratch + length, "R9=%X,", g_cpu[R9]);
+ if (g_cpu[R10] != g_cpu_prev[R10])
+ length += sprintf(reg_scratch + length, "R10=%X,", g_cpu[R10]);
+ if (g_cpu[R11] != g_cpu_prev[R11])
+ length += sprintf(reg_scratch + length, "R11=%X,", g_cpu[R11]);
+ if (g_cpu[R12] != g_cpu_prev[R12])
+ length += sprintf(reg_scratch + length, "R12=%X,", g_cpu[R12]);
+ if (g_cpu[SP] != g_cpu_prev[SP])
+ length += sprintf(reg_scratch + length, "SP=%X,", g_cpu[SP]);
+
+ uint64_t pc = GPOINTER_TO_UINT(udata);
+ length += sprintf(reg_scratch + length, "PC=%lX", pc);
+
+ for (int i = 0; i < g_mem_log_count; i++) {
+ mem_entry* entry = &g_mem_log[i];
+
+ // reconstruct info about the mem access
+ size_t access_size = 1 << (entry->info & 0xF);
+ char rw = qemu_plugin_mem_is_store(entry->info) ? 'w' : 'r';
+
+ length += sprintf(reg_scratch + length, ",m%c=%lX:", rw, entry->virt_addr);
+
+ // fetch the resulting memory
+ unsigned char access_data[16] = {};
+
+ // First way. If it doesn't work, try the second way.
+ // void *host_ptr = qemu_map_ram_ptr(NULL, entry->ram_addr);
+ // memcpy(access_data, host_ptr, access_size);
+
+ // Second way. If it doesn't work, try the third way.
+ // cpu_physical_memory_rw(entry->ram_addr, access_data, sizeof(access_data),
+ // 0);
+
+ // Third way. If it doesn't work, you're out of luck.
+ cpu_memory_rw_debug(
+ qemu_get_cpu(cpu_index), entry->ram_addr, (char*)access_data, access_size, 0);
+
+ for (int j = 0; j < access_size; j++)
+ length += sprintf(reg_scratch + length, "%02X", access_data[j]);
+ }
+
+ fprintf(g_out, "%s\n", reg_scratch);
+
+ reg_scratch[0] = 0;
+ g_mem_log_count = 0;
+
+ memcpy(g_cpu_prev, g_cpu, sizeof(g_cpu_prev));
+}
+
+static void vcpu_mem_access(unsigned int cpu_index,
+ qemu_plugin_meminfo_t mem_info,
+ uint64_t vaddr,
+ void* udata)
+{
+ struct qemu_plugin_hwaddr* hwaddr = qemu_plugin_get_hwaddr(mem_info, vaddr);
+ if (qemu_plugin_hwaddr_is_io(hwaddr))
+ return;
+
+ // uint64_t physaddr = qemu_plugin_hwaddr_phys_addr(hwaddr);
+ uint64_t physaddr = qemu_plugin_hwaddr_device_offset(hwaddr);
+ assert(physaddr < 0xFFFFFFFF);
+
+ mem_entry* entry = &g_mem_log[g_mem_log_count++];
+
+ entry->info = mem_info;
+ entry->virt_addr = vaddr;
+ entry->ram_addr = physaddr;
+}
+
+static void vcpu_tb_trans(qemu_plugin_id_t id, struct qemu_plugin_tb* tb)
+{
+ size_t n = qemu_plugin_tb_n_insns(tb);
+
+ g_cpu = get_cpu_regs();
+
+ for (size_t i = 0; i < n; i++) {
+ struct qemu_plugin_insn* insn = qemu_plugin_tb_get_insn(tb, i);
+ uint64_t vaddr = qemu_plugin_insn_vaddr(insn);
+ qemu_plugin_register_vcpu_insn_exec_cb(
+ insn, vcpu_insn_exec, QEMU_PLUGIN_CB_R_REGS, GUINT_TO_POINTER(vaddr));
+ qemu_plugin_register_vcpu_mem_cb(insn,
+ vcpu_mem_access,
+ QEMU_PLUGIN_CB_R_REGS,
+ QEMU_PLUGIN_MEM_RW,
+ GUINT_TO_POINTER(vaddr));
+ }
+}
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+ const qemu_info_t* info,
+ int argc,
+ char** argv)
+{
+ char* filepath = NULL;
+
+ if (argc)
+ filepath = argv[0];
+ else
+ filepath = (char*)"trace.log";
+
+ printf("Writing Tenet trace to %s\n", filepath);
+ g_out = fopen(filepath, "w");
+
+ memset(g_cpu_prev, 0xFF, sizeof(g_cpu_prev));
+ qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans);
+
+ return 0;
+}
diff --git a/tracers/qemu/README.md b/tracers/qemu/x86-32/README.md
similarity index 100%
rename from tracers/qemu/README.md
rename to tracers/qemu/x86-32/README.md
diff --git a/tracers/qemu/tenet.c b/tracers/qemu/x86-32/tenet.c
similarity index 100%
rename from tracers/qemu/tenet.c
rename to tracers/qemu/x86-32/tenet.c
From d9af21fe8a1cd8676a7daef093db93777589d923 Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Wed, 4 Oct 2023 18:53:59 +0300
Subject: [PATCH 2/8] Fix QT-related issues
---
plugins/tenet/ui/reg_view.py | 35 +++++++++++++++++++-------------
plugins/tenet/util/qt/util.py | 27 ++++++++++++++++--------
plugins/tenet/util/qt/waitbox.py | 33 ++++++++++++------------------
3 files changed, 52 insertions(+), 43 deletions(-)
diff --git a/plugins/tenet/ui/reg_view.py b/plugins/tenet/ui/reg_view.py
index 6f5ce2d..5281d15 100644
--- a/plugins/tenet/ui/reg_view.py
+++ b/plugins/tenet/ui/reg_view.py
@@ -4,6 +4,7 @@
from tenet.util.qt import *
from tenet.integration.api import disassembler
+
class RegisterView(QtWidgets.QWidget):
"""
A container for the the widgets that make up the Registers view.
@@ -33,8 +34,8 @@ def refresh(self):
self.reg_area.refresh()
self.idx_shell.update()
-class TimestampShell(QtWidgets.QWidget):
+class TimestampShell(QtWidgets.QWidget):
def __init__(self, controller, model, parent=None):
super(TimestampShell, self).__init__(parent)
self.model = model
@@ -52,7 +53,7 @@ def _init_ui(self):
# layout
layout = QtWidgets.QHBoxLayout(self)
- #layout.setContentsMargins(5, 0, 5, 0)
+ # layout.setContentsMargins(5, 0, 5, 0)
layout.setContentsMargins(5, 0, 0, 5)
layout.addWidget(self.head)
layout.addWidget(self.shell)
@@ -60,6 +61,7 @@ def _init_ui(self):
def refresh(self):
self.shell.setText(f"{self.model.idx:,}")
+
class TimestampLine(QtWidgets.QLineEdit):
def __init__(self, model, controller, parent=None):
super(TimestampLine, self).__init__(parent)
@@ -81,10 +83,12 @@ def _init_ui(self):
def _evaluate(self):
self.controller.evaluate_expression(self.text())
+
class RegisterArea(QtWidgets.QAbstractScrollArea):
"""
A Qt-based CPU register view.
"""
+
def __init__(self, controller, model, parent=None):
super(RegisterArea, self).__init__(parent)
self.pctx = controller.pctx
@@ -96,7 +100,7 @@ def __init__(self, controller, model, parent=None):
self.setFont(font)
fm = QtGui.QFontMetricsF(font)
- self._char_width = fm.width('9')
+ self._char_width = fm.width("9")
self._char_height = fm.height()
# default to fit roughly 50 printable characters
@@ -119,7 +123,9 @@ def __init__(self, controller, model, parent=None):
def sizeHint(self):
width = self._default_width
- height = (len(self._reg_fields) + 2) * self._char_height # +2 for line break before IP, and after IP
+ height = (
+ len(self._reg_fields) + 2
+ ) * self._char_height # +2 for line break before IP, and after IP
return QtCore.QSize(width, height)
def _init_ctx_menu(self):
@@ -150,8 +156,8 @@ def _init_reg_positions(self):
# compute rects for the average reg labels and values
fm = QtGui.QFontMetricsF(self.font())
- name_size = fm.boundingRect('X'*common_count).size()
- value_size = fm.boundingRect('0' * (self.model.arch.POINTER_SIZE * 2)).size()
+ name_size = fm.boundingRect("X" * common_count).size()
+ value_size = fm.boundingRect("0" * (self.model.arch.POINTER_SIZE * 2)).size()
arrow_size = (int(value_size.height() * 0.70) & 0xFE) + 1
# pre-compute the position of each register in the window
@@ -163,7 +169,7 @@ def _init_reg_positions(self):
y += self._char_height
name_rect = QtCore.QRect(0, 0, name_size.width(), name_size.height())
- name_rect.moveBottomLeft(QtCore.QPoint(name_x, y))
+ name_rect.moveBottomLeft(QtCore.QPoint(int(name_x), int(y)))
prev_rect = QtCore.QRect(0, 0, arrow_size, arrow_size)
next_rect = QtCore.QRect(0, 0, arrow_size, arrow_size)
@@ -175,7 +181,7 @@ def _init_reg_positions(self):
value_x = prev_x + prev_rect.width() + self._char_width
value_rect = QtCore.QRect(0, 0, value_size.width(), value_size.height())
- value_rect.moveBottomLeft(QtCore.QPoint(value_x, y))
+ value_rect.moveBottomLeft(QtCore.QPoint(int(value_x), int(y)))
next_x = value_x + value_size.width() + self._char_width
next_rect.moveCenter(name_rect.center())
@@ -448,7 +454,7 @@ def paintEvent(self, event):
if reg_value is None:
rendered_value = "?" * reg_nibbles
else:
- rendered_value = f'%0{reg_nibbles}X' % reg_value
+ rendered_value = f"%0{reg_nibbles}X" % reg_value
# color register if its value changed as a result of T-1 (previous instr)
if reg_name in self.model.delta_trace:
@@ -518,9 +524,9 @@ def _draw_arrow(self, painter, rect, index):
path.lineTo(top_x, top_y)
# dev / debug helper
- #painter.setPen(QtCore.Qt.green)
- #painter.setBrush(QtGui.QBrush(QtGui.QColor("white")))
- #painter.drawRect(rect)
+ # painter.setPen(QtCore.Qt.green)
+ # painter.setBrush(QtGui.QBrush(QtGui.QColor("white")))
+ # painter.drawRect(rect)
# paint the defined triangle
# TODO: don't hardcode colors
@@ -532,14 +538,15 @@ def _draw_arrow(self, painter, rect, index):
else:
painter.setBrush(self.pctx.palette.arrow_prev)
else:
- painter.setBrush(self.pctx.palette.arrow_idle)
+ painter.setBrush(self.pctx.palette.arrow_idle)
painter.drawPath(path)
+
class RegisterField(object):
def __init__(self, name, name_rect, value_rect, arrow_rects):
self.name = name
self.name_rect = name_rect
self.value_rect = value_rect
self.prev_rect = arrow_rects[0]
- self.next_rect = arrow_rects[1]
\ No newline at end of file
+ self.next_rect = arrow_rects[1]
diff --git a/plugins/tenet/util/qt/util.py b/plugins/tenet/util/qt/util.py
index 758f41a..8f18d10 100644
--- a/plugins/tenet/util/qt/util.py
+++ b/plugins/tenet/util/qt/util.py
@@ -3,9 +3,10 @@
from .shim import *
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Qt Fonts
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def MonospaceFont():
"""
@@ -15,9 +16,11 @@ def MonospaceFont():
font.setStyleHint(QtGui.QFont.TypeWriter)
return font
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Qt Util
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def copy_to_clipboard(data):
"""
@@ -27,6 +30,7 @@ def copy_to_clipboard(data):
cb.clear(mode=cb.Clipboard)
cb.setText(data, mode=cb.Clipboard)
+
def flush_qt_events():
"""
Flush the Qt event pipeline.
@@ -34,6 +38,7 @@ def flush_qt_events():
app = QtCore.QCoreApplication.instance()
app.processEvents()
+
def focus_window():
"""
Lame helper function to help with dev/debug.
@@ -44,6 +49,7 @@ def focus_window():
button = mb.button(QtWidgets.QMessageBox.Ok)
mb.exec_()
+
def get_dpi_scale():
"""
Get a DPI-afflicted value useful for consistent UI scaling.
@@ -55,14 +61,16 @@ def get_dpi_scale():
# xHeight is expected to be 40.0 at normal DPI
return fm.height() / 173.0
+
def normalize_font(font_size):
"""
Normalize the given font size based on the system DPI.
"""
- if sys.platform == "darwin": # macos is lame
+ if sys.platform == "darwin": # macos is lame
return font_size + 2
return font_size
+
def get_qmainwindow():
"""
Get the QMainWindow instance for the current Qt runtime.
@@ -70,6 +78,7 @@ def get_qmainwindow():
app = QtWidgets.QApplication.instance()
return [x for x in app.allWidgets() if x.__class__ is QtWidgets.QMainWindow][0]
+
def compute_color_on_gradient(percent, color1, color2):
"""
Compute the color specified by a percent between two colors.
@@ -78,9 +87,9 @@ def compute_color_on_gradient(percent, color1, color2):
r2, g2, b2, _ = color2.getRgb()
# compute the new color across the gradient of color1 -> color 2
- r = r1 + percent * (r2 - r1)
- g = g1 + percent * (g2 - g1)
- b = b1 + percent * (b2 - b1)
+ r = r1 + int(percent * (r2 - r1))
+ g = g1 + int(percent * (g2 - g1))
+ b = b1 + int(percent * (b2 - b1))
# return the new color
- return QtGui.QColor(r,g,b)
\ No newline at end of file
+ return QtGui.QColor(r, g, b)
diff --git a/plugins/tenet/util/qt/waitbox.py b/plugins/tenet/util/qt/waitbox.py
index c0b05f9..5b689c0 100644
--- a/plugins/tenet/util/qt/waitbox.py
+++ b/plugins/tenet/util/qt/waitbox.py
@@ -2,11 +2,13 @@
from .util import get_dpi_scale
import logging
+
logger = logging.getLogger("Tenet.Qt.WaitBox")
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
# Qt WaitBox
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
+
class WaitBox(QtWidgets.QDialog):
"""
@@ -41,28 +43,22 @@ def show(self, modal=True):
qta = QtCore.QCoreApplication.instance()
qta.processEvents()
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Initialization - UI
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _ui_init(self):
"""
Initialize UI elements.
"""
- self.setWindowFlags(
- self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint
- )
- self.setWindowFlags(
- self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint
- )
- self.setWindowFlags(
- self.windowFlags() & ~QtCore.Qt.WindowCloseButtonHint
- )
+ self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
+ self.setWindowFlags(self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint)
+ self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowCloseButtonHint)
# configure the main widget / form
self.setSizeGripEnabled(False)
self.setModal(True)
- self._dpi_scale = get_dpi_scale()*5.0
+ self._dpi_scale = get_dpi_scale() * 5
# initialize abort button
self._abort_button = QtWidgets.QPushButton("Cancel")
@@ -86,12 +82,9 @@ def _ui_layout(self):
self._abort_button.clicked.connect(self._abort)
v_layout.addWidget(self._abort_button)
- v_layout.setSpacing(self._dpi_scale*3)
+ v_layout.setSpacing(self._dpi_scale * 3)
v_layout.setContentsMargins(
- self._dpi_scale*5,
- self._dpi_scale,
- self._dpi_scale*5,
- self._dpi_scale
+ self._dpi_scale * 5, self._dpi_scale, self._dpi_scale * 5, self._dpi_scale
)
# scale widget dimensions based on DPI
@@ -99,4 +92,4 @@ def _ui_layout(self):
self.setMinimumHeight(height)
# compute the dialog layout
- self.setLayout(v_layout)
\ No newline at end of file
+ self.setLayout(v_layout)
From f130d25f285dda245add35f8ddab9e5fd01aa25d Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Wed, 4 Oct 2023 19:24:50 +0300
Subject: [PATCH 3/8] Plugin: Added basic ARM32 support
---
plugins/tenet/context.py | 99 +++--
plugins/tenet/integration/api/ida_api.py | 87 ++--
plugins/tenet/trace/arch/__init__.py | 3 +-
plugins/tenet/trace/arch/arm32.py | 30 ++
plugins/tenet/trace/file.py | 527 +++++++++++++----------
plugins/tenet/util/qt/util.py | 2 +-
tracers/qemu/arm32/tenet.c | 21 +-
7 files changed, 451 insertions(+), 318 deletions(-)
create mode 100644 plugins/tenet/trace/arch/arm32.py
diff --git a/plugins/tenet/context.py b/plugins/tenet/context.py
index 340e5bd..12cf1fc 100644
--- a/plugins/tenet/context.py
+++ b/plugins/tenet/context.py
@@ -13,15 +13,15 @@
from tenet.ui.trace_view import TraceDock
from tenet.types import BreakpointType
-from tenet.trace.arch import ArchAMD64, ArchX86
+from tenet.trace.arch import ArchAMD64, ArchX86, ArchArm32
from tenet.trace.reader import TraceReader
from tenet.integration.api import disassembler, DisassemblerContextAPI
logger = logging.getLogger("Tenet.Context")
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# context.py -- Plugin Database Context
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# The purpose of this file is to house and manage the plugin's
# disassembler database (eg, IDB/BNDB) specific runtime state.
@@ -29,7 +29,7 @@
# At a high level, a unique 'instance' of the plugin runtime & subsystems
# are initialized for each opened database in supported disassemblers. The
# plugin context object acts a bit like the database specific plugin core.
-#
+#
# For example, it is possible for multiple databases to be open at once
# in the Binary Ninja disassembler. Each opened database will have a
# unique plugin context object created and used to manage state, UI,
@@ -40,6 +40,7 @@
# not change how this context system works under the hood.
#
+
class TenetContext(object):
"""
A per-database encapsulation of the plugin components / state.
@@ -50,12 +51,23 @@ def __init__(self, core, db):
self.core = core
self.db = db
- # select a trace arch based on the binary the disassmbler has loaded
- if disassembler[self].is_64bit():
- self.arch = ArchAMD64()
+ # select a trace arch based on the binary the disassembler has loaded
+
+ if disassembler[self].get_processor_type().lower() == "arm":
+ if disassembler[self].is_64bit():
+ raise NotImplementedError("ARM64 is not yet supported")
+ else:
+ self.arch = ArchArm32()
+ elif disassembler[self].get_processor_type().lower() == "metapc":
+ if disassembler[self].is_64bit():
+ self.arch = ArchAMD64()
+ else:
+ self.arch = ArchX86()
else:
- self.arch = ArchX86()
-
+ raise NotImplementedError(
+ "Unsupported disassembler processor type, supported: arm, metapc (x86/x64)"
+ )
+
# this will hold the trace reader when a trace has been loaded
self.reader = None
@@ -68,7 +80,7 @@ def __init__(self, core, db):
# the directory to start the 'load trace file' dialog in
self._last_directory = None
-
+
# whether the plugin subsystems have been created / started
self._started = False
@@ -79,13 +91,14 @@ def __init__(self, core, db):
def _auto_launch(self):
"""
Automatically load a static trace file when the database has been opened.
-
+
NOTE/DEV: this is just to make it easier to test / develop / debug the
plugin when developing it and should not be called under normal use.
"""
def test_load():
import ida_loader
+
trace_filepath = ida_loader.get_plugin_options("Tenet")
focus_window()
self.load_trace(trace_filepath)
@@ -93,21 +106,21 @@ def test_load():
def dev_launch():
self._timer = QtCore.QTimer()
- self._timer.singleShot(500, test_load) # delay to let things settle
+ self._timer.singleShot(500, test_load) # delay to let things settle
self.core._ui_hooks.ready_to_run = dev_launch
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Properties
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
@property
def palette(self):
return self.core.palette
-
- #-------------------------------------------------------------------------
+
+ # -------------------------------------------------------------------------
# Setup / Teardown
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def start(self):
"""
@@ -129,10 +142,10 @@ def terminate(self):
This will be called when the database or disassembler is closing.
"""
self.close_trace()
-
- #-------------------------------------------------------------------------
+
+ # -------------------------------------------------------------------------
# Public API
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def trace_loaded(self):
"""
@@ -162,7 +175,9 @@ def load_trace(self, filepath):
if self.reader.analysis.slide != None:
pmsg(f"- {self.reader.analysis.slide:08X} ASLR slide...")
else:
- disassembler.warning("Failed to automatically detect ASLR base!\n\nSee console for more info...")
+ disassembler.warning(
+ "Failed to automatically detect ASLR base!\n\nSee console for more info..."
+ )
pmsg(" +------------------------------------------------------")
pmsg(" |- ERROR: Failed to detect ASLR base for this trace.")
pmsg(" | --------------------------------------- ")
@@ -226,7 +241,7 @@ def close_trace(self):
# misc / final cleanup
self.breakpoints.reset()
- #self.reader.close()
+ # self.reader.close()
self.reader = None
@@ -239,19 +254,20 @@ def show_ui(self):
matter much right now but this should be moved in the future.
"""
import ida_kernwin
+
self.registers.show(position=ida_kernwin.DP_RIGHT)
- #self.breakpoints.dockable.set_dock_position("CPU Registers", ida_kernwin.DP_BOTTOM)
- #self.breakpoints.dockable.show()
+ # self.breakpoints.dockable.set_dock_position("CPU Registers", ida_kernwin.DP_BOTTOM)
+ # self.breakpoints.dockable.show()
- #ida_kernwin.activate_widget(ida_kernwin.find_widget("Output window"), True)
- #ida_kernwin.set_dock_pos("Output window", None, ida_kernwin.DP_BOTTOM)
- #ida_kernwin.set_dock_pos("IPython Console", "Output", ida_kernwin.DP_INSIDE)
+ # ida_kernwin.activate_widget(ida_kernwin.find_widget("Output window"), True)
+ # ida_kernwin.set_dock_pos("Output window", None, ida_kernwin.DP_BOTTOM)
+ # ida_kernwin.set_dock_pos("IPython Console", "Output", ida_kernwin.DP_INSIDE)
- #self.memory.dockable.set_dock_position("Output window", ida_kernwin.DP_TAB | ida_kernwin.DP_BEFORE)
+ # self.memory.dockable.set_dock_position("Output window", ida_kernwin.DP_TAB | ida_kernwin.DP_BEFORE)
self.memory.show("Output window", ida_kernwin.DP_TAB | ida_kernwin.DP_BEFORE)
- #self.stack.dockable.set_dock_position("Memory View", ida_kernwin.DP_RIGHT)
+ # self.stack.dockable.set_dock_position("Memory View", ida_kernwin.DP_RIGHT)
self.stack.show("Memory View", ida_kernwin.DP_RIGHT)
mw = get_qmainwindow()
@@ -260,10 +276,10 @@ def show_ui(self):
# trigger update check
self.core.check_for_update()
-
- #-------------------------------------------------------------------------
+
+ # -------------------------------------------------------------------------
# Integrated UI Event Handlers
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def interactive_load_trace(self, reloading=False):
"""
@@ -293,9 +309,9 @@ def interactive_load_trace(self, reloading=False):
#
# if we are 're-loading', we are loading over an existing trace, so
# there should already be plugin UI elements visible and active.
- #
+ #
# do not attempt to show / re-position the UI elements as they may
- # have been moved by the user from their default positions into
+ # have been moved by the user from their default positions into
# locations that they prefer
#
@@ -304,7 +320,7 @@ def interactive_load_trace(self, reloading=False):
# show the plugin UI elements, and dock its windows as appropriate
self.show_ui()
-
+
def interactive_next_execution(self):
"""
Handle UI actions for seeking to the next execution of the selected address.
@@ -357,7 +373,7 @@ def _idx_changed(self, idx):
"""
Handle a trace reader event indicating that the current IDX has changed.
- This will make the disassembler track with the PC/IP of the trace reader.
+ This will make the disassembler track with the PC/IP of the trace reader.
"""
dctx = disassembler[self]
@@ -378,7 +394,7 @@ def _idx_changed(self, idx):
if not dctx.is_mapped(bin_address):
last_good_idx = self.reader.analysis.get_prev_mapped_idx(idx)
if last_good_idx == -1:
- return # navigation is just not gonna happen...
+ return # navigation is just not gonna happen...
# fetch the last instruction pointer to fall within the trace
last_good_trace_address = self.reader.get_ip(last_good_idx)
@@ -393,7 +409,7 @@ def _idx_changed(self, idx):
def _select_trace_file(self):
"""
Prompt a file selection dialog, returning file selections.
-
+
This will save & reuses the last known directory for subsequent calls.
"""
@@ -402,10 +418,7 @@ def _select_trace_file(self):
# create & configure a Qt File Dialog for immediate use
file_dialog = QtWidgets.QFileDialog(
- None,
- 'Open trace file',
- self._last_directory,
- 'All Files (*.*)'
+ None, "Open trace file", self._last_directory, "All Files (*.*)"
)
file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
@@ -426,4 +439,4 @@ def _select_trace_file(self):
logger.debug(" - %s" % name)
# return the captured filenames
- return filenames
\ No newline at end of file
+ return filenames
diff --git a/plugins/tenet/integration/api/ida_api.py b/plugins/tenet/integration/api/ida_api.py
index 2a62187..a85cd52 100644
--- a/plugins/tenet/integration/api/ida_api.py
+++ b/plugins/tenet/integration/api/ida_api.py
@@ -29,9 +29,10 @@
logger = logging.getLogger("Tenet.API.IDA")
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Utils
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def execute_sync(function, sync_type):
"""
@@ -61,11 +62,14 @@ def thunk():
# return the output of the synchronized execution
return output[0]
+
return wrapper
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Disassembler Core API (universal)
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
class IDACoreAPI(DisassemblerCoreAPI):
NAME = "IDA"
@@ -86,17 +90,17 @@ def _init_version(self):
self._version_minor = minor
self._version_patch = 0
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Properties
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@property
def headless(self):
return ida_kernwin.cvar.batch
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Synchronization Decorators
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@staticmethod
def execute_read(function):
@@ -110,9 +114,9 @@ def execute_write(function):
def execute_ui(function):
return execute_sync(function, ida_kernwin.MFF_FAST)
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# API Shims
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def get_disassembler_user_directory(self):
return ida_diskio.get_user_idadir()
@@ -134,11 +138,11 @@ def get_disassembly_background_color(self):
viewer_widget = ida_kernwin.PluginForm.TWidgetToPyQtWidget(viewer_twidget)
# fetch the background color property
- #viewer.Show() # TODO: re-enable!
+ # viewer.Show() # TODO: re-enable!
color = viewer_widget.property("line_bg_default")
# destroy the view as we no longer need it
- #viewer.Close()
+ # viewer.Close()
# return the color
return color
@@ -154,9 +158,9 @@ def warning(self, text):
def message(self, message):
print(message)
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# UI API Shims
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def create_dockable(self, window_title, widget):
@@ -171,35 +175,35 @@ def create_dockable(self, window_title, widget):
# return the dockable QtWidget / container
return dockable
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Disassembler Context API (database-specific)
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
-class IDAContextAPI(DisassemblerContextAPI):
+class IDAContextAPI(DisassemblerContextAPI):
def __init__(self, dctx):
super(IDAContextAPI, self).__init__(dctx)
@property
def busy(self):
- return not(ida_auto.auto_is_ok())
+ return not (ida_auto.auto_is_ok())
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# API Shims
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@IDACoreAPI.execute_read
def get_current_address(self):
return ida_kernwin.get_screen_ea()
- def get_processor_type(self):
- ## get the target arch, PLFM_386, PLFM_ARM, etc # TODO
- #arch = idaapi.ph_get_id()
- pass
+ def get_processor_type(self) -> str:
+ inf = ida_idaapi.get_inf_structure()
+ return inf.procname
def is_64bit(self):
inf = ida_idaapi.get_inf_structure()
- #target_filetype = inf.filetype
+ # target_filetype = inf.filetype
return inf.is_64bit()
def is_call_insn(self, address):
@@ -231,7 +235,7 @@ def get_instruction_addresses(self):
instruction_addresses.append(current_address)
# print(f"Seg {seg.start_ea:08X} --> {seg.end_ea:08X} CODE")
- #print(f" -- {len(instruction_addresses):,} instructions found")
+ # print(f" -- {len(instruction_addresses):,} instructions found")
return instruction_addresses
@@ -298,7 +302,9 @@ def navigate(self, address):
CENTER_AROUND_LINE_INDEX = 20
if widget:
- return ida_kernwin.ea_viewer_history_push_and_jump(widget, address, 0, CENTER_AROUND_LINE_INDEX, 0)
+ return ida_kernwin.ea_viewer_history_push_and_jump(
+ widget, address, 0, CENTER_AROUND_LINE_INDEX, 0
+ )
# ehh, whatever.. just let IDA navigate to yolo
else:
@@ -309,16 +315,18 @@ def navigate_to_function(self, function_address, address):
def set_function_name_at(self, function_address, new_name):
ida_name.set_name(function_address, new_name, ida_name.SN_NOWARN)
-
+
def set_breakpoint(self, address):
ida_dbg.add_bpt(address)
def delete_breakpoint(self, address):
ida_dbg.del_bpt(address)
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# HexRays Util
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def hexrays_available():
"""
@@ -326,10 +334,12 @@ def hexrays_available():
"""
try:
import ida_hexrays
+
return ida_hexrays.init_hexrays_plugin()
except ImportError:
return False
+
def map_line2citem(decompilation_text):
"""
Map decompilation line numbers to citems.
@@ -362,6 +372,7 @@ def map_line2citem(decompilation_text):
return line2citem
+
def map_line2node(cfunc, metadata, line2citem):
"""
Map decompilation line numbers to node (basic blocks) addresses.
@@ -415,7 +426,7 @@ def map_line2node(cfunc, metadata, line2citem):
# address not mapped to a node... weird. continue to the next citem
if not node:
- #logger.warning("Failed to map node to basic block")
+ # logger.warning("Failed to map node to basic block")
continue
#
@@ -436,6 +447,7 @@ def map_line2node(cfunc, metadata, line2citem):
# all done, return the computed map
return line2node
+
def lex_citem_indexes(line):
"""
Lex all ctree item indexes from a given line of text.
@@ -472,7 +484,7 @@ def lex_citem_indexes(line):
# in this context, it is actually the index number of a citem
#
- citem_index = int(line[i:i+idaapi.COLOR_ADDR_SIZE], 16)
+ citem_index = int(line[i : i + idaapi.COLOR_ADDR_SIZE], 16)
i += idaapi.COLOR_ADDR_SIZE
# save the extracted citem index
@@ -487,8 +499,8 @@ def lex_citem_indexes(line):
# return all the citem indexes extracted from this line of text
return indexes
-class DockableWindow(ida_kernwin.PluginForm):
+class DockableWindow(ida_kernwin.PluginForm):
def __init__(self, title, widget):
super(DockableWindow, self).__init__()
self.title = title
@@ -502,7 +514,7 @@ def __init__(self, title, widget):
self.__dock_filter = IDADockSizeHack()
def OnCreate(self, form):
- #print("Creating", self.title)
+ # print("Creating", self.title)
self.parent = self.FormToPyQtWidget(form)
layout = QtWidgets.QVBoxLayout()
@@ -515,7 +527,7 @@ def OnCreate(self, form):
def OnClose(self, foo):
self.visible = False
- #print("Closing", self.title)
+ # print("Closing", self.title)
def __dock_size_hack(self):
if self.widget.minimumWidth() == 0:
@@ -531,7 +543,7 @@ def show(self):
if ida_pro.IDA_SDK_VERSION < 760:
WOPN_SZHINT = 0x200
-
+
# create the dockable widget, without actually showing it
self.Show(self.title, options=ida_kernwin.PluginForm.WOPN_CREATE_ONLY)
@@ -566,10 +578,11 @@ def copy_dock_position(self, other):
self._dock_target = other._dock_target
self._dock_position = other._dock_position
+
class IDADockSizeHack(QtCore.QObject):
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.WindowActivate:
obj.setMinimumWidth(obj.min_width)
obj.setMaximumWidth(obj.max_width)
obj.removeEventFilter(self)
- return False
\ No newline at end of file
+ return False
diff --git a/plugins/tenet/trace/arch/__init__.py b/plugins/tenet/trace/arch/__init__.py
index 791fd3d..410501a 100644
--- a/plugins/tenet/trace/arch/__init__.py
+++ b/plugins/tenet/trace/arch/__init__.py
@@ -1,2 +1,3 @@
from .x86 import ArchX86
-from .amd64 import ArchAMD64
\ No newline at end of file
+from .amd64 import ArchAMD64
+from .arm32 import ArchArm32
diff --git a/plugins/tenet/trace/arch/arm32.py b/plugins/tenet/trace/arch/arm32.py
new file mode 100644
index 0000000..b69e201
--- /dev/null
+++ b/plugins/tenet/trace/arch/arm32.py
@@ -0,0 +1,30 @@
+class ArchArm32:
+ """
+ Arm32 CPU Architecture Definition.
+ """
+
+ MAGIC = 0x13371337
+
+ POINTER_SIZE = 4
+
+ IP = "PC"
+ SP = "SP"
+
+ REGISTERS = [
+ "R0",
+ "R1",
+ "R2",
+ "R3",
+ "R4",
+ "R5",
+ "R6",
+ "R7",
+ "R8",
+ "R9",
+ "R10",
+ "R11",
+ "R12",
+ "SP",
+ "LR",
+ "PC",
+ ]
diff --git a/plugins/tenet/trace/file.py b/plugins/tenet/trace/file.py
index 5ee49c9..29b330f 100644
--- a/plugins/tenet/trace/file.py
+++ b/plugins/tenet/trace/file.py
@@ -10,9 +10,9 @@
import itertools
import collections
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# file.py -- Trace File
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
#
# NOTE/PREFACE: Please be aware, this is a 100% prototype implementation
# of a basic trace log file specification. It has not been designed with
@@ -20,7 +20,7 @@
# exceed the recommended 'maximum' of 10,000,000 (10m) instructions.
#
# There are no dependencies. There is no multiprocessing. This is will
-# be a nightmare to maintain or scale further. It is 100% meant to be
+# be a nightmare to maintain or scale further. It is 100% meant to be
# thrown away in favor of a native backend.
#
# --------------
@@ -37,16 +37,16 @@
# Upon completion, the indexed+compressed trace file is saved to disk
# alongside the original trace, with the '.tt' (Tenet Trace) file
# extension. This original trace can be discarded by the user.
-#
+#
# The processed trace can be loaded and used in a fraction of the time
# versus the raw text trace. The trace file implementation will also seek
# out a matching file name with the '.tt' file extension, and prioritize
# loading that over a raw text trace.
#
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# Imports
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
#
# attempt plugin imports, assuming this file is being run / loaded in
@@ -55,7 +55,7 @@
try:
from tenet.util.log import pmsg
- from tenet.trace.arch import ArchAMD64, ArchX86
+ from tenet.trace.arch import ArchAMD64, ArchX86, ArchArm32
from tenet.trace.types import TraceMemory
#
@@ -65,53 +65,56 @@
#
except ImportError:
- from arch import ArchAMD64, ArchX86
- from .types import TraceMemory
+ from arch import ArchAMD64, ArchX86, ArchArm32
+ from .types import TraceMemory
+
pmsg = print
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# Definitions
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
-BYTE_MAX = (1 << 8) - 1
-USHRT_MAX = (1 << 16) - 1
-UINT_MAX = (1 << 32) - 1
+BYTE_MAX = (1 << 8) - 1
+USHRT_MAX = (1 << 16) - 1
+UINT_MAX = (1 << 32) - 1
ULLONG_MAX = (1 << 64) - 1
TRACE_MEM_READ = 0
TRACE_MEM_WRITE = 1
#
-# NOTE: some of this stuff is probably broken / cannot be easily toggled
+# NOTE: some of this stuff is probably broken / cannot be easily toggled
# anymore, so I wouldn't actually suggest playing around with them as things
# will probably break or behave erratically
#
TRACE_STATS = False
-#DEFAULT_COMPRESSION = zipfile.ZIP_BZIP2
-#DEFAULT_COMPRESSION = zipfile.ZIP_LZMA
+# DEFAULT_COMPRESSION = zipfile.ZIP_BZIP2
+# DEFAULT_COMPRESSION = zipfile.ZIP_LZMA
DEFAULT_COMPRESSION = zipfile.ZIP_DEFLATED
-#DEFAULT_SEGMENT_LENGTH = 250_000
-#DEFAULT_SEGMENT_LENGTH = 1_000_000
+# DEFAULT_SEGMENT_LENGTH = 250_000
+# DEFAULT_SEGMENT_LENGTH = 1_000_000
DEFAULT_SEGMENT_LENGTH = USHRT_MAX
REG_OFFSET_CACHE_SIZE = 16
REG_OFFSET_CACHE_INTERVAL = 4096
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# Utils
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
def hash_file(filepath):
"""
Return a CRC32 of the file at the given path.
"""
crc = 0
- with open(filepath, 'rb', 65536) as ins:
+ with open(filepath, "rb", 65536) as ins:
for x in range(int((os.stat(filepath).st_size / 65536)) + 1):
crc = zlib.crc32(ins.read(65536), crc)
- return (crc & 0xFFFFFFFF)
+ return crc & 0xFFFFFFFF
+
def number_of_bits_set(i):
"""
@@ -119,97 +122,99 @@ def number_of_bits_set(i):
"""
i = i - ((i >> 1) & 0x55555555)
i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
- return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xffffffff) >> 24
+ return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xFFFFFFFF) >> 24
+
def width_from_type(t):
"""
Return the byte width of a python 'struct' type definition.
"""
- if t == 'B':
+ if t == "B":
return 1
- elif t == 'H':
+ elif t == "H":
return 2
- elif t == 'I':
+ elif t == "I":
return 4
- elif t == 'Q':
+ elif t == "Q":
return 8
raise ValueError(f"Invalid type '{t}'")
+
def type_from_width(width):
"""
Return an appropriate integer type for the given byte width.
"""
if width == 1:
- return 'B'
+ return "B"
elif width == 2:
- return 'H'
+ return "H"
elif width == 4:
- return 'I'
+ return "I"
elif width == 8:
- return 'Q'
+ return "Q"
raise ValueError(f"Invalid type width {width}")
+
def type_from_limit(limit):
"""
Return an appropriate integer type for the maximum given value.
"""
if limit <= BYTE_MAX:
- return 'B'
+ return "B"
elif limit <= USHRT_MAX:
- return 'H'
+ return "H"
elif limit <= UINT_MAX:
- return 'I'
+ return "I"
elif limit <= ULLONG_MAX:
- return 'Q'
+ return "Q"
raise ValueError(f"Limit {limit:,} exceeds maximum type")
-#-----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
# Serialization Structures
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
class TraceInfo(ctypes.Structure):
- _pack_ = 1
+ _pack_ = 1
_fields_ = [
- ('arch_magic', ctypes.c_uint32),
- ('ip_num', ctypes.c_uint32),
- ('mem_addrs_num', ctypes.c_uint32),
- ('mask_num', ctypes.c_uint32),
- ('mem_idx_width', ctypes.c_uint8),
- ('mem_addr_width', ctypes.c_uint8),
- ('original_hash', ctypes.c_uint32),
+ ("arch_magic", ctypes.c_uint32),
+ ("ip_num", ctypes.c_uint32),
+ ("mem_addrs_num", ctypes.c_uint32),
+ ("mask_num", ctypes.c_uint32),
+ ("mem_idx_width", ctypes.c_uint8),
+ ("mem_addr_width", ctypes.c_uint8),
+ ("original_hash", ctypes.c_uint32),
]
+
class SegmentInfo(ctypes.Structure):
- _pack_ = 1
+ _pack_ = 1
_fields_ = [
- ('id', ctypes.c_uint32),
- ('base_idx', ctypes.c_uint32),
- ('length', ctypes.c_uint32),
-
- ('ip_num', ctypes.c_uint32),
- ('ip_length', ctypes.c_uint32),
-
- ('reg_mask_num', ctypes.c_uint32),
- ('reg_mask_length', ctypes.c_uint32),
- ('reg_data_length', ctypes.c_uint32),
-
- ('mem_read_num', ctypes.c_uint32),
- ('mem_read_data_length', ctypes.c_uint32),
-
- ('mem_write_num', ctypes.c_uint32),
- ('mem_write_data_length', ctypes.c_uint32),
+ ("id", ctypes.c_uint32),
+ ("base_idx", ctypes.c_uint32),
+ ("length", ctypes.c_uint32),
+ ("ip_num", ctypes.c_uint32),
+ ("ip_length", ctypes.c_uint32),
+ ("reg_mask_num", ctypes.c_uint32),
+ ("reg_mask_length", ctypes.c_uint32),
+ ("reg_data_length", ctypes.c_uint32),
+ ("mem_read_num", ctypes.c_uint32),
+ ("mem_read_data_length", ctypes.c_uint32),
+ ("mem_write_num", ctypes.c_uint32),
+ ("mem_write_data_length", ctypes.c_uint32),
]
+
class MemValue(ctypes.Structure):
_pack_ = 1
- _fields_ = [
- ('mask', ctypes.c_uint8),
- ('value', ctypes.c_uint8 * 8)
- ]
+ _fields_ = [("mask", ctypes.c_uint8), ("value", ctypes.c_uint8 * 8)]
+
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# Trace File
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
class TraceFile(object):
"""
@@ -226,7 +231,7 @@ def __init__(self, filepath, arch=None):
#
if not self.arch:
- self.arch = ArchAMD64()
+ raise ValueError("TraceFile requires an arch")
# a sorted array of all unique PC / IP (eg, EIP, or RIP) that appear in the trace
self.ip_addrs = None
@@ -240,15 +245,15 @@ def __init__(self, filepath, arch=None):
# where each bit specifies if that memory address was accessed over the
# course of the entire trace
#
- # e.g:
+ # e.g:
# mem_addrs[924] = 0x401448 (an 8-byte aligned memory address)
# mem_masks[924] = 0x0F (a 'mask' of what bytes exist in the trace)
# |
# |_ a bit mask of 00001111
- #
+ #
# In this example, we know that 0x401448 --> 0x40144C were either read
# or written at some point in this trace.
- #
+ #
# The alignment of pointers helps with basic id-based compression as
# these pointer id / 'mapped addresses' are used across the segments.
#
@@ -275,8 +280,8 @@ def __init__(self, filepath, arch=None):
# into this table of unique register masks (self.masks)
#
- self.masks = [] # TODO: rename to register_masks or something...
-
+ self.masks = [] # TODO: rename to register_masks or something...
+
# an O(1) lookup table for the 'byte size' of each register mask
self.mask_sizes = []
@@ -285,7 +290,7 @@ def __init__(self, filepath, arch=None):
# theses segments will have small indexes / summaries embedded in
# them to make them easier to search or ignore as applicable
#
- # for more information, look at the TraceSegments class
+ # for more information, look at the TraceSegments class
#
self.segments = []
@@ -303,9 +308,9 @@ def __init__(self, filepath, arch=None):
self._load_trace()
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Properties
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
@property
def name(self):
@@ -339,10 +344,10 @@ def length(self):
return 0
return self.segments[-1].base_idx + self.segments[-1].length
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Public
- #-------------------------------------------------------------------------
-
+ # -------------------------------------------------------------------------
+
#
# I should really define this somewhere more notable... but throughout
# this project you will see the term 'idx', this is a simple abbreviation
@@ -398,24 +403,26 @@ def get_reg_mask_ids_containing(self, reg_name):
"""
reg_id = self.arch.REGISTERS.index(reg_name.upper())
reg_mask = 1 << reg_id
-
+
found = set()
for i, current_mask in enumerate(self.masks):
if current_mask & reg_mask:
found.add(i)
-
+
return found
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Save / Serialization
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _save(self):
"""
Save the packed trace to disk.
"""
- with zipfile.ZipFile(self.packed_filepath, 'w', compression=DEFAULT_COMPRESSION) as zip_archive:
+ with zipfile.ZipFile(
+ self.packed_filepath, "w", compression=DEFAULT_COMPRESSION
+ ) as zip_archive:
self._save_header(zip_archive)
self._save_segments(zip_archive)
@@ -427,7 +434,7 @@ def _save_header(self, zip_archive):
"""
# populate the trace header
- header = TraceInfo()
+ header = TraceInfo()
header.arch_magic = self.arch.MAGIC
header.ip_num = len(self.ip_addrs)
header.mem_addrs_num = len(self.mem_addrs)
@@ -438,7 +445,7 @@ def _save_header(self, zip_archive):
mask_data = (ctypes.c_uint32 * len(self.masks))(*self.masks)
# save the global trace data / header to the zip
- with zip_archive.open('header', 'w') as f:
+ with zip_archive.open("header", "w") as f:
f.write(bytearray(header))
f.write(bytearray(self.ip_addrs))
f.write(bytearray(self.mem_addrs))
@@ -450,12 +457,12 @@ def _save_segments(self, zip_archive):
Save the trace segments to the packed trace.
"""
for segment in self.segments:
- with zip_archive.open(f'segments/{segment.id}', 'w') as f:
+ with zip_archive.open(f"segments/{segment.id}", "w") as f:
segment.dump(f)
-
- #-------------------------------------------------------------------------
+
+ # -------------------------------------------------------------------------
# Load / Deserialization
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _load_trace(self):
"""
@@ -500,7 +507,7 @@ def _load_packed_trace(self, filepath):
Load a packed trace from disk.
"""
- with zipfile.ZipFile(filepath, 'r') as zip_archive:
+ with zipfile.ZipFile(filepath, "r") as zip_archive:
self._load_header(zip_archive)
self._load_segments(zip_archive)
@@ -512,16 +519,20 @@ def _select_arch(self, magic):
"""
if ArchAMD64.MAGIC == magic:
self.arch = ArchAMD64()
- else:
+ elif ArchArm32.MAGIC == magic:
+ self.arch = ArchArm32()
+ elif ArchX86.MAGIC == magic:
self.arch = ArchX86()
+ raise ValueError(f"Invalid arch magic 0x{magic:08X}")
+
def _fetch_hash(self, filepath):
"""
Return the original file hash (CRC32) from the given packed trace filepath.
"""
header = TraceInfo()
- with zipfile.ZipFile(filepath, 'r') as zip_archive:
- with zip_archive.open('header', 'r') as f:
+ with zipfile.ZipFile(filepath, "r") as zip_archive:
+ with zip_archive.open("header", "r") as f:
f.readinto(header)
return header.original_hash
@@ -529,15 +540,15 @@ def _load_header(self, zip_archive):
"""
Load the trace header from a packed trace.
"""
- header = TraceInfo()
+ header = TraceInfo()
- with zip_archive.open('header', 'r') as f:
+ with zip_archive.open("header", "r") as f:
# read the main trace info from the packed trace header
f.readinto(header)
# select the cpu / arch for this trace
- #print(f"Loading magic 0x{header.arch_magic:08X}")
+ # print(f"Loading magic 0x{header.arch_magic:08X}")
self._select_arch(header.arch_magic)
# load the (sorted) ip address table from disk
@@ -548,18 +559,20 @@ def _load_header(self, zip_archive):
# ('mem_addr_width', ctypes.c_uint8),
self.mem_idx_type = type_from_width(header.mem_idx_width)
self.mem_addr_type = type_from_width(header.mem_addr_width)
- #self.mem_mask_width = type_from_width(header.mem_mask_width)
+ # self.mem_mask_width = type_from_width(header.mem_mask_width)
# load the (sorted, aligned) mem table from disk
self.mem_addrs = array.array(type_from_width(self.arch.POINTER_SIZE))
self.mem_addrs.fromfile(f, header.mem_addrs_num)
- self.mem_masks = array.array('B')
+ self.mem_masks = array.array("B")
self.mem_masks.fromfile(f, header.mem_addrs_num)
# ('mask_num', ctypes.c_uint32),
- self.masks = array.array('I')
+ self.masks = array.array("I")
self.masks.fromfile(f, header.mask_num)
- self.mask_sizes = [number_of_bits_set(mask) * self.arch.POINTER_SIZE for mask in self.masks]
+ self.mask_sizes = [
+ number_of_bits_set(mask) * self.arch.POINTER_SIZE for mask in self.masks
+ ]
# source file hash
self.original_hash = header.original_hash
@@ -572,11 +585,11 @@ def _load_segments(self, zip_archive):
for path in zip_archive.namelist():
# skip anything that is not a trace segment
- if not (path.startswith('segments/') and path[-1] != '/'):
+ if not (path.startswith("segments/") and path[-1] != "/"):
continue
# load a trace segment from the packed trace file
- with zip_archive.open(path, 'r') as f:
+ with zip_archive.open(path, "r") as f:
segment = TraceSegment(self)
segment.from_file(f)
@@ -591,7 +604,7 @@ def _load_text_trace(self, filepath):
Load a text trace from disk.
"""
idx = 0
-
+
# mappings of address/mask and their mapped (compressed) id
# - NOTE: these are only used when converting traces from text to binary
self.ip_map = collections.OrderedDict()
@@ -600,14 +613,14 @@ def _load_text_trace(self, filepath):
self.masks = []
# TODO: detect arch based on reg / lines in file
- #if not self.arch:
+ # if not self.arch:
# self._select_arch(0)
# hash (CRC32) the source / text filepath before loading it
self.original_hash = hash_file(filepath)
# load / parse a text trace into trace segments
- with open(filepath, 'r') as f:
+ with open(filepath, "r") as f:
# loop until all of the lines in the file have been processed
while True:
@@ -627,7 +640,7 @@ def _load_text_trace(self, filepath):
# save the segment
self.segments.append(segment)
- #break # for debugging...
+ # break # for debugging...
self._finalize()
self._save()
@@ -658,7 +671,7 @@ def get_mapped_ip(self, ip):
#
# TODO: note, uh.. these should all be refactored... gross
#
-
+
def get_aligned_address(self, address):
return (address >> 3) << 3
@@ -668,11 +681,11 @@ def get_mapped_address(self, address):
"""
#
- # TODO: use pointer size/alignment?? eg, this might make mem lookups faster
+ # TODO: use pointer size/alignment?? eg, this might make mem lookups faster
# if we tune it to 32bit vs 64bit (at the cost of possible trace size inflation)
#
- aligned_address = (address >> 3) << 3
+ aligned_address = (address >> 3) << 3
index = bisect.bisect_left(self.mem_addrs, aligned_address)
if index == len(self.mem_addrs):
@@ -689,7 +702,7 @@ def get_aligned_address_mask(self, address, length=8):
and write a comment to describe the mess we're in
"""
mask_offset = address % 8
- aligned_address = ((address >> 3) << 3)
+ aligned_address = (address >> 3) << 3
aligned_mask = (((1 << length) - 1) << mask_offset) & 0xFF
return aligned_mask
@@ -709,21 +722,17 @@ def _finalize(self):
ip_addrs = sorted(list(self.ip_map.keys()))
self.ip_addrs = array.array(pointer_type, ip_addrs)
- remapped_ip = {
- ip_map[address]: i for i, address in enumerate(ip_addrs)
- }
+ remapped_ip = {ip_map[address]: i for i, address in enumerate(ip_addrs)}
# bake the master (aligned) memory address table
mem_map = self.mem_map
mem_map_len = len(mem_map)
mem_addrs = sorted(list(mem_map.keys()))
self.mem_addrs = array.array(pointer_type, mem_addrs)
- self.mem_masks = array.array('B', [0] * len(mem_addrs))
+ self.mem_masks = array.array("B", [0] * len(mem_addrs))
# generate a temporary mem re-mapping map...
- remapped_mem = {
- mem_map[address]: i for i, address in enumerate(mem_addrs)
- }
+ remapped_mem = {mem_map[address]: i for i, address in enumerate(mem_addrs)}
# pre-compute the 'size' of the data represented by a register mask
self.mask_sizes = [number_of_bits_set(mask) * self.arch.POINTER_SIZE for mask in self.masks]
@@ -748,15 +757,15 @@ def _finalize(self):
if TRACE_STATS:
self._finalize_stats()
-
- #-------------------------------------------------------------------------
+
+ # -------------------------------------------------------------------------
# Trace Statistics
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _init_stats(self):
self.unique_mem_addr = set()
self.avg_unique_mem_addr = 0
- self.min_unique_mem_addr = 999999999
+ self.min_unique_mem_addr = 999999999
self.max_unique_mem_addr = -1
self.avg_unique_ip = 0
@@ -767,7 +776,7 @@ def _init_stats(self):
self.num_bytes_written = 0
self.num_bytes_read_info = 0
self.num_bytes_written_info = 0
-
+
self.num_bytes_ips = 0
self.num_bytes_reg_data = 0
self.num_bytes_reg_masks = 0
@@ -798,13 +807,13 @@ def _harvest_stats(self, seg):
self.num_bytes_written += seg.num_bytes_written
self.num_bytes_read_info += seg.num_bytes_read_info
self.num_bytes_written_info += seg.num_bytes_written_info
-
+
self.num_bytes_ips += seg.num_bytes_ips
self.num_bytes_reg_data += seg.num_bytes_reg_data
self.num_bytes_reg_masks += seg.num_bytes_reg_masks
self.raw_size += seg.raw_size_bytes
- #self.length += seg.length
+ # self.length += seg.length
def _finalize_stats(self):
self.avg_unique_ip = self.avg_unique_ip // len(self.segments)
@@ -841,18 +850,37 @@ def print_stats(self):
output.append("")
output.append(f" -- {self.raw_size/(1024*1024):0.2f}mb - raw size")
output.append("")
- output.append(f" ---- {self.num_bytes_unique_ip / (1024*1024):0.2f}mb ({(self.num_bytes_unique_ip / self.raw_size) * 100 :3.2f}%) - ip addrs")
- output.append(f" ---- {self.num_bytes_unique_mem / (1024*1024):0.2f}mb ({(self.num_bytes_unique_mem / self.raw_size) * 100 :3.2f}%) - mem addrs")
- output.append(f" ---- {self.num_bytes_ips / (1024*1024):0.2f}mb ({(self.num_bytes_ips / self.raw_size) * 100 :3.2f}%) - ip trace")
- output.append(f" ---- {self.num_bytes_reg_data / (1024*1024):0.2f}mb ({(self.num_bytes_reg_data / self.raw_size) * 100 :3.2f}%) - reg data")
- output.append(f" ---- {self.num_bytes_reg_masks / (1024*1024):0.2f}mb ({(self.num_bytes_reg_masks / self.raw_size) * 100 :3.2f}%) - reg masks")
+ output.append(
+ f" ---- {self.num_bytes_unique_ip / (1024*1024):0.2f}mb ({(self.num_bytes_unique_ip / self.raw_size) * 100 :3.2f}%) - ip addrs"
+ )
+ output.append(
+ f" ---- {self.num_bytes_unique_mem / (1024*1024):0.2f}mb ({(self.num_bytes_unique_mem / self.raw_size) * 100 :3.2f}%) - mem addrs"
+ )
+ output.append(
+ f" ---- {self.num_bytes_ips / (1024*1024):0.2f}mb ({(self.num_bytes_ips / self.raw_size) * 100 :3.2f}%) - ip trace"
+ )
+ output.append(
+ f" ---- {self.num_bytes_reg_data / (1024*1024):0.2f}mb ({(self.num_bytes_reg_data / self.raw_size) * 100 :3.2f}%) - reg data"
+ )
+ output.append(
+ f" ---- {self.num_bytes_reg_masks / (1024*1024):0.2f}mb ({(self.num_bytes_reg_masks / self.raw_size) * 100 :3.2f}%) - reg masks"
+ )
output.append("")
- output.append(f" ---- {self.num_bytes_read / (1024*1024):0.2f}mb ({(self.num_bytes_read / self.raw_size) * 100 :3.2f}%) - bytes read")
- output.append(f" ---- {self.num_bytes_written / (1024*1024):0.2f}mb ({(self.num_bytes_written / self.raw_size) * 100 :3.2f}%) - bytes written")
- output.append(f" ---- {self.num_bytes_read_info / (1024*1024):0.2f}mb ({(self.num_bytes_read_info / self.raw_size) * 100 :3.2f}%) - read pointers")
- output.append(f" ---- {self.num_bytes_written_info / (1024*1024):0.2f}mb ({(self.num_bytes_written_info / self.raw_size) * 100 :3.2f}%) - write pointers")
- print(''.join(output))
-
+ output.append(
+ f" ---- {self.num_bytes_read / (1024*1024):0.2f}mb ({(self.num_bytes_read / self.raw_size) * 100 :3.2f}%) - bytes read"
+ )
+ output.append(
+ f" ---- {self.num_bytes_written / (1024*1024):0.2f}mb ({(self.num_bytes_written / self.raw_size) * 100 :3.2f}%) - bytes written"
+ )
+ output.append(
+ f" ---- {self.num_bytes_read_info / (1024*1024):0.2f}mb ({(self.num_bytes_read_info / self.raw_size) * 100 :3.2f}%) - read pointers"
+ )
+ output.append(
+ f" ---- {self.num_bytes_written_info / (1024*1024):0.2f}mb ({(self.num_bytes_written_info / self.raw_size) * 100 :3.2f}%) - write pointers"
+ )
+ print("".join(output))
+
+
class TraceSegment(object):
"""
A segment of trace data.
@@ -883,14 +911,14 @@ def __init__(self, trace, id=0, base_idx=0):
self.mem_delta = collections.defaultdict(MemValue)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Properties
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
@property
def read_set(self):
return set(self.read_addrs)
-
+
@property
def write_set(self):
return set(self.write_addrs)
@@ -898,7 +926,7 @@ def write_set(self):
@property
def num_unique_ip(self):
return len(set(self.ips))
-
+
@property
def num_unique_mem_addresses(self):
return len(self.read_set | self.write_set)
@@ -911,12 +939,12 @@ def num_bytes_read(self):
def num_bytes_written(self):
return len(self.write_data)
- #@property
- #def num_bytes_read_info(self):
+ # @property
+ # def num_bytes_read_info(self):
# return ctypes.sizeof(self._mem_read_info)
-
- #@property
- #def num_bytes_written_info(self):
+
+ # @property
+ # def num_bytes_written_info(self):
# return ctypes.sizeof(self._mem_write_info)
@property
@@ -926,14 +954,14 @@ def num_bytes_reg_data(self):
@property
def num_bytes_ips(self):
return ctypes.sizeof(self.ips)
-
+
@property
def num_bytes_reg_masks(self):
return ctypes.sizeof(self.reg_masks)
@property
def raw_size_bytes(self):
- size = 0
+ size = 0
# reg data storage costs
size += self.num_bytes_ips
@@ -950,19 +978,21 @@ def raw_size_bytes(self):
@property
def raw_size_mb(self):
- return self.raw_size_bytes / (1024*1024)
+ return self.raw_size_bytes / (1024 * 1024)
def __str__(self):
output = []
output.append(f"Trace Segment -- IDX {self.base_idx}")
- output.append(f" -- Reg Data {len(self.reg_data)} bytes ({len(self.reg_data) / (1024*1024):0.2f}mb)")
+ output.append(
+ f" -- Reg Data {len(self.reg_data)} bytes ({len(self.reg_data) / (1024*1024):0.2f}mb)"
+ )
output.append(f" -- Unique IP {len(set(self.ips))}")
output.append(f" -- Raw Size {self.raw_size_mb:0.2f}mb")
- return ''.join(output)
+ return "".join(output)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Public
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def from_lines(self, lines):
"""
@@ -973,7 +1003,11 @@ def from_lines(self, lines):
self.ips = [0 for x in range(self.trace.segment_length)]
# register storage (minus IP)
- MAX_REG_DATA = self.trace.arch.POINTER_SIZE * len(self.trace.arch.REGISTERS) * self.trace.segment_length
+ MAX_REG_DATA = (
+ self.trace.arch.POINTER_SIZE
+ * len(self.trace.arch.REGISTERS)
+ * self.trace.segment_length
+ )
self.reg_data = bytearray(MAX_REG_DATA)
self.reg_offsets = array.array("I", [0] * REG_OFFSET_CACHE_SIZE)
self.reg_masks = [0 for x in range(self.trace.segment_length)]
@@ -988,7 +1022,7 @@ def from_lines(self, lines):
self._max_write_size = 0
self._process_lines(lines)
- #print(f"Snapshot entries: {len(self.mem_delta)}")
+ # print(f"Snapshot entries: {len(self.mem_delta)}")
def from_file(self, f):
"""
@@ -1002,7 +1036,7 @@ def get_ip(self, idx):
"""
relative_idx = idx - self.base_idx
return self.trace.ip_addrs[self.ips[relative_idx]]
-
+
def get_reg_delta(self, idx):
"""
Return the register delta for the given timestamp.
@@ -1018,8 +1052,8 @@ def get_reg_delta(self, idx):
# if no registers changed, nothing to do but return IP
if not mask:
return {self.trace.arch.IP: ip_address}
-
- #
+
+ #
# fetch the closest cached register data offset that we can start from
# for computing precisely where we should be working backwards from
#
@@ -1034,16 +1068,16 @@ def get_reg_delta(self, idx):
offset = cache_offset + sum([sizes[mask_id] for mask_id in offset_masks])
# compute the location of the packed register delta data
- #offset_slow = sum([sizes[mask_id] for mask_id in self.reg_masks[:relative_idx]])
- #assert offset == offset_slow
+ # offset_slow = sum([sizes[mask_id] for mask_id in self.reg_masks[:relative_idx]])
+ # assert offset == offset_slow
# fetch the register data
reg_names = self._mask2regs(mask)
num_regs = len(reg_names)
- reg_data = self.reg_data[offset:offset + (num_regs * self.arch.POINTER_SIZE)]
+ reg_data = self.reg_data[offset : offset + (num_regs * self.arch.POINTER_SIZE)]
# unpack the register data
- pack_fmt = 'Q' if self.arch.POINTER_SIZE == 8 else 'I'
+ pack_fmt = "Q" if self.arch.POINTER_SIZE == 8 else "I"
reg_values = struct.unpack(pack_fmt * num_regs, reg_data)
# pack all the registers into a dict that will be returned to the user
@@ -1077,9 +1111,21 @@ def _get_mem_delta(self, idx, mem_type):
found, offset = [], 0
if mem_type == TRACE_MEM_WRITE:
- idxs, addrs, masks, offsets, data = self.write_idxs, self.write_addrs, self.write_masks, self.write_offsets, self.write_data
+ idxs, addrs, masks, offsets, data = (
+ self.write_idxs,
+ self.write_addrs,
+ self.write_masks,
+ self.write_offsets,
+ self.write_data,
+ )
else:
- idxs, addrs, masks, offsets, data = self.read_idxs, self.read_addrs, self.read_masks, self.read_offsets, self.read_data
+ idxs, addrs, masks, offsets, data = (
+ self.read_idxs,
+ self.read_addrs,
+ self.read_masks,
+ self.read_offsets,
+ self.read_data,
+ )
try:
i = idxs.index(relative_idx)
@@ -1087,7 +1133,7 @@ def _get_mem_delta(self, idx, mem_type):
return []
while i < len(idxs) and idxs[i] == relative_idx:
-
+
#
# fetch the 'aligned' address for this memory access, and the
# mask which specifes which bytes were touched starting from
@@ -1100,10 +1146,10 @@ def _get_mem_delta(self, idx, mem_type):
# extract the raw data for this memory access
offset = offsets[i]
length = number_of_bits_set(masks[i])
- raw_data = data[offset:offset+length]
+ raw_data = data[offset : offset + length]
address = aligned_address
- seen_byte = False # TODO KLUDGE
+ seen_byte = False # TODO KLUDGE
while access_mask:
if access_mask & 1 == 0:
address += 1
@@ -1121,7 +1167,7 @@ def _get_mem_delta(self, idx, mem_type):
def get_reg_info(self, idx, reg_names):
"""
Given a starting timestamp and a list of register names, return
-
+
{ reg_name: (value, idx) }
... for each discoverable register in this segment.
@@ -1135,7 +1181,7 @@ def get_reg_info(self, idx, reg_names):
# compute a 32bit mask of the registers we need to find
target_mask = self._regs2mask(reg_names)
- #
+ #
# fetch the closest cached register data offset that we can start from
# for computing precisely where we should be working backwards from
#
@@ -1143,7 +1189,7 @@ def get_reg_info(self, idx, reg_names):
cache_index = int(start_idx / REG_OFFSET_CACHE_INTERVAL)
cache_offset = self.reg_offsets[cache_index]
cache_idx = cache_index * REG_OFFSET_CACHE_INTERVAL
-
+
# alias for faster access / readability
sizes = self.trace.mask_sizes
masks = self.trace.masks
@@ -1157,8 +1203,8 @@ def get_reg_info(self, idx, reg_names):
# loop backwards through the segment, starting from the given idx
search_masks = self.reg_masks[:start_idx][::-1]
- #offset_slow = sum([sizes[mask_id] for mask_id in search_masks])
- #assert offset == offset_slow
+ # offset_slow = sum([sizes[mask_id] for mask_id in search_masks])
+ # assert offset == offset_slow
for i, mask_id in enumerate(search_masks):
# translate the mask id for this step into its register bitfield
@@ -1183,7 +1229,7 @@ def get_reg_info(self, idx, reg_names):
# fetch the registers for this delta / timestamp
registers = self._unpack_registers(current_mask, offset)
- # add the found register names and the current (global) idx
+ # add the found register names and the current (global) idx
for reg_name in found_names:
found_registers[reg_name] = (registers[reg_name], (self.base_idx + (start_idx - i)))
@@ -1202,14 +1248,24 @@ def get_mem_data(self, mem_id, set_id, data_mask):
"""
if set_id == 1:
- addrs, masks, offsets, data = self.write_addrs, self.write_masks, self.write_offsets, self.write_data
+ addrs, masks, offsets, data = (
+ self.write_addrs,
+ self.write_masks,
+ self.write_offsets,
+ self.write_data,
+ )
else:
- addrs, masks, offsets, data = self.read_addrs, self.read_masks, self.read_offsets, self.read_data
-
- offset = offsets[mem_id] #sum([number_of_bits_set(mask) for mask in masks[:mem_id]])
- #offset = sum([number_of_bits_set(mask) for mask in masks[:mem_id]])
+ addrs, masks, offsets, data = (
+ self.read_addrs,
+ self.read_masks,
+ self.read_offsets,
+ self.read_data,
+ )
+
+ offset = offsets[mem_id] # sum([number_of_bits_set(mask) for mask in masks[:mem_id]])
+ # offset = sum([number_of_bits_set(mask) for mask in masks[:mem_id]])
length = number_of_bits_set(masks[mem_id])
- raw_data = data[offset:offset+length]
+ raw_data = data[offset : offset + length]
address = self.trace.mem_addrs[addrs[mem_id]]
output = TraceMemory(address, 8)
@@ -1224,13 +1280,13 @@ def get_mem_data(self, mem_id, set_id, data_mask):
i += 1
data_mask >>= 1
- #assert byte == length
+ # assert byte == length
return output
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Finalization
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def load(self, f):
"""
@@ -1249,7 +1305,7 @@ def load(self, f):
ip_itemsize = info.ip_length // info.ip_num
ip_type = type_from_width(ip_itemsize)
- # load the ip trace
+ # load the ip trace
self.ips = array.array(ip_type)
self.ips.fromfile(f, info.ip_num)
@@ -1269,7 +1325,7 @@ def load(self, f):
#
# memory
#
-
+
idx_type = self.trace.mem_idx_type
addr_type = self.trace.mem_addr_type
@@ -1278,7 +1334,7 @@ def load(self, f):
self.read_idxs.fromfile(f, info.mem_read_num)
self.read_addrs = array.array(addr_type)
self.read_addrs.fromfile(f, info.mem_read_num)
- self.read_masks = array.array('B')
+ self.read_masks = array.array("B")
self.read_masks.fromfile(f, info.mem_read_num)
# load the raw memory read data
@@ -1290,7 +1346,7 @@ def load(self, f):
self.write_idxs.fromfile(f, info.mem_write_num)
self.write_addrs = array.array(addr_type)
self.write_addrs.fromfile(f, info.mem_write_num)
- self.write_masks = array.array('B')
+ self.write_masks = array.array("B")
self.write_masks.fromfile(f, info.mem_write_num)
# load the raw memory write data
@@ -1315,13 +1371,13 @@ def dump(self, f):
info.id = self.id
info.base_idx = self.base_idx
info.length = self.length
-
+
info.ip_num = self.length
info.ip_length = info.ip_num * self.ips.itemsize
info.reg_mask_num = len(self.reg_masks)
info.reg_mask_length = info.reg_mask_num * self.reg_masks.itemsize
- info.reg_data_length = len(self.reg_data) # bytearray
+ info.reg_data_length = len(self.reg_data) # bytearray
info.mem_read_num = len(self.read_idxs)
info.mem_read_data_length = len(self.read_data)
@@ -1349,9 +1405,9 @@ def dump(self, f):
for mapped_address in sorted(set(self.read_addrs + self.write_addrs)):
f.write(bytearray(self.mem_delta[mapped_address]))
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Finalization
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def finalize(self, remapped_ip, remapped_mem):
"""
@@ -1379,7 +1435,7 @@ def _finalize_registers(self, remapped_ip):
del self.ips
self.ips = new_ips
-
+
#
# pack register masks
#
@@ -1389,7 +1445,7 @@ def _finalize_registers(self, remapped_ip):
del self.reg_masks
self.reg_masks = new_masks
-
+
def _finalize_memory(self, remapped_mem):
"""
Bake memory into ctype structures.
@@ -1404,7 +1460,7 @@ def _finalize_memory(self, remapped_mem):
# allocate fast, compact python arrays to hold our mem read info
read_idxs = array.array(idx_type)
read_addrs = array.array(addr_type)
- read_masks = array.array('B')
+ read_masks = array.array("B")
# transfer read metadata into compact / searchable arrays
for entry in self._mem_read_info:
@@ -1430,7 +1486,7 @@ def _finalize_memory(self, remapped_mem):
# allocate fast, compact python arrays to hold our mem write info
write_idxs = array.array(idx_type)
write_addrs = array.array(addr_type)
- write_masks = array.array('B')
+ write_masks = array.array("B")
# transfer write metadata into compact / searchable arrays
for entry in self._mem_write_info:
@@ -1443,7 +1499,7 @@ def _finalize_memory(self, remapped_mem):
write_idxs.append(idx)
write_addrs.append(mapped_address)
write_masks.append(mask)
-
+
del self._mem_write_info
self.write_idxs = write_idxs
self.write_addrs = write_addrs
@@ -1463,7 +1519,7 @@ def _finalize_memory(self, remapped_mem):
del self.mem_delta
self.mem_delta = new_delta
-
+
self._compute_mem_offsets()
def _compute_mem_offsets(self):
@@ -1472,13 +1528,10 @@ def _compute_mem_offsets(self):
"""
temp_sizes = {}
- self.read_offsets = array.array('I', [0] * len(self.read_masks))
- self.write_offsets = array.array('I', [0] * len(self.write_masks))
+ self.read_offsets = array.array("I", [0] * len(self.read_masks))
+ self.write_offsets = array.array("I", [0] * len(self.write_masks))
- mem_sets = [
- (self.read_offsets, self.read_masks),
- (self.write_offsets, self.write_masks)
- ]
+ mem_sets = [(self.read_offsets, self.read_masks), (self.write_offsets, self.write_masks)]
for offsets, masks in mem_sets:
offset = 0
@@ -1487,9 +1540,9 @@ def _compute_mem_offsets(self):
length = temp_sizes.setdefault(mask, number_of_bits_set(mask))
offset += length
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Processing / Logic
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _process_lines(self, lines):
"""
@@ -1512,7 +1565,7 @@ def _process_lines(self, lines):
pmsg(f"LINE PARSE FAILED, line ~{self.base_idx+relative_idx:,}, contents '{line}'")
pmsg(str(e))
- self.reg_data = bytearray(self.reg_data[:self._reg_offset])
+ self.reg_data = bytearray(self.reg_data[: self._reg_offset])
self.ips = self.ips[:relative_idx]
self.length = relative_idx
@@ -1522,7 +1575,7 @@ def _process_line(self, line, relative_idx):
"""
IP = self.trace.arch.IP
REGISTERS = self.trace.arch.REGISTERS
-
+
delta = line.split(",")
registers = {}
@@ -1553,14 +1606,16 @@ def _process_line(self, line, relative_idx):
address, hex_data = value.split(":")
address = int(address, 16)
- hex_data = bytes(hex_data.strip(), 'utf-8')
+ hex_data = bytes(hex_data.strip(), "utf-8")
data = binascii.unhexlify(hex_data)
self._process_mem_entry(address, data, name, relative_idx)
else:
- raise ValueError(f"Invalid line in text trace! '{line}' error on '{name}', (value '{value}')")
-
+ raise ValueError(
+ f"Invalid line in text trace! '{line}' error on '{name}', (value '{value}')"
+ )
+
self._pack_registers(registers, relative_idx)
return True
@@ -1572,50 +1627,50 @@ def _process_mem_entry(self, address, data, access_type, relative_idx):
byte = 0
for mapped_address, access_mask, access_data in self._map_mem_access(address, data):
-
+
# read
- if access_type == 'MR':
+ if access_type == "MR":
self._mem_read_info.append((relative_idx, mapped_address, access_mask))
self.read_data += access_data
- #self._max_read_size = max(self._max_read_size, data_len)
+ # self._max_read_size = max(self._max_read_size, data_len)
# write
- elif access_type == 'MW':
+ elif access_type == "MW":
self._mem_write_info.append((relative_idx, mapped_address, access_mask))
self.write_data += access_data
- #print(self._mem_write_info[-1], hexdump(data), "REAL OFFSET", len(self.write_data)-len(data))
- #self._max_write_size = max(self._max_write_size, data_len)
+ # print(self._mem_write_info[-1], hexdump(data), "REAL OFFSET", len(self.write_data)-len(data))
+ # self._max_write_size = max(self._max_write_size, data_len)
# read AND write (eg, inc [rax])
- elif access_type == 'MRW':
+ elif access_type == "MRW":
# read
self._mem_read_info.append((relative_idx, mapped_address, access_mask))
self.read_data += access_data
- #self._max_read_size = max(self._max_read_size, data_len)
+ # self._max_read_size = max(self._max_read_size, data_len)
# write
self._mem_write_info.append((relative_idx, mapped_address, access_mask))
self.write_data += access_data
- #self._max_write_size = max(self._max_write_size, data_len)
+ # self._max_write_size = max(self._max_write_size, data_len)
else:
raise ValueError("Unknown field in trace: '%s=...'" % access_type)
mv = self.mem_delta[mapped_address]
mv.mask |= access_mask
- #print(f"ADDRESS: 0x{address:08X} MASK: {access_mask:02X}")
+ # print(f"ADDRESS: 0x{address:08X} MASK: {access_mask:02X}")
# snapshot stuff
bit, byte = 0, 0
while access_mask:
if access_mask & 1:
- #print(bit, byte)
+ # print(bit, byte)
mv.value[bit] = access_data[byte]
- #byte_shift = (bit * 8)
- #byte_mask = 0xFF << byte_shift
- #value[0] = (value[0] & ~byte_mask) | (data[byte] << byte_shift)
+ # byte_shift = (bit * 8)
+ # byte_mask = 0xFF << byte_shift
+ # value[0] = (value[0] & ~byte_mask) | (data[byte] << byte_shift)
byte += 1
access_mask >>= 1
bit += 1
@@ -1630,7 +1685,7 @@ def _map_mem_access(self, address, data):
mask_offset = address % 8
remaining_mask = ((1 << data_len) - 1) << mask_offset
- aligned_address = ((address >> 3) << 3)
+ aligned_address = (address >> 3) << 3
access_length = min(len(access_data), (8 - mask_offset))
while remaining_mask:
@@ -1658,20 +1713,20 @@ def _pack_registers(self, registers, relative_idx):
# to help improve the speed of looking up register values in the data
# blob, we cache pre-computed offsets at finxed intervals throughout
# the segment.
- #
+ #
# at query time, we can pick the closest cached interval prior to the
# target idx and only re-compute a fraction of the offsets needed to
# find the correct offset into the data blob to fetch our reg delta
#
- if not(relative_idx % REG_OFFSET_CACHE_INTERVAL):
+ if not (relative_idx % REG_OFFSET_CACHE_INTERVAL):
cache_index = int(relative_idx / REG_OFFSET_CACHE_INTERVAL)
- #print(f"rIDX: {relative_idx:,} CACHE: {cache_index} LEN: {len(self.reg_offsets)}")
+ # print(f"rIDX: {relative_idx:,} CACHE: {cache_index} LEN: {len(self.reg_offsets)}")
self.reg_offsets[cache_index] = self._reg_offset
#
# XXX/TODO: BODGE FOR WHEN PEOPLE DON'T DUMP A FULL REGISTER STATE
- #
+ #
if self.base_idx == 0 and self._reg_offset == 0:
if num_regs != len(self.arch.REGISTERS):
@@ -1694,9 +1749,11 @@ def _pack_registers(self, registers, relative_idx):
self.reg_masks[relative_idx] = mapped_mask
- value_pairs = sorted([(self.arch.REGISTERS.index(name), value) for name, value in registers.items()])
+ value_pairs = sorted(
+ [(self.arch.REGISTERS.index(name), value) for name, value in registers.items()]
+ )
values = [x[1] for x in value_pairs]
- pack_fmt = 'Q' if self.arch.POINTER_SIZE == 8 else 'I'
+ pack_fmt = "Q" if self.arch.POINTER_SIZE == 8 else "I"
struct.pack_into(pack_fmt * num_regs, self.reg_data, self._reg_offset, *values)
self._reg_offset += num_regs * self.arch.POINTER_SIZE
@@ -1708,10 +1765,10 @@ def _unpack_registers(self, mask, offset):
# fetch the register data
num_regs = len(reg_names)
- reg_data = self.reg_data[offset:offset + (num_regs * self.arch.POINTER_SIZE)]
+ reg_data = self.reg_data[offset : offset + (num_regs * self.arch.POINTER_SIZE)]
# unpack the register data
- pack_fmt = 'Q' if self.arch.POINTER_SIZE == 8 else 'I'
+ pack_fmt = "Q" if self.arch.POINTER_SIZE == 8 else "I"
reg_values = struct.unpack(pack_fmt * num_regs, reg_data)
# pack all the registers into a dict that will be returned to the user
@@ -1720,9 +1777,9 @@ def _unpack_registers(self, mask, offset):
# return the completed register delta
return registers
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Util
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _regs2mask(self, regs):
"""
diff --git a/plugins/tenet/util/qt/util.py b/plugins/tenet/util/qt/util.py
index 8f18d10..8073505 100644
--- a/plugins/tenet/util/qt/util.py
+++ b/plugins/tenet/util/qt/util.py
@@ -59,7 +59,7 @@ def get_dpi_scale():
fm = QtGui.QFontMetricsF(font)
# xHeight is expected to be 40.0 at normal DPI
- return fm.height() / 173.0
+ return int(fm.height() / 173.0)
def normalize_font(font_size):
diff --git a/tracers/qemu/arm32/tenet.c b/tracers/qemu/arm32/tenet.c
index 54e7306..580f641 100644
--- a/tracers/qemu/arm32/tenet.c
+++ b/tracers/qemu/arm32/tenet.c
@@ -57,6 +57,10 @@ uint32_t* get_cpu_regs(void)
FILE* g_out = NULL;
+// Used to filter irrelevant PCs
+uint64_t g_pc_low = 0;
+uint64_t g_pc_high = 0xFFFFFFFFFFFFFFFF;
+
uint32_t* g_cpu = NULL;
uint32_t g_cpu_prev[NUM_REG] = {};
@@ -72,6 +76,10 @@ size_t g_mem_log_count = 0;
static void vcpu_insn_exec(unsigned int cpu_index, void* udata)
{
+ const uint64_t pc = GPOINTER_TO_UINT(udata);
+ if (pc > g_pc_high || pc < g_pc_low)
+ return;
+
int length = 0;
if (g_cpu[R0] != g_cpu_prev[R0])
@@ -103,7 +111,6 @@ static void vcpu_insn_exec(unsigned int cpu_index, void* udata)
if (g_cpu[SP] != g_cpu_prev[SP])
length += sprintf(reg_scratch + length, "SP=%X,", g_cpu[SP]);
- uint64_t pc = GPOINTER_TO_UINT(udata);
length += sprintf(reg_scratch + length, "PC=%lX", pc);
for (int i = 0; i < g_mem_log_count; i++) {
@@ -147,6 +154,10 @@ static void vcpu_mem_access(unsigned int cpu_index,
uint64_t vaddr,
void* udata)
{
+ const uint64_t pc = GPOINTER_TO_UINT(udata);
+ if (pc > g_pc_high || pc < g_pc_low)
+ return;
+
struct qemu_plugin_hwaddr* hwaddr = qemu_plugin_get_hwaddr(mem_info, vaddr);
if (qemu_plugin_hwaddr_is_io(hwaddr))
return;
@@ -193,6 +204,14 @@ QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
else
filepath = (char*)"trace.log";
+ if (argc == 3) {
+ const char hex_prefix[] = "0x";
+ g_pc_low = strtoull(!strcmp(hex_prefix, argv[1]) ? argv[1] + 2 : argv[1], NULL, 16);
+ g_pc_high = strtoull(!strcmp(hex_prefix, argv[2]) ? argv[2] + 2 : argv[2], NULL, 16);
+
+ printf("Filtering PCs between 0x%lX and 0x%lX\n", g_pc_low, g_pc_high);
+ }
+
printf("Writing Tenet trace to %s\n", filepath);
g_out = fopen(filepath, "w");
From 8faf6664b60c4e34e77ec34125c0839ed1554b11 Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Wed, 4 Oct 2023 19:26:56 +0300
Subject: [PATCH 4/8] Fix formatting
---
plugins/tenet/breakpoints.py | 18 +-
plugins/tenet/core.py | 42 ++--
plugins/tenet/hex.py | 32 +--
plugins/tenet/integration/api/__init__.py | 16 +-
plugins/tenet/integration/api/api.py | 50 ++--
plugins/tenet/integration/ida_integration.py | 161 ++++++-------
plugins/tenet/integration/ida_loader.py | 17 +-
plugins/tenet/memory.py | 9 +-
plugins/tenet/registers.py | 38 ++--
plugins/tenet/stack.py | 19 +-
plugins/tenet/trace/analysis.py | 25 +-
plugins/tenet/trace/arch/amd64.py | 8 +-
plugins/tenet/trace/arch/x86.py | 14 +-
plugins/tenet/trace/reader.py | 226 ++++++++++---------
plugins/tenet/trace/types.py | 33 +--
plugins/tenet/types.py | 70 +++---
plugins/tenet/ui/breakpoint_view.py | 6 +-
plugins/tenet/ui/hex_view.py | 88 ++++----
plugins/tenet/ui/palette.py | 45 ++--
plugins/tenet/ui/trace_view.py | 173 ++++++++------
plugins/tenet/util/debug.py | 17 +-
plugins/tenet/util/log.py | 49 ++--
plugins/tenet/util/misc.py | 51 +++--
plugins/tenet/util/qt/__init__.py | 2 +-
plugins/tenet/util/qt/shim.py | 15 +-
plugins/tenet/util/update.py | 28 ++-
plugins/tenet_plugin.py | 5 +-
27 files changed, 681 insertions(+), 576 deletions(-)
diff --git a/plugins/tenet/breakpoints.py b/plugins/tenet/breakpoints.py
index 37ed00b..8aa2415 100644
--- a/plugins/tenet/breakpoints.py
+++ b/plugins/tenet/breakpoints.py
@@ -6,9 +6,9 @@
from tenet.integration.api import DockableWindow
from tenet.integration.api import disassembler
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# breakpoints.py -- Breakpoint Controller
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# The purpose of this file is to house the 'headless' components of the
# breakpoints window and its underlying functionality. This is split into
@@ -24,6 +24,7 @@
# for managing and differentiating between breakpoints...
#
+
class BreakpointController(object):
"""
The Breakpoint Controller (Logic)
@@ -146,6 +147,7 @@ def _delete_disassembler_breakpoints(self):
dctx.delete_breakpoint(address)
self._ignore_signals = False
+
class BreakpointModel(object):
"""
The Breakpoint Model (Data)
@@ -154,9 +156,9 @@ class BreakpointModel(object):
def __init__(self):
self.reset()
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
self._breakpoints_changed_callbacks = []
@@ -172,15 +174,13 @@ def memory_breakpoints(self):
Return an iterable list of all memory breakpoints.
"""
bps = itertools.chain(
- self.bp_read.values(),
- self.bp_write.values(),
- self.bp_access.values()
+ self.bp_read.values(), self.bp_write.values(), self.bp_access.values()
)
return bps
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def breakpoints_changed(self, callback):
"""
diff --git a/plugins/tenet/core.py b/plugins/tenet/core.py
index 861900d..ba26cf3 100644
--- a/plugins/tenet/core.py
+++ b/plugins/tenet/core.py
@@ -8,9 +8,9 @@
logger = logging.getLogger("Tenet.Core")
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# core.py -- Plugin Core
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# The purpose of this file is to define a specification required by the
# plugin to integrate and load under a given disassembler.
@@ -18,29 +18,31 @@
# This is technically the 'lowest' level layer of the plugin, as it is
# loaded / unloaded directly by the disassembler. This means that there
# should be no database or user-specific data loaded into this layer.
-#
+#
# Supporting additional disassemblers will require one to subclass this
# abstract core as part of a disassembler-specific integration layer.
#
+
class TenetCore(object):
"""
The disassembler-wide plugin core.
"""
+
__metaclass__ = abc.ABCMeta
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Plugin Metadata
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
- PLUGIN_NAME = "Tenet"
+ PLUGIN_NAME = "Tenet"
PLUGIN_VERSION = "0.2.0"
PLUGIN_AUTHORS = "Markus Gaasedelen"
- PLUGIN_DATE = "2021"
+ PLUGIN_DATE = "2021"
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Initialization / Teardown
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def load(self):
"""
@@ -58,7 +60,7 @@ def load(self):
# all done, mark the core as loaded
self.loaded = True
-
+
# print plugin banner
pmsg(f"Loaded v{self.PLUGIN_VERSION} - (c) {self.PLUGIN_AUTHORS} - {self.PLUGIN_DATE}")
logger.info("Successfully loaded plugin")
@@ -84,7 +86,7 @@ def unload(self):
self.contexts = {}
# all done
- logger.info("-"*75)
+ logger.info("-" * 75)
logger.info("Plugin terminated")
@abc.abstractmethod
@@ -101,9 +103,9 @@ def unhook(self):
"""
pass
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Disassembler / Database Context Selector
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@abc.abstractmethod
def get_context(self, db, startup=True):
@@ -112,9 +114,9 @@ def get_context(self, db, startup=True):
"""
pass
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# UI Integration
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _install_ui(self):
"""
@@ -206,9 +208,9 @@ def _uninstall_final_execution(self):
"""
pass
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# UI Event Handlers
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _interactive_load_trace(self, db):
pctx = self.get_context(db)
@@ -230,16 +232,16 @@ def _interactive_prev_execution(self, db):
pctx = self.get_context(db)
pctx.interactive_prev_execution()
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Core Actions
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def refresh_theme(self):
"""
Refresh UI facing elements to reflect the current theme.
"""
for pctx in self.contexts.values():
- pass # TODO
+ pass # TODO
def check_for_update(self):
"""
diff --git a/plugins/tenet/hex.py b/plugins/tenet/hex.py
index bd8b173..5bf86dc 100644
--- a/plugins/tenet/hex.py
+++ b/plugins/tenet/hex.py
@@ -3,9 +3,9 @@
from tenet.util.qt.util import copy_to_clipboard
from tenet.integration.api import DockableWindow
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# hex.py -- Hex Dump Controller
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# The purpose of this file is to house the 'headless' components of a
# basic hex dump window and its underlying functionality. This is split
@@ -15,6 +15,7 @@
# views used by the plugin.
#
+
class HexController(object):
"""
A generalized controller for Hex View based window.
@@ -44,7 +45,7 @@ def show(self, target=None, position=0):
return
# the UI has already been created, and is also visible. nothing to do
- if (self.dockable and self.dockable.visible):
+ if self.dockable and self.dockable.visible:
return
#
@@ -63,7 +64,7 @@ def show(self, target=None, position=0):
if self.dockable:
new_dockable.copy_dock_position(self.dockable)
- elif (target or position):
+ elif target or position:
new_dockable.set_dock_position(target, position)
# make the dockable/widget visible
@@ -76,7 +77,7 @@ def hide(self):
"""
# if there is no view/dockable, then there's nothing to try and hide
- if not(self.view and self.dockable):
+ if not (self.view and self.dockable):
return
# hide the dockable, and drop references to the widgets
@@ -122,7 +123,7 @@ def navigate(self, address):
self.model.address = address
- #self.reset_selection(0)
+ # self.reset_selection(0)
self.refresh_memory()
def set_data_size(self, num_bytes):
@@ -138,7 +139,7 @@ def copy_selection(self, start_address, end_address):
"""
assert end_address > start_address
if not self.reader:
- return ''
+ return ""
# fetch memory for the selected region
num_bytes = end_address - start_address
@@ -152,7 +153,7 @@ def copy_selection(self, start_address, end_address):
else:
output.append("??")
- byte_string = ' '.join(output)
+ byte_string = " ".join(output)
copy_to_clipboard(byte_string)
return byte_string
@@ -192,9 +193,9 @@ def set_fade_threshold(self, address):
"""
self.model.fade_address = address
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Callbacks
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _idx_changed(self, idx):
"""
@@ -214,6 +215,7 @@ def _breakpoints_changed(self):
self.view.refresh()
+
class HexModel(object):
"""
A generalized model for Hex View based window.
@@ -249,9 +251,9 @@ def reset(self):
# pinned memory / breakpoint selections
self._pinned_selections = []
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Properties
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
@property
def memory_breakpoints(self):
@@ -280,7 +282,7 @@ def num_bytes_per_line(self, width):
raise ValueError("Bytes per line must be a multiple of display format type")
self._num_bytes_per_line = width
- #self._refresh_view_settings()
+ # self._refresh_view_settings()
@property
def hex_format(self):
@@ -291,7 +293,7 @@ def hex_format(self, value):
if value == self._hex_format:
return
self._hex_format = value
- #self.refresh()
+ # self.refresh()
@property
def aux_format(self):
@@ -302,4 +304,4 @@ def aux_format(self, value):
if value == self._aux_format:
return
self._aux_format = value
- #self.refresh()
\ No newline at end of file
+ # self.refresh()
diff --git a/plugins/tenet/integration/api/__init__.py b/plugins/tenet/integration/api/__init__.py
index 392938e..57d7803 100644
--- a/plugins/tenet/integration/api/__init__.py
+++ b/plugins/tenet/integration/api/__init__.py
@@ -1,6 +1,6 @@
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
# Disassembler API Selector
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
#
# this file will select and load the shimmed disassembler API for the
# appropriate (current) disassembler platform.
@@ -10,12 +10,13 @@
disassembler = None
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
# IDA API Shim
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
if disassembler == None:
from .ida_api import IDACoreAPI, IDAContextAPI, DockableWindow
+
disassembler = IDACoreAPI()
DisassemblerContextAPI = IDAContextAPI
@@ -23,7 +24,7 @@
## Binary Ninja API Shim
##--------------------------------------------------------------------------
#
-#if disassembler == None:
+# if disassembler == None:
# try:
# from .binja_api import BinjaCoreAPI, BinjaContextAPI
# disassembler = BinjaCoreAPI()
@@ -31,10 +32,9 @@
# except ImportError:
# pass
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
# Unknown Disassembler
-#--------------------------------------------------------------------------
+# --------------------------------------------------------------------------
if disassembler == None:
raise NotImplementedError("Unknown or unsupported disassembler!")
-
diff --git a/plugins/tenet/integration/api/api.py b/plugins/tenet/integration/api/api.py
index 37d7f8b..7f22677 100644
--- a/plugins/tenet/integration/api/api.py
+++ b/plugins/tenet/integration/api/api.py
@@ -5,9 +5,9 @@
logger = logging.getLogger("Tenet.Integration.API")
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Disassembler API
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# the purpose of this file is to provide an abstraction layer for the more
# generic disassembler APIs required by the plugin codebase. we strive to
@@ -25,10 +25,12 @@
# dependencies required by this plugin
#
+
class DisassemblerCoreAPI(object):
"""
An abstract implementation of the core disassembler APIs.
"""
+
__metaclass__ = abc.ABCMeta
# the name of the disassembler framework, eg 'IDA' or 'BINJA'
@@ -57,9 +59,9 @@ def __getitem__(self, key):
def __setitem__(self, key, value):
self._ctxs[key] = value
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Properties
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def version_major(self):
"""
@@ -89,9 +91,9 @@ def headless(self):
"""
pass
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Synchronization Decorators
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@staticmethod
def execute_read(function):
@@ -119,9 +121,9 @@ def execute_ui(function):
"""
raise NotImplementedError("execute_ui() has not been implemented")
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Disassembler Universal APIs
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@abc.abstractmethod
def get_disassembler_user_directory(self):
@@ -162,7 +164,12 @@ def warning(self, text):
text_width = fm.size(0, text).width()
# don't ask...
- spacer = QtWidgets.QSpacerItem(int(text_width*1.1 + icon_width), 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ spacer = QtWidgets.QSpacerItem(
+ int(text_width * 1.1 + icon_width),
+ 0,
+ QtWidgets.QSizePolicy.Minimum,
+ QtWidgets.QSizePolicy.Expanding,
+ )
layout = msgbox.layout()
layout.addItem(spacer, layout.rowCount(), 0, 1, layout.columnCount())
msgbox.setLayout(layout)
@@ -177,9 +184,9 @@ def message(self, function_address, new_name):
"""
pass
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# UI APIs
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@abc.abstractmethod
def create_dockable(self, dockable_name, widget):
@@ -188,9 +195,9 @@ def create_dockable(self, dockable_name, widget):
"""
pass
- #------------------------------------------------------------------------------
+ # ------------------------------------------------------------------------------
# WaitBox API
- #------------------------------------------------------------------------------
+ # ------------------------------------------------------------------------------
def show_wait_box(self, text, modal=True):
"""
@@ -214,23 +221,26 @@ def replace_wait_box(self, text):
assert QT_AVAILABLE, "This function can only be used in a Qt runtime"
self._waitbox.set_text(text)
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Disassembler Contextual API
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
class DisassemblerContextAPI(object):
"""
An abstract implementation of database/contextual disassembler APIs.
"""
+
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def __init__(self, dctx):
self.dctx = dctx
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Properties
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
@abc.abstractproperty
def busy(self):
@@ -239,9 +249,9 @@ def busy(self):
"""
pass
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# API Shims
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def is_64bit(self):
"""
@@ -323,4 +333,4 @@ def set_function_name_at(self, function_address, new_name):
"""
Set the function name at given address.
"""
- pass
\ No newline at end of file
+ pass
diff --git a/plugins/tenet/integration/ida_integration.py b/plugins/tenet/integration/ida_integration.py
index b865995..691b5ed 100644
--- a/plugins/tenet/integration/ida_integration.py
+++ b/plugins/tenet/integration/ida_integration.py
@@ -20,9 +20,10 @@
IDA_GLOBAL_CTX = "blah this value doesn't matter"
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# IDA UI Integration
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
class TenetIDA(TenetCore):
"""
@@ -44,7 +45,7 @@ def __init__(self):
#
self._hooked = False
-
+
self._ui_hooks = UIHooks()
self._ui_hooks.get_lines_rendering_info = self._render_lines
self._ui_hooks.finish_populating_widget_popup = self._popup_hook
@@ -115,31 +116,31 @@ def get_context(self, dctx, startup=True):
# return the plugin context object for this IDB
return self.contexts[dctx]
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# IDA Actions
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
- ACTION_LOAD_TRACE = "tenet:load_trace"
+ ACTION_LOAD_TRACE = "tenet:load_trace"
ACTION_FIRST_EXECUTION = "tenet:first_execution"
ACTION_FINAL_EXECUTION = "tenet:final_execution"
- ACTION_NEXT_EXECUTION = "tenet:next_execution"
- ACTION_PREV_EXECUTION = "tenet:prev_execution"
+ ACTION_NEXT_EXECUTION = "tenet:next_execution"
+ ACTION_PREV_EXECUTION = "tenet:prev_execution"
def _install_load_trace(self):
- # TODO: create a custom IDA icon
- #icon_path = plugin_resource(os.path.join("icons", "load.png"))
- #icon_data = open(icon_path, "rb").read()
- #self._icon_id_file = ida_kernwin.load_custom_icon(data=icon_data)
+ # TODO: create a custom IDA icon
+ # icon_path = plugin_resource(os.path.join("icons", "load.png"))
+ # icon_data = open(icon_path, "rb").read()
+ # self._icon_id_file = ida_kernwin.load_custom_icon(data=icon_data)
# describe a custom IDA UI action
action_desc = ida_kernwin.action_desc_t(
- self.ACTION_LOAD_TRACE, # The action name
- "~T~enet trace file...", # The action text
- IDACtxEntry(self._interactive_load_trace), # The action handler
- None, # Optional: action shortcut
- "Load a Tenet trace file", # Optional: tooltip
- -1 # Optional: the action icon
+ self.ACTION_LOAD_TRACE, # The action name
+ "~T~enet trace file...", # The action text
+ IDACtxEntry(self._interactive_load_trace), # The action handler
+ None, # Optional: action shortcut
+ "Load a Tenet trace file", # Optional: tooltip
+ -1, # Optional: the action icon
)
# register the action with IDA
@@ -148,9 +149,9 @@ def _install_load_trace(self):
# attach the action to the File-> dropdown menu
result = ida_kernwin.attach_action_to_menu(
- "File/Load file/", # Relative path of where to add the action
+ "File/Load file/", # Relative path of where to add the action
self.ACTION_LOAD_TRACE, # The action ID (see above)
- ida_kernwin.SETMENU_APP # We want to append the action after ^
+ ida_kernwin.SETMENU_APP, # We want to append the action after ^
)
assert result, f"Failed action attach {action_desc.name}"
@@ -163,12 +164,12 @@ def _install_next_execution(self):
# describe a custom IDA UI action
action_desc = ida_kernwin.action_desc_t(
- self.ACTION_NEXT_EXECUTION, # The action name
- "Go to next execution", # The action text
- IDACtxEntry(self._interactive_next_execution), # The action handler
- None, # Optional: action shortcut
- "Go to the next execution of the current address", # Optional: tooltip
- self._icon_id_next_execution # Optional: the action icon
+ self.ACTION_NEXT_EXECUTION, # The action name
+ "Go to next execution", # The action text
+ IDACtxEntry(self._interactive_next_execution), # The action handler
+ None, # Optional: action shortcut
+ "Go to the next execution of the current address", # Optional: tooltip
+ self._icon_id_next_execution, # Optional: the action icon
)
# register the action with IDA
@@ -183,12 +184,12 @@ def _install_prev_execution(self):
# describe a custom IDA UI action
action_desc = ida_kernwin.action_desc_t(
- self.ACTION_PREV_EXECUTION, # The action name
- "Go to previous execution", # The action text
- IDACtxEntry(self._interactive_prev_execution), # The action handler
- None, # Optional: action shortcut
- "Go to the previous execution of the current address", # Optional: tooltip
- self._icon_id_prev_execution # Optional: the action icon
+ self.ACTION_PREV_EXECUTION, # The action name
+ "Go to previous execution", # The action text
+ IDACtxEntry(self._interactive_prev_execution), # The action handler
+ None, # Optional: action shortcut
+ "Go to the previous execution of the current address", # Optional: tooltip
+ self._icon_id_prev_execution, # Optional: the action icon
)
# register the action with IDA
@@ -200,12 +201,12 @@ def _install_first_execution(self):
# describe a custom IDA UI action
action_desc = ida_kernwin.action_desc_t(
- self.ACTION_FIRST_EXECUTION, # The action name
- "Go to first execution", # The action text
- IDACtxEntry(self._interactive_first_execution), # The action handler
- None, # Optional: action shortcut
- "Go to the first execution of the current address", # Optional: tooltip
- -1 # Optional: the action icon
+ self.ACTION_FIRST_EXECUTION, # The action name
+ "Go to first execution", # The action text
+ IDACtxEntry(self._interactive_first_execution), # The action handler
+ None, # Optional: action shortcut
+ "Go to the first execution of the current address", # Optional: tooltip
+ -1, # Optional: the action icon
)
# register the action with IDA
@@ -217,12 +218,12 @@ def _install_final_execution(self):
# describe a custom IDA UI action
action_desc = ida_kernwin.action_desc_t(
- self.ACTION_FINAL_EXECUTION, # The action name
- "Go to final execution", # The action text
- IDACtxEntry(self._interactive_final_execution), # The action handler
- None, # Optional: action shortcut
- "Go to the final execution of the current address", # Optional: tooltip
- -1 # Optional: the action icon
+ self.ACTION_FINAL_EXECUTION, # The action name
+ "Go to final execution", # The action text
+ IDACtxEntry(self._interactive_final_execution), # The action handler
+ None, # Optional: action shortcut
+ "Go to the final execution of the current address", # Optional: tooltip
+ -1, # Optional: the action icon
)
# register the action with IDA
@@ -235,10 +236,7 @@ def _uninstall_load_trace(self):
logger.info("Removing the 'Tenet trace file...' menu entry...")
# remove the entry from the File-> menu
- result = ida_kernwin.detach_action_from_menu(
- "File/Load file/",
- self.ACTION_LOAD_TRACE
- )
+ result = ida_kernwin.detach_action_from_menu("File/Load file/", self.ACTION_LOAD_TRACE)
if not result:
logger.warning("Failed to detach action from menu...")
return False
@@ -250,7 +248,7 @@ def _uninstall_load_trace(self):
return False
# delete the entry's icon
- #ida_kernwin.free_custom_icon(self._icon_id_file) # TODO
+ # ida_kernwin.free_custom_icon(self._icon_id_file) # TODO
self._icon_id_file = ida_idaapi.BADADDR
logger.info("Successfully removed the menu entry!")
@@ -260,15 +258,15 @@ def _uninstall_next_execution(self):
result = self._uninstall_action(self.ACTION_NEXT_EXECUTION, self._icon_id_next_execution)
self._icon_id_next_execution = ida_idaapi.BADADDR
return result
-
+
def _uninstall_prev_execution(self):
result = self._uninstall_action(self.ACTION_PREV_EXECUTION, self._icon_id_prev_execution)
self._icon_id_prev_execution = ida_idaapi.BADADDR
return result
-
+
def _uninstall_first_execution(self):
return self._uninstall_action(self.ACTION_FIRST_EXECUTION)
-
+
def _uninstall_final_execution(self):
return self._uninstall_action(self.ACTION_FINAL_EXECUTION)
@@ -285,9 +283,9 @@ def _uninstall_action(self, action, icon_id=ida_idaapi.BADADDR):
logger.info(f"Uninstalled the {action} menu entry")
return True
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# UI Event Handlers
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _breakpoint_changed_hook(self, code, bpt):
"""
@@ -334,8 +332,8 @@ def _popup_hook(self, widget, popup):
widget,
popup,
self.ACTION_NEXT_EXECUTION, # The action ID (see above)
- "Rename", # Relative path of where to add the action
- ida_kernwin.SETMENU_APP # We want to append the action after ^
+ "Rename", # Relative path of where to add the action
+ ida_kernwin.SETMENU_APP, # We want to append the action after ^
)
#
@@ -358,25 +356,25 @@ def _popup_hook(self, widget, popup):
ida_kernwin.attach_action_to_popup(
widget,
popup,
- self.ACTION_FIRST_EXECUTION, # The action ID (see above)
- "Go to.../", # Relative path of where to add the action
- ida_kernwin.SETMENU_APP # We want to append the action after ^
+ self.ACTION_FIRST_EXECUTION, # The action ID (see above)
+ "Go to.../", # Relative path of where to add the action
+ ida_kernwin.SETMENU_APP, # We want to append the action after ^
)
ida_kernwin.attach_action_to_popup(
widget,
popup,
- self.ACTION_FINAL_EXECUTION, # The action ID (see above)
- "Go to.../", # Relative path of where to add the action
- ida_kernwin.SETMENU_APP # We want to append the action after ^
+ self.ACTION_FINAL_EXECUTION, # The action ID (see above)
+ "Go to.../", # Relative path of where to add the action
+ ida_kernwin.SETMENU_APP, # We want to append the action after ^
)
ida_kernwin.attach_action_to_popup(
widget,
popup,
self.ACTION_PREV_EXECUTION, # The action ID (see above)
- "Rename", # Relative path of where to add the action
- ida_kernwin.SETMENU_APP # We want to append the action after ^
+ "Rename", # Relative path of where to add the action
+ ida_kernwin.SETMENU_APP, # We want to append the action after ^
)
#
@@ -406,7 +404,7 @@ def _highlight_disassesmbly(self, lines_out, widget, lines_in):
ctx = self.get_context(IDA_GLOBAL_CTX)
if not ctx.reader:
return
-
+
trail_length = 6
forward_color = self.palette.trail_forward
@@ -415,7 +413,7 @@ def _highlight_disassesmbly(self, lines_out, widget, lines_in):
r, g, b, _ = current_color.getRgb()
current_color = 0xFF << 24 | b << 16 | g << 8 | r
-
+
step_over = False
modifiers = QtGui.QGuiApplication.keyboardModifiers()
step_over = bool(modifiers & QtCore.Qt.ShiftModifier)
@@ -426,8 +424,8 @@ def _highlight_disassesmbly(self, lines_out, widget, lines_in):
backward_trail, forward_trail = {}, {}
trails = [
- (backward_ips, backward_trail, backward_color),
- (forward_ips, forward_trail, forward_color)
+ (backward_ips, backward_trail, backward_color),
+ (forward_ips, forward_trail, forward_color),
]
for addresses, trail, color in trails:
@@ -457,7 +455,7 @@ def _highlight_disassesmbly(self, lines_out, widget, lines_in):
for section in lines_in.sections_lines:
for line in section:
address = line.at.toea()
-
+
if address in backward_trail:
color = backward_trail[address]
elif address in forward_trail:
@@ -467,12 +465,14 @@ def _highlight_disassesmbly(self, lines_out, widget, lines_in):
else:
continue
- entry = ida_kernwin.line_rendering_output_entry_t(line, ida_kernwin.LROEF_FULL_LINE, color)
+ entry = ida_kernwin.line_rendering_output_entry_t(
+ line, ida_kernwin.LROEF_FULL_LINE, color
+ )
lines_out.entries.push_back(entry)
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def ui_breakpoint_changed(self, callback):
register_callback(self._ui_breakpoint_changed_callbacks, callback)
@@ -480,9 +480,11 @@ def ui_breakpoint_changed(self, callback):
def _notify_ui_breakpoint_changed(self, address, code):
notify_callback(self._ui_breakpoint_changed_callbacks, address, code)
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# IDA UI Helpers
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
class IDACtxEntry(ida_kernwin.action_handler_t):
"""
@@ -508,18 +510,23 @@ def update(self, ctx):
"""
return ida_kernwin.AST_ENABLE_ALWAYS
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# IDA UI Event Hooks
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
class DbgHooks(ida_dbg.DBG_Hooks):
def dbg_bpt_changed(self, code, bpt):
pass
+
class UIHooks(ida_kernwin.UI_Hooks):
def get_lines_rendering_info(self, lines_out, widget, lines_in):
pass
+
def ready_to_run(self):
pass
+
def finish_populating_widget_popup(self, widget, popup):
- pass
\ No newline at end of file
+ pass
diff --git a/plugins/tenet/integration/ida_loader.py b/plugins/tenet/integration/ida_loader.py
index 3d44614..7503b64 100644
--- a/plugins/tenet/integration/ida_loader.py
+++ b/plugins/tenet/integration/ida_loader.py
@@ -9,9 +9,9 @@
logger = logging.getLogger("Tenet.IDA.Loader")
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# IDA Plugin Loader
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# This file contains a stub 'plugin' class for the plugin as required by
# IDA Pro. Practically speaking, there should be little to *no* logic placed
@@ -30,12 +30,14 @@
# There should be virtually no reason for you to modify this file.
#
+
def PLUGIN_ENTRY():
"""
Required plugin entry point for IDAPython Plugins.
"""
return TenetIDAPlugin()
+
class TenetIDAPlugin(ida_idaapi.plugin_t):
"""
The IDA plugin stub for Tenet.
@@ -54,9 +56,9 @@ class TenetIDAPlugin(ida_idaapi.plugin_t):
wanted_name = "Tenet"
wanted_hotkey = ""
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# IDA Plugin Overloads
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def init(self):
"""
@@ -90,7 +92,7 @@ def term(self):
logger.debug("IDA term started...")
start = time.time()
- logger.debug("-"*50)
+ logger.debug("-" * 50)
try:
self.core.unload()
@@ -99,7 +101,6 @@ def term(self):
logger.exception("Failed to cleanly unload Tenet from IDA.")
end = time.time()
- logger.debug("-"*50)
-
- logger.debug("IDA term done... (%.3f seconds...)" % (end-start))
+ logger.debug("-" * 50)
+ logger.debug("IDA term done... (%.3f seconds...)" % (end - start))
diff --git a/plugins/tenet/memory.py b/plugins/tenet/memory.py
index 08b4293..f6b49d6 100644
--- a/plugins/tenet/memory.py
+++ b/plugins/tenet/memory.py
@@ -1,17 +1,18 @@
from tenet.hex import HexController
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# memory.py -- Memory Dump Controller
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# The purpose of this file is to house the 'headless' components of the
# memory dump window and its underlying functionality. This is split into
-# a model and controller component, of a typical 'MVC' design pattern.
+# a model and controller component, of a typical 'MVC' design pattern.
#
# As our memory dumps are largely abstracted off a generic 'hex dump',
# there is very little code that actually has to be applied here (for now)
#
+
class MemoryController(HexController):
"""
The Memory Dump Controller (Logic)
@@ -20,4 +21,4 @@ class MemoryController(HexController):
def __init__(self, pctx):
super(MemoryController, self).__init__(pctx)
self._title = "Memory View"
- #self.model.hex_format = HexType.MAGIC
+ # self.model.hex_format = HexType.MAGIC
diff --git a/plugins/tenet/registers.py b/plugins/tenet/registers.py
index 7491b06..c8dfd9a 100644
--- a/plugins/tenet/registers.py
+++ b/plugins/tenet/registers.py
@@ -2,9 +2,9 @@
from tenet.util.misc import register_callback, notify_callback
from tenet.integration.api import DockableWindow, disassembler
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# registers.py -- Register Controller
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# The purpose of this file is to house the 'headless' components of the
# registers window and its underlying functionality. This is split into a
@@ -15,6 +15,7 @@
# enough to demand its own seperate structuring ... yet
#
+
class RegisterController(object):
"""
The Registers Controller (Logic)
@@ -43,7 +44,7 @@ def show(self, target=None, position=0):
return
# the UI has already been created, and is also visible. nothing to do
- if (self.dockable and self.dockable.visible):
+ if self.dockable and self.dockable.visible:
return
#
@@ -63,7 +64,7 @@ def show(self, target=None, position=0):
if self.dockable:
new_dockable.copy_dock_position(self.dockable)
- elif (target or position):
+ elif target or position:
new_dockable.set_dock_position(target, position)
# make the dockable/widget visible
@@ -76,7 +77,7 @@ def hide(self):
"""
# if there is no view/dockable, then there's nothing to try and hide
- if not(self.view and self.dockable):
+ if not (self.view and self.dockable):
return
# hide the dockable, and drop references to the widgets
@@ -180,7 +181,7 @@ def evaluate_expression(self, expression):
return
# a 'command' / alias idx was entered into the shell ('!...' prefix)
- if expression[0] == '!':
+ if expression[0] == "!":
self._handle_command(expression[1:])
return
@@ -189,7 +190,7 @@ def evaluate_expression(self, expression):
# -- e.g '5,218,121'
#
- idx_str = expression.replace(',', '')
+ idx_str = expression.replace(",", "")
try:
target_idx = int(idx_str)
except:
@@ -217,7 +218,7 @@ def _handle_seek_percent(self, expression):
eg: !0, or !100 to skip to the start/end of trace
"""
try:
- target_percent = float(expression) # float, so you could even do 42.1%
+ target_percent = float(expression) # float, so you could even do 42.1%
except:
return False
@@ -229,7 +230,7 @@ def _handle_seek_last(self, expression):
"""
Handle a seek to the last mapped address.
"""
- if expression != 'last':
+ if expression != "last":
return False
last_idx = self.reader.trace.length - 1
@@ -240,7 +241,7 @@ def _handle_seek_last(self, expression):
if not dctx.is_mapped(rebased_ip):
last_good_idx = self.reader.analysis.get_prev_mapped_idx(last_idx)
if last_good_idx == -1:
- return False # navigation is just not gonna happen...
+ return False # navigation is just not gonna happen...
last_idx = last_good_idx
# seek to the last known / good idx that is mapped within the disassembler
@@ -277,6 +278,7 @@ def _breakpoints_changed(self):
return
self.view.refresh()
+
class RegistersModel(object):
"""
The Registers Model (Data)
@@ -286,15 +288,15 @@ def __init__(self, pctx):
self._pctx = pctx
self.reset()
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
self._registers_changed_callbacks = []
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Properties
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
@property
def arch(self):
@@ -310,9 +312,9 @@ def execution_breakpoints(self):
"""
return self._pctx.breakpoints.model.bp_exec
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Public
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def reset(self):
@@ -359,9 +361,9 @@ def set_registers(self, registers, delta=None):
# notify the UI / listeners of the model that an update occurred
self._notify_registers_changed()
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def registers_changed(self, callback):
"""
diff --git a/plugins/tenet/stack.py b/plugins/tenet/stack.py
index 5d104bb..4631dde 100644
--- a/plugins/tenet/stack.py
+++ b/plugins/tenet/stack.py
@@ -4,19 +4,20 @@
from tenet.hex import HexController
from tenet.types import HexType, AuxType
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# stack.py -- Stack Dump Controller
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# The purpose of this file is to house the 'headless' components of the
# stack dump window and its underlying functionality. This is split into
-# a model and controller component, of a typical 'MVC' design pattern.
+# a model and controller component, of a typical 'MVC' design pattern.
#
# The stack dump window abstracts from a simple hex dump. We use the code
# below to configure our underlying hex dump to appear more like a typical
# stack view might instead.
#
+
class StackController(HexController):
"""
The Stack Dump Controller (Logic)
@@ -39,7 +40,7 @@ def follow_in_dump(self, stack_address):
"""
Follow the pointer at a given stack address in the memory dump.
"""
- POINTER_SIZE = self.pctx.reader.arch.POINTER_SIZE
+ POINTER_SIZE = self.pctx.reader.arch.POINTER_SIZE
# align the given stack address (which we will read..)
stack_address &= ~(POINTER_SIZE - 1)
@@ -53,8 +54,8 @@ def follow_in_dump(self, stack_address):
# attempt to carve the data and validity mask from the stack model
try:
- data = self.model.data[relative_index:relative_index+POINTER_SIZE]
- mask = self.model.mask[relative_index:relative_index+POINTER_SIZE]
+ data = self.model.data[relative_index : relative_index + POINTER_SIZE]
+ mask = self.model.mask[relative_index : relative_index + POINTER_SIZE]
except:
return False
@@ -64,10 +65,10 @@ def follow_in_dump(self, stack_address):
# unpack the carved data as a pointer
parsed_address = struct.unpack("I" if POINTER_SIZE == 4 else "Q", data)[0]
-
+
# navigate the memory dump window to the 'pointer' we carved off the stack
self.pctx.memory.navigate(parsed_address)
-
+
def _idx_changed(self, idx):
"""
Override the default hex view idx changed event handler.
@@ -102,4 +103,4 @@ def _idx_changed(self, idx):
# to provide a bit more awarness to pops/rets as they happen
#
- self.navigate(self.reader.sp - self.model.num_bytes_per_line * 3)
\ No newline at end of file
+ self.navigate(self.reader.sp - self.model.num_bytes_per_line * 3)
diff --git a/plugins/tenet/trace/analysis.py b/plugins/tenet/trace/analysis.py
index 0820c5f..b4f114e 100644
--- a/plugins/tenet/trace/analysis.py
+++ b/plugins/tenet/trace/analysis.py
@@ -3,9 +3,9 @@
from tenet.util.log import pmsg
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# analysis.py -- Trace Analysis
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
#
# This file should contain logic to further process, augment, optimize or
# annotate Tenet traces when a binary analysis framework such as IDA /
@@ -19,6 +19,7 @@
# service pointer annotations, and much more.
#
+
class TraceAnalysis(object):
"""
A high level, debugger-like interface for querying Tenet traces.
@@ -32,21 +33,21 @@ def __init__(self, trace, dctx):
self.slide = None
self._analyze()
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Public
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def rebase_pointer(self, address):
"""
Return a rebased version of the given address, if one exists.
"""
for m1, m2 in self._remapped_regions:
- #print(f"m1 start: {m1[0]:08X} address: {address:08X} m1 end: {m1[1]:08X}")
- #print(f"m2 start: {m2[0]:08X} address: {address:08X} m2 end: {m2[1]:08X}")
+ # print(f"m1 start: {m1[0]:08X} address: {address:08X} m1 end: {m1[1]:08X}")
+ # print(f"m2 start: {m2[0]:08X} address: {address:08X} m2 end: {m2[1]:08X}")
if m1[0] <= address <= m1[1]:
- return address + (m2[0] - m1[0])
+ return address + (m2[0] - m1[0])
if m2[0] <= address <= m2[1]:
- return address - (m2[0] - m1[0])
+ return address - (m2[0] - m1[0])
return address
def get_prev_mapped_idx(self, idx):
@@ -59,9 +60,9 @@ def get_prev_mapped_idx(self, idx):
except IndexError:
return -1
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Analysis
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _analyze(self):
"""
@@ -172,7 +173,7 @@ def _analyze_aslr(self):
#
if (hit / seen) > 0.95:
- #print(f"ASLR Slide: {k:08X} Quality: {hit/seen:0.2f} (h {hit} s {seen} e {expected})")
+ # print(f"ASLR Slide: {k:08X} Quality: {hit/seen:0.2f} (h {hit} s {seen} e {expected})")
slide = k
break
@@ -249,5 +250,5 @@ def _analyze_unmapped(self):
else:
last_good_idx = seg_base + relative_idx
- #print(f" - Unmapped Entry Points: {len(unmapped_entries)}")
+ # print(f" - Unmapped Entry Points: {len(unmapped_entries)}")
self._unmapped_entry_points = unmapped_entries
diff --git a/plugins/tenet/trace/arch/amd64.py b/plugins/tenet/trace/arch/amd64.py
index 5b4a741..2f7ee1b 100644
--- a/plugins/tenet/trace/arch/amd64.py
+++ b/plugins/tenet/trace/arch/amd64.py
@@ -2,6 +2,7 @@ class ArchAMD64:
"""
AMD64 CPU Architecture Definition.
"""
+
MAGIC = 0x41424344
POINTER_SIZE = 8
@@ -9,8 +10,7 @@ class ArchAMD64:
IP = "RIP"
SP = "RSP"
- REGISTERS = \
- [
+ REGISTERS = [
"RAX",
"RBX",
"RCX",
@@ -27,5 +27,5 @@ class ArchAMD64:
"R13",
"R14",
"R15",
- "RIP"
- ]
\ No newline at end of file
+ "RIP",
+ ]
diff --git a/plugins/tenet/trace/arch/x86.py b/plugins/tenet/trace/arch/x86.py
index 62ac474..a973350 100644
--- a/plugins/tenet/trace/arch/x86.py
+++ b/plugins/tenet/trace/arch/x86.py
@@ -2,6 +2,7 @@ class ArchX86:
"""
x86 CPU Architecture Definition.
"""
+
MAGIC = 0x386
POINTER_SIZE = 4
@@ -9,15 +10,4 @@ class ArchX86:
IP = "EIP"
SP = "ESP"
- REGISTERS = \
- [
- "EAX",
- "EBX",
- "ECX",
- "EDX",
- "EBP",
- "ESP",
- "ESI",
- "EDI",
- "EIP"
- ]
\ No newline at end of file
+ REGISTERS = ["EAX", "EBX", "ECX", "EDX", "EBP", "ESP", "ESI", "EDI", "EIP"]
diff --git a/plugins/tenet/trace/reader.py b/plugins/tenet/trace/reader.py
index 90ef9d6..2a2b2c3 100644
--- a/plugins/tenet/trace/reader.py
+++ b/plugins/tenet/trace/reader.py
@@ -11,9 +11,9 @@
logger = logging.getLogger("Tenet.Trace.Reader")
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# reader.py -- Trace Reader
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
#
# NOTE/PREFACE: If you have not already, please read through the overview
# comment at the start of the TraceFile (file.py) code. This file (the
@@ -39,6 +39,7 @@
# billions) of instructions.
#
+
class TraceDelta(object):
"""
Trace Delta
@@ -49,6 +50,7 @@ def __init__(self, registers, mem_read, mem_write):
self.mem_reads = mem_read
self.mem_writes = mem_write
+
class TraceReader(object):
"""
A high level, debugger-like interface for querying Tenet traces.
@@ -66,15 +68,15 @@ def __init__(self, filepath, architecture, dctx=None):
self._idx_cached_registers = -1
self._cached_registers = {}
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
self._idx_changed_callbacks = []
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Trace Properties
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
@property
def ip(self):
@@ -128,9 +130,9 @@ def delta(self):
return TraceDelta(regs, read_set, write_set)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Trace Navigation
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def seek(self, idx):
"""
@@ -169,7 +171,7 @@ def seek_to_final(self, address, access_type, length=1):
Returns True on success, False otherwise.
"""
- return self.seek_to_prev(address, access_type, length, self.trace.length-1)
+ return self.seek_to_prev(address, access_type, length, self.trace.length - 1)
def seek_to_next(self, address, access_type, length=1, start_idx=None):
"""
@@ -386,9 +388,9 @@ def _step_over_backward(self, n):
self.seek(prev_idx)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Timestamp API
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
#
# in this section, you will find references to 'resolution'. this is a
@@ -428,7 +430,7 @@ def get_executions_between(self, address, start_idx, end_idx, resolution=1):
assert resolution > 0
resolution = max(1, resolution)
- #logger.debug(f"Fetching executions from {start_idx:,} --> {end_idx:,} (res {resolution:0.2f}, normalized {resolution:0.2f}) for address 0x{address:08X}")
+ # logger.debug(f"Fetching executions from {start_idx:,} --> {end_idx:,} (res {resolution:0.2f}, normalized {resolution:0.2f}) for address 0x{address:08X}")
try:
mapped_address = self.trace.get_mapped_ip(address)
@@ -447,7 +449,7 @@ def get_executions_between(self, address, start_idx, end_idx, resolution=1):
# clamp the segment end if it extends past our segment
seg_end = min(seg_base + seg.length, end_idx)
- #logger.debug(f"Searching seg #{seg.id}, {seg_base:,} --> {seg_end:,}")
+ # logger.debug(f"Searching seg #{seg.id}, {seg_base:,} --> {seg_end:,}")
# snip the segment to start from the given global idx
relative_idx = idx - seg_base
@@ -471,14 +473,14 @@ def get_executions_between(self, address, start_idx, end_idx, resolution=1):
next_resolution_target = next_resolution_index * resolution
idx = round(next_resolution_target)
- #print(f"GOT HIT @ {current_idx:,}, skipping to {idx:,} (y = {current_idx/resolution})")
- #print(f" - Current resolution index {current_resolution_index}")
- #print(f" - Next resolution index {next_resolution_index}")
- #print(f" - Next resolution target {next_resolution_target:,}")
+ # print(f"GOT HIT @ {current_idx:,}, skipping to {idx:,} (y = {current_idx/resolution})")
+ # print(f" - Current resolution index {current_resolution_index}")
+ # print(f" - Next resolution index {next_resolution_index}")
+ # print(f" - Next resolution target {next_resolution_target:,}")
- seg_ips = seg.ips[idx-seg_base:]
+ seg_ips = seg.ips[idx - seg_base :]
- #logger.debug(f"Returning hits {output}")
+ # logger.debug(f"Returning hits {output}")
return output
def get_memory_accesses(self, address, resolution=1):
@@ -487,29 +489,34 @@ def get_memory_accesses(self, address, resolution=1):
"""
return self.get_memory_accesses_between(address, 0, self.trace.length, resolution)
-
def get_memory_reads_between(self, address, start_idx, end_idx, resolution=1):
"""
Return a list of timestamps that read from a given memory address in the given slice.
"""
- reads, _ = self.get_memory_accesses_between(address, start_idx, end_idx, resolution, BreakpointType.READ)
+ reads, _ = self.get_memory_accesses_between(
+ address, start_idx, end_idx, resolution, BreakpointType.READ
+ )
return reads
def get_memory_writes_between(self, address, start_idx, end_idx, resolution=1):
"""
Return a list of timestamps that write to a given memory address in the given slice.
"""
- _, writes = self.get_memory_accesses_between(address, start_idx, end_idx, resolution, BreakpointType.WRITE)
+ _, writes = self.get_memory_accesses_between(
+ address, start_idx, end_idx, resolution, BreakpointType.WRITE
+ )
return writes
- def get_memory_accesses_between(self, address, start_idx, end_idx, resolution=1, access_type=BreakpointType.ACCESS):
+ def get_memory_accesses_between(
+ self, address, start_idx, end_idx, resolution=1, access_type=BreakpointType.ACCESS
+ ):
"""
Return a tuple of lists (read, write) containing timestamps that access a given memory address in the given slice.
"""
assert resolution > 0
resolution = max(1, resolution)
- #logger.debug(f"MEMORY ACCESSES @ 0x{address:08X} // {start_idx:,} --> {end_idx:,} (rez {resolution:0.2f})")
+ # logger.debug(f"MEMORY ACCESSES @ 0x{address:08X} // {start_idx:,} --> {end_idx:,} (rez {resolution:0.2f})")
mapped_address = self.trace.get_mapped_address(address)
if mapped_address == -1:
@@ -534,7 +541,7 @@ def get_memory_accesses_between(self, address, start_idx, end_idx, resolution=1,
# clamp the segment end if it extends past our segment
seg_end = min(seg_base + seg.length, end_idx)
- #logger.debug(f"seg #{seg.id}, {seg.base_idx:,} --> {seg.base_idx+seg.length:,} -- IDX PTR {idx:,}")
+ # logger.debug(f"seg #{seg.id}, {seg.base_idx:,} --> {seg.base_idx+seg.length:,} -- IDX PTR {idx:,}")
mem_sets = []
@@ -566,12 +573,12 @@ def get_memory_accesses_between(self, address, start_idx, end_idx, resolution=1,
#
if not (masks[cumulative_index] & access_mask):
- addrs = addrs[index+1:]
- idxs = idxs[index+1:]
+ addrs = addrs[index + 1 :]
+ idxs = idxs[index + 1 :]
cumulative_index += 1
continue
- #print(f"FOUND ACCESS TO {self.trace.mem_addrs[mapped_address]:08X} (mask {masks[cumulative_index]:02X}), IDX {current_idx:,}")
+ # print(f"FOUND ACCESS TO {self.trace.mem_addrs[mapped_address]:08X} (mask {masks[cumulative_index]:02X}), IDX {current_idx:,}")
# we got a hit within the resolution window, save it
output.append(current_idx)
@@ -581,7 +588,7 @@ def get_memory_accesses_between(self, address, start_idx, end_idx, resolution=1,
next_resolution_index = current_resolution_index + 1
next_resolution_target = next_resolution_index * resolution
current_target = round(next_resolution_target)
- #print(f"NEXT TARGET: {current_target:,}")
+ # print(f"NEXT TARGET: {current_target:,}")
# now skip to the next resolution window
skip_index = bisect.bisect_left(idxs, current_target - seg_base)
@@ -591,7 +598,7 @@ def get_memory_accesses_between(self, address, start_idx, end_idx, resolution=1,
addrs = addrs[skip_index:]
idxs = idxs[skip_index:]
- cumulative_index += (skip_index - index)
+ cumulative_index += skip_index - index
next_resolution[i] = current_target
@@ -603,44 +610,56 @@ def get_memory_region_reads(self, address, length, resolution=1):
"""
Return a list of timestamps that read from the given memory region.
"""
- reads, _ = self.get_memory_region_accesses_between(address, length, 0, self.trace.length, resolution, BreakpointType.READ)
+ reads, _ = self.get_memory_region_accesses_between(
+ address, length, 0, self.trace.length, resolution, BreakpointType.READ
+ )
return reads
def get_memory_region_reads_between(self, address, length, start_idx, end_idx, resolution=1):
"""
Return a list of timestamps that read from the given memory region in the given time slice.
"""
- reads, _ = self.get_memory_region_accesses_between(address, length, start_idx, end_idx, resolution, BreakpointType.READ)
+ reads, _ = self.get_memory_region_accesses_between(
+ address, length, start_idx, end_idx, resolution, BreakpointType.READ
+ )
return reads
def get_memory_region_writes(self, address, length, resolution=1):
"""
Return a list of timestamps that write to the given memory region.
"""
- _, writes = self.get_memory_region_accesses_between(address, length, 0, self.trace.length, resolution, BreakpointType.WRITE)
+ _, writes = self.get_memory_region_accesses_between(
+ address, length, 0, self.trace.length, resolution, BreakpointType.WRITE
+ )
return writes
def get_memory_region_writes_between(self, address, length, start_idx, end_idx, resolution=1):
"""
Return a list of timestamps that write to the given memory region in the given time slice.
"""
- _, writes = self.get_memory_region_accesses_between(address, length, start_idx, end_idx, resolution, BreakpointType.WRITE)
+ _, writes = self.get_memory_region_accesses_between(
+ address, length, start_idx, end_idx, resolution, BreakpointType.WRITE
+ )
return writes
def get_memory_region_accesses(self, address, length, resolution=1):
"""
Return a tuple of (read, write) containing timestamps that access the given memory region.
"""
- return self.get_memory_region_accesses_between(address, length, 0, self.trace.length, resolution)
+ return self.get_memory_region_accesses_between(
+ address, length, 0, self.trace.length, resolution
+ )
- def get_memory_region_accesses_between(self, address, length, start_idx, end_idx, resolution=1, access_type=BreakpointType.ACCESS):
+ def get_memory_region_accesses_between(
+ self, address, length, start_idx, end_idx, resolution=1, access_type=BreakpointType.ACCESS
+ ):
"""
Return a tuple of (read, write) containing timestamps that access the given memory region in the given time slice.
"""
assert resolution > 0
resolution = max(1, resolution)
- #logger.debug(f"REGION ACCESS BETWEEN @ 0x{address:08X} + {length} // {start_idx:,} --> {end_idx:,} (rez {resolution:0.2f})")
+ # logger.debug(f"REGION ACCESS BETWEEN @ 0x{address:08X} + {length} // {start_idx:,} --> {end_idx:,} (rez {resolution:0.2f})")
reads, writes = [], []
targets = self._region_to_targets(address, length)
@@ -662,8 +681,8 @@ def get_memory_region_accesses_between(self, address, length, start_idx, end_idx
# clamp the segment end if it extends past our segment
seg_end = min(seg_base + seg.length, end_idx)
- #print("-"*50)
- #print(f"seg #{seg.id}, {seg.base_idx:,} --> {seg.base_idx+seg.length:,} -- IDX PTR {idx:,}")
+ # print("-"*50)
+ # print(f"seg #{seg.id}, {seg.base_idx:,} --> {seg.base_idx+seg.length:,} -- IDX PTR {idx:,}")
mem_sets = []
@@ -722,10 +741,10 @@ def get_memory_region_accesses_between(self, address, length, start_idx, end_idx
if not target_mask:
continue
- #print("CLOSE! DOES MASK MATCH?")
- #print(f" TARGET: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {target_mask:02X}")
- #print(f" CURRENT: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {masks[index]:02X}")
- #print(f" RESULT: {target_mask & masks[index]:02X}")
+ # print("CLOSE! DOES MASK MATCH?")
+ # print(f" TARGET: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {target_mask:02X}")
+ # print(f" CURRENT: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {masks[index]:02X}")
+ # print(f" RESULT: {target_mask & masks[index]:02X}")
#
# got the first hit for this set.. great! save it and
@@ -1061,7 +1080,7 @@ def _find_next_mem_op(self, address, bp_type, idx=None):
break
# the hit was no good.. 'step' past it and keep searching
- search_addrs = search_addrs[index+1:]
+ search_addrs = search_addrs[index + 1 :]
normal_index += 1
#
@@ -1071,7 +1090,7 @@ def _find_next_mem_op(self, address, bp_type, idx=None):
#
if accesses:
- return min(accesses, key=lambda x:abs(x-idx))
+ return min(accesses, key=lambda x: abs(x - idx))
# fail, reached end of trace
return -1
@@ -1133,11 +1152,11 @@ def _find_prev_mem_op(self, address, bp_type, idx=None):
break
# the hit was no good.. 'step' past it and keep searching
- search_addrs = search_addrs[reverse_index+1:]
+ search_addrs = search_addrs[reverse_index + 1 :]
normal_index -= 1
if accesses:
- return min(accesses, key=lambda x:abs(x-idx))
+ return min(accesses, key=lambda x: abs(x - idx))
# fail, reached start of trace
return -1
@@ -1160,14 +1179,16 @@ def find_next_region_access(self, address, length, idx=None):
"""
return self._find_next_region_access(address, length, idx, BreakpointType.ACCESS)
- def _find_next_region_access(self, address, length, idx=None, access_type=BreakpointType.ACCESS):
+ def _find_next_region_access(
+ self, address, length, idx=None, access_type=BreakpointType.ACCESS
+ ):
"""
Return the next timestamp to access the given memory region.
"""
if idx is None:
idx = self.idx + 1
- #logger.debug(f"FIND NEXT REGION ACCESS FOR 0x{address:08X} -> 0x{address+length:08X} STARTING AT IDX {idx:,}")
+ # logger.debug(f"FIND NEXT REGION ACCESS FOR 0x{address:08X} -> 0x{address+length:08X} STARTING AT IDX {idx:,}")
accesses, mem_sets = [], []
targets = self._region_to_targets(address, length)
@@ -1206,7 +1227,7 @@ def _find_next_region_access(self, address, length, idx=None, access_type=Breakp
try:
index = addrs.index(address_id)
first_hit = min(index, first_hit)
- #print(f"HIT ON 0x{self.trace.mem_addrs[address_id]:08X} @ IDX {seg_base+idxs[index]}")
+ # print(f"HIT ON 0x{self.trace.mem_addrs[address_id]:08X} @ IDX {seg_base+idxs[index]}")
#
# no hits for any bytes within this aligned address,
@@ -1237,10 +1258,10 @@ def _find_next_region_access(self, address, length, idx=None, access_type=Breakp
if not target_mask:
continue
- #print("CLOSE! DOES MASK MATCH?")
- #print(f" TARGET: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {target_mask:02X}")
- #print(f" CURRENT: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {masks[index]:02X}")
- #print(f" RESULT: {target_mask & masks[index]:02X}")
+ # print("CLOSE! DOES MASK MATCH?")
+ # print(f" TARGET: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {target_mask:02X}")
+ # print(f" CURRENT: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {masks[index]:02X}")
+ # print(f" RESULT: {target_mask & masks[index]:02X}")
#
# got the first hit for this set.. great! save it and
@@ -1252,7 +1273,7 @@ def _find_next_region_access(self, address, length, idx=None, access_type=Breakp
if hit_idx < idx:
continue
accesses.append(hit_idx)
- #print(f"FOUND HIT AT IDX {hit_idx}")
+ # print(f"FOUND HIT AT IDX {hit_idx}")
break
#
@@ -1262,8 +1283,8 @@ def _find_next_region_access(self, address, length, idx=None, access_type=Breakp
#
if accesses:
- #print("ALL ACCESSES", accesses)
- return min(accesses, key=lambda x:abs(x-idx))
+ # print("ALL ACCESSES", accesses)
+ return min(accesses, key=lambda x: abs(x - idx))
# fail, reached end of trace
return -1
@@ -1287,7 +1308,7 @@ def find_prev_region_access(self, address, length, idx=None, access_type=Breakpo
if idx is None:
idx = self.idx - 1
- #logger.debug(f"FIND PREV REGION ACCESS FOR 0x{address:08X} -> 0x{address+length:08X} STARTING AT IDX {idx:,}")
+ # logger.debug(f"FIND PREV REGION ACCESS FOR 0x{address:08X} -> 0x{address+length:08X} STARTING AT IDX {idx:,}")
accesses, mem_sets = [], []
targets = self._region_to_targets(address, length)
@@ -1327,7 +1348,7 @@ def find_prev_region_access(self, address, length, idx=None, access_type=Breakpo
try:
index = reverse_addrs.index(address_id)
first_hit = min(index, first_hit)
- #print(f"HIT ON 0x{self.trace.mem_addrs[address_id]:08X} @ IDX {seg_base+idxs[index]}")
+ # print(f"HIT ON 0x{self.trace.mem_addrs[address_id]:08X} @ IDX {seg_base+idxs[index]}")
#
# no hits for any bytes within this aligned address,
@@ -1342,7 +1363,7 @@ def find_prev_region_access(self, address, length, idx=None, access_type=Breakpo
# because we are searching FORWARD, deeper into time
#
- #if seg_base + idxs[index] <= idx:
+ # if seg_base + idxs[index] <= idx:
# print(f"TOSSING {seg_base+idxs[index]:,}, TOO CLOSE!")
# continue
@@ -1368,10 +1389,10 @@ def find_prev_region_access(self, address, length, idx=None, access_type=Breakpo
if not target_mask:
continue
- #print("CLOSE! DOES MASK MATCH?")
- #print(f" TARGET: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {target_mask:02X}")
- #print(f" CURRENT: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {masks[index]:02X}")
- #print(f" RESULT: {target_mask & masks[index]:02X}")
+ # print("CLOSE! DOES MASK MATCH?")
+ # print(f" TARGET: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {target_mask:02X}")
+ # print(f" CURRENT: 0x{self.trace.mem_addrs[address_id]:08X} MASK: {masks[index]:02X}")
+ # print(f" RESULT: {target_mask & masks[index]:02X}")
normal_index = num_addrs - reverse_index - 1
@@ -1385,7 +1406,7 @@ def find_prev_region_access(self, address, length, idx=None, access_type=Breakpo
if hit_idx > idx:
continue
accesses.append(hit_idx)
- #print(f"FOUND HIT AT IDX {hit_idx}")
+ # print(f"FOUND HIT AT IDX {hit_idx}")
break
#
@@ -1395,7 +1416,7 @@ def find_prev_region_access(self, address, length, idx=None, access_type=Breakpo
#
if accesses:
- return min(accesses, key=lambda x:abs(x-idx))
+ return min(accesses, key=lambda x: abs(x - idx))
# fail, reached end of trace
return -1
@@ -1408,14 +1429,14 @@ def find_next_register_change(self, reg_name, idx=None):
idx = self.idx + 1
# if the idx is invalid, then there is nothing to do
- if not(0 < idx < self.trace.length):
+ if not (0 < idx < self.trace.length):
return -1
starting_segment = self.trace.get_segment(idx)
target_mask_ids = self.trace.get_reg_mask_ids_containing(reg_name)
# search forward through the remaining segments
- for seg_id in range(starting_segment.id , len(self.trace.segments)):
+ for seg_id in range(starting_segment.id, len(self.trace.segments)):
seg = self.trace.segments[seg_id]
seg_base = seg.base_idx
@@ -1454,7 +1475,7 @@ def find_prev_register_change(self, reg_name, idx=None):
idx = self.idx - 1
# if the idx is invalid, then there is nothing to do
- if not(0 < idx < self.trace.length):
+ if not (0 < idx < self.trace.length):
return -1
starting_segment = self.trace.get_segment(idx)
@@ -1493,7 +1514,7 @@ def _region_to_targets(self, address, length):
"""
Convert an (address, len) region definition into a list of [(addr_id, access_mask), ...].
"""
- ADDRESS_ALIGMENT = 8 # TODO: this is gross!
+ ADDRESS_ALIGMENT = 8 # TODO: this is gross!
output = []
#
@@ -1507,10 +1528,10 @@ def _region_to_targets(self, address, length):
mapped_address = self.trace.get_mapped_address(address)
if mapped_address != -1:
output.append((mapped_address, aligned_mask))
- #print(f"aligned: 0x{aligned_address} - mask {aligned_mask}")
+ # print(f"aligned: 0x{aligned_address} - mask {aligned_mask}")
# the bytes consumed so far
- length -= (ADDRESS_ALIGMENT - (address - aligned_address))
+ length -= ADDRESS_ALIGMENT - (address - aligned_address)
aligned_address += ADDRESS_ALIGMENT
# process the remaining.. aligned.. addresses
@@ -1530,7 +1551,7 @@ def _region_to_targets(self, address, length):
mask_length = ADDRESS_ALIGMENT if length > ADDRESS_ALIGMENT else length
access_mask = self.trace.get_aligned_address_mask(aligned_address, mask_length)
- #print(f"aligned: 0x{aligned_address:08X} - mask {access_mask:02X} - mask len {mask_length}")
+ # print(f"aligned: 0x{aligned_address:08X} - mask {access_mask:02X} - mask len {mask_length}")
output.append((mapped_address, access_mask))
@@ -1538,14 +1559,14 @@ def _region_to_targets(self, address, length):
length -= ADDRESS_ALIGMENT
aligned_address += ADDRESS_ALIGMENT
- #for addr, mask in output:
+ # for addr, mask in output:
# print(f"TARGET {self.trace.mem_addrs[addr]:08X} MASK {mask:02X}")
return output
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# State API
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def get_ip(self, idx=None):
"""
@@ -1643,7 +1664,7 @@ def get_registers(self, reg_names=None, idx=None):
# discard the found register from the search set
target_registers.remove(reg_name)
- #print(f"Finished Seg #{segment.id}, still missing {target_registers}")
+ # print(f"Finished Seg #{segment.id}, still missing {target_registers}")
# found all the desired registers!
if not target_registers:
@@ -1682,7 +1703,7 @@ def get_memory(self, address, length, idx=None):
if idx is None:
idx = self.idx
- #print(f"STARTING MEM FETCH AT IDX {idx} (reader @ {self.idx})")
+ # print(f"STARTING MEM FETCH AT IDX {idx} (reader @ {self.idx})")
buffer = TraceMemory(address, length)
#
@@ -1701,7 +1722,7 @@ def get_memory(self, address, length, idx=None):
# translate the aligned addresses to their mapped addresses (a simple id)
mapped_address = get_mapped_address(address)
- #print(f"SHOULD SEARCH? {address:08X} --> {mapped_address}")
+ # print(f"SHOULD SEARCH? {address:08X} --> {mapped_address}")
#
# if the symbolic address (a mapped id) doesn't appear in the trace
@@ -1717,7 +1738,7 @@ def get_memory(self, address, length, idx=None):
#
missing_mem[mapped_address] = mem_masks[mapped_address]
- #print(f"MISSING 0x{address:08x} - MASK {mem_masks[mapped_address]:02X}")
+ # print(f"MISSING 0x{address:08x} - MASK {mem_masks[mapped_address]:02X}")
missing_mem.pop(-1, None)
@@ -1729,8 +1750,7 @@ def get_memory(self, address, length, idx=None):
seg = starting_seg
# NOTE: writes should have priority in this list
- mem_sets = \
- [
+ mem_sets = [
(seg.read_idxs, seg.read_addrs, seg.read_masks),
(seg.write_idxs, seg.write_addrs, seg.write_masks),
]
@@ -1751,7 +1771,7 @@ def get_memory(self, address, length, idx=None):
#
relative_idx = idx - starting_seg.base_idx
- #print(f"ATTEMPTING TO SLICE AT RELATIVE IDX {relative_idx} (idx {idx})")
+ # print(f"ATTEMPTING TO SLICE AT RELATIVE IDX {relative_idx} (idx {idx})")
index = bisect.bisect_right(idxs, relative_idx)
idxs = idxs[:index]
@@ -1766,8 +1786,8 @@ def get_memory(self, address, length, idx=None):
for hit_id in range(len(addrs) - 1, -1, -1):
current_address = addrs[hit_id]
missing_mask = missing_mem.get(current_address, 0)
- #print(f"MEM ACCESS {self.trace.mem_addrs[current_address]:08X}")
- #print(f" - MISSING MASK? {missing_mask:02X}")
+ # print(f"MEM ACCESS {self.trace.mem_addrs[current_address]:08X}")
+ # print(f" - MISSING MASK? {missing_mask:02X}")
# the current memory access does not fall into the region
# we care about... ignore it and keep moving
@@ -1786,7 +1806,7 @@ def get_memory(self, address, length, idx=None):
#
for mapped_address, hits in segment_hits.items():
- #print(f"PROCESSING HIT {self.trace.mem_addrs[mapped_address]:08X}")
+ # print(f"PROCESSING HIT {self.trace.mem_addrs[mapped_address]:08X}")
#
# sort the hits to an aligned address by highest idx (most-recent)
@@ -1794,7 +1814,7 @@ def get_memory(self, address, length, idx=None):
#
hits = sorted(hits, reverse=True)
- #print(hits)
+ # print(hits)
#
# go through each hit for the aligned address, until its value
@@ -1807,8 +1827,8 @@ def get_memory(self, address, length, idx=None):
missing_mask = missing_mem[mapped_address]
current_mask = masks[hit_id]
- #assert relative_idx < (idx - seg.base_idx), f"rel {relative_idx} vs {idx} .. {idx - seg.base_idx}"
- #print(f"rel {relative_idx} vs {idx} .. {idx - seg.base_idx}")
+ # assert relative_idx < (idx - seg.base_idx), f"rel {relative_idx} vs {idx} .. {idx - seg.base_idx}"
+ # print(f"rel {relative_idx} vs {idx} .. {idx - seg.base_idx}")
# if this access doesn't contain any new data of interest, ignore it
if not missing_mask & current_mask:
@@ -1816,9 +1836,9 @@ def get_memory(self, address, length, idx=None):
found_mask = missing_mask & current_mask
found_mem = seg.get_mem_data(hit_id, set_id, found_mask)
- #print(f"FOUND MEM {found_mem} FOUND MASK {found_mask:02X}")
- #print(f" - ADDR: 0x{found_mem.address:08X}")
- #print(f" - BADDR: 0x{buffer.address:08X}, LEN {buffer.length}")
+ # print(f"FOUND MEM {found_mem} FOUND MASK {found_mask:02X}")
+ # print(f" - ADDR: 0x{found_mem.address:08X}")
+ # print(f" - BADDR: 0x{buffer.address:08X}, LEN {buffer.length}")
# update the output buffer with the found memory
buffer.update(found_mem)
@@ -1839,7 +1859,7 @@ def get_memory(self, address, length, idx=None):
# attempt to resolve the remaining missing memory
#
- for seg_id in range(starting_seg.id-1, -1, -1):
+ for seg_id in range(starting_seg.id - 1, -1, -1):
seg = self.trace.segments[seg_id]
mem_delta = seg.mem_delta
@@ -1854,7 +1874,7 @@ def get_memory(self, address, length, idx=None):
for mapped_address, missing_mask in missing_mem.items():
# skip the current address if it doesn't get touched by this seg
- if not(mapped_address in mem_delta):
+ if not (mapped_address in mem_delta):
continue
#
@@ -1896,12 +1916,12 @@ def get_memory(self, address, length, idx=None):
other_remaining = 8 - other_index
overlap = min(buffer_remaining, other_remaining)
- #print(f"HIT 0x{other_address:08X} IN SEG {seg_id} (started from {starting_seg.id})", ' '.join(["%02X" % x for x in mv.value]))
+ # print(f"HIT 0x{other_address:08X} IN SEG {seg_id} (started from {starting_seg.id})", ' '.join(["%02X" % x for x in mv.value]))
for i in range(overlap):
- if (found_mask >> (other_index+i)) & 1:
- #print(f"- GRABBING BYTE @ 0x{other_address+other_index+i:08X}, ({mv.value[other_index+i]:02X})")
- buffer.data[buffer_index+i] = mv.value[other_index+i]
- buffer.mask[buffer_index+i] = 0xFF
+ if (found_mask >> (other_index + i)) & 1:
+ # print(f"- GRABBING BYTE @ 0x{other_address+other_index+i:08X}, ({mv.value[other_index+i]:02X})")
+ buffer.data[buffer_index + i] = mv.value[other_index + i]
+ buffer.mask[buffer_index + i] = 0xFF
missing_mem[mapped_address] = missing_mask
@@ -1909,7 +1929,7 @@ def get_memory(self, address, length, idx=None):
for mapped_address in to_remove:
missing_mem.pop(mapped_address)
- #print("STILL MISSING", ["0x%08X" % self.trace.mem_addrs[x] for x in missing_mem])
+ # print("STILL MISSING", ["0x%08X" % self.trace.mem_addrs[x] for x in missing_mem])
# return the final / found buffer
return buffer
@@ -1927,12 +1947,12 @@ def read_pointer(self, address, idx=None):
if not len(set(buffer.mask)) == 1 and buffer.mask[0] == 0xFF:
raise ValueError("Could not fully resolve memory at address")
- pack_fmt = 'Q' if self.arch.POINTER_SIZE == 8 else 'I'
+ pack_fmt = "Q" if self.arch.POINTER_SIZE == 8 else "I"
return struct.unpack(pack_fmt, buffer.data)[0]
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def idx_changed(self, callback):
"""
diff --git a/plugins/tenet/trace/types.py b/plugins/tenet/trace/types.py
index be3f747..671be5b 100644
--- a/plugins/tenet/trace/types.py
+++ b/plugins/tenet/trace/types.py
@@ -1,17 +1,18 @@
import array
+
class TraceMemory(object):
"""
A Trace Memory Buffer.
- TODO: this is pretty trash / overraught and should be refactored. also
- this can probably be moved into tenet.types?
+ TODO: this is pretty trash / overraught and should be refactored. also
+ this can probably be moved into tenet.types?
"""
def __init__(self, address, length):
self.address = address
- self.data = array.array('B', [0]) * length
- self.mask = array.array('B', [0]) * length
+ self.data = array.array("B", [0]) * length
+ self.mask = array.array("B", [0]) * length
def __contains__(self, address):
if self.address <= address < self.end_address:
@@ -38,10 +39,10 @@ def consume(self, other):
#
if new_length > self.length:
- new_data = array.array('B', [0]) * new_length
- new_mask = array.array('B', [0]) * new_length
- new_data[:self.length] = self.data[:self.length]
- new_mask[:self.length] = self.mask[:self.length]
+ new_data = array.array("B", [0]) * new_length
+ new_mask = array.array("B", [0]) * new_length
+ new_data[: self.length] = self.data[: self.length]
+ new_mask[: self.length] = self.mask[: self.length]
self.data = new_data
self.mask = new_mask
@@ -69,16 +70,16 @@ def update(self, other):
this_length_left = self.length - this_start
overlapped_length = min(other_length_left, this_length_left)
- #print('-'*50)
- #print(f" Self Addr 0x{self.address:08X}, Len {self.length}")
- #print(f"Other Addr 0x{other.address:08X}, Len {other.length}")
- #print(f" Overlapping Bytes: {overlapped_length}, self start {this_start}, other start {other_start}")
+ # print('-'*50)
+ # print(f" Self Addr 0x{self.address:08X}, Len {self.length}")
+ # print(f"Other Addr 0x{other.address:08X}, Len {other.length}")
+ # print(f" Overlapping Bytes: {overlapped_length}, self start {this_start}, other start {other_start}")
for i in range(overlapped_length):
- if other.mask[other_start+i]:
- self.data[this_start+i] = other.data[other_start+i]
- self.mask[this_start+i] = 0xFF
+ if other.mask[other_start + i]:
+ self.data[this_start + i] = other.data[other_start + i]
+ self.mask[this_start + i] = 0xFF
def __str__(self):
output = ["%02X" % byte if mask else "??" for byte, mask in zip(self.data, self.mask)]
- return ' '.join(output)
\ No newline at end of file
+ return " ".join(output)
diff --git a/plugins/tenet/types.py b/plugins/tenet/types.py
index 23fefed..825d58c 100644
--- a/plugins/tenet/types.py
+++ b/plugins/tenet/types.py
@@ -1,72 +1,80 @@
import enum
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# types.py -- Plugin Types
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
#
# This purpose of this file is to host basic types / primitievs that
-# may need to be used cross-plugin, and could be prone to causing
+# may need to be used cross-plugin, and could be prone to causing
# cyclic dependency problems if left with their respective subsystems.
#
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
# Hexdump Types
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
class HexType(enum.Enum):
- BYTE = 0
- SHORT = 1
- DWORD = 2
- QWORD = 3
+ BYTE = 0
+ SHORT = 1
+ DWORD = 2
+ QWORD = 3
POINTER = 4
- MAGIC = 5
+ MAGIC = 5
+
class AuxType(enum.Enum):
- NONE = 0
+ NONE = 0
ASCII = 1
STACK = 2
-HEX_TYPE_WIDTH = \
-{
- HexType.BYTE: 1,
- HexType.SHORT: 2,
- HexType.DWORD: 4,
- HexType.QWORD: 8,
+
+HEX_TYPE_WIDTH = {
+ HexType.BYTE: 1,
+ HexType.SHORT: 2,
+ HexType.DWORD: 4,
+ HexType.QWORD: 8,
HexType.POINTER: 8, # XXX: should be 4 or 8
- HexType.MAGIC: 1,
+ HexType.MAGIC: 1,
}
+
class HexItem(object):
def __init__(self, value, mask, width, item_type):
self.value = value
self.mask = mask
- self.width = width # width in bytes
+ self.width = width # width in bytes
self.type = item_type
-#-----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
# Breakpoint Types
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
class BreakpointType(enum.IntEnum):
- NONE = 1 << 0
- READ = 1 << 1
- WRITE = 1 << 2
- EXEC = 1 << 3
- ACCESS = (READ | WRITE)
+ NONE = 1 << 0
+ READ = 1 << 1
+ WRITE = 1 << 2
+ EXEC = 1 << 3
+ ACCESS = READ | WRITE
+
class BreakpointEvent(enum.Enum):
- ADDED = 0
- REMOVED = 1
- ENABLED = 2
+ ADDED = 0
+ REMOVED = 1
+ ENABLED = 2
DISABLED = 3
+
class TraceBreakpoint(object):
"""
A simple class to encapsulate the properties of a breakpoint definition.
"""
+
def __init__(self, address, access_type=BreakpointType.NONE, length=1):
- assert not(address is None)
+ assert not (address is None)
self.type = access_type
self.address = address
self.length = length
- self.enabled = True
\ No newline at end of file
+ self.enabled = True
diff --git a/plugins/tenet/ui/breakpoint_view.py b/plugins/tenet/ui/breakpoint_view.py
index ad33a7c..ff89696 100644
--- a/plugins/tenet/ui/breakpoint_view.py
+++ b/plugins/tenet/ui/breakpoint_view.py
@@ -5,16 +5,19 @@
from tenet.util.qt import *
+
class BreakpointDock(QtWidgets.QDockWidget):
"""
Dockable wrapper of a Breakpoint view.
"""
+
def __init__(self, view, parent=None):
super(BreakpointDock, self).__init__(parent)
self.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
self.setWindowTitle("Breakpoints")
self.setWidget(view)
+
class BreakpointView(QtWidgets.QWidget):
"""
The Breakpoint Widget (UI)
@@ -25,7 +28,7 @@ def __init__(self, controller, model, parent=None):
self.controller = controller
self.model = model
self._init_ui()
-
+
def _init_ui(self):
self.setMinimumHeight(100)
@@ -42,4 +45,3 @@ def _init_table(self):
self._table.insertColumn(2)
self._table.insertColumn(3)
self._table.setHorizontalHeaderLabels(["Type", "Enabled", "Address", "Delete"])
-
diff --git a/plugins/tenet/ui/hex_view.py b/plugins/tenet/ui/hex_view.py
index 89bfb75..f3995d3 100644
--- a/plugins/tenet/ui/hex_view.py
+++ b/plugins/tenet/ui/hex_view.py
@@ -5,6 +5,7 @@
INVALID_ADDRESS = -1
+
class HexView(QtWidgets.QAbstractScrollArea):
"""
A Qt based hex / memory viewer.
@@ -29,9 +30,9 @@ def __init__(self, controller, model, parent=None):
self.setMouseTracking(True)
fm = QtGui.QFontMetricsF(font)
- self._char_width = fm.width('9')
- self._char_height = int(fm.tightBoundingRect('9').height() * 1.75)
- self._char_descent = self._char_height - fm.descent()*0.75
+ self._char_width = fm.width("9")
+ self._char_height = int(fm.tightBoundingRect("9").height() * 1.75)
+ self._char_descent = self._char_height - fm.descent() * 0.75
self._click_timer = QtCore.QTimer(self)
self._click_timer.setSingleShot(True)
@@ -63,11 +64,10 @@ def _init_ctx_menu(self):
self._action_clear = QtWidgets.QAction("Clear mem breakpoints", None)
self._action_follow_in_dump = QtWidgets.QAction("Follow in dump", None)
- bp_types = \
- [
+ bp_types = [
("Read", BreakpointType.READ),
("Write", BreakpointType.WRITE),
- ("Access", BreakpointType.ACCESS)
+ ("Access", BreakpointType.ACCESS),
]
#
@@ -99,8 +99,7 @@ def _init_ctx_menu(self):
self._action_first[QtWidgets.QAction(name, None)] = bp_type
self._action_final[QtWidgets.QAction(name, None)] = bp_type
- self._goto_menus = \
- [
+ self._goto_menus = [
(QtWidgets.QMenu("Go to first..."), self._action_first),
(QtWidgets.QMenu("Go to previous..."), self._action_prev),
(QtWidgets.QMenu("Go to next..."), self._action_next),
@@ -114,9 +113,9 @@ def _init_ctx_menu(self):
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._ctx_menu_handler)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Properties
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
@property
def num_lines_visible(self):
@@ -159,9 +158,9 @@ def hovered_breakpoint(self):
return None
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Internal
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def refresh(self):
"""
@@ -176,10 +175,12 @@ def _refresh_painting_metrics(self):
"""
# 2 chars per byte of data, eg '00'
- self._chars_in_line = self.model.num_bytes_per_line * 2
+ self._chars_in_line = self.model.num_bytes_per_line * 2
# add 1 char for each space between elements (bytes, dwords, qwords...)
- self._chars_in_line += (self.model.num_bytes_per_line // HEX_TYPE_WIDTH[self.model.hex_format])
+ self._chars_in_line += (
+ self.model.num_bytes_per_line // HEX_TYPE_WIDTH[self.model.hex_format]
+ )
# the x position to draw the text address (left side of view)
self._pos_addr = self._char_width // 2
@@ -228,27 +229,27 @@ def point_to_index(self, position):
return -1
cutoff = self._pos_hex + self._width_hex - padding
- #print(f"Position: {position} Cutoff: {cutoff} Pos Hex: {self._pos_hex} Width Hex: {self._width_hex} Padding: {padding}")
+ # print(f"Position: {position} Cutoff: {cutoff} Pos Hex: {self._pos_hex} Width Hex: {self._width_hex} Padding: {padding}")
if position.x() >= cutoff:
return -1
# convert 'gloabl' x in the viewport, to an x that is 'relative' to the hex area
hex_x = (position.x() - self._pos_hex) + padding
- #print("- Hex x", hex_x)
+ # print("- Hex x", hex_x)
# the number of items (eg, bytes, qwords) per line
num_items = self.model.num_bytes_per_line // HEX_TYPE_WIDTH[self.model.hex_format]
- #print("- Num items", num_items)
+ # print("- Num items", num_items)
# compute the pixel width each rendered item on the line takes up
item_width = (self._char_width * 2) * HEX_TYPE_WIDTH[self.model.hex_format]
item_width_padded = item_width + self._char_width
- #print("- Item Width", item_width)
- #print("- Item Width Padded", item_width_padded)
+ # print("- Item Width", item_width)
+ # print("- Item Width Padded", item_width_padded)
# compute the item index on a line (the x-axis) that the point falls within
item_index = int(hex_x // item_width_padded)
- #print("- Item Index", item_index)
+ # print("- Item Index", item_index)
# compute which byte is hovered in the item
if self.model.hex_format != HexType.BYTE:
@@ -263,8 +264,8 @@ def point_to_index(self, position):
elif item_byte_index >= self.model.num_bytes_per_line:
item_byte_index = self.model.num_bytes_per_line - 1
- #print("- Item Byte X", item_byte_x)
- #print("- Item Byte Index", item_byte_index)
+ # print("- Item Byte X", item_byte_x)
+ # print("- Item Byte Index", item_byte_index)
item_byte_index = (HEX_TYPE_WIDTH[self.model.hex_format] - 1) - item_byte_index
byte_x = item_index * HEX_TYPE_WIDTH[self.model.hex_format] + item_byte_index
@@ -274,11 +275,11 @@ def point_to_index(self, position):
# compute the line number (the y-axis) that the point falls within
byte_y = position.y() // self._char_height
- #print("- Byte (X, Y)", byte_x, byte_y)
+ # print("- Byte (X, Y)", byte_x, byte_y)
# compute the final byte index from the start address in the window
byte_index = (byte_y * self.model.num_bytes_per_line) + byte_x
- #print("- Byte Index", byte_index)
+ # print("- Byte Index", byte_index)
return byte_index
@@ -357,12 +358,12 @@ def _commit_selection(self):
self._pending_selection_end = INVALID_ADDRESS
# notify listeners of our selection change
- #self._notify_selection_changed(new_start, new_end)
+ # self._notify_selection_changed(new_start, new_end)
self.viewport().update()
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Signals
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _ctx_menu_handler(self, position):
"""
@@ -465,9 +466,9 @@ def _ctx_menu_handler(self, position):
self.controller.pin_memory(selected_address, selected_type, selected_length)
self.reset_selection()
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Qt Overloads
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def mouseDoubleClickEvent(self, event):
"""
@@ -531,12 +532,14 @@ def mousePressEvent(self, event):
byte_address = self.point_to_address(event.pos())
- if not(self._selection_start <= byte_address < self._selection_end):
+ if not (self._selection_start <= byte_address < self._selection_end):
self.reset_selection()
self._pending_selection_origin = byte_address
self._pending_selection_start = byte_address
- self._pending_selection_end = (byte_address + 1) if byte_address != INVALID_ADDRESS else INVALID_ADDRESS
+ self._pending_selection_end = (
+ (byte_address + 1) if byte_address != INVALID_ADDRESS else INVALID_ADDRESS
+ )
self.viewport().update()
@@ -601,6 +604,7 @@ def keyPressEvent(self, e):
"""
if e.key() == QtCore.Qt.Key_G:
import ida_kernwin, ida_idaapi
+
address = ida_kernwin.ask_addr(self.model.address, "Jump to address in memory")
if address != None and address != ida_idaapi.BADADDR:
self.controller.navigate(address)
@@ -624,7 +628,7 @@ def wheelEvent(self, event):
for bp in self.model.memory_breakpoints:
# skip this breakpoint if the current byte does not fall within its range
- if not(bp.address <= byte_address < bp.address + bp.length):
+ if not (bp.address <= byte_address < bp.address + bp.length):
continue
#
@@ -677,9 +681,9 @@ def resizeEvent(self, event):
self._refresh_painting_metrics()
self.controller.set_data_size(self.num_bytes_visible)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Painting
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def paintEvent(self, event):
"""
@@ -733,7 +737,7 @@ def _paint_line(self, painter, line_idx):
# draw the address text
pack_len = self.model.pointer_size
- address_fmt = '%016X' if pack_len == 8 else '%08X'
+ address_fmt = "%016X" if pack_len == 8 else "%08X"
address_text = address_fmt % address
painter.drawText(self._pos_addr, y, address_text)
@@ -770,8 +774,8 @@ def _paint_line(self, painter, line_idx):
painter.setPen(self._palette.hex_text_faded_fg)
ch = self.model.data[i]
- if ((ch < 0x20) or (ch > 0x7e)):
- ch = '.'
+ if (ch < 0x20) or (ch > 0x7E):
+ ch = "."
else:
ch = chr(ch)
@@ -801,7 +805,7 @@ def _paint_hex_item(self, painter, byte_idx, stop_idx, x, y):
raise NotImplementedError("Unknown HexType format! %s" % self.model.hex_format)
- #return (byte_idx, x, y)
+ # return (byte_idx, x, y)
def _paint_byte(self, painter, byte_idx, x, y):
"""
@@ -899,7 +903,7 @@ def _paint_text(self, painter, byte_idx, padding, x, y):
for bp in self.model.memory_breakpoints:
# skip this breakpoint if the current byte does not fall within its range
- if not(bp.address <= byte_address < bp.address + bp.length):
+ if not (bp.address <= byte_address < bp.address + bp.length):
continue
#
@@ -986,13 +990,13 @@ def _paint_magic(self, painter, byte_idx, stop_idx, x, y):
# ensure that all the bytes for the 'pointer' to analyze are known
pack_len = self.model.pointer_size
- pack_fmt = 'Q' if pack_len == 8 else 'I'
- mask = struct.unpack(pack_fmt, self.model.mask[byte_idx:byte_idx+pack_len])[0]
+ pack_fmt = "Q" if pack_len == 8 else "I"
+ mask = struct.unpack(pack_fmt, self.model.mask[byte_idx : byte_idx + pack_len])[0]
if mask != 0xFFFFFFFFFFFFFFFF:
return self._paint_byte(painter, byte_idx, x, y)
# read and analyze the value to determine if it is a pointer
- value = struct.unpack(pack_fmt, self.model.data[byte_idx:byte_idx+pack_len])[0]
+ value = struct.unpack(pack_fmt, self.model.data[byte_idx : byte_idx + pack_len])[0]
if not self.controller.pctx.is_pointer(value):
return self._paint_byte(painter, byte_idx, x, y)
diff --git a/plugins/tenet/ui/palette.py b/plugins/tenet/ui/palette.py
index 7921fb8..14596ea 100644
--- a/plugins/tenet/ui/palette.py
+++ b/plugins/tenet/ui/palette.py
@@ -12,9 +12,10 @@
logger = logging.getLogger("Plugin.UI.Palette")
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Plugin Color Palette
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
class PluginPalette(object):
"""
@@ -34,11 +35,7 @@ def __init__(self):
self._user_disassembly_hint = "dark"
self.theme = None
- self._default_themes = \
- {
- "dark": "synth.json",
- "light": "horizon.json"
- }
+ self._default_themes = {"dark": "synth.json", "light": "horizon.json"}
# list of objects requesting a callback after a theme change
self._theme_changed_callbacks = []
@@ -66,14 +63,13 @@ def get_user_theme_dir():
Return the user theme directory.
"""
theme_directory = os.path.join(
- disassembler.get_disassembler_user_directory(),
- "tenet_themes"
+ disassembler.get_disassembler_user_directory(), "tenet_themes"
)
return theme_directory
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def theme_changed(self, callback):
"""
@@ -87,9 +83,9 @@ def _notify_theme_changed(self):
"""
notify_callback(self._theme_changed_callbacks)
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Public
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def warmup(self):
"""
@@ -125,10 +121,7 @@ def interactive_change_theme(self):
# create & configure a Qt File Dialog for immediate use
file_dialog = QtWidgets.QFileDialog(
- None,
- "Open plugin theme file",
- self._last_directory,
- "JSON Files (*.json)"
+ None, "Open plugin theme file", self._last_directory, "JSON Files (*.json)"
)
file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
@@ -228,9 +221,9 @@ def gen_arrow_icon(self, color, rotation):
return ba.data()
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Theme Internals
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _populate_user_theme_dir(self):
"""
@@ -475,9 +468,9 @@ def _pick_best_color(self, field_name, color_entry):
return light
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Theme Inference
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _refresh_theme_hints(self):
"""
@@ -545,7 +538,7 @@ def _qt_theme_hint(self):
if disassembler.NAME == "BINJA":
test_widget.setAttribute(QtCore.Qt.WA_DontShowOnScreen)
else:
- test_widget.setAttribute(103) # taken from http://doc.qt.io/qt-5/qt.html
+ test_widget.setAttribute(103) # taken from http://doc.qt.io/qt-5/qt.html
# render the (invisible) widget
test_widget.show()
@@ -560,15 +553,17 @@ def _qt_theme_hint(self):
# return 'dark' or 'light'
return test_color_brightness(bg_color)
-#-----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
# Palette Util
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
def test_color_brightness(color):
"""
Test the brightness of a color.
"""
- if color.lightness() > 255.0/2:
+ if color.lightness() > 255.0 / 2:
return "light"
else:
return "dark"
diff --git a/plugins/tenet/ui/trace_view.py b/plugins/tenet/ui/trace_view.py
index 7f3644f..451f72c 100644
--- a/plugins/tenet/ui/trace_view.py
+++ b/plugins/tenet/ui/trace_view.py
@@ -17,14 +17,15 @@
# this will probably happen sooner than later, to keep everything consistent
#
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# TraceView
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
INVALID_POS = -1
INVALID_IDX = -1
INVALID_DENSITY = -1
+
class TraceBar(QtWidgets.QWidget):
"""
A trace visualization.
@@ -71,26 +72,26 @@ def __init__(self, pctx, zoom=False, parent=None):
# listen for breakpoint changed events
pctx.breakpoints.model.breakpoints_changed(self._breakpoints_changed)
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Styling
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# the width (in pixels) of the border around the trace bar
self._trace_border = 1
# the width (in pixels) of the border around trace cells
- self._cell_border = 0 # computed dynamically
+ self._cell_border = 0 # computed dynamically
self._cell_min_border = 1
self._cell_max_border = 1
# the height (in pixels) of the trace cells
- self._cell_height = 0 # computed dynamically
+ self._cell_height = 0 # computed dynamically
self._cell_min_height = 2
self._cell_max_height = 10
# the amount of space between cells (in pixels)
# - NOTE: no limit to cell spacing at max magnification!
- self._cell_spacing = 0 # computed dynamically
+ self._cell_spacing = 0 # computed dynamically
self._cell_min_spacing = self._cell_min_border
# the width (in pixels) of the border around user region selection
@@ -99,9 +100,9 @@ def __init__(self, pctx, zoom=False, parent=None):
# create the rest of the painting vars
self._init_painting()
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
self._selection_changed_callbacks = []
@@ -123,24 +124,28 @@ def _init_painting(self):
self._painter_cursor = None
self._painter_final = None
- self._pen_cursor = QtGui.QPen(self.pctx.palette.trace_cursor_highlight, 1, QtCore.Qt.SolidLine)
+ self._pen_cursor = QtGui.QPen(
+ self.pctx.palette.trace_cursor_highlight, 1, QtCore.Qt.SolidLine
+ )
- self._pen_selection = QtGui.QPen(self.pctx.palette.trace_selection, self._selection_border, QtCore.Qt.SolidLine)
+ self._pen_selection = QtGui.QPen(
+ self.pctx.palette.trace_selection, self._selection_border, QtCore.Qt.SolidLine
+ )
self._brush_selection = QtGui.QBrush(QtCore.Qt.Dense6Pattern)
self._brush_selection.setColor(self.pctx.palette.trace_selection_border)
self._last_hovered = INVALID_IDX
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Properties
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
@property
def length(self):
"""
Return the number of idx visible in the trace visualization.
"""
- return (self.end_idx - self.start_idx)
+ return self.end_idx - self.start_idx
@property
def cells_visible(self):
@@ -154,7 +159,7 @@ def density(self):
"""
Return the density of idx (instructions) per y-pixel of the trace visualization.
"""
- density = (self.length / (self.height() - self._trace_border * 2))
+ density = self.length / (self.height() - self._trace_border * 2)
if density > 0:
return density
return INVALID_DENSITY
@@ -184,9 +189,9 @@ def viz_size(self):
h = max(0, int(self.height() - (self._trace_border * 2)))
return (w, h)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Public
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def attach_reader(self, reader):
"""
@@ -266,9 +271,9 @@ def refresh(self, *args):
"""
self.update()
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Qt Overloads
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def mouseMoveEvent(self, event):
"""
@@ -365,9 +370,9 @@ def resizeEvent(self, _):
"""
self._resize_timer.start(500)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Helpers (Internal)
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
#
# NOTE: this stuff should probably only be called by the 'mainthread'
# to ensure density / viz dimensions and stuff don't change.
@@ -405,7 +410,7 @@ def _refresh_painting_metrics(self):
# don't draw the trace vizualization as cells if the density is too high
if given_space_per_cell < min_full_cell_height:
- #logger.debug(f"No need for cells -- {given_space_per_cell}, min req {min_full_cell_height}")
+ # logger.debug(f"No need for cells -- {given_space_per_cell}, min req {min_full_cell_height}")
return
# compute the pixel height of a cell at maximum height (including borders)
@@ -422,10 +427,14 @@ def _refresh_painting_metrics(self):
return
# dynamically compute cell dimensions for drawing
- self._cell_height = max(self._cell_min_height, min(int(given_space_per_cell * 0.95), self._cell_max_height))
- self._cell_border = max(self._cell_min_border, min(int(given_space_per_cell * 0.05), self._cell_max_border))
+ self._cell_height = max(
+ self._cell_min_height, min(int(given_space_per_cell * 0.95), self._cell_max_height)
+ )
+ self._cell_border = max(
+ self._cell_min_border, min(int(given_space_per_cell * 0.05), self._cell_max_border)
+ )
self._cell_spacing = int(given_space_per_cell - (self._cell_height + self._cell_border * 2))
- #logger.debug(f"Dynamic cells -- Given: {given_space_per_cell}, Height {self._cell_height}, Border: {self._cell_border}, Spacing: {self._cell_spacing}")
+ # logger.debug(f"Dynamic cells -- Given: {given_space_per_cell}, Height {self._cell_height}, Border: {self._cell_border}, Spacing: {self._cell_spacing}")
# if there's not enough to justify having spacing, use shared borders between cells (usually very small cells)
if self._cell_spacing < self._cell_min_spacing:
@@ -436,7 +445,7 @@ def _refresh_painting_metrics(self):
# compute how many cells we can *actually* show in the space available
num_cell_allowed = int(viz_h / used_space_per_cell) + 1
- #logger.debug(f"Num Cells {num_cell} vs Available Space {num_cell_allowed}")
+ # logger.debug(f"Num Cells {num_cell} vs Available Space {num_cell_allowed}")
self.end_idx = self.start_idx + num_cell_allowed
@@ -445,12 +454,12 @@ def _idx2pos(self, idx):
Translate a given idx to its first Y coordinate.
"""
if idx < self.start_idx or idx >= self.end_idx:
- #logger.warn(f"idx2pos failed (start: {self.start_idx:,} idx: {idx:,} end: {self.end_idx:,}")
+ # logger.warn(f"idx2pos failed (start: {self.start_idx:,} idx: {idx:,} end: {self.end_idx:,}")
return INVALID_POS
density = self.density
if density == INVALID_DENSITY:
- #logger.warn(f"idx2pos failed (INVALID_DENSITY)")
+ # logger.warn(f"idx2pos failed (INVALID_DENSITY)")
return INVALID_POS
# convert the absolute idx to one that is 'relative' to the viz
@@ -476,16 +485,16 @@ def _idx2pos(self, idx):
# return the approximate y position of the given timestamp
return y
- #assert self._cell_spacing % 2 == 0
+ # assert self._cell_spacing % 2 == 0
# compute the y position of the 'first' cell
- y += self._cell_spacing / 2 # pad out from top
- y += self._cell_border # top border of cell
+ y += self._cell_spacing / 2 # pad out from top
+ y += self._cell_border # top border of cell
# compute the y position of any given cell after the first
y += self._cell_height * relative_idx # cell body
y += self._cell_border * relative_idx # cell bottom border
- y += self._cell_spacing * relative_idx # full space between cells
+ y += self._cell_spacing * relative_idx # full space between cells
y += self._cell_border * relative_idx # cell top border
# return the y position of the cell corresponding to the given timestamp
@@ -506,7 +515,7 @@ def _pos2idx(self, y):
density = self.density
if density == INVALID_DENSITY:
- #logger.warn(f"pos2idx failed (INVALID_DENSITY)")
+ # logger.warn(f"pos2idx failed (INVALID_DENSITY)")
return INVALID_IDX
# translate/rebase global y to viz relative y
@@ -549,7 +558,7 @@ def _compute_pixel_distance(self, y, idx):
#
if self.cells_visible:
- y_idx += int(self._cell_height/2)
+ y_idx += int(self._cell_height / 2)
# return the on-screen pixel distance between the two y coords
return abs(y - y_idx)
@@ -570,7 +579,7 @@ def _update_hover(self, current_y):
# do any special hover highlighting...
#
- if not(self.start_idx <= closest_idx < self.end_idx):
+ if not (self.start_idx <= closest_idx < self.end_idx):
return
#
@@ -579,7 +588,7 @@ def _update_hover(self, current_y):
#
px_distance = self._compute_pixel_distance(current_y, closest_idx)
- #logger.debug(f"hovered idx {hovered_idx:,}, closest idx {closest_idx:,}, dist {px_distance} (start: {self.start_idx:,} end: {self.end_idx:,}")
+ # logger.debug(f"hovered idx {hovered_idx:,}, closest idx {closest_idx:,}, dist {px_distance} (start: {self.start_idx:,} end: {self.end_idx:,}")
if px_distance == -1:
return
@@ -645,7 +654,7 @@ def _commit_click(self):
self._idx_pending_selection_end = INVALID_IDX
# does the click fall within the existing selected region?
- within_region = (self._idx_selection_start <= selected_idx <= self._idx_selection_end)
+ within_region = self._idx_selection_start <= selected_idx <= self._idx_selection_end
# nope click is outside the region, so clear the region selection
if not within_region:
@@ -653,7 +662,7 @@ def _commit_click(self):
self._idx_selection_end = INVALID_IDX
self._notify_selection_changed(INVALID_IDX, INVALID_IDX)
- #print(f"Jumping to {selected_idx:,}")
+ # print(f"Jumping to {selected_idx:,}")
self.reader.seek(selected_idx)
self.refresh()
@@ -743,31 +752,45 @@ def _refresh_trace_highlights(self):
# fetch executions for all breakpoints
for bp in model.bp_exec.values():
- executions = reader.get_executions_between(bp.address, self.start_idx, self.end_idx, density)
+ executions = reader.get_executions_between(
+ bp.address, self.start_idx, self.end_idx, density
+ )
self._idx_executions.extend(executions)
# fetch all memory read (only) breakpoints hits
for bp in model.bp_read.values():
if bp.length == 1:
- reads = reader.get_memory_reads_between(bp.address, self.start_idx, self.end_idx, density)
+ reads = reader.get_memory_reads_between(
+ bp.address, self.start_idx, self.end_idx, density
+ )
else:
- reads = reader.get_memory_region_reads_between(bp.address, bp.length, self.start_idx, self.end_idx, density)
+ reads = reader.get_memory_region_reads_between(
+ bp.address, bp.length, self.start_idx, self.end_idx, density
+ )
self._idx_reads.extend(reads)
# fetch all memory write (only) breakpoint hits
for bp in model.bp_write.values():
if bp.length == 1:
- writes = reader.get_memory_writes_between(bp.address, self.start_idx, self.end_idx, density)
+ writes = reader.get_memory_writes_between(
+ bp.address, self.start_idx, self.end_idx, density
+ )
else:
- writes = reader.get_memory_region_writes_between(bp.address, bp.length, self.start_idx, self.end_idx, density)
+ writes = reader.get_memory_region_writes_between(
+ bp.address, bp.length, self.start_idx, self.end_idx, density
+ )
self._idx_writes.extend(writes)
# fetch memory access for all breakpoints
for bp in model.bp_access.values():
if bp.length == 1:
- reads, writes = reader.get_memory_accesses_between(bp.address, self.start_idx, self.end_idx, density)
+ reads, writes = reader.get_memory_accesses_between(
+ bp.address, self.start_idx, self.end_idx, density
+ )
else:
- reads, writes = reader.get_memory_region_accesses_between(bp.address, bp.length, self.start_idx, self.end_idx, density)
+ reads, writes = reader.get_memory_region_accesses_between(
+ bp.address, bp.length, self.start_idx, self.end_idx, density
+ )
self._idx_reads.extend(reads)
self._idx_writes.extend(writes)
@@ -781,9 +804,9 @@ def _clamp_idx(self, idx):
return self.end_idx - 1
return idx
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Drawing
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def paintEvent(self, event):
"""
@@ -830,7 +853,7 @@ def paintEvent(self, event):
self._draw_cursor()
painter.drawImage(0, 0, self._image_cursor)
- #painter.drawImage(0, 0, self._image_final)
+ # painter.drawImage(0, 0, self._image_final)
def _draw_base(self):
"""
@@ -846,7 +869,7 @@ def _draw_base(self):
self._image_base = QtGui.QImage(self.width(), self.height(), QtGui.QImage.Format_ARGB32)
self._image_base.fill(self.pctx.palette.trace_bedrock)
- #self._image_base.fill(QtGui.QColor("red")) # NOTE/debug
+ # self._image_base.fill(QtGui.QColor("red")) # NOTE/debug
self._painter_base = QtGui.QPainter(self._image_base)
# redraw instructions
@@ -919,7 +942,7 @@ def _draw_code_cells(self, painter):
ratio = (self._cell_border / (self._cell_height - 1)) * 0.5
lighten = 100 + int(ratio * 100)
cell_color = self.pctx.palette.trace_instruction.lighter(lighten)
- #print(f"Lightened by {lighten}% (Border: {self._cell_border}, Body: {self._cell_height}")
+ # print(f"Lightened by {lighten}% (Border: {self._cell_border}, Body: {self._cell_height}")
else:
cell_color = self.pctx.palette.trace_instruction
@@ -967,7 +990,9 @@ def _draw_highlights(self):
del self._painter_highlights
- self._image_highlights = QtGui.QImage(self.width(), self.height(), QtGui.QImage.Format_ARGB32)
+ self._image_highlights = QtGui.QImage(
+ self.width(), self.height(), QtGui.QImage.Format_ARGB32
+ )
self._image_highlights.fill(QtCore.Qt.transparent)
self._painter_highlights = QtGui.QPainter(self._image_highlights)
@@ -983,8 +1008,7 @@ def _draw_highlights_cells(self, painter):
viz_w, _ = self.viz_size
viz_x, _ = self.viz_pos
- access_sets = \
- [
+ access_sets = [
(self._idx_reads, self.pctx.palette.mem_read_bg),
(self._idx_writes, self.pctx.palette.mem_write_bg),
(self._idx_executions, self.pctx.palette.breakpoint),
@@ -1000,7 +1024,7 @@ def _draw_highlights_cells(self, painter):
for idx in entries:
# skip entries that fall outside the visible zoom
- if not(self.start_idx <= idx < self.end_idx):
+ if not (self.start_idx <= idx < self.end_idx):
continue
# slight tweak of y because we are only drawing a highlighted
@@ -1017,8 +1041,7 @@ def _draw_highlights_trace(self, painter):
viz_w, _ = self.viz_size
viz_x, _ = self.viz_pos
- access_sets = \
- [
+ access_sets = [
(self._idx_reads, self.pctx.palette.mem_read_bg),
(self._idx_writes, self.pctx.palette.mem_write_bg),
(self._idx_executions, self.pctx.palette.breakpoint),
@@ -1030,7 +1053,7 @@ def _draw_highlights_trace(self, painter):
for idx in entries:
# skip entries that fall outside the visible zoom
- if not(self.start_idx <= idx < self.end_idx):
+ if not (self.start_idx <= idx < self.end_idx):
continue
y = self._idx2pos(idx)
@@ -1057,11 +1080,13 @@ def _draw_cursor(self):
if self.cells_visible:
cell_y = cursor_y + self._cell_border
cell_body_height = self._cell_height - self._cell_border
- cursor_y += self._cell_height/2
+ cursor_y += self._cell_height / 2
# the top point of the triangle
top_x = 0
- top_y = cursor_y - (size // 2) # vertically align the triangle so the tip matches the cross section
+ top_y = cursor_y - (
+ size // 2
+ ) # vertically align the triangle so the tip matches the cross section
# bottom point of the triangle
bottom_x = top_x
@@ -1132,7 +1157,9 @@ def _draw_selection(self):
del self._painter_selection
viz_w, viz_h = self.viz_size
- self._image_selection = QtGui.QImage(self.width(), self.height(), QtGui.QImage.Format_ARGB32)
+ self._image_selection = QtGui.QImage(
+ self.width(), self.height(), QtGui.QImage.Format_ARGB32
+ )
self._image_selection.fill(QtCore.Qt.transparent)
self._painter_selection = QtGui.QPainter(self._image_selection)
@@ -1192,7 +1219,7 @@ def _draw_border(self):
self._painter_border = QtGui.QPainter(self._image_border)
color = self.pctx.palette.trace_border
- #color = QtGui.QColor("red") # NOTE: for dev/debug testing
+ # color = QtGui.QColor("red") # NOTE: for dev/debug testing
border_pen = QtGui.QPen(color, self._trace_border, QtCore.Qt.SolidLine)
self._painter_border.setPen(border_pen)
@@ -1202,9 +1229,9 @@ def _draw_border(self):
# draw the border around the tracebar using a blank rect + stroke (border)
self._painter_border.drawRect(0, 0, w, h)
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def selection_changed(self, callback):
"""
@@ -1218,12 +1245,13 @@ def _notify_selection_changed(self, start_idx, end_idx):
"""
notify_callback(self._selection_changed_callbacks, start_idx, end_idx)
-#-----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
# Trace View
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
-class TraceView(QtWidgets.QWidget):
+class TraceView(QtWidgets.QWidget):
def __init__(self, pctx, parent=None):
super(TraceView, self).__init__(parent)
self.pctx = pctx
@@ -1285,9 +1313,9 @@ def _init_ctx_menu(self):
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._ctx_menu_handler)
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
# Signals
- #--------------------------------------------------------------------------
+ # --------------------------------------------------------------------------
def _ctx_menu_handler(self, position):
"""
@@ -1308,9 +1336,11 @@ def update_from_model(self):
# this will insert the children (tracebars) and apply spacing as appropriate
self.bar_container.setLayout(self.hbox)
-#-----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
# Dockable Trace Visualization
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
class TraceDock(QtWidgets.QToolBar):
"""
@@ -1320,6 +1350,7 @@ class TraceDock(QtWidgets.QToolBar):
around the QMainWindow in Qt-based applications. This allows us to pin
the visualizations to areas where they will not be dist
"""
+
def __init__(self, pctx, parent=None):
super(TraceDock, self).__init__(parent)
self.pctx = pctx
@@ -1332,4 +1363,4 @@ def attach_reader(self, reader):
self.view.attach_reader(reader)
def detach_reader(self):
- self.view.detach_reader()
\ No newline at end of file
+ self.view.detach_reader()
diff --git a/plugins/tenet/util/debug.py b/plugins/tenet/util/debug.py
index 22d5aa2..34c8a97 100644
--- a/plugins/tenet/util/debug.py
+++ b/plugins/tenet/util/debug.py
@@ -1,19 +1,20 @@
import time
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Debug / Profiling Helpers
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def timeit(method):
def timed(*args, **kw):
ts = time.time()
result = method(*args, **kw)
te = time.time()
- if 'log_time' in kw:
- name = kw.get('log_name', method.__name__.upper())
- kw['log_time'][name] = int((te - ts) * 1000)
+ if "log_time" in kw:
+ name = kw.get("log_name", method.__name__.upper())
+ kw["log_time"][name] = int((te - ts) * 1000)
else:
- print('%r %2.2f ms' % \
- (method.__name__, (te - ts) * 1000))
+ print("%r %2.2f ms" % (method.__name__, (te - ts) * 1000))
return result
- return timed
\ No newline at end of file
+
+ return timed
diff --git a/plugins/tenet/util/log.py b/plugins/tenet/util/log.py
index 4c4a3ed..88172ae 100644
--- a/plugins/tenet/util/log.py
+++ b/plugins/tenet/util/log.py
@@ -5,9 +5,10 @@
from .misc import makedirs, is_plugin_dev
from ..integration.api import disassembler
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Log / Print helpers
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def pmsg(message):
"""
@@ -23,34 +24,36 @@ def pmsg(message):
else:
logger.info(message)
+
def get_log_dir():
"""
Return the plugin log directory.
"""
- log_directory = os.path.join(
- disassembler.get_disassembler_user_directory(),
- "tenet_logs"
- )
+ log_directory = os.path.join(disassembler.get_disassembler_user_directory(), "tenet_logs")
return log_directory
+
def logging_started():
"""
Check if logging has been started.
"""
- return 'logger' in globals()
+ return "logger" in globals()
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Logger Proxy
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
class LoggerProxy(object):
"""
Fake file-like stream object that redirects writes to a logger instance.
"""
+
def __init__(self, logger, stream, log_level=logging.INFO):
- self._logger = logger
+ self._logger = logger
self._log_level = log_level
- self._stream = stream
+ self._stream = stream
def write(self, buf):
for line in buf.rstrip().splitlines():
@@ -64,11 +67,14 @@ def flush(self):
def isatty(self):
pass
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Initialize Logging
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
MAX_LOGS = 10
+
+
def cleanup_log_directory(log_directory):
"""
Retain only the last 15 logs.
@@ -100,6 +106,7 @@ def cleanup_log_directory(log_directory):
logger.error("Failed to delete log %s" % filetimes[log_time])
logger.error(e)
+
def start_logging():
global logger
@@ -129,14 +136,14 @@ def start_logging():
# config the logger
logging.basicConfig(
filename=log_path,
- format='%(asctime)s | %(name)28s | %(levelname)7s: %(message)s',
- datefmt='%m-%d-%Y %H:%M:%S',
- level=logging.DEBUG
+ format="%(asctime)s | %(name)28s | %(levelname)7s: %(message)s",
+ datefmt="%m-%d-%Y %H:%M:%S",
+ level=logging.DEBUG,
)
# proxy STDOUT/STDERR to the log files too
- stdout_logger = logging.getLogger('Tenet.STDOUT')
- stderr_logger = logging.getLogger('Tenet.STDERR')
+ stdout_logger = logging.getLogger("Tenet.STDOUT")
+ stderr_logger = logging.getLogger("Tenet.STDERR")
sys.stdout = LoggerProxy(stdout_logger, sys.stdout, logging.INFO)
sys.stderr = LoggerProxy(stderr_logger, sys.stderr, logging.ERROR)
@@ -145,9 +152,11 @@ def start_logging():
return logger
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Log Helpers
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def log_config_warning(self, logger, section, field):
logger.warning("Config missing field '%s' in section '%s", field, section)
diff --git a/plugins/tenet/util/misc.py b/plugins/tenet/util/misc.py
index efe59ff..f539e68 100644
--- a/plugins/tenet/util/misc.py
+++ b/plugins/tenet/util/misc.py
@@ -4,32 +4,31 @@
import weakref
import threading
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Plugin Util
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
PLUGIN_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+
def is_plugin_dev():
"""
Return True if the plugin is in developer mode.
"""
return bool(os.getenv("TENET_DEV"))
+
def plugin_resource(resource_name):
"""
Return the full path for a given plugin resource file.
"""
- return os.path.join(
- PLUGIN_PATH,
- "ui",
- "resources",
- resource_name
- )
+ return os.path.join(PLUGIN_PATH, "ui", "resources", resource_name)
+
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Thread Util
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def is_mainthread():
"""
@@ -37,41 +36,51 @@ def is_mainthread():
"""
return isinstance(threading.current_thread(), threading._MainThread)
+
def assert_mainthread(f):
"""
A sanity decorator to ensure that a function is always called from the main thread.
"""
+
def wrapper(*args, **kwargs):
assert is_mainthread()
return f(*args, **kwargs)
+
return wrapper
+
def assert_async(f):
"""
A sanity decorator to ensure that a function is never called from the main thread.
"""
+
def wrapper(*args, **kwargs):
assert not is_mainthread()
return f(*args, **kwargs)
+
return wrapper
-#-----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
# Python Utils
-#-----------------------------------------------------------------------------
+# -----------------------------------------------------------------------------
+
def chunks(lst, n):
"""
Yield successive n-sized chunks from lst.
"""
for i in range(0, len(lst), n):
- yield lst[i:i + n]
+ yield lst[i : i + n]
+
def hexdump(data):
"""
Return an ascii hexdump of the given data.
"""
- return '\n'.join([' '.join([f"{x:02X}" for x in chunk]) for chunk in chunks(data, 16)])
-
+ return "\n".join([" ".join([f"{x:02X}" for x in chunk]) for chunk in chunks(data, 16)])
+
+
def makedirs(path, exists_ok=True):
"""
Create directories along a fully qualified path.
@@ -83,16 +92,19 @@ def makedirs(path, exists_ok=True):
raise e
if not exists_ok:
raise e
-
+
+
def swap_rgb(i):
"""
Swap a 32bit RRGGBB (integer) to BBGGRR.
"""
return struct.unpack("I", i))[0] >> 8
-#------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
# Python Callback / Signals
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
+
def register_callback(callback_list, callback):
"""
@@ -112,6 +124,7 @@ def register_callback(callback_list, callback):
# 'register' the callback
callback_list.append(callback_ref)
+
def notify_callback(callback_list, *args):
"""
Notify the given list of registered callbacks of an event.
@@ -171,4 +184,4 @@ def notify_callback(callback_list, *args):
# remove the deleted callbacks
for callback_ref in cleanup:
- callback_list.remove(callback_ref)
\ No newline at end of file
+ callback_list.remove(callback_ref)
diff --git a/plugins/tenet/util/qt/__init__.py b/plugins/tenet/util/qt/__init__.py
index 810ea60..6a4efb8 100644
--- a/plugins/tenet/util/qt/__init__.py
+++ b/plugins/tenet/util/qt/__init__.py
@@ -2,4 +2,4 @@
if QT_AVAILABLE:
from .util import *
- from .waitbox import WaitBox
\ No newline at end of file
+ from .waitbox import WaitBox
diff --git a/plugins/tenet/util/qt/shim.py b/plugins/tenet/util/qt/shim.py
index dd4793c..048e740 100644
--- a/plugins/tenet/util/qt/shim.py
+++ b/plugins/tenet/util/qt/shim.py
@@ -1,4 +1,3 @@
-
#
# this global is used to indicate whether Qt bindings for python are present
# and available for use by Lighthouse.
@@ -6,9 +5,9 @@
QT_AVAILABLE = False
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# PyQt5 <--> PySide2 Compatibility
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
#
# we use this file to shim/re-alias a few Qt API's to ensure compatibility
# between the popular Qt frameworks. these shims serve to reduce the number
@@ -22,9 +21,9 @@
USING_PYQT5 = False
USING_PYSIDE2 = False
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# PyQt5 Compatibility
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# attempt to load PyQt5
if QT_AVAILABLE == False:
@@ -42,9 +41,9 @@
except ImportError:
pass
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# PySide2 Compatibility
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# if PyQt5 did not import, try to load PySide
if QT_AVAILABLE == False:
@@ -63,4 +62,4 @@
# import failed. No Qt / UI bindings available...
except ImportError:
- pass
\ No newline at end of file
+ pass
diff --git a/plugins/tenet/util/update.py b/plugins/tenet/util/update.py
index 195089c..f6042a2 100644
--- a/plugins/tenet/util/update.py
+++ b/plugins/tenet/util/update.py
@@ -7,23 +7,28 @@
logger = logging.getLogger("Tenet.Util.Update")
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Update Checking
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
UPDATE_URL = "https://api.github.com/repos/gaasedelen/tenet/releases/latest"
+
def check_for_update(current_version, callback):
"""
Perform a plugin update check.
"""
update_thread = threading.Thread(
target=async_update_check,
- args=(current_version, callback,),
- name="UpdateChecker"
+ args=(
+ current_version,
+ callback,
+ ),
+ name="UpdateChecker",
)
update_thread.start()
+
def async_update_check(current_version, callback):
"""
An async worker thread to check for an plugin update.
@@ -40,8 +45,8 @@ def async_update_check(current_version, callback):
return
# convert vesrion #'s to integer for easy compare...
- version_remote = int(''.join(re.findall('\d+', remote_version)))
- version_local = int(''.join(re.findall('\d+', current_version)))
+ version_remote = int("".join(re.findall("\d+", remote_version)))
+ version_local = int("".join(re.findall("\d+", current_version)))
# no updates available...
logger.debug(" - Local: 'v%s' vs Remote: '%s'" % (current_version, remote_version))
@@ -50,10 +55,11 @@ def async_update_check(current_version, callback):
return
# notify the user if an update is available
- update_message = "An update is available for Tenet!\n\n" \
- " - Latest Version: %s\n" % (remote_version) + \
- " - Current Version: v%s\n\n" % (current_version) + \
- "Please go download the update from GitHub."
+ update_message = (
+ "An update is available for Tenet!\n\n"
+ " - Latest Version: %s\n" % (remote_version)
+ + " - Current Version: v%s\n\n" % (current_version)
+ + "Please go download the update from GitHub."
+ )
callback(update_message)
-
diff --git a/plugins/tenet_plugin.py b/plugins/tenet_plugin.py
index e1896ac..a714743 100644
--- a/plugins/tenet_plugin.py
+++ b/plugins/tenet_plugin.py
@@ -4,9 +4,9 @@
if not logging_started():
logger = start_logging()
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Disassembler Agnonstic Plugin Loader
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
logger.debug("Resolving disassembler platform for Tenet...")
@@ -20,4 +20,3 @@
else:
raise NotImplementedError("DISASSEMBLER-SPECIFIC SHIM MISSING")
-
From 4b3a2d535657ae41114cf437894da6900954dc1b Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Wed, 4 Oct 2023 20:29:27 +0300
Subject: [PATCH 5/8] Fix all QT float issues
---
plugins/tenet/trace/file.py | 4 ++--
plugins/tenet/ui/hex_view.py | 2 +-
plugins/tenet/ui/reg_view.py | 16 ++++++++--------
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/plugins/tenet/trace/file.py b/plugins/tenet/trace/file.py
index 29b330f..1246515 100644
--- a/plugins/tenet/trace/file.py
+++ b/plugins/tenet/trace/file.py
@@ -523,8 +523,8 @@ def _select_arch(self, magic):
self.arch = ArchArm32()
elif ArchX86.MAGIC == magic:
self.arch = ArchX86()
-
- raise ValueError(f"Invalid arch magic 0x{magic:08X}")
+ else:
+ raise ValueError(f"Invalid arch magic 0x{magic:08X}")
def _fetch_hash(self, filepath):
"""
diff --git a/plugins/tenet/ui/hex_view.py b/plugins/tenet/ui/hex_view.py
index f3995d3..f500b1c 100644
--- a/plugins/tenet/ui/hex_view.py
+++ b/plugins/tenet/ui/hex_view.py
@@ -30,7 +30,7 @@ def __init__(self, controller, model, parent=None):
self.setMouseTracking(True)
fm = QtGui.QFontMetricsF(font)
- self._char_width = fm.width("9")
+ self._char_width = int(fm.width("9"))
self._char_height = int(fm.tightBoundingRect("9").height() * 1.75)
self._char_descent = self._char_height - fm.descent() * 0.75
diff --git a/plugins/tenet/ui/reg_view.py b/plugins/tenet/ui/reg_view.py
index 5281d15..441e7a2 100644
--- a/plugins/tenet/ui/reg_view.py
+++ b/plugins/tenet/ui/reg_view.py
@@ -100,8 +100,8 @@ def __init__(self, controller, model, parent=None):
self.setFont(font)
fm = QtGui.QFontMetricsF(font)
- self._char_width = fm.width("9")
- self._char_height = fm.height()
+ self._char_width = int(fm.width("9"))
+ self._char_height = int(fm.height())
# default to fit roughly 50 printable characters
self._default_width = self._char_width * (self.pctx.arch.POINTER_SIZE * 2 + 16)
@@ -113,7 +113,7 @@ def __init__(self, controller, model, parent=None):
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
- self.setMinimumWidth(self._reg_pos[0] + self._default_width)
+ self.setMinimumWidth(int(self._reg_pos[0] + self._default_width))
self.setMouseTracking(True)
self._init_ctx_menu()
@@ -168,22 +168,22 @@ def _init_reg_positions(self):
if reg_name == self.model.arch.IP:
y += self._char_height
- name_rect = QtCore.QRect(0, 0, name_size.width(), name_size.height())
+ name_rect = QtCore.QRect(0, 0, int(name_size.width()), int(name_size.height()))
name_rect.moveBottomLeft(QtCore.QPoint(int(name_x), int(y)))
prev_rect = QtCore.QRect(0, 0, arrow_size, arrow_size)
next_rect = QtCore.QRect(0, 0, arrow_size, arrow_size)
arrow_rects = [prev_rect, next_rect]
- prev_x = name_x + name_size.width() + self._char_width
+ prev_x = name_x + int(name_size.width()) + self._char_width
prev_rect.moveCenter(name_rect.center())
prev_rect.moveLeft(prev_x)
- value_x = prev_x + prev_rect.width() + self._char_width
- value_rect = QtCore.QRect(0, 0, value_size.width(), value_size.height())
+ value_x = prev_x + int(prev_rect.width()) + self._char_width
+ value_rect = QtCore.QRect(0, 0, int(value_size.width()), int(value_size.height()))
value_rect.moveBottomLeft(QtCore.QPoint(int(value_x), int(y)))
- next_x = value_x + value_size.width() + self._char_width
+ next_x = value_x + int(value_size.width()) + self._char_width
next_rect.moveCenter(name_rect.center())
next_rect.moveLeft(next_x)
From 099b8b14a8748732b3f2fbd1015f29d7a728c93e Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Thu, 5 Oct 2023 15:45:28 +0300
Subject: [PATCH 6/8] Various enchancements:
- Fixed `get_instruction_addresses` (not all CODE segments were detected)
- Fixed remaining Qt float/int related issues
- Updated arm32 qemu-tracer's README
- Qemu arm32 tracer - fixed memory reading
---
plugins/tenet/integration/api/ida_api.py | 6 +++--
plugins/tenet/registers.py | 15 -------------
plugins/tenet/trace/file.py | 6 +++--
plugins/tenet/trace/reader.py | 2 +-
plugins/tenet/ui/hex_view.py | 2 +-
plugins/tenet/util/debug.py | 18 +++++++++++++++
tracers/qemu/arm32/README.md | 28 ++++++++++++++----------
tracers/qemu/arm32/tenet.c | 4 +++-
8 files changed, 48 insertions(+), 33 deletions(-)
diff --git a/plugins/tenet/integration/api/ida_api.py b/plugins/tenet/integration/api/ida_api.py
index a85cd52..050d78a 100644
--- a/plugins/tenet/integration/api/ida_api.py
+++ b/plugins/tenet/integration/api/ida_api.py
@@ -222,19 +222,21 @@ def get_instruction_addresses(self):
# fetch code segments
seg = ida_segment.getseg(seg_address)
- if seg.sclass != ida_segment.SEG_CODE:
+ if ida_segment.get_segm_class(seg) != "CODE":
continue
current_address = seg_address
end_address = seg.end_ea
+ # print(f"Segment {seg.start_ea:08X} --> {seg.end_ea:08X} CODE")
+
# save the address of each instruction in the segment
while current_address < end_address:
current_address = ida_bytes.next_head(current_address, end_address)
+ # print(f"{hex(current_address)} -> {ida_bytes.get_flags(current_address)}")
if ida_bytes.is_code(ida_bytes.get_flags(current_address)):
instruction_addresses.append(current_address)
- # print(f"Seg {seg.start_ea:08X} --> {seg.end_ea:08X} CODE")
# print(f" -- {len(instruction_addresses):,} instructions found")
return instruction_addresses
diff --git a/plugins/tenet/registers.py b/plugins/tenet/registers.py
index c8dfd9a..a8ff75b 100644
--- a/plugins/tenet/registers.py
+++ b/plugins/tenet/registers.py
@@ -263,21 +263,6 @@ def _breakpoints_changed(self):
return
self.view.refresh()
- def _idx_changed(self, idx):
- """
- The trace position has been changed.
- """
- self.model.idx = idx
- self.set_registers(self.reader.registers, self.reader.trace.get_reg_delta(idx).keys())
-
- def _breakpoints_changed(self):
- """
- Handle breakpoints changed event.
- """
- if not self.view:
- return
- self.view.refresh()
-
class RegistersModel(object):
"""
diff --git a/plugins/tenet/trace/file.py b/plugins/tenet/trace/file.py
index 1246515..a2d00c0 100644
--- a/plugins/tenet/trace/file.py
+++ b/plugins/tenet/trace/file.py
@@ -10,6 +10,8 @@
import itertools
import collections
+from typing import List
+
# -----------------------------------------------------------------------------
# file.py -- Trace File
# -----------------------------------------------------------------------------
@@ -267,7 +269,7 @@ def __init__(self, filepath, arch=None):
self.mem_masks = None
#
- # register data is stored in a contiguos blob for each trace segment.
+ # register data is stored in a contiguous blob for each trace segment.
#
# for each step / 'instruction' of the trace, we create a 32bit
# register mask that defines which registers changed. each bit in
@@ -293,7 +295,7 @@ def __init__(self, filepath, arch=None):
# for more information, look at the TraceSegments class
#
- self.segments = []
+ self.segments: List[TraceSegment] = []
# the number of timestamps / 'instructions' for each trace segment
self.segment_length = DEFAULT_SEGMENT_LENGTH
diff --git a/plugins/tenet/trace/reader.py b/plugins/tenet/trace/reader.py
index 2a2b2c3..04b912f 100644
--- a/plugins/tenet/trace/reader.py
+++ b/plugins/tenet/trace/reader.py
@@ -1696,7 +1696,7 @@ def get_registers(self, reg_names=None, idx=None):
def get_memory(self, address, length, idx=None):
"""
- Return the requested memeory.
+ Return the requested memory.
If a timestamp (idx) is provided, that will be used instead of the current timestamp.
"""
diff --git a/plugins/tenet/ui/hex_view.py b/plugins/tenet/ui/hex_view.py
index f500b1c..4f43a6a 100644
--- a/plugins/tenet/ui/hex_view.py
+++ b/plugins/tenet/ui/hex_view.py
@@ -32,7 +32,7 @@ def __init__(self, controller, model, parent=None):
fm = QtGui.QFontMetricsF(font)
self._char_width = int(fm.width("9"))
self._char_height = int(fm.tightBoundingRect("9").height() * 1.75)
- self._char_descent = self._char_height - fm.descent() * 0.75
+ self._char_descent = int(self._char_height - fm.descent() * 0.75)
self._click_timer = QtCore.QTimer(self)
self._click_timer.setSingleShot(True)
diff --git a/plugins/tenet/util/debug.py b/plugins/tenet/util/debug.py
index 34c8a97..b4a0f04 100644
--- a/plugins/tenet/util/debug.py
+++ b/plugins/tenet/util/debug.py
@@ -18,3 +18,21 @@ def timed(*args, **kw):
return result
return timed
+
+
+class TimeIt:
+ def __init__(self, name):
+ self.name = name
+ self.start = 0.0
+ self.end = 0.0
+
+ def __enter__(self):
+ self.start = time.time()
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.end = time.time()
+
+ print(f"{self.name} took {self.end - self.start} seconds")
+
+ return True
diff --git a/tracers/qemu/arm32/README.md b/tracers/qemu/arm32/README.md
index 0244e22..e283ca6 100644
--- a/tracers/qemu/arm32/README.md
+++ b/tracers/qemu/arm32/README.md
@@ -3,20 +3,24 @@
In this folder is a sample arm32 QEMU-based Tenet tracer built on the QEMU plugin API + some black magic. This tracer is mostly based on the x86-32 version: [README.md](../x86-32/README.md).
Tested on QEMU v5.0.0 (fdd76fecdde1ad444ff4deb7f1c4f7e4a1ef97d6).
-Configure cmd: `CC=gcc-9 ../configure --enable-plugins --disable-strip --enable-linux-user --target-list=arm-softmmu,arm-linux-user`
-Make cmd: `make -j8`
+
+Build instructions:
+
+1. `mkdir build && cd build`
+2. `CC=gcc-9 ../configure --enable-plugins --disable-strip --enable-linux-user --target-list=arm-softmmu,arm-linux-user`
+3. `make -j8 && cd tests/plugin && make -j8 && cd ../../..`
## Usage
-This QEMU plugin can be used to trace [XEMU](https://github.com/mborgerson/xemu), and maybe some other x86 based QEMU projects (with some adaptations). Please note, XEMU needs to be built with `--enable-plugins` (add it to `build.sh`) to use the provided plugin.
+This QEMU plugin can be used to trace [QEMU](https://gitlab.com/qemu-project/qemu), and maybe some other arm based QEMU projects (with some adaptations). Please note, QEMU needs to be built with `--enable-plugins` (add it to `build.sh`) to use the provided plugin.
Example usage:
-```
-~/xemu/dist/xemu -plugin ~/xemu/tests/plugin/libtenet.so
+```bash
+/build/arm-linux-user/qemu-arm -plugin /build/tests/plugin/libtenet.so
```
-This will start the system and generate a `trace.log` file. Since there is no filtering of any sort, I would recommend skipping the startup animation, or modifying the plugin to trace specific areas of interest. Otherwise you will get a raw, 'full-system' trace.
+This will start the system and generate a `trace.log` file. This plugin supports pc-based filtering, and can be configured by setting the lower and upper bounds of the pc range to trace in the plugin's arguments: `libtenet.so,arg=trace.log,arg=0x400000,arg=0x600000`
## Compilation
@@ -24,19 +28,21 @@ QEMU's native plugin API does not provide access to guest registers or memory ma
### Finding magic offsets
-1. Place this line near the end of `~/xemu/target/i386/cpu.h`
+1. Place this line near the end of `qemu/target/arm/cpu.h`
```c
- char __foo[] = {[offsetof(X86CPU, env)] = ""};
+ char __foo[] = {[offsetof(ARMCPU, env)] = ""};
```
-2. Attempt to build XEMU/QEMU and let the compiler explode, the error message will print the magic offset value we will need to hardcode into the QEMU plugin / tracer.
+2. Attempt to build QEMU and let the compiler explode, the error message will print the magic offset value we will need to hardcode into the QEMU plugin / tracer.
3. Remove / comment out the line added in Step 1
+N.B. For some reason I couldn't get this to work with `offsetof(ARMCPU, regs)`, so I had to use `gdb` to find the offset of `env` manually.
+
### Building libtenet
-1. Place `tenet.c` in `~/xemu/tests/plugin/`
+1. Place `tenet.c` in `qemu/tests/plugin/`
2. Add `NAMES += tenet` to the `Makefile` in this directory
-3. Modify `tenet.c`, and replace `34928` with the magic offset found using the above steps
+3. Modify `tenet.c`, and replace `33488` with the magic offset found using the above steps
4. Run `make`
5. There should be a resulting libtenet.so in this plugins directory
diff --git a/tracers/qemu/arm32/tenet.c b/tracers/qemu/arm32/tenet.c
index 580f641..8a590b6 100644
--- a/tracers/qemu/arm32/tenet.c
+++ b/tracers/qemu/arm32/tenet.c
@@ -110,6 +110,8 @@ static void vcpu_insn_exec(unsigned int cpu_index, void* udata)
length += sprintf(reg_scratch + length, "R12=%X,", g_cpu[R12]);
if (g_cpu[SP] != g_cpu_prev[SP])
length += sprintf(reg_scratch + length, "SP=%X,", g_cpu[SP]);
+ if (g_cpu[LR] != g_cpu_prev[LR])
+ length += sprintf(reg_scratch + length, "LR=%X,", g_cpu[LR]);
length += sprintf(reg_scratch + length, "PC=%lX", pc);
@@ -135,7 +137,7 @@ static void vcpu_insn_exec(unsigned int cpu_index, void* udata)
// Third way. If it doesn't work, you're out of luck.
cpu_memory_rw_debug(
- qemu_get_cpu(cpu_index), entry->ram_addr, (char*)access_data, access_size, 0);
+ qemu_get_cpu(cpu_index), entry->virt_addr, (char*)access_data, access_size, 0);
for (int j = 0; j < access_size; j++)
length += sprintf(reg_scratch + length, "%02X", access_data[j]);
From 8aebc42d8d8c15073e502faf04f1013a58ba0c48 Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Thu, 5 Oct 2023 16:30:45 +0300
Subject: [PATCH 7/8] Update readme
---
README.md | 32 ++++++++++++++++----------------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index c340270..09bdfff 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Special thanks to [QIRA](https://github.com/geohot/qira) / [geohot](https://twit
Tenet is a cross-platform (Windows, macOS, Linux) Python 3 plugin. It takes zero third party dependencies, making the code both portable and easy to install.
1. From your disassembler's python console, run the following command to find its plugin directory:
- - **IDA Pro**: `import idaapi, os; os.path.join(idaapi.get_user_idadir(), "plugins")`
+ * **IDA Pro**: `import idaapi, os; os.path.join(idaapi.get_user_idadir(), "plugins")`
2. Copy the contents of this repository's `/plugins/` folder to the listed directory.
3. Restart your disassembler.
@@ -41,13 +41,13 @@ As this is the initial release, Tenet only accepts simple human-readable text tr
## Bidirectional Exploration
-While using Tenet, the plugin will 'paint' trails to indicate the flow of execution forwards (blue) and backwards (red) from your present position in the active execution trace.
+While using Tenet, the plugin will 'paint' trails to indicate the flow of execution forwards (blue) and backwards (red) from your present position in the active execution trace.
-To `step` forwards or backwards through time, you simply *scroll while hovering over the timeline* on the right side of the disassembler. To `step over` function calls, hold `SHIFT` while scrolling.
+To `step` forwards or backwards through time, you simply *scroll while hovering over the timeline* on the right side of the disassembler. To `step over` function calls, hold `SHIFT` while scrolling.
## Trace Timeline
@@ -57,7 +57,7 @@ The trace timeline will be docked on the right side of the disassembler. This wi
-By *clicking and dragging across the timeline*, it is possible to zoom in on a specific section of the execution trace. This action can be repeated any number of times to reach the desired granularity.
+By *clicking and dragging across the timeline*, it is possible to zoom in on a specific section of the execution trace. This action can be repeated any number of times to reach the desired granularity.
## Execution Breakpoints
@@ -69,13 +69,13 @@ Double clicking the instruction pointer in the registers window will highlight i
To jump between executions, *scroll up or down while hovering the highlighted instruction pointer*.
-Additionally, you can *right click in the disassembly listing* and select one of the navigation-based menu entries to quickly seek to the execution of an instruction of interest.
+Additionally, you can *right click in the disassembly listing* and select one of the navigation-based menu entries to quickly seek to the execution of an instruction of interest.
-IDA's native `F2` hotkey can also be used to set breakpoints on arbitrary instructions.
+IDA's native `F2` hotkey can also be used to set breakpoints on arbitrary instructions.
## Memory Breakpoints
@@ -93,7 +93,7 @@ Memory breakpoints can be navigated using the same technique described for execu
-To navigate the memory view to an arbitrary address, click onto the memory view and hit `G` to enter either an address or database symbol to seek the view to.
+To navigate the memory view to an arbitrary address, click onto the memory view and hit `G` to enter either an address or database symbol to seek the view to.
## Region Breakpoints
@@ -103,13 +103,13 @@ It is possible to set a memory breakpoint across a region of memory by highlight
-As with normal memory breakpoints, hovering the region and *scrolling* can used to traverse between the accesses made to the selected region of memory.
+As with normal memory breakpoints, hovering the region and *scrolling* can used to traverse between the accesses made to the selected region of memory.
## Register Seeking
-In reverse engineering, it's pretty common to encounter situations where you ask yourself *"Which instruction set this register to its current value?"*
+In reverse engineering, it's pretty common to encounter situations where you ask yourself *"Which instruction set this register to its current value?"*
-Using Tenet, you can seek backwards to that instruction in a single click.
+Using Tenet, you can seek backwards to that instruction in a single click.
@@ -119,7 +119,7 @@ Seeking backwards is by far the most common direction to navigate across registe
## Timestamp Shell
-A simple 'shell' is provided to navigate to specific timestamps in the trace. Pasting (or typing...) a timestamp into the shell with or without commas will suffice.
+A simple 'shell' is provided to navigate to specific timestamps in the trace. Pasting (or typing...) a timestamp into the shell with or without commas will suffice.
@@ -147,7 +147,7 @@ Tenet will remember your theme preference for future loads and uses.
#### Q: What trace architectures does Tenet support loading?
-* *A: Only x86 and AMD64, but the codebase is almost entirely architecture agnostic.*
+* *A: x86, AMD64 and Arm32, but the codebase is almost entirely architecture agnostic.*
#### Q: How big of a trace file can Tenet load / navigate?
@@ -164,11 +164,11 @@ Tenet will remember your theme preference for future loads and uses.
#### Q: Memory in my trace is changing, but there are no writes to the region. Is this a bug!?
* *A: Your log file may not have captured all memory writes. For example, usermode DBI generally do not get a memory callback for external writes to process memory. This is most common when reading from a file, or from socket -- it is the kernel that writes memory into your designated usermode buffer, making the event invisible to traditional instrumentation.*
- * Microsoft TTD generally exhibits the same behavior, it's tricky to solve without modeling syscalls.
+ * Microsoft TTD generally exhibits the same behavior, it's tricky to solve without modeling syscalls.
-#### Q: Will this be ported to Binary Ninja / Ghidra / ... ?
+#### Q: Will this be ported to Binary Ninja / Ghidra / ... ?
-* *A: Possibly, but not anytime soon (unless there is __significant__ incentive). As a research oriented project, the driving motivation is on developing novel strategies to organize and explore program execution -- not porting them.*
+* *A: Possibly, but not anytime soon (unless there is **significant** incentive). As a research oriented project, the driving motivation is on developing novel strategies to organize and explore program execution -- not porting them.*
#### Q: My organization would like to support this project, how can we help?
@@ -186,7 +186,7 @@ Time and ~~motivation~~ funding permitting, future work may include:
* Trace cartography, improved summarization and representation of trace geography
* Make the 'cpu architecture' selection/detection slightly less hardcoded
* More out-of-the-box tracing bridges, DynamoRIO, TTD, RR, QEMU, Bochs, ...
-* Support for Hex-Rays / decompiled views (besides basic view sync)
+* Support for Hex-Rays / decompiled views (besides basic view sync)
* Improved workflow for automatically loading or iterating on traces
* Differential analysis, high level 'trace diffing'
* Better navigation and breakdown of threads, quantum's
From 132e127b4c95c3d2bf8457be6e1b9f9bd10d1d02 Mon Sep 17 00:00:00 2001
From: Theodor Arsenij
Date: Mon, 23 Oct 2023 16:53:17 +0300
Subject: [PATCH 8/8] Fixed another incorrect argument for the `drawRect`
---
plugins/tenet/ui/trace_view.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/tenet/ui/trace_view.py b/plugins/tenet/ui/trace_view.py
index 451f72c..2c5a505 100644
--- a/plugins/tenet/ui/trace_view.py
+++ b/plugins/tenet/ui/trace_view.py
@@ -498,7 +498,7 @@ def _idx2pos(self, idx):
y += self._cell_border * relative_idx # cell top border
# return the y position of the cell corresponding to the given timestamp
- return y
+ return int(y)
def _pos2idx(self, y):
"""