diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py index 5326cc5be5..b98cfa1c93 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/methods.py @@ -144,8 +144,8 @@ def find_thread_by_regs_obj(object): def find_frame_by_level(level): for f in util.dbg._base.backtrace_list(): if f.FrameNumber == level: - return f - #return dbg().backtrace_list()[level] + return f + # return dbg().backtrace_list()[level] def find_frame_by_pattern(pattern, object, err_msg): @@ -206,8 +206,8 @@ def execute(cmd: str, to_string: bool=False): @REGISTRY.method(action='evaluate', display='Evaluate') # @util.dbg.eng_thread def evaluate( - session: sch.Schema('Session'), - expr: ParamDesc(str, display='Expr')): + session: sch.Schema('Session'), + expr: ParamDesc(str, display='Expr')): """Evaluate a Python3 expression.""" return str(eval(expr, shared_globals)) @@ -328,8 +328,8 @@ def remove_process(process: sch.Schema('Process')): @REGISTRY.method(action='connect', display='Connect') @util.dbg.eng_thread def target( - session: sch.Schema('Session'), - cmd: ParamDesc(str, display='Command')): + session: sch.Schema('Session'), + cmd: ParamDesc(str, display='Command')): """Connect to a target machine or process.""" dbg().attach_kernel(cmd) @@ -345,8 +345,8 @@ def attach_obj(target: sch.Schema('Attachable')): @REGISTRY.method(action='attach', display='Attach by pid') @util.dbg.eng_thread def attach_pid( - session: sch.Schema('Session'), - pid: ParamDesc(str, display='PID')): + session: sch.Schema('Session'), + pid: ParamDesc(str, display='PID')): """Attach the process to the given target.""" dbg().attach_proc(int(pid)) @@ -354,8 +354,8 @@ def attach_pid( @REGISTRY.method(action='attach', display='Attach by name') @util.dbg.eng_thread def attach_name( - session: sch.Schema('Session'), - name: ParamDesc(str, display='Name')): + session: sch.Schema('Session'), + name: ParamDesc(str, display='Name')): """Attach the process to the given target.""" dbg().attach_proc(name) @@ -369,7 +369,7 @@ def detach(process: sch.Schema('Process')): @REGISTRY.method(action='launch', display='Launch') def launch_loader( - session: sch.Schema('Session'), + session: sch.Schema('Session'), file: ParamDesc(str, display='File'), args: ParamDesc(str, display='Arguments')=''): """ @@ -383,7 +383,7 @@ def launch_loader( @REGISTRY.method(action='launch', display='LaunchEx') def launch( - session: sch.Schema('Session'), + session: sch.Schema('Session'), file: ParamDesc(str, display='File'), args: ParamDesc(str, display='Arguments')='', initial_break: ParamDesc(bool, display='Initial Break')=True, @@ -405,7 +405,7 @@ def kill(process: sch.Schema('Process')): commands.ghidra_trace_kill() -@REGISTRY.method(action='resume') +@REGISTRY.method(action='resume', display="Go") def go(process: sch.Schema('Process')): """Continue execution of the process.""" util.dbg.run_async(lambda: dbg().go()) @@ -456,7 +456,7 @@ def break_address(process: sch.Schema('Process'), address: Address): dbg().bp(expr=address.offset) -@REGISTRY.method(action='break_sw_execute') +@REGISTRY.method(action='break_ext', display='Set Breakpoint') @util.dbg.eng_thread def break_expression(expression: str): """Set a breakpoint.""" @@ -472,7 +472,7 @@ def break_hw_address(process: sch.Schema('Process'), address: Address): dbg().ba(expr=address.offset) -@REGISTRY.method(action='break_hw_execute') +@REGISTRY.method(action='break_ext', display='Set Hardware Breakpoint') @util.dbg.eng_thread def break_hw_expression(expression: str): """Set a hardware-assisted breakpoint.""" @@ -482,50 +482,50 @@ def break_hw_expression(expression: str): @REGISTRY.method(action='break_read') @util.dbg.eng_thread def break_read_range(process: sch.Schema('Process'), range: AddressRange): - """Set a read watchpoint.""" + """Set a read breakpoint.""" find_proc_by_obj(process) dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ) -@REGISTRY.method(action='break_read') +@REGISTRY.method(action='break_ext', display='Set Read Breakpoint') @util.dbg.eng_thread def break_read_expression(expression: str): - """Set a read watchpoint.""" + """Set a read breakpoint.""" dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ) @REGISTRY.method(action='break_write') @util.dbg.eng_thread def break_write_range(process: sch.Schema('Process'), range: AddressRange): - """Set a watchpoint.""" + """Set a write breakpoint.""" find_proc_by_obj(process) dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_WRITE) -@REGISTRY.method(action='break_write') +@REGISTRY.method(action='break_ext', display='Set Write Breakpoint') @util.dbg.eng_thread def break_write_expression(expression: str): - """Set a watchpoint.""" + """Set a write breakpoint.""" dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_WRITE) @REGISTRY.method(action='break_access') @util.dbg.eng_thread def break_access_range(process: sch.Schema('Process'), range: AddressRange): - """Set an access watchpoint.""" + """Set an access breakpoint.""" find_proc_by_obj(process) dbg().ba(expr=range.min, size=range.length(), access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE) -@REGISTRY.method(action='break_access') +@REGISTRY.method(action='break_ext', display='Set Access Breakpoint') @util.dbg.eng_thread def break_access_expression(expression: str): - """Set an access watchpoint.""" + """Set an access breakpoint.""" dbg().ba(expr=expression, access=DbgEng.DEBUG_BREAK_READ | DbgEng.DEBUG_BREAK_WRITE) -@REGISTRY.method(action='toggle') +@REGISTRY.method(action='toggle', display='Toggle Breakpoint') @util.dbg.eng_thread def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool): """Toggle a breakpoint.""" @@ -536,7 +536,7 @@ def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool): dbg().bd(bpt.GetId()) -@REGISTRY.method(action='delete') +@REGISTRY.method(action='delete', display='Delete Breakpoint') @util.dbg.eng_thread def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')): """Delete a breakpoint.""" 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 a019d4b160..30263b3fe0 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 @@ -391,19 +391,19 @@ def refresh_sections(node: sch.Schema('Module')): gdb.execute(f'ghidra trace put-sections "{modname}"') -@REGISTRY.method(action='activate') +@REGISTRY.method(action='activate', display="Activate Inferior") def activate_inferior(inferior: sch.Schema('Inferior')): """Switch to the inferior.""" switch_inferior(find_inf_by_obj(inferior)) -@REGISTRY.method(action='activate') +@REGISTRY.method(action='activate', display="Activate Thread") def activate_thread(thread: sch.Schema('Thread')): """Switch to the thread.""" find_thread_by_obj(thread).switch() -@REGISTRY.method(action='activate') +@REGISTRY.method(action='activate', display="Activate Frame") def activate_frame(frame: sch.Schema('StackFrame')): """Select the frame.""" find_frame_by_obj(frame).select() @@ -415,7 +415,7 @@ def add_inferior(container: sch.Schema('InferiorContainer')): gdb.execute('add-inferior') -@REGISTRY.method(action='delete') +@REGISTRY.method(action='delete', display="Delete Inferior") def delete_inferior(inferior: sch.Schema('Inferior')): """Remove the inferior.""" inf = find_inf_by_obj(inferior) @@ -433,7 +433,7 @@ def connect(inferior: sch.Schema('Inferior'), spec: str): @REGISTRY.method(action='attach', display='Attach') def attach_obj(target: sch.Schema('Attachable')): """Attach the inferior to the given target.""" - #switch_inferior(find_inf_by_obj(inferior)) + # switch_inferior(find_inf_by_obj(inferior)) pid = find_availpid_by_obj(target) gdb.execute(f'attach {pid}') @@ -577,7 +577,7 @@ def break_sw_execute_address(inferior: sch.Schema('Inferior'), address: Address) gdb.execute(f'break *0x{offset:x}') -@REGISTRY.method(action='break_sw_execute') +@REGISTRY.method(action='break_ext', display="Set Breakpoint") def break_sw_execute_expression(expression: str): """Set a breakpoint (break).""" # TODO: Escape? @@ -592,7 +592,7 @@ def break_hw_execute_address(inferior: sch.Schema('Inferior'), address: Address) gdb.execute(f'hbreak *0x{offset:x}') -@REGISTRY.method(action='break_hw_execute') +@REGISTRY.method(action='break_ext', display="Set Hardware Breakpoint") def break_hw_execute_expression(expression: str): """Set a hardware-assisted breakpoint (hbreak).""" # TODO: Escape? @@ -609,7 +609,7 @@ def break_read_range(inferior: sch.Schema('Inferior'), range: AddressRange): f'rwatch -location *((char(*)[{range.length()}]) 0x{offset_start:x})') -@REGISTRY.method(action='break_read') +@REGISTRY.method(action='break_ext', display="Set Read Watchpoint") def break_read_expression(expression: str): """Set a read watchpoint (rwatch).""" gdb.execute(f'rwatch {expression}') @@ -625,7 +625,7 @@ def break_write_range(inferior: sch.Schema('Inferior'), range: AddressRange): f'watch -location *((char(*)[{range.length()}]) 0x{offset_start:x})') -@REGISTRY.method(action='break_write') +@REGISTRY.method(action='break_ext', display="Set Watchpoint") def break_write_expression(expression: str): """Set a watchpoint (watch).""" gdb.execute(f'watch {expression}') @@ -641,7 +641,7 @@ def break_access_range(inferior: sch.Schema('Inferior'), range: AddressRange): f'awatch -location *((char(*)[{range.length()}]) 0x{offset_start:x})') -@REGISTRY.method(action='break_access') +@REGISTRY.method(action='break_ext', display="Set Access Watchpoint") def break_access_expression(expression: str): """Set an access watchpoint (awatch).""" gdb.execute(f'awatch {expression}') @@ -653,21 +653,23 @@ def break_event(inferior: sch.Schema('Inferior'), spec: str): gdb.execute(f'catch {spec}') -@REGISTRY.method(action='toggle') +@REGISTRY.method(action='toggle', display="Toggle Breakpoint") def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool): """Toggle a breakpoint.""" bpt = find_bpt_by_obj(breakpoint) bpt.enabled = enabled -@REGISTRY.method(action='toggle', condition=util.GDB_VERSION.major >= 13) +@REGISTRY.method(action='toggle', display="Toggle Breakpoint Location", + condition=util.GDB_VERSION.major >= 13) def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool): """Toggle a breakpoint location.""" loc = find_bpt_loc_by_obj(location) loc.enabled = enabled -@REGISTRY.method(action='toggle', condition=util.GDB_VERSION.major < 13) +@REGISTRY.method(action='toggle', display="Toggle Breakpoint Location", + condition=util.GDB_VERSION.major < 13) def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool): """Toggle a breakpoint location.""" bptnum, locnum = find_bptlocnum_by_obj(location) @@ -675,7 +677,7 @@ def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabl gdb.execute(f'{cmd} {bptnum}.{locnum}') -@REGISTRY.method(action='delete') +@REGISTRY.method(action='delete', display="Delete Breakpoint") def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')): """Delete a breakpoint.""" bpt = find_bpt_by_obj(breakpoint) diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py index 66592d6d9a..4a0e04c2fe 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/py/src/ghidralldb/methods.py @@ -17,11 +17,11 @@ from concurrent.futures import Future, ThreadPoolExecutor import re import sys +import lldb + from ghidratrace import sch from ghidratrace.client import MethodRegistry, ParamDesc, Address, AddressRange -import lldb - from . import commands, util @@ -248,7 +248,7 @@ def execute(cmd: str, to_string: bool=False): return exec_convert_errors(cmd, to_string) -@REGISTRY.method +@REGISTRY.method(display='Evaluate') def evaluate(expr: str): """Evaluate an expression.""" value = util.get_target().EvaluateExpression(expr) @@ -257,7 +257,7 @@ def evaluate(expr: str): return commands.convert_value(value) -@REGISTRY.method +@REGISTRY.method(display="Python Evaluate") def pyeval(expr: str): return eval(expr) @@ -345,28 +345,28 @@ def refresh_modules(node: sch.Schema('ModuleContainer')): exec_convert_errors('ghidra trace put-modules') -@REGISTRY.method(action='activate') +@REGISTRY.method(action='activate', display='Activate Process') def activate_process(process: sch.Schema('Process')): """Switch to the process.""" # TODO return -@REGISTRY.method(action='activate') +@REGISTRY.method(action='activate', display='Activate Thread') def activate_thread(thread: sch.Schema('Thread')): """Switch to the thread.""" t = find_thread_by_obj(thread) t.process.SetSelectedThread(t) -@REGISTRY.method(action='activate') +@REGISTRY.method(action='activate', display='Activate Frame') def activate_frame(frame: sch.Schema('StackFrame')): """Select the frame.""" f = find_frame_by_obj(frame) f.thread.SetSelectedFrame(f.GetFrameID()) -@REGISTRY.method(action='delete') +@REGISTRY.method(action='delete', display='Remove Process') def remove_process(process: sch.Schema('Process')): """Remove the process.""" proc = find_proc_by_obj(process) @@ -442,7 +442,7 @@ def kill(process: sch.Schema('Process')): exec_convert_errors('process kill') -@REGISTRY.method(name='continue', action='resume') +@REGISTRY.method(name='continue', action='resume', display="Continue") def _continue(process: sch.Schema('Process')): """Continue execution of the process.""" exec_convert_errors('process continue') @@ -510,7 +510,7 @@ def break_address(process: sch.Schema('Process'), address: Address): exec_convert_errors(f'breakpoint set -a 0x{offset:x}') -@REGISTRY.method(action='break_sw_execute') +@REGISTRY.method(action='break_ext', display='Set Breakpoint') def break_expression(expression: str): """Set a breakpoint.""" # TODO: Escape? @@ -525,7 +525,7 @@ def break_hw_address(process: sch.Schema('Process'), address: Address): exec_convert_errors(f'breakpoint set -H -a 0x{offset:x}') -@REGISTRY.method(action='break_hw_execute') +@REGISTRY.method(action='break_ext', display='Set Hardware Breakpoint') def break_hw_expression(expression: str): """Set a hardware-assisted breakpoint.""" # TODO: Escape? @@ -543,7 +543,7 @@ def break_read_range(process: sch.Schema('Process'), range: AddressRange): f'watchpoint set expression -s {sz} -w read -- {offset_start}') -@REGISTRY.method(action='break_read') +@REGISTRY.method(action='break_ext', display='Set Read Watchpoint') def break_read_expression(expression: str, size=None): """Set a read watchpoint.""" size_part = '' if size is None else f'-s {size}' @@ -562,7 +562,7 @@ def break_write_range(process: sch.Schema('Process'), range: AddressRange): f'watchpoint set expression -s {sz} -- {offset_start}') -@REGISTRY.method(action='break_write') +@REGISTRY.method(action='break_ext', display='Set Watchpoint') def break_write_expression(expression: str, size=None): """Set a watchpoint.""" size_part = '' if size is None else f'-s {size}' @@ -572,7 +572,7 @@ def break_write_expression(expression: str, size=None): @REGISTRY.method(action='break_access') def break_access_range(process: sch.Schema('Process'), range: AddressRange): - """Set an access watchpoint.""" + """Set a read/write watchpoint.""" proc = find_proc_by_obj(process) offset_start = process.trace.memory_mapper.map_back( proc, Address(range.space, range.min)) @@ -581,9 +581,9 @@ def break_access_range(process: sch.Schema('Process'), range: AddressRange): f'watchpoint set expression -s {sz} -w read_write -- {offset_start}') -@REGISTRY.method(action='break_access') +@REGISTRY.method(action='break_ext', display='Set Read/Write Watchpoint') def break_access_expression(expression: str, size=None): - """Set an access watchpoint.""" + """Set a read/write watchpoint.""" size_part = '' if size is None else f'-s {size}' exec_convert_errors( f'watchpoint set expression {size_part} -w read_write -- {expression}') @@ -595,7 +595,7 @@ def break_exception(lang: str): exec_convert_errors(f'breakpoint set -E {lang}') -@REGISTRY.method(action='toggle') +@REGISTRY.method(action='toggle', display='Toggle Watchpoint') def toggle_watchpoint(watchpoint: sch.Schema('WatchpointSpec'), enabled: bool): """Toggle a watchpoint.""" wpt = find_wpt_by_obj(watchpoint) @@ -604,7 +604,7 @@ def toggle_watchpoint(watchpoint: sch.Schema('WatchpointSpec'), enabled: bool): exec_convert_errors(f'watchpoint {cmd} {wpt.GetID()}') -@REGISTRY.method(action='toggle') +@REGISTRY.method(action='toggle', display='Toggle Breakpoint') def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool): """Toggle a breakpoint.""" bpt = find_bpt_by_obj(breakpoint) @@ -612,7 +612,7 @@ def toggle_breakpoint(breakpoint: sch.Schema('BreakpointSpec'), enabled: bool): exec_convert_errors(f'breakpoint {cmd} {bpt.GetID()}') -@REGISTRY.method(action='toggle') +@REGISTRY.method(action='toggle', display='Toggle Breakpoint Location') def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabled: bool): """Toggle a breakpoint location.""" bptnum, locnum = find_bptlocnum_by_obj(location) @@ -620,7 +620,7 @@ def toggle_breakpoint_location(location: sch.Schema('BreakpointLocation'), enabl exec_convert_errors(f'breakpoint {cmd} {bptnum}.{locnum}') -@REGISTRY.method(action='delete') +@REGISTRY.method(action='delete', display='Delete Watchpoint') def delete_watchpoint(watchpoint: sch.Schema('WatchpointSpec')): """Delete a watchpoint.""" wpt = find_wpt_by_obj(watchpoint) @@ -628,7 +628,7 @@ def delete_watchpoint(watchpoint: sch.Schema('WatchpointSpec')): exec_convert_errors(f'watchpoint delete {wptnum}') -@REGISTRY.method(action='delete') +@REGISTRY.method(action='delete', display='Delete Breakpoint') def delete_breakpoint(breakpoint: sch.Schema('BreakpointSpec')): """Delete a breakpoint.""" bpt = find_bpt_by_obj(breakpoint) 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 eecb36a0ef..214c4631e1 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 @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,8 +15,16 @@ */ package ghidra.debug.api.target; -import java.util.HashMap; -import java.util.Map; +import java.awt.event.InputEvent; +import java.util.*; + +import javax.swing.Icon; + +import docking.ActionContext; +import generic.theme.GIcon; +import ghidra.app.context.ProgramLocationActionContext; +import ghidra.trace.model.TraceExecutionState; +import ghidra.trace.model.target.TraceObject; /** * A name for a commonly-recognized target action. @@ -35,21 +43,137 @@ import java.util.Map; * 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 + * @param show when to show the general UI action for this debugger action + * @param enabler determines when the action is enabled, based on the object + * @param display the default text to display + * @param icon the default icon for menus and dialogs + * @param okText the default text for confirm buttons in dialogs */ -public record ActionName(String name, boolean builtIn) { +public record ActionName(String name, Show show, Enabler enabler, String display, Icon icon, + String okText) { + + private static final Icon ICON_ATTACH = new GIcon("icon.debugger.attach"); + private static final Icon ICON_CONNECT = new GIcon("icon.debugger.connect"); + private static final Icon ICON_DETACH = new GIcon("icon.debugger.detach"); + private static final Icon ICON_INTERRUPT = new GIcon("icon.debugger.interrupt"); + private static final Icon ICON_KILL = new GIcon("icon.debugger.kill"); + private static final Icon ICON_LAUNCH = new GIcon("icon.debugger.launch"); + private static final Icon ICON_REFRESH = new GIcon("icon.debugger.refresh"); + private static final Icon ICON_RESUME = new GIcon("icon.debugger.resume"); + private static final Icon ICON_STEP_BACK = new GIcon("icon.debugger.step.back"); + private static final Icon ICON_STEP_INTO = new GIcon("icon.debugger.step.into"); + private static final Icon ICON_STEP_LAST = new GIcon("icon.debugger.step.last"); + private static final Icon ICON_STEP_OUT = new GIcon("icon.debugger.step.finish"); + private static final Icon ICON_STEP_OVER = new GIcon("icon.debugger.step.over"); + private static final Icon ICON_SKIP_OVER = new GIcon("icon.debugger.skip.over"); + private static final Icon ICON_SET_BREAKPOINT = new GIcon("icon.debugger.breakpoint.set"); + private static final Map NAMES = new HashMap<>(); + /** + * Specifies when an action should appear in the menus. For diagnostics, a user may override + * this by holding SHIFT when right-clicking, causing all applicable general actions to appear. + */ + public enum Show { + /** + * Don't show general actions. The tool has built-in actions that already know how to invoke + * this. + */ + BUILTIN { + @Override + boolean doIsShowing(ActionContext context) { + return false; + } + }, + /** + * Only show general actions in address-based context, e.g., when right-clicking in the + * listing. + */ + ADDRESS { + @Override + boolean doIsShowing(ActionContext context) { + return context instanceof ProgramLocationActionContext; + } + }, + /** + * Show in all contexts. This is the default. + */ + EXTENDED { + @Override + boolean doIsShowing(ActionContext context) { + return true; + } + }; + + public boolean isShowing(ActionContext context) { + if (isOverriden(context)) { + return true; + } + return doIsShowing(context); + } + + abstract boolean doIsShowing(ActionContext context); + + private boolean isOverriden(ActionContext context) { + return (context.getEventClickModifiers() & InputEvent.SHIFT_DOWN_MASK) != 0; + } + } + + public enum Enabler { + ALWAYS { + @Override + public boolean isEnabled(TraceObject obj, long snap) { + return true; + } + }, + NOT_RUNNING { + @Override + boolean doIsEnabled(TraceExecutionState state) { + return state != null && state != TraceExecutionState.RUNNING; + } + }, + NOT_STOPPED { + @Override + boolean doIsEnabled(TraceExecutionState state) { + return state != TraceExecutionState.STOPPED; + } + }, + NOT_DEAD { + @Override + boolean doIsEnabled(TraceExecutionState state) { + return state != TraceExecutionState.TERMINATED; + } + }; + + private TraceExecutionState getState(TraceObject obj, long snap) { + try { + return obj.getExecutionState(snap); + } + catch (NoSuchElementException e) { + return TraceExecutionState.TERMINATED; + } + } + + boolean doIsEnabled(TraceExecutionState state) { + return true; + } + + public boolean isEnabled(TraceObject obj, long snap) { + return doIsEnabled(getState(obj, snap)); + } + } + public static ActionName name(String name) { synchronized (NAMES) { - return NAMES.computeIfAbsent(name, n -> new ActionName(n, false)); + return NAMES.computeIfAbsent(name, + n -> new ActionName(n, Show.EXTENDED, Enabler.ALWAYS, n, null, "OK")); } } - private static ActionName builtIn(String name) { + private static ActionName create(String name, Show show, Enabler enabler, String display, + Icon icon, String okText) { synchronized (NAMES) { - ActionName action = new ActionName(name, true); + ActionName action = new ActionName(name, show, enabler, display, icon, okText); if (NAMES.put(name, action) != null) { throw new AssertionError(); } @@ -57,24 +181,16 @@ public record ActionName(String name, boolean builtIn) { } } - private static ActionName extended(String name) { - synchronized (NAMES) { - ActionName action = new ActionName(name, false); - if (NAMES.put(name, action) != null) { - throw new AssertionError(); - } - return action; - } - } - - public static final ActionName REFRESH = extended("refresh"); + public static final ActionName REFRESH = + create("refresh", Show.EXTENDED, Enabler.ALWAYS, "Refresh", ICON_REFRESH, "Refresh"); /** * Activate a given object and optionally a time * *

* Forms: (focus:Object), (focus:Object, snap:LONG), (focus:Object, time:STR) */ - public static final ActionName ACTIVATE = builtIn("activate"); + public static final ActionName ACTIVATE = + create("activate", Show.BUILTIN, Enabler.ALWAYS, "Activate", null, "Activate"); /** * A weaker form of activate. * @@ -83,9 +199,12 @@ public record ActionName(String name, boolean builtIn) { * used to communicate selection (i.e., highlight) of the object. Whereas, double-clicking or * pressing enter would more likely invoke 'activate.' */ - public static final ActionName FOCUS = builtIn("focus"); - public static final ActionName TOGGLE = builtIn("toggle"); - public static final ActionName DELETE = builtIn("delete"); + public static final ActionName FOCUS = + create("focus", Show.BUILTIN, Enabler.ALWAYS, "Focus", null, "Focus"); + public static final ActionName TOGGLE = + create("toggle", Show.BUILTIN, Enabler.ALWAYS, "Toggle", null, "Toggle"); + public static final ActionName DELETE = + create("delete", Show.BUILTIN, Enabler.ALWAYS, "Delete", null, "Delete"); /** * Execute a CLI command @@ -93,7 +212,8 @@ public record ActionName(String name, boolean builtIn) { *

* Forms: (cmd:STRING):STRING; Optional arguments: capture:BOOL */ - public static final ActionName EXECUTE = builtIn("execute"); + public static final ActionName EXECUTE = + create("execute", Show.BUILTIN, Enabler.ALWAYS, "Execute", null, "Execute"); /** * Connect the back-end to a (usually remote) target @@ -101,23 +221,31 @@ public record ActionName(String name, boolean builtIn) { *

* Forms: (spec:STRING) */ - public static final ActionName CONNECT = extended("connect"); + public static final ActionName CONNECT = + create("connect", Show.EXTENDED, Enabler.ALWAYS, "Connect", ICON_CONNECT, "Connect"); /** * Forms: (target:Attachable), (pid:INT), (spec:STRING) */ - public static final ActionName ATTACH = extended("attach"); - public static final ActionName DETACH = extended("detach"); + public static final ActionName ATTACH = + create("attach", Show.EXTENDED, Enabler.ALWAYS, "Attach", ICON_ATTACH, "Attach"); + public static final ActionName DETACH = + create("detach", Show.EXTENDED, Enabler.ALWAYS, "Detach", ICON_DETACH, "Detach"); /** * Forms: (command_line:STRING), (file:STRING,args:STRING), (file:STRING,args:STRING_ARRAY), * (ANY*) */ - public static final ActionName LAUNCH = extended("launch"); - public static final ActionName KILL = builtIn("kill"); + public static final ActionName LAUNCH = + create("launch", Show.EXTENDED, Enabler.ALWAYS, "Launch", ICON_LAUNCH, "Launch"); + public static final ActionName KILL = + create("kill", Show.BUILTIN, Enabler.NOT_DEAD, "Kill", ICON_KILL, "Kill"); - public static final ActionName RESUME = builtIn("resume"); - public static final ActionName INTERRUPT = builtIn("interrupt"); + public static final ActionName RESUME = + create("resume", Show.BUILTIN, Enabler.NOT_RUNNING, "Resume", ICON_RESUME, "Resume"); + public static final ActionName INTERRUPT = + create("interrupt", Show.BUILTIN, Enabler.NOT_STOPPED, "Interrupt", ICON_INTERRUPT, + "Interrupt"); /** * All of these will show in the "step" portion of the control toolbar, if present. The @@ -128,25 +256,31 @@ public record ActionName(String name, boolean builtIn) { * context. (Multiple will appear, but may confuse the user.) You can have as many extended step * actions as you like. They will be ordered lexicographically by name. */ - public static final ActionName STEP_INTO = builtIn("step_into"); - public static final ActionName STEP_OVER = builtIn("step_over"); - public static final ActionName STEP_OUT = builtIn("step_out"); + public static final ActionName STEP_INTO = + create("step_into", Show.BUILTIN, Enabler.NOT_RUNNING, "Step Into", ICON_STEP_INTO, "Step"); + public static final ActionName STEP_OVER = + create("step_over", Show.BUILTIN, Enabler.NOT_RUNNING, "Step Over", ICON_STEP_OVER, "Step"); + public static final ActionName STEP_OUT = + create("step_out", Show.BUILTIN, Enabler.NOT_RUNNING, "Step Out", ICON_STEP_OUT, "Step"); /** * Skip is not typically available, except in emulators. If the back-end debugger does not have * a command for this action out-of-the-box, we do not recommend trying to implement it * yourself. The purpose of these actions just to expose/map each command to the UI, not to * invent new features for the back-end debugger. */ - public static final ActionName STEP_SKIP = builtIn("step_skip"); + public static final ActionName STEP_SKIP = + create("step_skip", Show.BUILTIN, Enabler.NOT_RUNNING, "Skip Over", ICON_SKIP_OVER, "Skip"); /** * Step back is not typically available, except in emulators and timeless (or time-travel) * debuggers. */ - public static final ActionName STEP_BACK = builtIn("step_back"); + public static final ActionName STEP_BACK = + create("step_back", Show.BUILTIN, Enabler.NOT_RUNNING, "Step Back", ICON_STEP_BACK, "Back"); /** * The action for steps that don't fit one of the common stepping actions. */ - public static final ActionName STEP_EXT = extended("step_ext"); + public static final ActionName STEP_EXT = + create("step_ext", Show.ADDRESS, Enabler.NOT_RUNNING, null, ICON_STEP_LAST, "Step"); /** * Forms: (addr:ADDRESS), R/W(rng:RANGE), (expr:STRING) @@ -158,25 +292,39 @@ public record ActionName(String name, boolean builtIn) { * The client may pass either null or "" for condition and/or commands to indicate omissions of * those arguments. */ - public static final ActionName BREAK_SW_EXECUTE = builtIn("break_sw_execute"); - public static final ActionName BREAK_HW_EXECUTE = builtIn("break_hw_execute"); - public static final ActionName BREAK_READ = builtIn("break_read"); - public static final ActionName BREAK_WRITE = builtIn("break_write"); - public static final ActionName BREAK_ACCESS = builtIn("break_access"); - public static final ActionName BREAK_EXT = extended("break_ext"); + public static final ActionName BREAK_SW_EXECUTE = + create("break_sw_execute", Show.BUILTIN, Enabler.ALWAYS, "Set Software Breakpoint", + ICON_SET_BREAKPOINT, "Set"); + public static final ActionName BREAK_HW_EXECUTE = + create("break_hw_execute", Show.BUILTIN, Enabler.ALWAYS, "Set Hardware Breakpoint", + ICON_SET_BREAKPOINT, "Set"); + public static final ActionName BREAK_READ = + create("break_read", Show.BUILTIN, Enabler.ALWAYS, "Set Read Breakpoint", + ICON_SET_BREAKPOINT, "Set"); + public static final ActionName BREAK_WRITE = + create("break_write", Show.BUILTIN, Enabler.ALWAYS, "Set Write Breakpoint", + ICON_SET_BREAKPOINT, "Set"); + public static final ActionName BREAK_ACCESS = + create("break_access", Show.BUILTIN, Enabler.ALWAYS, "Set Access Breakpont", + ICON_SET_BREAKPOINT, "Set"); + public static final ActionName BREAK_EXT = + create("break_ext", Show.BUILTIN, Enabler.ALWAYS, null, ICON_SET_BREAKPOINT, "Set"); /** * Forms: (rng:RANGE) */ - public static final ActionName READ_MEM = builtIn("read_mem"); + public static final ActionName READ_MEM = + create("read_mem", Show.BUILTIN, Enabler.ALWAYS, "Read Memory", null, "Read"); /** * Forms: (addr:ADDRESS,data:BYTES) */ - public static final ActionName WRITE_MEM = builtIn("write_mem"); + public static final ActionName WRITE_MEM = + create("write_mem", Show.BUILTIN, Enabler.ALWAYS, "Write Memory", null, "Write"); // NOTE: no read_reg. Use refresh(RegContainer), refresh(RegGroup), refresh(Register) /** * Forms: (frame:Frame,name:STRING,value:BYTES), (register:Register,value:BYTES) */ - public static final ActionName WRITE_REG = builtIn("write_reg"); + public static final ActionName WRITE_REG = + create("write_reg", Show.BUILTIN, Enabler.NOT_RUNNING, "Write Register", null, "Write"); } 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 133f186892..4f6eb5e6cd 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 @@ -18,16 +18,17 @@ package ghidra.debug.api.target; import java.util.Map; import java.util.Set; import java.util.concurrent.*; -import java.util.function.BooleanSupplier; -import java.util.function.Function; + +import javax.swing.Icon; import docking.ActionContext; +import ghidra.debug.api.target.ActionName.Show; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; -import ghidra.trace.model.TraceExecutionState; import ghidra.trace.model.Trace; +import ghidra.trace.model.TraceExecutionState; import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.guest.TracePlatform; @@ -58,28 +59,72 @@ public interface Target { * just invoked implicitly. Often, the two suppliers are implemented using lambda functions, and * those functions will keep whatever some means of querying UI and/or target context in their * closures. - * - * @param display the text to display on UI actions associated with this entry - * @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, - long specificity, BooleanSupplier enabled, - Function> action) { + interface ActionEntry { + + /** + * Get the text to display on UI actions associated with this entry + * + * @return the display + */ + String display(); + + /** + * Get the name of a common debugger command this action implements + * + * @return the name + */ + ActionName name(); + + /** + * Get the icon to display in menus and dialogs + * + * @return the icon + */ + Icon icon(); + + /** + * Get the text providing more details, usually displayed in a tool tip + * + * @return the details + */ + String details(); + + /** + * Check whether invoking the action requires further user interaction + * + * @return true if prompting is required + */ + boolean requiresPrompt(); + + /** + * Get a relative score of specificity. + * + *

+ * These are only meaningful when compared among entries returned in the same collection. + * + * @return the specificity + */ + long specificity(); + + /** + * Invoke the action asynchronously, prompting if desired. + * + *

+ * The implementation is not required to provide a timeout; however, downstream components + * may. + * + * @param prompt whether or not to prompt the user for arguments + * @return the future result, often {@link Void} + */ + CompletableFuture invokeAsyncWithoutTimeout(boolean prompt); /** * Check if this action is currently enabled * * @return true if enabled */ - public boolean isEnabled() { - return enabled.getAsBoolean(); - } + boolean isEnabled(); /** * Invoke the action asynchronously, prompting if desired @@ -90,8 +135,9 @@ public interface Target { * @param prompt whether or not to prompt the user for arguments * @return the future result, often {@link Void} */ - public CompletableFuture invokeAsync(boolean prompt) { - return action.apply(prompt).orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + default CompletableFuture invokeAsync(boolean prompt) { + return invokeAsyncWithoutTimeout(prompt).orTimeout(TIMEOUT_MILLIS, + TimeUnit.MILLISECONDS); } /** @@ -103,7 +149,7 @@ public interface Target { * * @param prompt whether or not to prompt the user for arguments */ - public void run(boolean prompt) { + default void run(boolean prompt) { get(prompt); } @@ -113,7 +159,7 @@ public interface Target { * @param prompt whether or not to prompt the user for arguments * @return the resulting value, if applicable */ - public Object get(boolean prompt) { + default Object get(boolean prompt) { if (Swing.isSwingThread()) { throw new AssertionError("Refusing to block the Swing thread. Use a Task."); } @@ -130,8 +176,8 @@ public interface Target { * * @return true if built in. */ - public boolean builtIn() { - return name != null && name.builtIn(); + default Show getShow() { + return name() == null ? Show.EXTENDED : name().show(); } } 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 4838df5716..e4325badd7 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 @@ -20,6 +20,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Function; +import javax.swing.Icon; + import ghidra.async.AsyncUtils; import ghidra.debug.api.target.ActionName; import ghidra.trace.model.Trace; @@ -62,6 +64,20 @@ public interface RemoteMethod { */ String display(); + /** + * The icon to display in menu's and in the prompt dialog. + * + * @return the icon + */ + Icon icon(); + + /** + * Text to display in the OK button of any prompt dialog. + * + * @return the text + */ + String okText(); + /** * A description of the method. * diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java index d36d03eab5..f72685d120 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java @@ -49,7 +49,8 @@ import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryOperations; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.program.TraceProgramView; -import ghidra.trace.model.target.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.path.KeyPath; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; @@ -1740,7 +1741,7 @@ public interface FlatDebuggerAPI { * @return true if alive */ default boolean isTargetAlive(Trace trace) { - return getExecutionState(trace).isAlive(); + return getExecutionState(trace) != TraceExecutionState.TERMINATED; } /** @@ -1763,7 +1764,7 @@ public interface FlatDebuggerAPI { * @return true if alive */ default boolean isThreadAlive(TraceThread thread) { - return getExecutionState(thread).isAlive(); + return getExecutionState(thread) != TraceExecutionState.TERMINATED; } /** @@ -1792,7 +1793,7 @@ public interface FlatDebuggerAPI { * @throws TimeoutException if the timeout expires */ default void waitForBreak(Trace trace, long timeout, TimeUnit unit) throws TimeoutException { - if (!getExecutionState(trace).isRunning()) { + if (getExecutionState(trace) != TraceExecutionState.RUNNING) { return; } var listener = new DomainObjectListener() { @@ -1800,14 +1801,14 @@ public interface FlatDebuggerAPI { @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (!getExecutionState(trace).isRunning()) { + if (getExecutionState(trace) != TraceExecutionState.RUNNING) { future.complete(null); } } }; trace.addListener(listener); try { - if (!getExecutionState(trace).isRunning()) { + if (getExecutionState(trace) != TraceExecutionState.RUNNING) { return; } listener.future.get(timeout, unit); diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiConnector.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiConnector.java index c7fce87c50..7b85272d16 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiConnector.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/rmi/jpda/JdiConnector.java @@ -144,20 +144,10 @@ public class JdiConnector { } public void registerRemoteMethod(JdiMethods methods, java.lang.reflect.Method m, String name) { - String action = name; - String display = name; - String description = name; TraceMethod annot = m.getAnnotation(TraceMethod.class); if (annot == null) { return; } - action = annot.action(); - if (annot.display() != null) { - display = annot.display(); - } - if (annot.description() != null) { - description = annot.description(); - } int pcount = m.getParameterCount(); if (pcount < 1) { return; @@ -167,8 +157,8 @@ public class JdiConnector { * collection routines currently use the return type, so just use ANY for now. */ TraceObjectSchema schema = PrimitiveTraceObjectSchema.ANY; - RmiRemoteMethod method = new RmiRemoteMethod(rootSchema.getContext(), name, action, display, - description, schema, methods, m); + RmiRemoteMethod method = new RmiRemoteMethod(rootSchema.getContext(), name, annot.action(), + annot.display(), annot.description(), annot.okText(), annot.icon(), schema, methods, m); remoteMethodRegistry.putMethod(name, method); } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiClient.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiClient.java index 405b35c3fc..c9fada1ef8 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiClient.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiClient.java @@ -605,9 +605,11 @@ public class RmiClient { private Method buildMethod(RmiRemoteMethod method) { Method.Builder builder = Method.newBuilder() .setName(method.getName()) - .setDescription(method.getDescription()) .setAction(method.getAction()) - .setDisplay(method.getDisplay()); + .setDisplay(method.getDisplay()) + .setDescription(method.getDescription()) + .setOkText(method.getOkText()) + .setIcon(method.getIcon()); int i = 0; for (RmiRemoteMethodParameter p : method.getParameters()) { MethodParameter param = buildParameter(p); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodRegistry.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodRegistry.java index c00d65f37a..a929e0b578 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodRegistry.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiMethodRegistry.java @@ -32,6 +32,10 @@ public class RmiMethodRegistry { String display() default ""; String description() default ""; + + String okText() default ""; + + String icon() default ""; } Map map = new HashMap<>(); diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethod.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethod.java index 54e1034c35..f7473316e0 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethod.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/client/tracermi/RmiRemoteMethod.java @@ -25,22 +25,27 @@ import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; public class RmiRemoteMethod { private final SchemaContext schemaContext; - private String name; - private String action; - private String display; - private String description; - private RmiRemoteMethodParameter[] params; - private TraceObjectSchema schema; - private RmiMethods instance; - private Method m; + private final String name; + private final String action; + private final String display; + private final String description; + private final String okText; + private final String icon; + private final RmiRemoteMethodParameter[] params; + private final TraceObjectSchema schema; + private final RmiMethods instance; + private final Method m; public RmiRemoteMethod(SchemaContext schemaContext, String name, String action, String display, - String description, TraceObjectSchema schema, RmiMethods instance, Method m) { + String description, String okText, String icon, TraceObjectSchema schema, + RmiMethods instance, Method m) { this.schemaContext = schemaContext; this.name = name; this.action = action; this.display = display; this.description = description; + this.okText = okText; + this.icon = icon; this.params = new RmiRemoteMethodParameter[m.getParameterCount()]; this.schema = schema; this.instance = instance; @@ -69,6 +74,14 @@ public class RmiRemoteMethod { return description; } + public String getOkText() { + return okText; + } + + public String getIcon() { + return icon; + } + public String getAction() { return action; } 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 ae57681c30..7742df15df 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 @@ -17,14 +17,33 @@ package ghidra.app.plugin.core.debug.service.tracermi; import java.util.Map; +import javax.swing.Icon; + import ghidra.debug.api.target.ActionName; -import ghidra.debug.api.tracermi.*; +import ghidra.debug.api.tracermi.RemoteMethod; +import ghidra.debug.api.tracermi.RemoteParameter; import ghidra.trace.model.Trace; import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; public record RecordRemoteMethod(TraceRmiHandler handler, String name, ActionName action, - String display, String description, Map parameters, - SchemaName retType) implements RemoteMethod { + String display, Icon icon, String okText, String description, + Map parameters, SchemaName retType) implements RemoteMethod { + + public RecordRemoteMethod { + if (display == null || display.isBlank()) { + display = action.display(); + } + if (display == null || display.isBlank()) { // still + display = name; + } + if (icon == null) { + icon = action.icon(); + } + if (okText.isEmpty() || okText.isBlank()) { + okText = action.okText(); + } + } + @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 bb2d959c9b..8ffb81edee 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 @@ -31,6 +31,7 @@ import org.apache.commons.lang3.ArrayUtils; import com.google.protobuf.ByteString; import db.Transaction; +import generic.theme.GIcon; import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin; import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; import ghidra.app.services.DebuggerControlService; @@ -1027,7 +1028,9 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection { } for (Method m : req.getMethodsList()) { RemoteMethod rm = new RecordRemoteMethod(this, m.getName(), - ActionName.name(m.getAction()), m.getDisplay(), m.getDescription(), + ActionName.name(m.getAction()), m.getDisplay(), + m.getIcon().isBlank() ? null : new GIcon(m.getIcon()), m.getOkText(), + 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 5c91394553..327bea8d65 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 @@ -19,9 +19,11 @@ import java.io.IOException; import java.math.BigInteger; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.function.*; +import java.util.function.Supplier; import java.util.stream.Collectors; +import javax.swing.Icon; + import org.apache.commons.lang3.exception.ExceptionUtils; import docking.ActionContext; @@ -60,6 +62,70 @@ import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; public class TraceRmiTarget extends AbstractTarget { + + class TraceRmiActionEntry implements ActionEntry { + private final RemoteMethod method; + private final Map args; + private final boolean requiresPrompt; + private final long specificity; + private final ParamAndObjectArg first; + + public TraceRmiActionEntry(RemoteMethod method, Map args) { + this.method = method; + this.args = args; + + this.requiresPrompt = args.values().contains(Missing.MISSING); + this.specificity = computeSpecificity(args); + this.first = getFirstObjectArgument(method, args); + } + + @Override + public String display() { + return method.display(); + } + + @Override + public ActionName name() { + return method.action(); + } + + @Override + public Icon icon() { + return method.icon(); + } + + @Override + public String details() { + return method.description(); + } + + @Override + public boolean requiresPrompt() { + return requiresPrompt; + } + + @Override + public long specificity() { + return specificity; + } + + @Override + public CompletableFuture invokeAsyncWithoutTimeout(boolean prompt) { + return invokeMethod(prompt, method, args); + } + + @Override + public boolean isEnabled() { + if (first == null) { + return true; + } + if (first.obj == null) { + return false; + } + return name().enabler().isEnabled(first.obj, getSnap()); + } + } + private final TraceRmiConnection connection; private final Trace trace; @@ -224,8 +290,12 @@ public class TraceRmiTarget extends AbstractTarget { return null; } + /** + * A singleton to indicate missing arguments + */ public enum Missing { - MISSING; // The argument requires a prompt + /** The argument requires a prompt */ + MISSING; } protected Object findArgument(ActionName action, RemoteParameter parameter, @@ -266,28 +336,7 @@ public class TraceRmiTarget extends AbstractTarget { return args; } - private TraceExecutionState getStateOf(TraceObject object) { - try { - return object.getExecutionState(getSnap()); - } - catch (NoSuchElementException e) { - return TraceExecutionState.TERMINATED; - } - } - - private boolean whenState(TraceObject object, - Predicate predicate) { - try { - TraceExecutionState state = getStateOf(object); - return state == null || predicate.test(state); - } - catch (Exception e) { - Msg.error(this, "Could not get state: " + e); - return false; - } - } - - protected long computeSpecificity(Map args) { + protected static long computeSpecificity(Map args) { long score = 0; for (Object o : args.values()) { if (o instanceof TraceObject obj) { @@ -297,46 +346,34 @@ public class TraceRmiTarget extends AbstractTarget { return score; } - protected BooleanSupplier chooseEnabler(RemoteMethod method, Map args) { - ActionName name = method.action(); + protected RemoteParameter getFirstObjectParameter(RemoteMethod method) { SchemaContext ctx = getSchemaContext(); if (ctx == null) { - return () -> true; + return null; } - RemoteParameter firstParam = method.parameters() + return method.parameters() .values() .stream() .filter( p -> TraceObjectInterfaceUtils.isTraceObject(ctx.getSchema(p.type()).getType())) .findFirst() .orElse(null); + } + + record ParamAndObjectArg(RemoteParameter param, TraceObject obj) {} + + protected ParamAndObjectArg getFirstObjectArgument(RemoteMethod method, + Map args) { + RemoteParameter firstParam = getFirstObjectParameter(method); if (firstParam == null) { - return () -> true; + return null; } Object firstArg = args.get(firstParam.name()); - if (firstArg == null || firstArg == Missing.MISSING) { + if (firstArg == null || !(firstArg instanceof TraceObject obj)) { Msg.trace(this, "MISSING first argument for " + method + "(" + firstParam + ")"); - return () -> false; + return new ParamAndObjectArg(firstParam, null); } - TraceObject obj = (TraceObject) firstArg; - if (ActionName.RESUME.equals(name) || - ActionName.STEP_BACK.equals(name) || - ActionName.STEP_EXT.equals(name) || - ActionName.STEP_INTO.equals(name) || - ActionName.STEP_OUT.equals(name) || - ActionName.STEP_OVER.equals(name) || - ActionName.STEP_SKIP.equals(name)) { - return () -> whenState(obj, - state -> state != null && (state.isStopped() || state.isUnknown())); - } - else if (ActionName.INTERRUPT.equals(name)) { - return () -> whenState(obj, - state -> state == null || state.isRunning() || state.isUnknown()); - } - else if (ActionName.KILL.equals(name)) { - return () -> whenState(obj, state -> state == null || !state.isTerminated()); - } - return () -> true; + return new ParamAndObjectArg(firstParam, obj); } private Map promptArgs(RemoteMethod method, Map defaults) { @@ -346,7 +383,7 @@ public class TraceRmiTarget extends AbstractTarget { */ Map> defs = ValStr.fromPlainMap(defaults); RemoteMethodInvocationDialog dialog = new RemoteMethodInvocationDialog(tool, - getSchemaContext(), method.display(), method.display(), null); + getSchemaContext(), method.display(), method.okText(), method.icon()); Map> args = dialog.promptArguments(method.parameters(), defs, defs); return args == null ? null : ValStr.toPlainMap(args); } @@ -374,10 +411,7 @@ public class TraceRmiTarget extends AbstractTarget { boolean allowContextObject, boolean allowCoordsObject, boolean allowSuitableObject) { Map args = collectArguments(method, context, allowContextObject, allowCoordsObject, allowSuitableObject); - boolean requiresPrompt = args.values().contains(Missing.MISSING); - return new ActionEntry(method.display(), method.action(), method.description(), - requiresPrompt, computeSpecificity(args), chooseEnabler(method, args), - prompt -> invokeMethod(prompt, method, args)); + return new TraceRmiActionEntry(method, args); } protected Map collectFromMethods(Collection methods, @@ -432,6 +466,17 @@ public class TraceRmiTarget extends AbstractTarget { true); } + @Override + public Map collectActions(ActionName name, ActionContext context) { + if (name == null) { + if (context instanceof ProgramLocationActionContext ctx) { + return collectAddressActions(ctx); + } + return collectAllActions(context); + } + return collectByName(name, context); + } + @Override protected Map collectResumeActions(ActionContext context) { return collectByName(ActionName.RESUME, context); 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 0ff04e0fb1..f469723ea8 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 @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -423,6 +423,8 @@ message Method { // 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 = 6; + string ok_text = 7; + string icon = 8; } 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 6b3c2d10ac..40784b67a1 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 @@ -27,10 +27,11 @@ from . import sch from . import trace_rmi_pb2 as bufs from .util import send_delimited, recv_delimited + # This need not be incremented every Ghidra release. When a breaking protocol # change is made, this should be updated to match the first Ghidra release that # includes the change. -# +# # Other places to change: # * every pyproject.toml file (incl. deps) # * TraceRmiHandler.VERSION @@ -98,7 +99,8 @@ class Receiver(Thread): while not self._is_shutdown: #print("Receiving message") try: - reply = recv_delimited(self.client.s, bufs.RootMessage(), dbg_seq) + reply = recv_delimited( + self.client.s, bufs.RootMessage(), dbg_seq) except BaseException as e: self._is_shutdown = True return @@ -448,6 +450,8 @@ class RemoteMethod: name: str action: str display: str + icon: str + ok_text: str description: str parameters: List[RemoteParameter] return_schema: sch.Schema @@ -508,30 +512,28 @@ class MethodRegistry(object): @classmethod def create_method(cls, function, name=None, action=None, display=None, - description=None) -> RemoteMethod: + icon=None, ok_text=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 '' + description = function.__doc__ sig = inspect.signature(function) params = [] 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, display, description, params, - return_schema, function) + return RemoteMethod(name, action, display, icon, ok_text, description, + params, return_schema, function) def method(self, func=None, *, name=None, action=None, display=None, - description='', condition=True): + icon=None, ok_text=None, description=None, condition=True): def _method(func): if condition: method = self.create_method(func, name, action, display, - description) + icon, ok_text, description) self.register_method(method) return func @@ -705,8 +707,10 @@ 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 + to.display = method.display or '' + to.icon = method.icon or '' + to.ok_text = method.ok_text or '' + to.description = method.description or '' 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 12901a480a..3ba6116ba0 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 @@ -24,6 +24,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.swing.Icon; + import db.Transaction; import ghidra.app.services.DebuggerTargetService; import ghidra.async.*; @@ -56,6 +58,11 @@ public abstract class TestTraceRmiConnection extends AbstractTraceRmiConnection String description, Map parameters, SchemaName retType, AsyncPairingQueue> argQueue, AsyncPairingQueue retQueue) implements RemoteMethod { + + public TestRemoteMethod { + Objects.requireNonNull(action); + } + public TestRemoteMethod(String name, ActionName action, String display, String description, Map parameters, SchemaName retType) { this(name, action, display, description, parameters, retType, new AsyncPairingQueue<>(), @@ -106,6 +113,16 @@ public abstract class TestTraceRmiConnection extends AbstractTraceRmiConnection result.thenApply(ar -> ar.ret).handle(AsyncUtils.copyTo(retQueue().give())); return result.thenApply(ar -> ar.args); } + + @Override + public String okText() { + return "OK"; + } + + @Override + public Icon icon() { + return null; + } } public record TestRemoteParameter(String name, SchemaName type, boolean required, diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html index d4681846f3..daa63d1045 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html @@ -163,6 +163,13 @@ "help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoint Marker Actions in the listings.

+

Set Breakpoint

+ +

This is a dropdown of actions provided by the back-end debugger, usually for setting + breakpoints by symbol, expression, etc. Setting breakpoints by address is typically done from + the Listings. If no such actions are available, or there is no live target, this action is + disabled.

+

Enable

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/images/DebuggerBreakpointsPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/images/DebuggerBreakpointsPlugin.png index 4188c674e0..77722233b8 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/images/DebuggerBreakpointsPlugin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerBreakpointsPlugin/images/DebuggerBreakpointsPlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/InvokeActionEntryAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/InvokeActionEntryAction.java new file mode 100644 index 0000000000..49a566de69 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/InvokeActionEntryAction.java @@ -0,0 +1,39 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui; + +import docking.ActionContext; +import docking.action.DockingAction; +import ghidra.app.plugin.core.debug.gui.control.TargetActionTask; +import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; + +public class InvokeActionEntryAction extends DockingAction { + protected final PluginTool tool; + protected final ActionEntry entry; + + public InvokeActionEntryAction(Plugin plugin, ActionEntry entry) { + super(entry.display(), plugin.getName()); + this.tool = plugin.getTool(); + this.entry = entry; + } + + @Override + public void actionPerformed(ActionContext context) { + TargetActionTask.runAction(tool, entry.display(), entry); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java index 04ffb56e72..8b7a29c28b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java @@ -29,19 +29,23 @@ import docking.ActionContext; import docking.WindowPosition; import docking.action.*; import docking.action.builder.ActionBuilder; +import docking.menu.MultiActionDockingAction; import docking.widgets.table.*; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import ghidra.app.context.ProgramLocationActionContext; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; +import ghidra.app.plugin.core.debug.gui.InvokeActionEntryAction; import ghidra.app.services.*; import ghidra.app.services.DebuggerControlService.ControlModeChangeListener; import ghidra.debug.api.breakpoint.LogicalBreakpoint; import ghidra.debug.api.breakpoint.LogicalBreakpoint.State; import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener; 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; import ghidra.framework.model.DomainObjectEvent; import ghidra.framework.plugintool.*; @@ -263,6 +267,78 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter return contextHasMatchingBreakpoints(context, lb -> true, loc -> true); } + protected class GenericSetBreakpointAction extends InvokeActionEntryAction { + public GenericSetBreakpointAction(ActionEntry entry) { + super(plugin, entry); + setMenuBarData(new MenuData(new String[] { getName() }, entry.icon())); + setHelpLocation(AbstractSetBreakpointAction.help(plugin)); + } + } + + protected class StubSetBreakpointAction extends DockingAction { + public StubSetBreakpointAction() { + super("(Use the Listings to Set Breakpoints)", plugin.getName()); + setMenuBarData(new MenuData(new String[] { getName() })); + setHelpLocation(AbstractSetBreakpointAction.help(plugin)); + setEnabled(false); + } + + @Override + public void actionPerformed(ActionContext context) { + } + } + + protected class SetBreakpointAction extends MultiActionDockingAction { + public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS; + + private final List stub = List.of(new StubSetBreakpointAction()); + + public SetBreakpointAction() { + super("Set Breakpoint", plugin.getName()); + // TODO: Different icon? + setToolBarData(new ToolBarData(DebuggerResources.ICON_ADD, GROUP)); + setHelpLocation(AbstractSetBreakpointAction.help(plugin)); + addLocalAction(this); + } + + @Override + public List getActionList(ActionContext context) { + if (traceManager == null) { + return stub; + } + Trace trace = traceManager.getCurrentTrace(); + if (trace == null) { + return stub; + } + + // TODO: Set-by-address (like the listing one) always present? + if (controlService == null) { + return stub; + } + ControlMode mode = controlService.getCurrentMode(trace); + if (!mode.isTarget()) { + return stub; + // TODO: Consider a Sleigh expression for emulation? + // Actually, any "Address" field could be a Sleigh expression.... + } + + Target target = traceManager.getCurrent().getTarget(); + if (target == null) { + return stub; + } + List result = new ArrayList<>(); + for (ActionEntry entry : target.collectActions(ActionName.BREAK_EXT, context) + .values()) { + result.add(new GenericSetBreakpointAction(entry)); + } + if (result.isEmpty()) { + return stub; + } + Collections.sort(result, Comparator.comparing(a -> a.getName())); + return result; + } + } + protected class EnableSelectedBreakpointsAction extends AbstractEnableSelectedBreakpointsAction { public static final String GROUP = DebuggerResources.GROUP_BREAKPOINTS; @@ -708,6 +784,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter new DebuggerMakeBreakpointsEffectiveActionContext(); // package access for testing + SetBreakpointAction actionSetBreakpoint; EnableSelectedBreakpointsAction actionEnableSelectedBreakpoints; EnableAllBreakpointsAction actionEnableAllBreakpoints; DisableSelectedBreakpointsAction actionDisableSelectedBreakpoints; @@ -1163,6 +1240,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter } protected void createActions() { + actionSetBreakpoint = new SetBreakpointAction(); actionEnableSelectedBreakpoints = new EnableSelectedBreakpointsAction(); actionEnableAllBreakpoints = new EnableAllBreakpointsAction(); actionDisableSelectedBreakpoints = new DisableSelectedBreakpointsAction(); 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 b4ab401c7b..68db4d689e 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 @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -288,6 +288,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } actionsTargetStepExt.add(TargetStepExtAction.builder(entry.display(), this) .description(entry.details()) + .toolBarIcon(entry.icon()) .enabledWhen(ctx -> entry.isEnabled()) .onAction(ctx -> TargetActionTask.runAction(tool, entry.display(), entry)) .build()); 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 9a77a0c6a6..a3074a91c3 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 @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,21 +18,19 @@ 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.*; +import docking.action.DockingActionIf; +import docking.action.MenuData; import docking.actions.PopupActionProvider; 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.InvokeActionEntryAction; import ghidra.app.services.*; import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.model.DebuggerObjectActionContext; -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; @@ -57,23 +55,10 @@ import ghidra.trace.model.program.TraceProgramView; public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionProvider { public static final String GROUP_METHODS = "Debugger Methods"; - class InvokeActionEntryAction extends DockingAction { - private final ActionEntry entry; - - public InvokeActionEntryAction(ActionEntry entry) { - super(entry.display(), DebuggerMethodActionsPlugin.this.getName()); - this.entry = entry; - 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) { - TargetActionTask.runAction(tool, entry.display(), entry); + class MethodAction extends InvokeActionEntryAction { + public MethodAction(ActionEntry entry) { + super(DebuggerMethodActionsPlugin.this, entry); + setPopupMenuData(new MenuData(new String[] { getName() }, entry.icon(), GROUP_METHODS)); } } @@ -136,10 +121,10 @@ public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionPr List result = new ArrayList<>(); for (ActionEntry entry : target.collectActions(null, context).values()) { //if (entry.requiresPrompt() || entry.builtIn()) { - if (!entry.isEnabled() || entry.builtIn()) { + if (!entry.isEnabled() || !entry.getShow().isShowing(context)) { continue; } - result.add(new InvokeActionEntryAction(entry)); + result.add(new MethodAction(entry)); } return result; } 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 6868eeb61f..72efd87cf7 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 @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -65,9 +65,11 @@ class TargetDockingAction extends DockingAction { protected void updateFromContext(ActionContext context) { entry = findEntry(context); if (entry == null) { + getToolBarData().setIcon(action.icon()); setDescription(defaultDescription); } else { + getToolBarData().setIcon(entry.icon()); setDescription(entry.details()); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceExecutionState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceExecutionState.java index 641b045544..b9bd7efd5a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceExecutionState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceExecutionState.java @@ -26,7 +26,7 @@ public enum TraceExecutionState { * This may apply, e.g., to a GDB "Inferior," which has no yet been used to launch or attach to * a process. */ - INACTIVE(false, false, false, false), + INACTIVE, /** * The object is alive, but its execution state is unspecified @@ -38,12 +38,12 @@ public enum TraceExecutionState { * all of its threads are stopped. For the clients' sakes, all models should implement * these conventions internally. */ - ALIVE(true, false, false, false), + ALIVE, /** * The object is alive, but not executing */ - STOPPED(true, false, true, false), + STOPPED, /** * The object is alive and executing @@ -53,7 +53,7 @@ public enum TraceExecutionState { * thread is currently executing, waiting on an event, or scheduled for execution. It does not * necessarily mean it is executing on a CPU at this exact moment. */ - RUNNING(true, true, false, false), + RUNNING, /** * The object is no longer alive @@ -63,63 +63,5 @@ public enum TraceExecutionState { * stale handles to objects which may still be queried (e.g., for a process exit code), or e.g., * a GDB "Inferior," which could be re-used to launch or attach to another process. */ - TERMINATED(false, false, false, true); - - private final boolean alive; - private final boolean running; - private final boolean stopped; - private final boolean terminated; - - private TraceExecutionState(boolean alive, boolean running, boolean stopped, - boolean terminated) { - this.alive = alive; - this.running = running; - this.stopped = stopped; - this.terminated = terminated; - } - - /** - * Check if this state implies the object is alive - * - * @return true if alive - */ - public boolean isAlive() { - return alive; - } - - /** - * Check if this state implies the object is running - * - * @return true if running - */ - public boolean isRunning() { - return running; - } - - /** - * Check if this state implies the object is stopped - * - * @return true if stopped - */ - public boolean isStopped() { - return stopped; - } - - /** - * Check if this state implies the object was terminated - * - * @return true if terminated - */ - public boolean isTerminated() { - return terminated; - } - - /** - * Check if this state is ambiguous - * - * @return true if terminated - */ - public boolean isUnknown() { - return !stopped && !running && !terminated; - } + TERMINATED; } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java index cd5590c358..55b7bd891b 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java @@ -31,6 +31,7 @@ import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.plugin.core.debug.service.tracermi.TestTraceRmiConnection.*; import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.debug.api.target.ActionName; import ghidra.program.util.ProgramLocation; import ghidra.trace.model.Lifespan; import ghidra.trace.model.target.schema.PrimitiveTraceObjectSchema; @@ -57,7 +58,7 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge protected void addMethods() { TestRemoteMethodRegistry reg = rmiCx.getMethods(); - rmiMethodAdvance = new TestRemoteMethod("advance", null, "Advance", + rmiMethodAdvance = new TestRemoteMethod("advance", ActionName.STEP_EXT, "Advance", "Advance to the given address", PrimitiveTraceObjectSchema.VOID, new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread", "The thread to advance"), @@ -65,14 +66,15 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge "Target", "The target address")); reg.add(rmiMethodAdvance); - rmiMethodStepExt = new TestRemoteMethod("step_ext", null, "StepExt", + rmiMethodStepExt = new TestRemoteMethod("step_ext", ActionName.STEP_EXT, "StepExt", "Step in some special way", PrimitiveTraceObjectSchema.VOID, new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread", "The thread to step")); reg.add(rmiMethodStepExt); - rmiMethodAdvanceWithFlag = new TestRemoteMethod("advance_flag", null, "Advance With Flag", - "Advance to the given address, with flag", PrimitiveTraceObjectSchema.VOID, + rmiMethodAdvanceWithFlag = new TestRemoteMethod("advance_flag", ActionName.STEP_EXT, + "Advance With Flag", "Advance to the given address, with flag", + PrimitiveTraceObjectSchema.VOID, new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread", "The thread to advance"), new TestRemoteParameter("target", PrimitiveTraceObjectSchema.ADDRESS, true, null, @@ -81,7 +83,7 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge "Flag", "The flag")); reg.add(rmiMethodAdvanceWithFlag); - rmiMethodBetween = new TestRemoteMethod("between", null, "Between", + rmiMethodBetween = new TestRemoteMethod("between", ActionName.STEP_EXT, "Between", "Advance between two given addresses", PrimitiveTraceObjectSchema.VOID, new TestRemoteParameter("thread", new SchemaName("Thread"), true, null, "Thread", "The thread to advance"), diff --git a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html index ebb31ae6ed..bd758d9b6f 100644 --- a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html +++ b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html @@ -99,7 +99,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

Ghidra Debugger

diff --git a/GhidraDocs/GhidraClass/Debugger/A2-UITour.html b/GhidraDocs/GhidraClass/Debugger/A2-UITour.html index 4835a64152..63e0f8913d 100644 --- a/GhidraDocs/GhidraClass/Debugger/A2-UITour.html +++ b/GhidraDocs/GhidraClass/Debugger/A2-UITour.html @@ -99,7 +99,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

Ghidra Debugger

diff --git a/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.html b/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.html index 0189f37b89..40731c17b3 100644 --- a/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.html +++ b/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.html @@ -99,7 +99,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

Ghidra Debugger

@@ -160,22 +161,26 @@ Memory/Hex, and the Decompiler, right-click and select set breakpoint Set Breakpoint, press K on the keyboard, or double-click the margin. +
  • From the Breakpoints window, use the Set Breakpoint +dropdown to to access the various breakpoint actions defined by +GDB.
  • From the Terminal window, use the GDB command, e.g., break main.
  • The advantage of using the listings is that you can quickly set a -breakpoint at any address. The advantage of using the Terminal window is -that you can specify something other than an address. Often, those -specifications still resolve to addresses, and Ghidra will display them. -Ghidra will memorize breakpoints by recording them as special bookmarks -in the program database. There is some iconography to communicate the -various states of a breakpoint. When all is well and normal, you should -only see enabled and disabled disabled breakpoint -breakpoints. If the target is terminated (or not launched yet), you may -also see ineffective breakpoints.

    +breakpoint at any address. The advantage of using the dropdown action or +Terminal window is that you can specify something other than an address. +Often, those specifications still resolve to addresses, and Ghidra will +display them. Ghidra will memorize breakpoints by recording them as +special bookmarks in the program database. There is some iconography to +communicate the various states of a breakpoint. When all is well and +normal, you should only see enabled enabled breakpoint and +disabled breakpoints. If the target is terminated +(or not launched yet), you may also see ineffective ineffective breakpoint +breakpoints.

    Examining Minesweeper Board Setup

    diff --git a/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.md b/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.md index ebf3c86fbd..954ee14dfd 100644 --- a/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.md +++ b/GhidraDocs/GhidraClass/Debugger/A3-Breakpoints.md @@ -17,10 +17,11 @@ From here, you can toggle and delete existing breakpoints. There are several ways to set a new breakpoint: 1. From any static or dynamic listing window, including Disassembly, Memory/Hex, and the Decompiler, right-click and select ![set breakpoint](images/breakpoint-enable.png) Set Breakpoint, press **`K`** on the keyboard, or double-click the margin. +1. From the Breakpoints window, use the **Set Breakpoint** dropdown to to access the various breakpoint actions defined by GDB. 1. From the Terminal window, use the GDB command, e.g., `break main`. The advantage of using the listings is that you can quickly set a breakpoint at any address. -The advantage of using the Terminal window is that you can specify something other than an address. +The advantage of using the dropdown action or Terminal window is that you can specify something other than an address. Often, those specifications still resolve to addresses, and Ghidra will display them. Ghidra will memorize breakpoints by recording them as special bookmarks in the program database. There is some iconography to communicate the various states of a breakpoint. diff --git a/GhidraDocs/GhidraClass/Debugger/A4-MachineState.html b/GhidraDocs/GhidraClass/Debugger/A4-MachineState.html index 6fc4a3ca2a..a0ca3dd35b 100644 --- a/GhidraDocs/GhidraClass/Debugger/A4-MachineState.html +++ b/GhidraDocs/GhidraClass/Debugger/A4-MachineState.html @@ -35,7 +35,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

    Ghidra Debugger

    diff --git a/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html b/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html index 5c0a2be03e..e40e48f46c 100644 --- a/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html +++ b/GhidraDocs/GhidraClass/Debugger/A5-Navigation.html @@ -35,7 +35,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

    Ghidra Debugger

    diff --git a/GhidraDocs/GhidraClass/Debugger/A6-MemoryMap.html b/GhidraDocs/GhidraClass/Debugger/A6-MemoryMap.html index c72d319760..c35e1f8d6e 100644 --- a/GhidraDocs/GhidraClass/Debugger/A6-MemoryMap.html +++ b/GhidraDocs/GhidraClass/Debugger/A6-MemoryMap.html @@ -35,7 +35,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

    Ghidra Debugger

    diff --git a/GhidraDocs/GhidraClass/Debugger/B1-RemoteTargets.html b/GhidraDocs/GhidraClass/Debugger/B1-RemoteTargets.html index 288fb4200e..0fbe63ac0a 100644 --- a/GhidraDocs/GhidraClass/Debugger/B1-RemoteTargets.html +++ b/GhidraDocs/GhidraClass/Debugger/B1-RemoteTargets.html @@ -99,7 +99,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

    Ghidra Debugger

    diff --git a/GhidraDocs/GhidraClass/Debugger/B2-Emulation.html b/GhidraDocs/GhidraClass/Debugger/B2-Emulation.html index 0d95b521f4..2d5a708ed3 100644 --- a/GhidraDocs/GhidraClass/Debugger/B2-Emulation.html +++ b/GhidraDocs/GhidraClass/Debugger/B2-Emulation.html @@ -99,7 +99,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

    Ghidra Debugger

    diff --git a/GhidraDocs/GhidraClass/Debugger/B3-Scripting.html b/GhidraDocs/GhidraClass/Debugger/B3-Scripting.html index d382eea0da..0a29e84350 100644 --- a/GhidraDocs/GhidraClass/Debugger/B3-Scripting.html +++ b/GhidraDocs/GhidraClass/Debugger/B3-Scripting.html @@ -99,7 +99,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

    Ghidra Debugger

    @@ -379,7 +380,7 @@ class="sourceCode numberSource java numberLines">< throw new AssertionError("The current program must be termmines"); } -if (getExecutionState(trace).isRunning()) { +if (getExecutionState(trace) != TraceExecutionState.STOPPED) { monitor.setMessage("Interrupting target and waiting for STOPPED"); interrupt(); waitForBreak(3, TimeUnit.SECONDS); diff --git a/GhidraDocs/GhidraClass/Debugger/B3-Scripting.md b/GhidraDocs/GhidraClass/Debugger/B3-Scripting.md index bd54ccfbcc..5edd3b3bcd 100644 --- a/GhidraDocs/GhidraClass/Debugger/B3-Scripting.md +++ b/GhidraDocs/GhidraClass/Debugger/B3-Scripting.md @@ -210,7 +210,7 @@ if (!"termmines".equals(currentProgram.getName())) { throw new AssertionError("The current program must be termmines"); } -if (getExecutionState(trace).isRunning()) { +if (getExecutionState(trace) != TraceExecutionState.STOPPED) { monitor.setMessage("Interrupting target and waiting for STOPPED"); interrupt(); waitForBreak(3, TimeUnit.SECONDS); diff --git a/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html b/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html index b1929f6147..837eb91088 100644 --- a/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html +++ b/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html @@ -99,7 +99,8 @@ class="advanced" href="B1-RemoteTargets.html">Remote TargetsEmulationScriptingModeling + class="advanced" href="B4-Modeling.html">ModelingAdding Debuggers

    Ghidra Debugger

    @@ -634,46 +635,47 @@ class="sourceCode numberSource java numberLines">< } @Override - public Expr modBeforeStore(int sizeinAddress, Expr inAddress, int sizeinValue, - Expr inValue) { + public Expr modBeforeStore(int sizeinOffset, AddressSpace space, Expr inOffset, + int sizeinValue, Expr inValue) { return inValue; } @Override - public Expr modAfterLoad(int sizeinAddress, Expr inAddress, int sizeinValue, Expr inValue) { - return inValue; - } - - @Override - public Expr fromConst(byte[] value) { - if (endian.isBigEndian()) { - return new LitExpr(new BigInteger(1, value), value.length); - } - byte[] reversed = Arrays.copyOf(value, value.length); - ArrayUtils.reverse(reversed); - return new LitExpr(new BigInteger(1, reversed), reversed.length); - } - - @Override - public Expr fromConst(BigInteger value, int size, boolean isContextreg) { - return new LitExpr(value, size); - } - - @Override - public Expr fromConst(long value, int size) { - return fromConst(BigInteger.valueOf(value), size); - } - - @Override - public byte[] toConcrete(Expr value, Purpose purpose) { - throw new UnsupportedOperationException(); - } - - @Override - public long sizeOf(Expr value) { - throw new UnsupportedOperationException(); - } -} + public Expr modAfterLoad(int sizeinOffset, AddressSpace space, Expr inOffset, + int sizeinValue, Expr inValue) { + return inValue; + } + + @Override + public Expr fromConst(byte[] value) { + if (endian.isBigEndian()) { + return new LitExpr(new BigInteger(1, value), value.length); + } + byte[] reversed = Arrays.copyOf(value, value.length); + ArrayUtils.reverse(reversed); + return new LitExpr(new BigInteger(1, reversed), reversed.length); + } + + @Override + public Expr fromConst(BigInteger value, int size, boolean isContextreg) { + return new LitExpr(value, size); + } + + @Override + public Expr fromConst(long value, int size) { + return fromConst(BigInteger.valueOf(value), size); + } + + @Override + public byte[] toConcrete(Expr value, Purpose purpose) { + throw new UnsupportedOperationException(); + } + + @Override + public long sizeOf(Expr value) { + throw new UnsupportedOperationException(); + } +}

    We have implemented two arithmetic models: one for big-endian languages and one for little-endian. The endianness comes into play when we encode constant values passed to fromConst(). We must @@ -706,11 +708,10 @@ storage mechanism. For example, were this a dynamic taint analyzer, we could use modAfterLoad() to record that a value was retrieved via a tainted address. The inValue parameter gives the Expr actually retrieved from the emulator’s -storage, and inAddress gives the address (really just the -Expr piece) used to retrieve it. Conversely, in -modBeforeStore(), inValue gives the value -about to be stored, and inAddress gives the address used to -store it.

    +storage, and inOffset gives the offset used to retrieve it. +Conversely, in modBeforeStore(), inValue gives +the value about to be stored, and inOffset gives the offset +used to store it.

    We implement neither toConcrete() nor sizeOf(). Since we will be augmenting a concrete emulator, these methods will be provided by the concrete piece. If this model is diff --git a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ZeroTimerScript.java b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ZeroTimerScript.java index c958258858..f2ce28f99b 100644 --- a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ZeroTimerScript.java +++ b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ZeroTimerScript.java @@ -27,8 +27,8 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; import ghidra.program.model.symbol.Symbol; import ghidra.program.util.ProgramLocation; -import ghidra.trace.model.TraceExecutionState; import ghidra.trace.model.Trace; +import ghidra.trace.model.TraceExecutionState; public class ZeroTimerScript extends GhidraScript implements FlatDebuggerAPI { @Override @@ -43,7 +43,7 @@ public class ZeroTimerScript extends GhidraScript implements FlatDebuggerAPI { throw new AssertionError("The current program must be termmines"); } - if (getExecutionState(trace).isRunning()) { + if (getExecutionState(trace) != TraceExecutionState.STOPPED) { monitor.setMessage("Interrupting target and waiting for STOPPED"); interrupt(); waitForBreak(3, TimeUnit.SECONDS); @@ -87,7 +87,7 @@ public class ZeroTimerScript extends GhidraScript implements FlatDebuggerAPI { // -------------------------------- while (true) { - monitor.checkCanceled(); + monitor.checkCancelled(); TraceExecutionState execState = getExecutionState(trace); switch (execState) { diff --git a/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_EmptyAfterLaunch.png b/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_EmptyAfterLaunch.png index 0bda62bfb5..3955c95344 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_EmptyAfterLaunch.png and b/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_EmptyAfterLaunch.png differ diff --git a/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_PopAfterSRandRand.png b/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_PopAfterSRandRand.png index a4cf484439..26f01c8170 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_PopAfterSRandRand.png and b/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_PopAfterSRandRand.png differ diff --git a/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_SyncedAfterImportLibC.png b/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_SyncedAfterImportLibC.png index 9c373cddcc..79b1d06403 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_SyncedAfterImportLibC.png and b/GhidraDocs/GhidraClass/Debugger/images/Breakpoints_SyncedAfterImportLibC.png differ diff --git a/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_DisassemblyAfterLaunch.png b/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_DisassemblyAfterLaunch.png index 7660499e0c..73022b83b2 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_DisassemblyAfterLaunch.png and b/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_DisassemblyAfterLaunch.png differ diff --git a/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_ToolWSpecimen.png b/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_ToolWSpecimen.png index 4f6f2a0b7a..a5482ea5f6 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_ToolWSpecimen.png and b/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_ToolWSpecimen.png differ