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.
-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.
-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
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/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/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/api/ida_api.py b/plugins/tenet/integration/api/ida_api.py
index 2a62187..050d78a 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):
@@ -218,20 +222,22 @@ 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")
+ # print(f" -- {len(instruction_addresses):,} instructions found")
return instruction_addresses
@@ -298,7 +304,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 +317,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 +336,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 +374,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 +428,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 +449,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 +486,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 +501,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 +516,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 +529,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 +545,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 +580,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/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..a8ff75b 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
@@ -262,20 +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):
"""
@@ -286,15 +273,15 @@ def __init__(self, pctx):
self._pctx = pctx
self.reset()
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Callbacks
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
self._registers_changed_callbacks = []
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Properties
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
@property
def arch(self):
@@ -310,9 +297,9 @@ def execution_breakpoints(self):
"""
return self._pctx.breakpoints.model.bp_exec
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
# Public
- #----------------------------------------------------------------------
+ # ----------------------------------------------------------------------
def reset(self):
@@ -359,9 +346,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/__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/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/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/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/file.py b/plugins/tenet/trace/file.py
index 5ee49c9..a2d00c0 100644
--- a/plugins/tenet/trace/file.py
+++ b/plugins/tenet/trace/file.py
@@ -10,9 +10,11 @@
import itertools
import collections
-#-----------------------------------------------------------------------------
+from typing import List
+
+# -----------------------------------------------------------------------------
# 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 +22,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 +39,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 +57,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 +67,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 +124,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 +233,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 +247,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.
#
@@ -262,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
@@ -275,8 +282,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,10 +292,10 @@ 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 = []
+ self.segments: List[TraceSegment] = []
# the number of timestamps / 'instructions' for each trace segment
self.segment_length = DEFAULT_SEGMENT_LENGTH
@@ -303,9 +310,9 @@ def __init__(self, filepath, arch=None):
self._load_trace()
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Properties
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
@property
def name(self):
@@ -339,10 +346,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 +405,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 +436,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 +447,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 +459,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 +509,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 +521,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()
+ else:
+ 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 +542,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 +561,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 +587,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 +606,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 +615,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 +642,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 +673,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 +683,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 +704,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 +724,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 +759,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 +778,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 +809,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 +852,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 +913,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 +928,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 +941,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 +956,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 +980,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 +1005,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 +1024,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 +1038,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 +1054,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 +1070,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 +1113,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 +1135,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 +1148,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 +1169,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 +1183,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 +1191,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 +1205,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 +1231,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 +1250,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 +1282,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 +1307,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 +1327,7 @@ def load(self, f):
#
# memory
#
-
+
idx_type = self.trace.mem_idx_type
addr_type = self.trace.mem_addr_type
@@ -1278,7 +1336,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 +1348,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 +1373,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 +1407,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 +1437,7 @@ def _finalize_registers(self, remapped_ip):
del self.ips
self.ips = new_ips
-
+
#
# pack register masks
#
@@ -1389,7 +1447,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 +1462,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 +1488,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 +1501,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 +1521,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 +1530,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 +1542,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 +1567,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 +1577,7 @@ def _process_line(self, line, relative_idx):
"""
IP = self.trace.arch.IP
REGISTERS = self.trace.arch.REGISTERS
-
+
delta = line.split(",")
registers = {}
@@ -1553,14 +1608,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 +1629,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 +1687,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 +1715,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 +1751,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 +1767,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 +1779,9 @@ def _unpack_registers(self, mask, offset):
# return the completed register delta
return registers
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
# Util
- #-------------------------------------------------------------------------
+ # -------------------------------------------------------------------------
def _regs2mask(self, regs):
"""
diff --git a/plugins/tenet/trace/reader.py b/plugins/tenet/trace/reader.py
index 90ef9d6..04b912f 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:
@@ -1675,14 +1696,14 @@ 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.
"""
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..4f43a6a 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 = int(fm.width("9"))
+ self._char_height = int(fm.tightBoundingRect("9").height() * 1.75)
+ self._char_descent = int(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/reg_view.py b/plugins/tenet/ui/reg_view.py
index 6f5ce2d..441e7a2 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,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)
@@ -109,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()
@@ -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
@@ -162,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.moveBottomLeft(QtCore.QPoint(name_x, y))
+ 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_rect.moveBottomLeft(QtCore.QPoint(value_x, y))
+ 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)
@@ -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/ui/trace_view.py b/plugins/tenet/ui/trace_view.py
index 7f3644f..2c5a505 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,20 +485,20 @@ 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
- return y
+ return int(y)
def _pos2idx(self, y):
"""
@@ -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..b4a0f04 100644
--- a/plugins/tenet/util/debug.py
+++ b/plugins/tenet/util/debug.py
@@ -1,19 +1,38 @@
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
+
+
+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/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/qt/util.py b/plugins/tenet/util/qt/util.py
index 758f41a..8073505 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.
@@ -53,16 +59,18 @@ 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):
"""
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)
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")
-
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..e283ca6
--- /dev/null
+++ b/tracers/qemu/arm32/README.md
@@ -0,0 +1,48 @@
+# 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).
+
+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 [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:
+
+```bash
+