mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-09-13 21:56:19 +00:00
GP-3984: Have gdb connector delay section info until asked.
This commit is contained in:
parent
683dfc6da3
commit
644e2c53e5
|
@ -467,11 +467,14 @@ def ghidra_trace_set_snap(snap, *, is_mi, **kwargs):
|
|||
STATE.require_trace().set_snap(int(gdb.parse_and_eval(snap)))
|
||||
|
||||
|
||||
def quantize_pages(start, end):
|
||||
return (start // PAGE_SIZE * PAGE_SIZE, (end+PAGE_SIZE-1) // PAGE_SIZE*PAGE_SIZE)
|
||||
|
||||
|
||||
def put_bytes(start, end, pages, is_mi, from_tty):
|
||||
trace = STATE.require_trace()
|
||||
if pages:
|
||||
start = start // PAGE_SIZE * PAGE_SIZE
|
||||
end = (end + PAGE_SIZE - 1) // PAGE_SIZE * PAGE_SIZE
|
||||
start, end = quantize_pages(start, end)
|
||||
inf = gdb.selected_inferior()
|
||||
buf = bytes(inf.read_memory(start, end - start))
|
||||
|
||||
|
@ -486,6 +489,8 @@ def put_bytes(start, end, pages, is_mi, from_tty):
|
|||
|
||||
|
||||
def eval_address(address):
|
||||
if isinstance(address, int):
|
||||
return address
|
||||
try:
|
||||
return int(gdb.parse_and_eval(address))
|
||||
except gdb.error as e:
|
||||
|
@ -494,10 +499,13 @@ def eval_address(address):
|
|||
|
||||
def eval_range(address, length):
|
||||
start = eval_address(address)
|
||||
try:
|
||||
end = start + int(gdb.parse_and_eval(length))
|
||||
except gdb.error as e:
|
||||
raise gdb.GdbError("Cannot convert '{}' to length".format(length))
|
||||
if isinstance(length, int):
|
||||
end = start + length
|
||||
else:
|
||||
try:
|
||||
end = start + int(gdb.parse_and_eval(length))
|
||||
except gdb.error as e:
|
||||
raise gdb.GdbError("Cannot convert '{}' to length".format(length))
|
||||
return start, end
|
||||
|
||||
|
||||
|
@ -532,6 +540,18 @@ def ghidra_trace_putval(value, pages=True, *, is_mi, from_tty=True, **kwargs):
|
|||
return put_bytes(start, end, pages, is_mi, from_tty)
|
||||
|
||||
|
||||
def putmem_state(address, length, state, pages=True):
|
||||
STATE.trace.validate_state(state)
|
||||
start, end = eval_range(address, length)
|
||||
if pages:
|
||||
start, end = quantize_pages(start, end)
|
||||
inf = gdb.selected_inferior()
|
||||
base, addr = STATE.trace.memory_mapper.map(inf, start)
|
||||
if base != addr.space:
|
||||
trace.create_overlay_space(base, addr.space)
|
||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||
|
||||
|
||||
@cmd('ghidra trace putmem-state', '-ghidra-trace-putmem-state', gdb.COMMAND_DATA, True)
|
||||
def ghidra_trace_putmem_state(address, length, state, *, is_mi, **kwargs):
|
||||
"""
|
||||
|
@ -539,13 +559,7 @@ def ghidra_trace_putmem_state(address, length, state, *, is_mi, **kwargs):
|
|||
"""
|
||||
|
||||
STATE.require_tx()
|
||||
STATE.trace.validate_state(state)
|
||||
start, end = eval_range(address, length)
|
||||
inf = gdb.selected_inferior()
|
||||
base, addr = STATE.trace.memory_mapper.map(inf, start)
|
||||
if base != addr.space:
|
||||
trace.create_overlay_space(base, addr.space)
|
||||
STATE.trace.set_memory_state(addr.extend(end - start), state)
|
||||
putmem_state(address, length, state, True)
|
||||
|
||||
|
||||
@cmd('ghidra trace delmem', '-ghidra-trace-delmem', gdb.COMMAND_DATA, True)
|
||||
|
@ -1223,9 +1237,10 @@ def ghidra_trace_put_regions(*, is_mi, **kwargs):
|
|||
put_regions()
|
||||
|
||||
|
||||
def put_modules():
|
||||
def put_modules(modules=None, sections=False):
|
||||
inf = gdb.selected_inferior()
|
||||
modules = util.MODULE_INFO_READER.get_modules()
|
||||
if modules is None:
|
||||
modules = util.MODULE_INFO_READER.get_modules()
|
||||
mapper = STATE.trace.memory_mapper
|
||||
mod_keys = []
|
||||
for mk, m in modules.items():
|
||||
|
@ -1237,29 +1252,32 @@ def put_modules():
|
|||
if base_base != base_addr.space:
|
||||
STATE.trace.create_overlay_space(base_base, base_addr.space)
|
||||
modobj.set_value('_range', base_addr.extend(m.max - m.base))
|
||||
sec_keys = []
|
||||
for sk, s in m.sections.items():
|
||||
spath = mpath + SECTION_ADD_PATTERN.format(secname=sk)
|
||||
secobj = STATE.trace.create_object(spath)
|
||||
sec_keys.append(SECTION_KEY_PATTERN.format(secname=sk))
|
||||
start_base, start_addr = mapper.map(inf, s.start)
|
||||
if start_base != start_addr.space:
|
||||
STATE.trace.create_overlay_space(
|
||||
start_base, start_addr.space)
|
||||
secobj.set_value('_range', start_addr.extend(s.end - s.start))
|
||||
secobj.set_value('_offset', s.offset)
|
||||
secobj.set_value('_attrs', s.attrs, schema=sch.STRING_ARR)
|
||||
secobj.insert()
|
||||
# In case there are no sections, we must still insert the module
|
||||
modobj.insert()
|
||||
STATE.trace.proxy_object_path(
|
||||
mpath + SECTIONS_ADD_PATTERN).retain_values(sec_keys)
|
||||
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
||||
infnum=inf.num)).retain_values(mod_keys)
|
||||
if sections:
|
||||
sec_keys = []
|
||||
for sk, s in m.sections.items():
|
||||
spath = mpath + SECTION_ADD_PATTERN.format(secname=sk)
|
||||
secobj = STATE.trace.create_object(spath)
|
||||
sec_keys.append(SECTION_KEY_PATTERN.format(secname=sk))
|
||||
start_base, start_addr = mapper.map(inf, s.start)
|
||||
if start_base != start_addr.space:
|
||||
STATE.trace.create_overlay_space(
|
||||
start_base, start_addr.space)
|
||||
secobj.set_value('_range', start_addr.extend(s.end - s.start))
|
||||
secobj.set_value('_offset', s.offset)
|
||||
secobj.set_value('_attrs', s.attrs, schema=sch.STRING_ARR)
|
||||
secobj.insert()
|
||||
STATE.trace.proxy_object_path(
|
||||
mpath + SECTIONS_ADD_PATTERN).retain_values(sec_keys)
|
||||
|
||||
scpath = mpath + SECTIONS_ADD_PATTERN
|
||||
sec_container_obj = STATE.trace.create_object(scpath)
|
||||
sec_container_obj.insert()
|
||||
if not sections:
|
||||
STATE.trace.proxy_object_path(MODULES_PATTERN.format(
|
||||
infnum=inf.num)).retain_values(mod_keys)
|
||||
|
||||
|
||||
@cmd('ghidra trace put-modules', '-ghidra-trace-put-modules', gdb.COMMAND_DATA,
|
||||
True)
|
||||
@cmd('ghidra trace put-modules', '-ghidra-trace-put-modules', gdb.COMMAND_DATA, True)
|
||||
def ghidra_trace_put_modules(*, is_mi, **kwargs):
|
||||
"""
|
||||
Gather object files, if applicable, and write to the trace's Modules.
|
||||
|
@ -1270,6 +1288,25 @@ def ghidra_trace_put_modules(*, is_mi, **kwargs):
|
|||
put_modules()
|
||||
|
||||
|
||||
@cmd('ghidra trace put-sections', '-ghidra-trace-put-sections', gdb.COMMAND_DATA, True)
|
||||
def ghidra_trace_put_sections(module_name, *, is_mi, **kwargs):
|
||||
"""
|
||||
Write the sections of the given module or all modules
|
||||
"""
|
||||
|
||||
modules = None
|
||||
if module_name != '-all-objects':
|
||||
modules = {mk: m for mk, m in util.MODULE_INFO_READER.get_modules(
|
||||
).items() if mk == module_name}
|
||||
if len(modules) == 0:
|
||||
raise gdb.GdbError(
|
||||
"No module / object named {}".format(module_name))
|
||||
|
||||
STATE.require_tx()
|
||||
with STATE.client.batch() as b:
|
||||
put_modules(modules, True)
|
||||
|
||||
|
||||
def convert_state(t):
|
||||
if t.is_exited():
|
||||
return 'TERMINATED'
|
||||
|
@ -1405,8 +1442,8 @@ def ghidra_trace_put_all(*, is_mi, **kwargs):
|
|||
ghidra_trace_putmem("$sp", "1", is_mi=is_mi)
|
||||
put_inferiors()
|
||||
put_environment()
|
||||
put_regions()
|
||||
put_modules()
|
||||
put_regions()
|
||||
put_threads()
|
||||
put_frames()
|
||||
put_breakpoints()
|
||||
|
|
|
@ -96,9 +96,11 @@ class InferiorState(object):
|
|||
self.visited.add(hashable_frame)
|
||||
if first or self.regions or self.threads or self.modules:
|
||||
# Sections, memory syscalls, or stack allocations
|
||||
commands.put_modules()
|
||||
self.modules = False
|
||||
commands.put_regions()
|
||||
self.regions = False
|
||||
if first or self.modules:
|
||||
elif first or self.modules:
|
||||
commands.put_modules()
|
||||
self.modules = False
|
||||
if first or self.breaks:
|
||||
|
|
|
@ -78,6 +78,7 @@ FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P<level>\\d*)\]')
|
|||
REGS_PATTERN = extre(FRAME_PATTERN, '.Registers')
|
||||
MEMORY_PATTERN = extre(INFERIOR_PATTERN, '\.Memory')
|
||||
MODULES_PATTERN = extre(INFERIOR_PATTERN, '\.Modules')
|
||||
MODULE_PATTERN = extre(MODULES_PATTERN, '\[(?P<modname>.*)\]')
|
||||
|
||||
|
||||
def find_availpid_by_pattern(pattern, object, err_msg):
|
||||
|
@ -132,6 +133,17 @@ def find_inf_by_modules_obj(object):
|
|||
return find_inf_by_pattern(object, MODULES_PATTERN, "a ModuleContainer")
|
||||
|
||||
|
||||
def find_inf_by_mod_obj(object):
|
||||
return find_inf_by_pattern(object, MODULE_PATTERN, "a Module")
|
||||
|
||||
|
||||
def find_module_name_by_mod_obj(object):
|
||||
mat = MODULE_PATTERN.fullmatch(object.path)
|
||||
if mat is None:
|
||||
raise TypeError(f"{object} is not a Module")
|
||||
return mat['modname']
|
||||
|
||||
|
||||
def find_thread_by_num(inf, tnum):
|
||||
for t in inf.threads():
|
||||
if t.num == tnum:
|
||||
|
@ -257,7 +269,7 @@ def find_bpt_loc_by_obj(object):
|
|||
def switch_inferior(inferior):
|
||||
if gdb.selected_inferior().num == inferior.num:
|
||||
return
|
||||
gdb.execute("inferior {}".format(inferior.num))
|
||||
gdb.execute(f'inferior {inferior.num}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
|
@ -266,14 +278,14 @@ def execute(cmd: str, to_string: bool=False):
|
|||
return gdb.execute(cmd, to_string=to_string)
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Available')
|
||||
def refresh_available(node: sch.Schema('AvailableContainer')):
|
||||
"""List processes on gdb's host system."""
|
||||
with commands.open_tracked_tx('Refresh Available'):
|
||||
gdb.execute('ghidra trace put-available')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Breakpoints')
|
||||
def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
||||
"""
|
||||
Refresh the list of breakpoints (including locations for the current
|
||||
|
@ -283,14 +295,14 @@ def refresh_breakpoints(node: sch.Schema('BreakpointContainer')):
|
|||
gdb.execute('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Inferiors')
|
||||
def refresh_inferiors(node: sch.Schema('InferiorContainer')):
|
||||
"""Refresh the list of inferiors."""
|
||||
with commands.open_tracked_tx('Refresh Inferiors'):
|
||||
gdb.execute('ghidra trace put-inferiors')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Breakpoint Locations')
|
||||
def refresh_inf_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
||||
"""
|
||||
Refresh the breakpoint locations for the inferior.
|
||||
|
@ -303,7 +315,7 @@ def refresh_inf_breakpoints(node: sch.Schema('BreakpointLocationContainer')):
|
|||
gdb.execute('ghidra trace put-breakpoints')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Environment')
|
||||
def refresh_environment(node: sch.Schema('Environment')):
|
||||
"""Refresh the environment descriptors (arch, os, endian)."""
|
||||
switch_inferior(find_inf_by_env_obj(node))
|
||||
|
@ -311,7 +323,7 @@ def refresh_environment(node: sch.Schema('Environment')):
|
|||
gdb.execute('ghidra trace put-environment')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Threads')
|
||||
def refresh_threads(node: sch.Schema('ThreadContainer')):
|
||||
"""Refresh the list of threads in the inferior."""
|
||||
switch_inferior(find_inf_by_threads_obj(node))
|
||||
|
@ -319,7 +331,7 @@ def refresh_threads(node: sch.Schema('ThreadContainer')):
|
|||
gdb.execute('ghidra trace put-threads')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Stack')
|
||||
def refresh_stack(node: sch.Schema('Stack')):
|
||||
"""Refresh the backtrace for the thread."""
|
||||
find_thread_by_stack_obj(node).switch()
|
||||
|
@ -327,7 +339,7 @@ def refresh_stack(node: sch.Schema('Stack')):
|
|||
gdb.execute('ghidra trace put-frames')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Registers')
|
||||
def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
||||
"""Refresh the register values for the frame."""
|
||||
find_frame_by_regs_obj(node).select()
|
||||
|
@ -336,7 +348,7 @@ def refresh_registers(node: sch.Schema('RegisterValueContainer')):
|
|||
gdb.execute('ghidra trace putreg')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display='Refresh Memory')
|
||||
def refresh_mappings(node: sch.Schema('Memory')):
|
||||
"""Refresh the list of memory regions for the inferior."""
|
||||
switch_inferior(find_inf_by_mem_obj(node))
|
||||
|
@ -344,18 +356,38 @@ def refresh_mappings(node: sch.Schema('Memory')):
|
|||
gdb.execute('ghidra trace put-regions')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh')
|
||||
@REGISTRY.method(action='refresh', display="Refresh Modules")
|
||||
def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
"""
|
||||
Refresh the modules and sections list for the inferior.
|
||||
|
||||
This will refresh the sections for all modules, not just the selected one.
|
||||
Refresh the modules list for the inferior.
|
||||
"""
|
||||
switch_inferior(find_inf_by_modules_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Modules'):
|
||||
gdb.execute('ghidra trace put-modules')
|
||||
|
||||
|
||||
# node is Module so this appears in Modules panel
|
||||
@REGISTRY.method(display='Load all Modules and all Sections')
|
||||
def load_all_sections(node: sch.Schema('Module')):
|
||||
"""
|
||||
Load/refresh all modules and all sections.
|
||||
"""
|
||||
switch_inferior(find_inf_by_mod_obj(node))
|
||||
with commands.open_tracked_tx('Refresh all Modules and all Sections'):
|
||||
gdb.execute('ghidra trace put-sections -all-objects')
|
||||
|
||||
|
||||
@REGISTRY.method(action='refresh', display="Refresh Module and Sections")
|
||||
def refresh_sections(node: sch.Schema('Module')):
|
||||
"""
|
||||
Load/refresh the module and its sections.
|
||||
"""
|
||||
switch_inferior(find_inf_by_mod_obj(node))
|
||||
with commands.open_tracked_tx('Refresh Module and Sections'):
|
||||
modname = find_module_name_by_mod_obj(node)
|
||||
gdb.execute(f'ghidra trace put-sections {modname}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='activate')
|
||||
def activate_inferior(inferior: sch.Schema('Inferior')):
|
||||
"""Switch to the inferior."""
|
||||
|
@ -374,7 +406,7 @@ def activate_frame(frame: sch.Schema('StackFrame')):
|
|||
find_frame_by_obj(frame).select()
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display='Add Inferior')
|
||||
def add_inferior(container: sch.Schema('InferiorContainer')):
|
||||
"""Add a new inferior."""
|
||||
gdb.execute('add-inferior')
|
||||
|
@ -388,14 +420,14 @@ def delete_inferior(inferior: sch.Schema('Inferior')):
|
|||
|
||||
|
||||
# TODO: Separate method for each of core, exec, remote, etc...?
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display='Connect Target')
|
||||
def connect(inferior: sch.Schema('Inferior'), spec: str):
|
||||
"""Connect to a target machine or process."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'target {spec}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@REGISTRY.method(action='attach', display='Attach by Available')
|
||||
def attach_obj(inferior: sch.Schema('Inferior'), target: sch.Schema('Attachable')):
|
||||
"""Attach the inferior to the given target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
|
@ -403,21 +435,21 @@ def attach_obj(inferior: sch.Schema('Inferior'), target: sch.Schema('Attachable'
|
|||
gdb.execute(f'attach {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='attach')
|
||||
@REGISTRY.method(action='attach', display='Attach by PID')
|
||||
def attach_pid(inferior: sch.Schema('Inferior'), pid: int):
|
||||
"""Attach the inferior to the given target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute(f'attach {pid}')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
@REGISTRY.method(display='Detach')
|
||||
def detach(inferior: sch.Schema('Inferior')):
|
||||
"""Detach the inferior's target."""
|
||||
switch_inferior(find_inf_by_obj(inferior))
|
||||
gdb.execute('detach')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
@REGISTRY.method(action='launch', display='Launch at main')
|
||||
def launch_main(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
|
@ -435,7 +467,8 @@ def launch_main(inferior: sch.Schema('Inferior'),
|
|||
''')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch', condition=util.GDB_VERSION.major >= 9)
|
||||
@REGISTRY.method(action='launch', display='Launch at Loader',
|
||||
condition=util.GDB_VERSION.major >= 9)
|
||||
def launch_loader(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
|
@ -451,7 +484,7 @@ def launch_loader(inferior: sch.Schema('Inferior'),
|
|||
''')
|
||||
|
||||
|
||||
@REGISTRY.method(action='launch')
|
||||
@REGISTRY.method(action='launch', display='Land and Run')
|
||||
def launch_run(inferior: sch.Schema('Inferior'),
|
||||
file: ParamDesc(str, display='File'),
|
||||
args: ParamDesc(str, display='Arguments')=''):
|
||||
|
@ -514,7 +547,7 @@ def step_out(thread: sch.Schema('Thread')):
|
|||
gdb.execute('finish')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext', name='Advance')
|
||||
@REGISTRY.method(action='step_ext', display='Advance')
|
||||
def step_advance(thread: sch.Schema('Thread'), address: Address):
|
||||
"""Continue execution up to the given address (advance)."""
|
||||
t = find_thread_by_obj(thread)
|
||||
|
@ -523,7 +556,7 @@ def step_advance(thread: sch.Schema('Thread'), address: Address):
|
|||
gdb.execute(f'advance *0x{offset:x}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='step_ext', name='Return')
|
||||
@REGISTRY.method(action='step_ext', display='Return')
|
||||
def step_return(thread: sch.Schema('Thread'), value: int=None):
|
||||
"""Skip the remainder of the current function (return)."""
|
||||
find_thread_by_obj(thread).switch()
|
||||
|
@ -611,7 +644,7 @@ def break_access_expression(expression: str):
|
|||
gdb.execute(f'awatch {expression}')
|
||||
|
||||
|
||||
@REGISTRY.method(action='break_ext')
|
||||
@REGISTRY.method(action='break_ext', display='Catch Event')
|
||||
def break_event(spec: str):
|
||||
"""Set a catchpoint (catch)."""
|
||||
gdb.execute(f'catch {spec}')
|
||||
|
@ -653,7 +686,12 @@ def read_mem(inferior: sch.Schema('Inferior'), range: AddressRange):
|
|||
offset_start = inferior.trace.memory_mapper.map_back(
|
||||
inf, Address(range.space, range.min))
|
||||
with commands.open_tracked_tx('Read Memory'):
|
||||
gdb.execute(f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
||||
try:
|
||||
gdb.execute(
|
||||
f'ghidra trace putmem 0x{offset_start:x} {range.length()}')
|
||||
except:
|
||||
commands.putmem_state(
|
||||
offset_start, offset_start+range.length() - 1, 'error')
|
||||
|
||||
|
||||
@REGISTRY.method
|
||||
|
|
|
@ -33,6 +33,10 @@ import java.util.Map;
|
|||
* list may change over time, but that shouldn't matter much. Each back-end should make its best
|
||||
* effort to match its methods to these stock actions where applicable, but ultimately, it is up to
|
||||
* the UI to decide what is presented where.
|
||||
*
|
||||
* @param name the name of the action (given as the action attribute on method annotations)
|
||||
* @param builtIn true if the action should <em>not</em> be presented in generic contexts, but
|
||||
* reserved for built-in, purpose-specific actions
|
||||
*/
|
||||
public record ActionName(String name, boolean builtIn) {
|
||||
private static final Map<String, ActionName> NAMES = new HashMap<>();
|
||||
|
@ -63,7 +67,7 @@ public record ActionName(String name, boolean builtIn) {
|
|||
}
|
||||
}
|
||||
|
||||
public static final ActionName REFRESH = builtIn("refresh");
|
||||
public static final ActionName REFRESH = extended("refresh");
|
||||
/**
|
||||
* Activate a given object and optionally a time
|
||||
*
|
||||
|
|
|
@ -63,11 +63,14 @@ public interface Target {
|
|||
* @param name the name of a common debugger command this action implements
|
||||
* @param details text providing more details, usually displayed in a tool tip
|
||||
* @param requiresPrompt true if invoking the action requires further user interaction
|
||||
* @param specificity a relative score of specificity. These are only meaningful when compared
|
||||
* among entries returned in the same collection.
|
||||
* @param enabled a supplier to determine whether an associated action in the UI is enabled.
|
||||
* @param action a function for invoking this action asynchronously
|
||||
*/
|
||||
record ActionEntry(String display, ActionName name, String details, boolean requiresPrompt,
|
||||
BooleanSupplier enabled, Function<Boolean, CompletableFuture<?>> action) {
|
||||
long specificity, BooleanSupplier enabled,
|
||||
Function<Boolean, CompletableFuture<?>> action) {
|
||||
|
||||
/**
|
||||
* Check if this action is currently enabled
|
||||
|
|
|
@ -56,6 +56,13 @@ public interface RemoteMethod {
|
|||
*/
|
||||
ActionName action();
|
||||
|
||||
/**
|
||||
* A title to display in the UI for this action.
|
||||
*
|
||||
* @return the title
|
||||
*/
|
||||
String display();
|
||||
|
||||
/**
|
||||
* A description of the method.
|
||||
*
|
||||
|
|
|
@ -23,8 +23,8 @@ import ghidra.debug.api.tracermi.*;
|
|||
import ghidra.trace.model.Trace;
|
||||
|
||||
public record RecordRemoteMethod(TraceRmiHandler handler, String name, ActionName action,
|
||||
String description, Map<String, RemoteParameter> parameters, SchemaName retType)
|
||||
implements RemoteMethod {
|
||||
String display, String description, Map<String, RemoteParameter> parameters,
|
||||
SchemaName retType) implements RemoteMethod {
|
||||
@Override
|
||||
public DefaultRemoteAsyncResult invokeAsync(Map<String, Object> arguments) {
|
||||
Trace trace = validate(arguments);
|
||||
|
|
|
@ -484,10 +484,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
RootMessage.Builder dispatch(RootMessage req, RootMessage.Builder rep) throws Exception;
|
||||
|
||||
default RootMessage handle(RootMessage req) {
|
||||
String desc = toString(req);
|
||||
/*String desc = toString(req);
|
||||
if (desc != null) {
|
||||
TimedMsg.debug(this, "HANDLING: " + desc);
|
||||
}
|
||||
}*/
|
||||
RootMessage.Builder rep = RootMessage.newBuilder();
|
||||
try {
|
||||
rep = dispatch(req, rep);
|
||||
|
@ -814,7 +814,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
|
||||
protected ReplyActivate handleActivate(RequestActivate req) {
|
||||
OpenTrace open = requireOpenTrace(req.getOid());
|
||||
TraceObject object = open.getObject(req.getObject(), true);
|
||||
TraceObject object = open.getObject(req.getObject(), false);
|
||||
DebuggerCoordinates coords = traceManager.getCurrent();
|
||||
if (coords.getTrace() != open.trace) {
|
||||
coords = DebuggerCoordinates.NOWHERE;
|
||||
|
@ -822,7 +822,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
if (open.lastSnapshot != null && followsPresent(open.trace)) {
|
||||
coords = coords.snap(open.lastSnapshot.getKey());
|
||||
}
|
||||
DebuggerCoordinates finalCoords = coords.object(object);
|
||||
DebuggerCoordinates finalCoords = object == null ? coords : coords.object(object);
|
||||
Swing.runLater(() -> {
|
||||
if (!traceManager.getOpenTraces().contains(open.trace)) {
|
||||
traceManager.openTrace(open.trace);
|
||||
|
@ -1031,7 +1031,7 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
|||
}
|
||||
for (Method m : req.getMethodsList()) {
|
||||
RemoteMethod rm = new RecordRemoteMethod(this, m.getName(),
|
||||
ActionName.name(m.getAction()),
|
||||
ActionName.name(m.getAction()), m.getDisplay(),
|
||||
m.getDescription(), m.getParametersList()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(MethodParameter::getName, this::makeParameter)),
|
||||
|
|
|
@ -240,6 +240,16 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
}
|
||||
}
|
||||
|
||||
protected long computeSpecificity(Map<String, Object> args) {
|
||||
long score = 0;
|
||||
for (Object o : args.values()) {
|
||||
if (o instanceof TraceObject obj) {
|
||||
score += obj.getCanonicalPath().getKeyList().size();
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
protected BooleanSupplier chooseEnabler(RemoteMethod method, Map<String, Object> args) {
|
||||
ActionName name = method.action();
|
||||
SchemaContext ctx = getSchemaContext();
|
||||
|
@ -282,7 +292,7 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
private Map<String, Object> promptArgs(RemoteMethod method, Map<String, Object> defaults) {
|
||||
SchemaContext ctx = getSchemaContext();
|
||||
RemoteMethodInvocationDialog dialog = new RemoteMethodInvocationDialog(tool,
|
||||
method.name(), method.name(), null);
|
||||
method.display(), method.display(), null);
|
||||
while (true) {
|
||||
for (RemoteParameter param : method.parameters().values()) {
|
||||
Object val = defaults.get(param.name());
|
||||
|
@ -325,8 +335,9 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
Map<String, Object> args = collectArguments(method, context, allowContextObject,
|
||||
allowCoordsObject, allowSuitableObject);
|
||||
boolean requiresPrompt = args.values().contains(Missing.MISSING);
|
||||
return new ActionEntry(method.name(), method.action(), method.description(), requiresPrompt,
|
||||
chooseEnabler(method, args), prompt -> invokeMethod(prompt, method, args));
|
||||
return new ActionEntry(method.display(), method.action(), method.description(),
|
||||
requiresPrompt, computeSpecificity(args), chooseEnabler(method, args),
|
||||
prompt -> invokeMethod(prompt, method, args));
|
||||
}
|
||||
|
||||
protected Map<String, ActionEntry> collectFromMethods(Collection<RemoteMethod> methods,
|
||||
|
@ -415,6 +426,11 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
return collectByName(ActionName.STEP_EXT, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, ActionEntry> collectRefreshActions(ActionContext context) {
|
||||
return collectByName(ActionName.REFRESH, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportsFocus() {
|
||||
TargetObjectSchema schema = trace.getObjectManager().getRootSchema();
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.apache.commons.lang3.ArrayUtils;
|
|||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.rmi.trace.TraceRmi.*;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public interface ValueDecoder {
|
||||
ValueDecoder DEFAULT = new ValueDecoder() {};
|
||||
|
@ -104,7 +105,8 @@ public interface ValueDecoder {
|
|||
case STRING_VALUE -> value.getStringValue();
|
||||
case BOOL_ARR_VALUE -> ArrayUtils.toPrimitive(
|
||||
value.getBoolArrValue().getArrList().stream().toArray(Boolean[]::new));
|
||||
case BYTES_VALUE -> value.getBytesValue().toByteArray();
|
||||
case BYTES_VALUE -> NumericUtilities
|
||||
.convertBytesToString(value.getBytesValue().toByteArray(), ":");
|
||||
case CHAR_ARR_VALUE -> value.getCharArrValue().toCharArray();
|
||||
case SHORT_ARR_VALUE -> ArrayUtils.toPrimitive(
|
||||
value.getShortArrValue()
|
||||
|
|
|
@ -417,11 +417,12 @@ message MethodArgument {
|
|||
message Method {
|
||||
string name = 1;
|
||||
string action = 2;
|
||||
string description = 3;
|
||||
repeated MethodParameter parameters = 4;
|
||||
string display = 3;
|
||||
string description = 4;
|
||||
repeated MethodParameter parameters = 5;
|
||||
// I'd like to make them all void, but I think executing a command and capturing its output
|
||||
// justifies being able to return a result. It should be used very sparingly.
|
||||
ValueType return_type = 5;
|
||||
ValueType return_type = 6;
|
||||
}
|
||||
|
||||
message RequestNegotiate {
|
||||
|
|
|
@ -425,6 +425,7 @@ class ParamDesc:
|
|||
class RemoteMethod:
|
||||
name: str
|
||||
action: str
|
||||
display: str
|
||||
description: str
|
||||
parameters: List[RemoteParameter]
|
||||
return_schema: sch.Schema
|
||||
|
@ -484,11 +485,14 @@ class MethodRegistry(object):
|
|||
cls._to_display(p.annotation), cls._to_description(p.annotation))
|
||||
|
||||
@classmethod
|
||||
def create_method(cls, function, name=None, action=None, description=None) -> RemoteMethod:
|
||||
def create_method(cls, function, name=None, action=None, display=None,
|
||||
description=None) -> RemoteMethod:
|
||||
if name is None:
|
||||
name = function.__name__
|
||||
if action is None:
|
||||
action = name
|
||||
if display is None:
|
||||
display = name
|
||||
if description is None:
|
||||
description = function.__doc__ or ''
|
||||
sig = inspect.signature(function)
|
||||
|
@ -496,14 +500,16 @@ class MethodRegistry(object):
|
|||
for p in sig.parameters.values():
|
||||
params.append(cls._make_param(p))
|
||||
return_schema = cls._to_schema(sig, sig.return_annotation)
|
||||
return RemoteMethod(name, action, description, params, return_schema, function)
|
||||
return RemoteMethod(name, action, display, description, params,
|
||||
return_schema, function)
|
||||
|
||||
def method(self, func=None, *, name=None, action=None, description='',
|
||||
condition=True):
|
||||
def method(self, func=None, *, name=None, action=None, display=None,
|
||||
description='', condition=True):
|
||||
|
||||
def _method(func):
|
||||
if condition:
|
||||
method = self.create_method(func, name, action, description)
|
||||
method = self.create_method(func, name, action, display,
|
||||
description)
|
||||
self.register_method(method)
|
||||
return func
|
||||
|
||||
|
@ -669,6 +675,7 @@ class Client(object):
|
|||
def _write_method(to: bufs.Method, method: RemoteMethod):
|
||||
to.name = method.name
|
||||
to.action = method.action
|
||||
to.display = method.display
|
||||
to.description = method.description
|
||||
Client._write_parameters(to.parameters, method.parameters)
|
||||
to.return_type.name = method.return_schema.name
|
||||
|
|
|
@ -48,19 +48,19 @@ public class TestTraceRmiConnection implements TraceRmiConnection {
|
|||
}
|
||||
}
|
||||
|
||||
public record TestRemoteMethod(String name, ActionName action, String description,
|
||||
Map<String, RemoteParameter> parameters, SchemaName retType,
|
||||
public record TestRemoteMethod(String name, ActionName action, String display,
|
||||
String description, Map<String, RemoteParameter> parameters, SchemaName retType,
|
||||
AsyncPairingQueue<Map<String, Object>> argQueue, AsyncPairingQueue<Object> retQueue)
|
||||
implements RemoteMethod {
|
||||
public TestRemoteMethod(String name, ActionName action, String description,
|
||||
public TestRemoteMethod(String name, ActionName action, String display, String description,
|
||||
Map<String, RemoteParameter> parameters, SchemaName retType) {
|
||||
this(name, action, description, parameters, retType, new AsyncPairingQueue<>(),
|
||||
this(name, action, display, description, parameters, retType, new AsyncPairingQueue<>(),
|
||||
new AsyncPairingQueue<>());
|
||||
}
|
||||
|
||||
public TestRemoteMethod(String name, ActionName action, String description,
|
||||
public TestRemoteMethod(String name, ActionName action, String display, String description,
|
||||
SchemaName retType, RemoteParameter... parameters) {
|
||||
this(name, action, description, Stream.of(parameters)
|
||||
this(name, action, display, description, Stream.of(parameters)
|
||||
.collect(Collectors.toMap(RemoteParameter::name, p -> p)),
|
||||
retType);
|
||||
}
|
||||
|
|
|
@ -284,19 +284,6 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
|||
updateActions();
|
||||
}
|
||||
|
||||
protected void executeTask(Task task) {
|
||||
if (progressService != null) {
|
||||
progressService.execute(task);
|
||||
}
|
||||
else {
|
||||
tool.execute(task);
|
||||
}
|
||||
}
|
||||
|
||||
protected void runTask(String title, ActionEntry entry) {
|
||||
executeTask(new TargetActionTask(tool, title, entry));
|
||||
}
|
||||
|
||||
protected void addTargetStepExtActions(Target target) {
|
||||
for (ActionEntry entry : target.collectActions(ActionName.STEP_EXT, context).values()) {
|
||||
if (entry.requiresPrompt()) {
|
||||
|
@ -305,7 +292,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
|||
actionsTargetStepExt.add(TargetStepExtAction.builder(entry.display(), this)
|
||||
.description(entry.details())
|
||||
.enabledWhen(ctx -> entry.isEnabled())
|
||||
.onAction(ctx -> runTask(entry.display(), entry))
|
||||
.onAction(ctx -> TargetActionTask.runAction(tool, entry.display(), entry))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
@ -370,7 +357,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
|||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
executeTask(new Task("Disconnect", false, false, false) {
|
||||
TargetActionTask.executeTask(tool, new Task("Disconnect", false, false, false) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
|
|
|
@ -18,6 +18,8 @@ package ghidra.app.plugin.core.debug.gui.control;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.Tool;
|
||||
import docking.action.*;
|
||||
|
@ -26,9 +28,11 @@ import ghidra.app.context.ProgramActionContext;
|
|||
import ghidra.app.context.ProgramLocationActionContext;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerObjectActionContext;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.control.ControlMode;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
|
@ -59,12 +63,17 @@ public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionPr
|
|||
public InvokeActionEntryAction(ActionEntry entry) {
|
||||
super(entry.display(), DebuggerMethodActionsPlugin.this.getName());
|
||||
this.entry = entry;
|
||||
setPopupMenuData(new MenuData(new String[] { getName() }, GROUP_METHODS));
|
||||
Icon icon = null;
|
||||
if (ActionName.REFRESH.equals(entry.name())) {
|
||||
// TODO: Allow method annotation to specify icon?
|
||||
icon = DebuggerResources.ICON_REFRESH;
|
||||
}
|
||||
setPopupMenuData(new MenuData(new String[] { getName() }, icon, GROUP_METHODS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
tool.execute(new TargetActionTask(tool, entry.display(), entry));
|
||||
TargetActionTask.runAction(tool, entry.display(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,17 +18,19 @@ package ghidra.app.plugin.core.debug.gui.control;
|
|||
import docking.ActionContext;
|
||||
import docking.action.builder.AbstractActionBuilder;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
||||
class TargetActionBuilder
|
||||
extends AbstractActionBuilder<TargetDockingAction, ActionContext, TargetActionBuilder> {
|
||||
private final DebuggerControlPlugin plugin;
|
||||
private final PluginTool tool;
|
||||
|
||||
private ActionName action;
|
||||
private String defaultDescription;
|
||||
|
||||
public TargetActionBuilder(String name, DebuggerControlPlugin plugin) {
|
||||
super(name, plugin.getName());
|
||||
this.plugin = plugin;
|
||||
public TargetActionBuilder(String name, Plugin owner) {
|
||||
super(name, owner.getName());
|
||||
this.tool = owner.getTool();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +64,7 @@ class TargetActionBuilder
|
|||
// Make the super.validate() hush
|
||||
});
|
||||
validate();
|
||||
TargetDockingAction result = new TargetDockingAction(name, owner, keyBindingType, plugin,
|
||||
TargetDockingAction result = new TargetDockingAction(name, owner, keyBindingType, tool,
|
||||
action, defaultDescription);
|
||||
decorateAction(result);
|
||||
return result;
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.control;
|
|||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.services.DebuggerConsoleService;
|
||||
import ghidra.app.services.ProgressService;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -24,7 +25,22 @@ import ghidra.util.exception.CancelledException;
|
|||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
class TargetActionTask extends Task {
|
||||
public class TargetActionTask extends Task {
|
||||
|
||||
public static void executeTask(PluginTool tool, Task task) {
|
||||
ProgressService progressService = tool.getService(ProgressService.class);
|
||||
if (progressService != null) {
|
||||
progressService.execute(task);
|
||||
}
|
||||
else {
|
||||
tool.execute(task);
|
||||
}
|
||||
}
|
||||
|
||||
public static void runAction(PluginTool tool, String title, ActionEntry entry) {
|
||||
executeTask(tool, new TargetActionTask(tool, title, entry));
|
||||
}
|
||||
|
||||
private final PluginTool tool;
|
||||
private final ActionEntry entry;
|
||||
|
||||
|
|
|
@ -15,41 +15,51 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.control;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.KeyBindingType;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
||||
class TargetDockingAction extends DockingAction {
|
||||
private final DebuggerControlPlugin plugin;
|
||||
private final PluginTool tool;
|
||||
private final ActionName action;
|
||||
private final String defaultDescription;
|
||||
|
||||
private ActionEntry entry;
|
||||
|
||||
public TargetDockingAction(String name, String owner, KeyBindingType keyBindingType,
|
||||
DebuggerControlPlugin plugin, ActionName action, String defaultDescription) {
|
||||
PluginTool tool, ActionName action, String defaultDescription) {
|
||||
super(name, owner, keyBindingType);
|
||||
this.plugin = plugin;
|
||||
this.tool = tool;
|
||||
this.action = action;
|
||||
this.defaultDescription = defaultDescription;
|
||||
}
|
||||
|
||||
private ActionEntry findEntry(ActionContext context) {
|
||||
Target target = plugin.current.getTarget();
|
||||
DebuggerTraceManagerService traceManager =
|
||||
tool.getService(DebuggerTraceManagerService.class);
|
||||
if (traceManager == null) {
|
||||
return null;
|
||||
}
|
||||
Target target = traceManager.getCurrent().getTarget();
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
for (ActionEntry ent : target.collectActions(action, context).values()) {
|
||||
if (ent.requiresPrompt()) {
|
||||
continue;
|
||||
}
|
||||
return ent;
|
||||
// TODO: What if multiple match? Do I care to display the extras?
|
||||
}
|
||||
return null;
|
||||
return target.collectActions(action, context)
|
||||
.values()
|
||||
.stream()
|
||||
.filter(e -> !e.requiresPrompt())
|
||||
.sorted(Comparator.comparing(e -> -e.specificity()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
// TODO: What if multiple match? Do I care to display the extras?
|
||||
// Esp., step process vs step thread
|
||||
}
|
||||
|
||||
protected void updateFromContext(ActionContext context) {
|
||||
|
@ -73,6 +83,6 @@ class TargetDockingAction extends DockingAction {
|
|||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
plugin.runTask(getName(), entry);
|
||||
TargetActionTask.runAction(tool, getName(), entry);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -267,8 +267,8 @@ public class TraceRecorderTarget extends AbstractTarget {
|
|||
|
||||
private ActionEntry makeEntry(boolean requiresPrompt, TargetMethod method,
|
||||
Map<String, ?> arguments) {
|
||||
return new ActionEntry(method.getDisplay(), null, null, requiresPrompt, () -> true,
|
||||
prompt -> invokeMethod(prompt, method, arguments));
|
||||
return new ActionEntry(method.getDisplay(), null, null, requiresPrompt,
|
||||
method.getPath().size(), () -> true, prompt -> invokeMethod(prompt, method, arguments));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -289,7 +289,7 @@ public class TraceRecorderTarget extends AbstractTarget {
|
|||
return Map.of();
|
||||
}
|
||||
return Map.of(display, new ActionEntry(display, name, description, false,
|
||||
() -> enabled.test(object), prompt -> action.apply(object)));
|
||||
object.getPath().size(), () -> enabled.test(object), prompt -> action.apply(object)));
|
||||
}
|
||||
|
||||
private TargetExecutionState getStateOf(TargetObject object) {
|
||||
|
@ -358,6 +358,12 @@ public class TraceRecorderTarget extends AbstractTarget {
|
|||
steppable -> steppable.step(TargetStepKind.EXTENDED));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, ActionEntry> collectRefreshActions(ActionContext context) {
|
||||
// Not necessary to support this here
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return recorder.getTrace();
|
||||
|
|
|
@ -197,7 +197,8 @@ public abstract class AbstractTarget implements Target {
|
|||
collectStepIntoActions(context),
|
||||
collectStepOverActions(context),
|
||||
collectStepOutActions(context),
|
||||
collectStepExtActions(context))
|
||||
collectStepExtActions(context),
|
||||
collectRefreshActions(context))
|
||||
.flatMap(m -> m.entrySet().stream())
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||
}
|
||||
|
@ -216,6 +217,8 @@ public abstract class AbstractTarget implements Target {
|
|||
|
||||
protected abstract Map<String, ActionEntry> collectStepExtActions(ActionContext context);
|
||||
|
||||
protected abstract Map<String, ActionEntry> collectRefreshActions(ActionContext context);
|
||||
|
||||
@Override
|
||||
public Map<String, ActionEntry> collectActions(ActionName name, ActionContext context) {
|
||||
if (name == null) {
|
||||
|
@ -245,6 +248,9 @@ public abstract class AbstractTarget implements Target {
|
|||
else if (ActionName.STEP_EXT.equals(name)) {
|
||||
return collectStepExtActions(context);
|
||||
}
|
||||
else if (ActionName.REFRESH.equals(name)) {
|
||||
return collectRefreshActions(context);
|
||||
}
|
||||
Msg.warn(this, "Unrecognized action name: " + name);
|
||||
return Map.of();
|
||||
}
|
||||
|
|
|
@ -853,7 +853,7 @@ public class GdbMethodsTest extends AbstractGdbTraceRmiTest {
|
|||
%s
|
||||
start"""
|
||||
.formatted(INSTRUMENT_STOPPED));
|
||||
RemoteMethod stepAdvance = conn.getMethod("Advance");
|
||||
RemoteMethod stepAdvance = conn.getMethod("step_advance");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/gdb/bash")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
@ -881,7 +881,7 @@ public class GdbMethodsTest extends AbstractGdbTraceRmiTest {
|
|||
%s
|
||||
start"""
|
||||
.formatted(INSTRUMENT_STOPPED));
|
||||
RemoteMethod stepReturn = conn.getMethod("Return");
|
||||
RemoteMethod stepReturn = conn.getMethod("step_return");
|
||||
try (ManagedDomainObject mdo = openDomainObject("/New Traces/gdb/bash")) {
|
||||
tb = new ToyDBTraceBuilder((Trace) mdo.get());
|
||||
waitStopped();
|
||||
|
|
|
@ -150,34 +150,34 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
}
|
||||
|
||||
protected void addControlMethods() {
|
||||
rmiMethodResume = new TestRemoteMethod("resume", ActionName.RESUME,
|
||||
rmiMethodResume = new TestRemoteMethod("resume", ActionName.RESUME, "Resume",
|
||||
"Resume the target", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process to resume"));
|
||||
|
||||
rmiMethodInterrupt = new TestRemoteMethod("interrupt", ActionName.INTERRUPT,
|
||||
rmiMethodInterrupt = new TestRemoteMethod("interrupt", ActionName.INTERRUPT, "Interrupt",
|
||||
"Interrupt the target", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process to interrupt"));
|
||||
|
||||
rmiMethodKill = new TestRemoteMethod("kill", ActionName.KILL,
|
||||
rmiMethodKill = new TestRemoteMethod("kill", ActionName.KILL, "Kill",
|
||||
"Kill the target", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process to kill"));
|
||||
|
||||
rmiMethodStepInto = new TestRemoteMethod("step_into", ActionName.STEP_INTO,
|
||||
rmiMethodStepInto = new TestRemoteMethod("step_into", ActionName.STEP_INTO, "Step Into",
|
||||
"Step the thread, descending into subroutines",
|
||||
EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
|
||||
"The thread to step"));
|
||||
|
||||
rmiMethodStepOver = new TestRemoteMethod("step_over", ActionName.STEP_OVER,
|
||||
rmiMethodStepOver = new TestRemoteMethod("step_over", ActionName.STEP_OVER, "Step Over",
|
||||
"Step the thread, without descending into subroutines",
|
||||
EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
|
||||
"The thread to step"));
|
||||
|
||||
rmiMethodStepOut = new TestRemoteMethod("step_out", ActionName.STEP_OUT,
|
||||
rmiMethodStepOut = new TestRemoteMethod("step_out", ActionName.STEP_OUT, "Step Out",
|
||||
"Allow the thread to finish the current subroutine",
|
||||
EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread",
|
||||
|
@ -194,6 +194,7 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
|
||||
protected void addBreakpointMethods() {
|
||||
rmiMethodSetHwBreak = new TestRemoteMethod("set_hw_break", ActionName.BREAK_HW_EXECUTE,
|
||||
"Hardware Breakpoint",
|
||||
"Place a hardware execution breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
|
@ -201,6 +202,7 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
null, "Address", "The desired address"));
|
||||
|
||||
rmiMethodSetSwBreak = new TestRemoteMethod("set_sw_break", ActionName.BREAK_SW_EXECUTE,
|
||||
"Software Breakpoint",
|
||||
"Place a software execution breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
|
@ -208,6 +210,7 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
null, "Address", "The desired address"));
|
||||
|
||||
rmiMethodSetReadBreak = new TestRemoteMethod("set_read_break", ActionName.BREAK_READ,
|
||||
"Read Breakpoint",
|
||||
"Place a read breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
|
@ -215,6 +218,7 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
null, "Range", "The desired address range"));
|
||||
|
||||
rmiMethodSetWriteBreak = new TestRemoteMethod("set_write_break", ActionName.BREAK_WRITE,
|
||||
"Write Breakpoint",
|
||||
"Place a write breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
|
@ -222,6 +226,7 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
null, "Range", "The desired address range"));
|
||||
|
||||
rmiMethodSetAccessBreak = new TestRemoteMethod("set_acc_break", ActionName.BREAK_ACCESS,
|
||||
"Access Breakpoint",
|
||||
"Place an access breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("process", new SchemaName("Process"), true, null, "Process",
|
||||
"The process in which to place the breakpoint"),
|
||||
|
@ -229,6 +234,7 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
null, "Range", "The desired address range"));
|
||||
|
||||
rmiMethodToggleBreak = new TestRemoteMethod("toggle_break", ActionName.TOGGLE,
|
||||
"Toggle Breakpoint",
|
||||
"Toggle a breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("breakpoint", new SchemaName("BreakpointSpec"), true, null,
|
||||
"Breakpoint", "The breakpoint to toggle"),
|
||||
|
@ -236,6 +242,7 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
null, "Enable", "True to enable. False to disable"));
|
||||
|
||||
rmiMethodDeleteBreak = new TestRemoteMethod("delete_break", ActionName.DELETE,
|
||||
"Delete Breakpoint",
|
||||
"Delete a breakpoint", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("breakpoint", new SchemaName("BreakpointSpec"), true, null,
|
||||
"Breakpoint", "The breakpoint to delete"));
|
||||
|
@ -251,13 +258,13 @@ public class AbstractGhidraHeadedDebuggerIntegrationTest
|
|||
}
|
||||
|
||||
protected void addRegisterMethods() {
|
||||
rmiMethodReadRegs = new TestRemoteMethod("read_regs", ActionName.REFRESH,
|
||||
rmiMethodReadRegs = new TestRemoteMethod("read_regs", ActionName.REFRESH, "Read Registers",
|
||||
"Read registers", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("container", new SchemaName("RegisterContainer"), true, null,
|
||||
"Registers", "The registers node to read"));
|
||||
|
||||
rmiMethodWriteReg = new TestRemoteMethod("write_reg", ActionName.WRITE_REG,
|
||||
"Write a register", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
"Write Register", "Write a register", EnumerableTargetObjectSchema.VOID.getName(),
|
||||
new TestRemoteParameter("frame", new SchemaName("Frame"), false, 0, "Frame",
|
||||
"The frame to write to"),
|
||||
new TestRemoteParameter("name", EnumerableTargetObjectSchema.STRING.getName(), true,
|
||||
|
|
Loading…
Reference in a new issue