The percent script simulates the % key in VI command mode, more stack memory items. Basically this means that for a push, this makes you jump to the associated pop. For a call, this makes you jump to the corresponding ret, and so on.
NOTE: This example make use of the low-level api.
It is built as an Axion script, so this also shows the use of a callback in Axion.
1 from axion_api
import axion
3 from PythonQt
import Qt
6 def register_command(callback, desc, name, shortcut):
7 action = Qt.QAction(desc, axion)
8 axion.shortcuts().register_action(action, name, Qt.QKeySequence(shortcut))
9 action.connect(
"triggered(bool)", callback)
11 def reven_connection_from_axion(axion):
12 host, port = axion.connection_info()
13 if port == 0
or not host:
15 return reven_api.reven_connection(host.encode(), port)
17 def is_memory_history_enabled(client):
18 return 'memory_range_history' in [ i.name
for i
in client.engine_get_current_preset()
if i.properties.enabled ]
21 def pick_memory_access(client, point):
22 "Return first non null logical address accessed by instruction at given execution point or None"
24 null_logical = reven_api.logical_address(0, 0)
27 accesses = client.memory_get_history_instruction(point)
28 for access
in accesses:
29 if access.logical != null_logical:
30 if not (result
and result.write
and not access.write):
34 def get_matching_instruction(client, point):
35 "Return execution point of matching instruction for given execution point or None if not found."
38 point_range = reven_api.execution_range(point.run_name, point.sequence_identifier, 1, point.instruction_index)
40 context = client.run_get_running_context_between(point_range, reven_api.vector_of_logical_address_range())
43 ss_before = context.before.numeric_registers[
'ss'].value
44 ss_after = context.after.numeric_registers[
'ss'].value
47 if ss_before != ss_after:
49 return client.run_search_next_register_use(point, forward=(ss_before > ss_after), read=
False, write=
True, register_name=
"ss")
52 access = pick_memory_access(client, point)
55 return client.run_search_next_memory_use(point, forward=
True, read=
True, write=
False, address=access.logical, size=access.size)
58 return client.run_search_next_memory_use(point, forward=
False, read=
False, write=
True, address=access.logical, size=access.size)
61 esp_before = context.before.numeric_registers[
'esp'].value
62 esp_after = context.after.numeric_registers[
'esp'].value
65 if esp_before != esp_after:
66 if esp_before > esp_after:
67 stack = reven_api.logical_address(ss_after, esp_after)
68 return client.run_search_next_memory_use(point, forward=
True, read=
True, write=
False, address=stack, size=4)
70 if esp_before < esp_after:
71 stack = reven_api.logical_address(ss_before, esp_before)
72 return client.run_search_next_memory_use(point, forward=
False, read=
False, write=
True, address=stack, size=4)
74 def trigger_percent():
75 "Callback to call when this plugin is enabled."
78 run, seq, instr = axion.selected_sequence()
81 client = reven_connection_from_axion(axion)
84 point = reven_api.execution_point(run.encode(), seq, instr)
87 result = get_matching_instruction(client, point)
89 if result ==
None or not result.valid():
90 axion.status_message(
"No matching instruction recorded for [%s@%d:%d]" % (run, seq, instr))
93 axion.select_sequence(result.run_name, result.sequence_identifier, result.instruction_index)
94 axion.status_message(
"Jumped to matching instruction at [%s@%d:%d] from [%s@%d:%d]" % (result.run_name, result.sequence_identifier, result.instruction_index, run, seq, instr))
97 def get_disabled_callback(reason):
98 "Generate a callback printing reason for having disabled this plugin in status bar"
101 axion.status_message(
"Percent plugin is disabled (%s)" % reason)
106 "Try to enable this plugin, check connection and memory history availability"
108 global current_callback
110 client = reven_connection_from_axion(axion)
112 current_callback = get_disabled_callback(
"Not connected to Reven server")
115 if not is_memory_history_enabled(client) :
116 reason =
"requires inspector memory range history"
117 current_callback = get_disabled_callback(reason)
118 axion.log_warning(
"Percent plugin disabled (%s)" % reason)
120 current_callback = trigger_percent
124 def axion_callback():
125 "Actual callback auto-called by axion when this plugin is triggered"
129 def axion_connected():
130 "Callback auto-called by axion when connected to a new server"
134 def axion_disconnected():
135 "Callback auto-called by axion when disconnected from current server"
137 current_callback = get_disabled_callback(
"Not connected to Reven server")
140 current_callback = get_disabled_callback(
"Not initialised")
141 register_command(axion_callback,
"percent",
"plugin.percent",
"%")