From 644e2c53e540a2ab5a7365b915af22b20057a4c4 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 1 Feb 2024 08:50:27 -0500 Subject: [PATCH] GP-3984: Have gdb connector delay section info until asked. --- .../src/main/py/src/ghidragdb/commands.py | 111 ++++++++++++------ .../src/main/py/src/ghidragdb/hooks.py | 4 +- .../src/main/py/src/ghidragdb/methods.py | 90 ++++++++++---- .../ghidra/debug/api/target/ActionName.java | 6 +- .../java/ghidra/debug/api/target/Target.java | 5 +- .../debug/api/tracermi/RemoteMethod.java | 7 ++ .../service/tracermi/RecordRemoteMethod.java | 4 +- .../service/tracermi/TraceRmiHandler.java | 10 +- .../service/tracermi/TraceRmiTarget.java | 22 +++- .../debug/service/tracermi/ValueDecoder.java | 4 +- .../src/main/proto/trace-rmi.proto | 7 +- .../src/main/py/src/ghidratrace/client.py | 17 ++- .../tracermi/TestTraceRmiConnection.java | 12 +- .../gui/control/DebuggerControlPlugin.java | 17 +-- .../control/DebuggerMethodActionsPlugin.java | 13 +- .../gui/control/TargetActionBuilder.java | 12 +- .../debug/gui/control/TargetActionTask.java | 18 ++- .../gui/control/TargetDockingAction.java | 36 ++++-- .../service/model/TraceRecorderTarget.java | 12 +- .../debug/service/target/AbstractTarget.java | 8 +- .../java/agent/gdb/rmi/GdbMethodsTest.java | 4 +- ...ctGhidraHeadedDebuggerIntegrationTest.java | 23 ++-- 22 files changed, 301 insertions(+), 141 deletions(-) diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py index 3f90891766..a706e13dc5 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/commands.py @@ -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() diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py index dc1654a7de..96a282006d 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/hooks.py @@ -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: diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py index 9a38dd3f2e..101c0cd425 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/py/src/ghidragdb/methods.py @@ -78,6 +78,7 @@ FRAME_PATTERN = extre(STACK_PATTERN, '\[(?P\\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.*)\]') 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 diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java index 9d31777cea..eecb36a0ef 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java @@ -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 not be presented in generic contexts, but + * reserved for built-in, purpose-specific actions */ public record ActionName(String name, boolean builtIn) { private static final Map 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 * diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java index 96822accca..6d4cbb3c6b 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java @@ -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> action) { + long specificity, BooleanSupplier enabled, + Function> action) { /** * Check if this action is currently enabled diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java index 44cac0d15b..08d54c1dbd 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracermi/RemoteMethod.java @@ -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. * diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/RecordRemoteMethod.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/RecordRemoteMethod.java index d217f1e83f..f155a67427 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/RecordRemoteMethod.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/RecordRemoteMethod.java @@ -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 parameters, SchemaName retType) - implements RemoteMethod { + String display, String description, Map parameters, + SchemaName retType) implements RemoteMethod { @Override public DefaultRemoteAsyncResult invokeAsync(Map arguments) { Trace trace = validate(arguments); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java index b3d02184e7..e7697d01b6 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiHandler.java @@ -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)), diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java index da64a34c3a..e5c442266e 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java @@ -240,6 +240,16 @@ public class TraceRmiTarget extends AbstractTarget { } } + protected long computeSpecificity(Map 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 args) { ActionName name = method.action(); SchemaContext ctx = getSchemaContext(); @@ -282,7 +292,7 @@ public class TraceRmiTarget extends AbstractTarget { private Map promptArgs(RemoteMethod method, Map 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 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 collectFromMethods(Collection methods, @@ -415,6 +426,11 @@ public class TraceRmiTarget extends AbstractTarget { return collectByName(ActionName.STEP_EXT, context); } + @Override + protected Map collectRefreshActions(ActionContext context) { + return collectByName(ActionName.REFRESH, context); + } + @Override public boolean isSupportsFocus() { TargetObjectSchema schema = trace.getObjectManager().getRootSchema(); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/ValueDecoder.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/ValueDecoder.java index 473233b352..68546e278c 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/ValueDecoder.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/ValueDecoder.java @@ -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() diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto b/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto index 9506f8d549..0ff04e0fb1 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/proto/trace-rmi.proto @@ -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 { diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py index 2cd8e3fb9d..70aa994a7f 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py @@ -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 diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java index a54fc9c2a7..baddfb52fa 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/test/java/ghidra/app/plugin/core/debug/service/tracermi/TestTraceRmiConnection.java @@ -48,19 +48,19 @@ public class TestTraceRmiConnection implements TraceRmiConnection { } } - public record TestRemoteMethod(String name, ActionName action, String description, - Map parameters, SchemaName retType, + public record TestRemoteMethod(String name, ActionName action, String display, + String description, Map parameters, SchemaName retType, AsyncPairingQueue> argQueue, AsyncPairingQueue retQueue) implements RemoteMethod { - public TestRemoteMethod(String name, ActionName action, String description, + public TestRemoteMethod(String name, ActionName action, String display, String description, Map 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); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java index 46d7308428..cd3880e76f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java @@ -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 { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java index a02bcbc9ea..ccac7eae1f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java @@ -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); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionBuilder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionBuilder.java index 8bac04b8cc..bbad67c4f1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionBuilder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionBuilder.java @@ -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 { - 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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java index a3fd70154f..5508e2577f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java @@ -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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java index 17d1028065..6868eeb61f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java @@ -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); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java index 1271c2737d..91934b60ef 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceRecorderTarget.java @@ -267,8 +267,8 @@ public class TraceRecorderTarget extends AbstractTarget { private ActionEntry makeEntry(boolean requiresPrompt, TargetMethod method, Map 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 collectRefreshActions(ActionContext context) { + // Not necessary to support this here + return Map.of(); + } + @Override public Trace getTrace() { return recorder.getTrace(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java index 92df10029b..29307c8b53 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java @@ -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 collectStepExtActions(ActionContext context); + protected abstract Map collectRefreshActions(ActionContext context); + @Override public Map 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(); } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbMethodsTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbMethodsTest.java index bdbe1b2bd3..723e5e049b 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbMethodsTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test.slow/java/agent/gdb/rmi/GdbMethodsTest.java @@ -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(); diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java index b42ee57fdb..13dc3ea13e 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerIntegrationTest.java @@ -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,