mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
GP-3857: Port most Debugger components to TraceRmi.
This commit is contained in:
parent
7e4d2bcfaa
commit
fd4380c07a
222 changed files with 7241 additions and 3752 deletions
|
@ -1,35 +0,0 @@
|
|||
/* ###
|
||||
* 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.services;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.debug.api.workflow.DebuggerBot;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
||||
public interface DebuggerWorkflowService {
|
||||
PluginTool getTool();
|
||||
|
||||
Set<DebuggerBot> getAllBots();
|
||||
|
||||
Set<DebuggerBot> getEnabledBots();
|
||||
|
||||
Set<DebuggerBot> getDisabledBots();
|
||||
|
||||
void enableBots(Set<DebuggerBot> actors);
|
||||
|
||||
void disableBots(Set<DebuggerBot> actors);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/* ###
|
||||
* 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.services;
|
||||
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
|
||||
@ServiceInfo(
|
||||
defaultProviderName = "ghidra.app.plugin.core.debug.service.workflow.DebuggerWorkflowServiceProxyPlugin",
|
||||
description = "Service for managing automatic debugger actions and analysis")
|
||||
public interface DebuggerWorkflowToolService extends DebuggerWorkflowService {
|
||||
|
||||
}
|
|
@ -18,7 +18,6 @@ package ghidra.app.services;
|
|||
import java.util.Collection;
|
||||
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOpinion;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
|
@ -29,13 +28,6 @@ import ghidra.program.model.listing.Program;
|
|||
description = "Manages and presents launchers for Trace RMI Targets",
|
||||
defaultProviderName = "ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin")
|
||||
public interface TraceRmiLauncherService {
|
||||
/**
|
||||
* Get all of the installed opinions
|
||||
*
|
||||
* @return the opinions
|
||||
*/
|
||||
Collection<TraceRmiLaunchOpinion> getOpinions();
|
||||
|
||||
/**
|
||||
* Get all offers for the given program
|
||||
*
|
||||
|
|
|
@ -87,4 +87,14 @@ public interface LocationTracker {
|
|||
* @return true if re-computation and "goto" is warranted
|
||||
*/
|
||||
boolean affectedByStackChange(TraceStack stack, DebuggerCoordinates coordinates);
|
||||
|
||||
/**
|
||||
* Indicates whether the user should expect instructions at the tracked location.
|
||||
*
|
||||
* <p>
|
||||
* Essentially, is this tracking the program counter?
|
||||
*
|
||||
* @return true to disassemble, false not to
|
||||
*/
|
||||
boolean shouldDisassemble();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
*/
|
||||
package ghidra.debug.api.target;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A name for a commonly-recognized target action.
|
||||
*
|
||||
|
@ -31,15 +34,43 @@ package ghidra.debug.api.target;
|
|||
* effort to match its methods to these stock actions where applicable, but ultimately, it is up to
|
||||
* the UI to decide what is presented where.
|
||||
*/
|
||||
public record ActionName(String name) {
|
||||
public static final ActionName REFRESH = new ActionName("refresh");
|
||||
public record ActionName(String name, boolean builtIn) {
|
||||
private static final Map<String, ActionName> NAMES = new HashMap<>();
|
||||
|
||||
public static ActionName name(String name) {
|
||||
synchronized (NAMES) {
|
||||
return NAMES.computeIfAbsent(name, n -> new ActionName(n, false));
|
||||
}
|
||||
}
|
||||
|
||||
private static ActionName builtIn(String name) {
|
||||
synchronized (NAMES) {
|
||||
ActionName action = new ActionName(name, true);
|
||||
if (NAMES.put(name, action) != null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
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 = builtIn("refresh");
|
||||
/**
|
||||
* Activate a given object and optionally a time
|
||||
*
|
||||
* <p>
|
||||
* Forms: (focus:Object), (focus:Object, snap:LONG), (focus:Object, time:STR)
|
||||
*/
|
||||
public static final ActionName ACTIVATE = new ActionName("activate");
|
||||
public static final ActionName ACTIVATE = builtIn("activate");
|
||||
/**
|
||||
* A weaker form of activate.
|
||||
*
|
||||
|
@ -48,9 +79,9 @@ public record ActionName(String name) {
|
|||
* 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 = new ActionName("focus");
|
||||
public static final ActionName TOGGLE = new ActionName("toggle");
|
||||
public static final ActionName DELETE = new ActionName("delete");
|
||||
public static final ActionName FOCUS = builtIn("focus");
|
||||
public static final ActionName TOGGLE = builtIn("toggle");
|
||||
public static final ActionName DELETE = builtIn("delete");
|
||||
|
||||
/**
|
||||
* Execute a CLI command
|
||||
|
@ -58,7 +89,7 @@ public record ActionName(String name) {
|
|||
* <p>
|
||||
* Forms: (cmd:STRING):STRING; Optional arguments: capture:BOOL
|
||||
*/
|
||||
public static final ActionName EXECUTE = new ActionName("execute");
|
||||
public static final ActionName EXECUTE = builtIn("execute");
|
||||
|
||||
/**
|
||||
* Connect the back-end to a (usually remote) target
|
||||
|
@ -66,23 +97,23 @@ public record ActionName(String name) {
|
|||
* <p>
|
||||
* Forms: (spec:STRING)
|
||||
*/
|
||||
public static final ActionName CONNECT = new ActionName("connect");
|
||||
public static final ActionName CONNECT = extended("connect");
|
||||
|
||||
/**
|
||||
* Forms: (target:Attachable), (pid:INT), (spec:STRING)
|
||||
*/
|
||||
public static final ActionName ATTACH = new ActionName("attach");
|
||||
public static final ActionName DETACH = new ActionName("detach");
|
||||
public static final ActionName ATTACH = extended("attach");
|
||||
public static final ActionName DETACH = extended("detach");
|
||||
|
||||
/**
|
||||
* Forms: (command_line:STRING), (file:STRING,args:STRING), (file:STRING,args:STRING_ARRAY),
|
||||
* (ANY*)
|
||||
*/
|
||||
public static final ActionName LAUNCH = new ActionName("launch");
|
||||
public static final ActionName KILL = new ActionName("kill");
|
||||
public static final ActionName LAUNCH = extended("launch");
|
||||
public static final ActionName KILL = builtIn("kill");
|
||||
|
||||
public static final ActionName RESUME = new ActionName("resume");
|
||||
public static final ActionName INTERRUPT = new ActionName("interrupt");
|
||||
public static final ActionName RESUME = builtIn("resume");
|
||||
public static final ActionName INTERRUPT = builtIn("interrupt");
|
||||
|
||||
/**
|
||||
* All of these will show in the "step" portion of the control toolbar, if present. The
|
||||
|
@ -93,25 +124,25 @@ public record ActionName(String name) {
|
|||
* 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 = new ActionName("step_into");
|
||||
public static final ActionName STEP_OVER = new ActionName("step_over");
|
||||
public static final ActionName STEP_OUT = new ActionName("step_out");
|
||||
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");
|
||||
/**
|
||||
* 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 = new ActionName("step_skip");
|
||||
public static final ActionName STEP_SKIP = builtIn("step_skip");
|
||||
/**
|
||||
* Step back is not typically available, except in emulators and timeless (or time-travel)
|
||||
* debuggers.
|
||||
*/
|
||||
public static final ActionName STEP_BACK = new ActionName("step_back");
|
||||
public static final ActionName STEP_BACK = builtIn("step_back");
|
||||
/**
|
||||
* The action for steps that don't fit one of the common stepping actions.
|
||||
*/
|
||||
public static final ActionName STEP_EXT = new ActionName("step_ext");
|
||||
public static final ActionName STEP_EXT = extended("step_ext");
|
||||
|
||||
/**
|
||||
* Forms: (addr:ADDRESS), R/W(rng:RANGE), (expr:STRING)
|
||||
|
@ -123,25 +154,25 @@ public record ActionName(String name) {
|
|||
* 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 = new ActionName("break_sw_execute");
|
||||
public static final ActionName BREAK_HW_EXECUTE = new ActionName("break_hw_execute");
|
||||
public static final ActionName BREAK_READ = new ActionName("break_read");
|
||||
public static final ActionName BREAK_WRITE = new ActionName("break_write");
|
||||
public static final ActionName BREAK_ACCESS = new ActionName("break_access");
|
||||
public static final ActionName BREAK_EXT = new ActionName("break_ext");
|
||||
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");
|
||||
|
||||
/**
|
||||
* Forms: (rng:RANGE)
|
||||
*/
|
||||
public static final ActionName READ_MEM = new ActionName("read_mem");
|
||||
public static final ActionName READ_MEM = builtIn("read_mem");
|
||||
/**
|
||||
* Forms: (addr:ADDRESS,data:BYTES)
|
||||
*/
|
||||
public static final ActionName WRITE_MEM = new ActionName("write_mem");
|
||||
public static final ActionName WRITE_MEM = builtIn("write_mem");
|
||||
|
||||
// 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 = new ActionName("write_reg");
|
||||
public static final ActionName WRITE_REG = builtIn("write_reg");
|
||||
}
|
||||
|
|
|
@ -19,14 +19,11 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import java.util.function.Function;
|
||||
|
||||
import docking.ActionContext;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
|
@ -38,39 +35,81 @@ import ghidra.trace.model.memory.TraceMemoryState;
|
|||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* The interface between the front-end UI and the back-end connector.
|
||||
*
|
||||
* <p>
|
||||
* Anything the UI might command a target to do must be defined as a method here. Each
|
||||
* implementation can then sort out, using context from the UI as appropriate, how best to effect
|
||||
* the command using the protocol and resources available on the back-end.
|
||||
*/
|
||||
public interface Target {
|
||||
long TIMEOUT_MILLIS = 10000;
|
||||
|
||||
/**
|
||||
* A description of a UI action provided by this target.
|
||||
*
|
||||
* <p>
|
||||
* In most cases, this will generate a menu entry or a toolbar button, but in some cases, it's
|
||||
* 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 enabled a supplier to determine whether an associated action in the UI is enabled.
|
||||
* @param action a function for invoking this action asynchronously
|
||||
*/
|
||||
record ActionEntry(String display, ActionName name, String details, boolean requiresPrompt,
|
||||
BooleanSupplier enabled, Supplier<CompletableFuture<?>> action) {
|
||||
BooleanSupplier enabled, Function<Boolean, CompletableFuture<?>> action) {
|
||||
|
||||
/**
|
||||
* Check if this action is currently enabled
|
||||
*
|
||||
* @return true if enabled
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled.getAsBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the action asynchronously, prompting if desired
|
||||
*
|
||||
* <p>
|
||||
* Note this will impose a timeout of {@value Target#TIMEOUT_MILLIS} milliseconds.
|
||||
*
|
||||
* @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.get().orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public CompletableFuture<?> invokeAsyncLogged(boolean prompt, PluginTool tool) {
|
||||
return invokeAsync(prompt).exceptionally(ex -> {
|
||||
if (tool != null) {
|
||||
tool.setStatusInfo(display + " failed: " + ex, true);
|
||||
}
|
||||
Msg.error(this, display + " failed: " + ex, ex);
|
||||
return ExceptionUtils.rethrow(ex);
|
||||
});
|
||||
return action.apply(prompt).orTimeout(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the action synchronously
|
||||
*
|
||||
* <p>
|
||||
* To avoid blocking the Swing thread on a remote socket, this method cannot be called on
|
||||
* the Swing thread.
|
||||
*
|
||||
* @param prompt whether or not to prompt the user for arguments
|
||||
*/
|
||||
public void run(boolean prompt) {
|
||||
get(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the action synchronously, getting its result
|
||||
*
|
||||
* @param prompt whether or not to prompt the user for arguments
|
||||
* @return the resulting value, if applicable
|
||||
*/
|
||||
public Object get(boolean prompt) {
|
||||
if (Swing.isSwingThread()) {
|
||||
throw new AssertionError("Refusing to block the Swing thread. Use a Task.");
|
||||
|
@ -82,30 +121,107 @@ public interface Target {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this action's name is built in
|
||||
*
|
||||
* @return true if built in.
|
||||
*/
|
||||
public boolean builtIn() {
|
||||
return name != null && name.builtIn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the target is still valid
|
||||
*
|
||||
* @return true if valid
|
||||
*/
|
||||
boolean isValid();
|
||||
|
||||
/**
|
||||
* Get the trace into which this target is recorded
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
Trace getTrace();
|
||||
|
||||
/**
|
||||
* Get the current snapshot key for the target
|
||||
*
|
||||
* <p>
|
||||
* For most targets, this is the most recently created snapshot.
|
||||
*
|
||||
* @return the snapshot
|
||||
*/
|
||||
// TODO: Should this be TraceSchedule getTime()?
|
||||
long getSnap();
|
||||
|
||||
/**
|
||||
* Collect all actions that implement the given common debugger command
|
||||
*
|
||||
* @param name the action name
|
||||
* @param context applicable context from the UI
|
||||
* @return the collected actions
|
||||
*/
|
||||
Map<String, ActionEntry> collectActions(ActionName name, ActionContext context);
|
||||
|
||||
/**
|
||||
* Get the trace thread that contains the given object
|
||||
*
|
||||
* @param path the path of the object
|
||||
* @return the thread, or null
|
||||
*/
|
||||
TraceThread getThreadForSuccessor(TraceObjectKeyPath path);
|
||||
|
||||
/**
|
||||
* Get the execution state of the given thread
|
||||
*
|
||||
* @param thread the thread
|
||||
* @return the state
|
||||
*/
|
||||
TargetExecutionState getThreadExecutionState(TraceThread thread);
|
||||
|
||||
/**
|
||||
* Get the trace stack frame that contains the given object
|
||||
*
|
||||
* @param path the path of the object
|
||||
* @return the stack frame, or null
|
||||
*/
|
||||
TraceStackFrame getStackFrameForSuccessor(TraceObjectKeyPath path);
|
||||
|
||||
/**
|
||||
* Check if the target supports synchronizing focus
|
||||
*
|
||||
* @return true if supported
|
||||
*/
|
||||
boolean isSupportsFocus();
|
||||
|
||||
/**
|
||||
* Get the object that currently has focus on the back end's UI
|
||||
*
|
||||
* @return the focused object's path, or null
|
||||
*/
|
||||
TraceObjectKeyPath getFocus();
|
||||
|
||||
/**
|
||||
* @see #activate(DebuggerCoordinates, DebuggerCoordinates)
|
||||
*/
|
||||
CompletableFuture<Void> activateAsync(DebuggerCoordinates prev, DebuggerCoordinates coords);
|
||||
|
||||
/**
|
||||
* Request that the back end's focus be set to the same as the front end's (Ghidra's) GUI.
|
||||
*
|
||||
* @param prev the GUI's immediately previous coordinates
|
||||
* @param coords the GUI's current coordinates
|
||||
*/
|
||||
void activate(DebuggerCoordinates prev, DebuggerCoordinates coords);
|
||||
|
||||
/**
|
||||
* @see #invalidateMemoryCaches()
|
||||
*/
|
||||
CompletableFuture<Void> invalidateMemoryCachesAsync();
|
||||
|
||||
/**
|
||||
* Invalidate any caches on the target's back end or on the client side of the connection.
|
||||
*
|
||||
|
@ -118,11 +234,6 @@ public interface Target {
|
|||
* <b>NOTE:</b> This method exists for invalidating model-based target caches. It may be
|
||||
* deprecated and removed, unless it turns out we need this for Trace RMI, too.
|
||||
*/
|
||||
CompletableFuture<Void> invalidateMemoryCachesAsync();
|
||||
|
||||
/**
|
||||
* See {@link #invalidateMemoryCachesAsync()}
|
||||
*/
|
||||
void invalidateMemoryCaches();
|
||||
|
||||
/**
|
||||
|
@ -135,11 +246,11 @@ public interface Target {
|
|||
*
|
||||
* <p>
|
||||
* The target may read more than the requested memory, usually because it will read all pages
|
||||
* containing any portion of the requested set.
|
||||
*
|
||||
* <p>
|
||||
* This task is relatively error tolerant. If a range cannot be captured -- a common occurrence
|
||||
* -- the error is logged without throwing an exception.
|
||||
* containing any portion of the requested set. The target should attempt to read at least the
|
||||
* given memory. To the extent it is successful, it must cause the values to be recorded into
|
||||
* the trace <em>before</em> this method returns. Only if the request is <em>entirely</em>
|
||||
* unsuccessful should this method throw an exception. Otherwise, the failed portions, if any,
|
||||
* should be logged without throwing an exception.
|
||||
*
|
||||
* @param set the addresses to capture
|
||||
* @param monitor a monitor for displaying task steps
|
||||
|
@ -147,30 +258,97 @@ public interface Target {
|
|||
*/
|
||||
void readMemory(AddressSetView set, TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
/**
|
||||
* @see #readMemory(AddressSetView, TaskMonitor)
|
||||
*/
|
||||
CompletableFuture<Void> writeMemoryAsync(Address address, byte[] data);
|
||||
|
||||
/**
|
||||
* Write data to the target's memory
|
||||
*
|
||||
* <p>
|
||||
* The target should attempt to write the memory. To the extent it is successful, it must cause
|
||||
* the effects to be recorded into the trace <em>before</em> this method returns. Only if the
|
||||
* request is <em>entirely</em> unsuccessful should this method throw an exception. Otherwise,
|
||||
* the failed portions, if any, should be logged without throwing an exception.
|
||||
*
|
||||
* @param address the starting address
|
||||
* @param data the bytes to write
|
||||
*/
|
||||
void writeMemory(Address address, byte[] data);
|
||||
|
||||
/**
|
||||
* @see #readRegisters(TracePlatform, TraceThread, int, Set)
|
||||
*/
|
||||
CompletableFuture<Void> readRegistersAsync(TracePlatform platform, TraceThread thread,
|
||||
int frame, Set<Register> registers);
|
||||
|
||||
/**
|
||||
* Read and capture the named target registers for the given platform, thread, and frame.
|
||||
*
|
||||
* <p>
|
||||
* Target target should read the registers and, to the extent it is successful, cause the values
|
||||
* to be recorded into the trace <em>before</em> this method returns. Only if the request is
|
||||
* <em>entirely</em> unsuccessful should this method throw an exception. Otherwise, the failed
|
||||
* registers, if any, should be logged without throwing an exception.
|
||||
*
|
||||
* @param platform the platform defining the registers
|
||||
* @param thread the thread whose context contains the register values
|
||||
* @param frame the frame, if applicable, for saved register values. 0 for current values.
|
||||
* @param registers the registers to read
|
||||
*/
|
||||
void readRegisters(TracePlatform platform, TraceThread thread, int frame,
|
||||
Set<Register> registers);
|
||||
|
||||
/**
|
||||
* @see #readRegistersAsync(TracePlatform, TraceThread, int, AddressSetView)
|
||||
*/
|
||||
CompletableFuture<Void> readRegistersAsync(TracePlatform platform, TraceThread thread,
|
||||
int frame, AddressSetView guestSet);
|
||||
|
||||
/**
|
||||
* Read and capture the target registers in the given address set.
|
||||
*
|
||||
* <p>
|
||||
* Aside from how registers are named, this works equivalently to
|
||||
* {@link #readRegisters(TracePlatform, TraceThread, int, Set)}.
|
||||
*/
|
||||
void readRegisters(TracePlatform platform, TraceThread thread, int frame,
|
||||
AddressSetView guestSet);
|
||||
|
||||
/**
|
||||
* @see #writeRegister(TracePlatform, TraceThread, int, RegisterValue)
|
||||
*/
|
||||
CompletableFuture<Void> writeRegisterAsync(TracePlatform platform, TraceThread thread,
|
||||
int frame, RegisterValue value);
|
||||
|
||||
/**
|
||||
* Write a value to a target register for the given platform, thread, and frame
|
||||
*
|
||||
* <p>
|
||||
* The target should attempt to write the register. If successful, it must cause the effects to
|
||||
* be recorded into the trace <em>before</em> this method returns. If the request is
|
||||
* unsuccessful, this method throw an exception.
|
||||
*
|
||||
* @param address the starting address
|
||||
* @param data the bytes to write
|
||||
*/
|
||||
void writeRegister(TracePlatform platform, TraceThread thread, int frame, RegisterValue value);
|
||||
|
||||
/**
|
||||
* @see #writeRegister(TracePlatform, TraceThread, int, Address, byte[])
|
||||
*/
|
||||
CompletableFuture<Void> writeRegisterAsync(TracePlatform platform, TraceThread thread,
|
||||
int frame, Address address, byte[] data);
|
||||
|
||||
/**
|
||||
* Write a value to a target register by its address
|
||||
*
|
||||
* <p>
|
||||
* Aside from how the register is named, this works equivalently to
|
||||
* {@link #writeRegister(TracePlatform, TraceThread, int, RegisterValue)}. The address is the
|
||||
* one defined by Ghidra.
|
||||
*/
|
||||
void writeRegister(TracePlatform platform, TraceThread thread, int frame, Address address,
|
||||
byte[] data);
|
||||
|
||||
|
@ -179,7 +357,7 @@ public interface Target {
|
|||
*
|
||||
* @param platform the platform whose language defines the registers
|
||||
* @param thread if a register, the thread whose registers to examine
|
||||
* @param frameLevel the frame, usually 0.
|
||||
* @param frame the frame level, usually 0.
|
||||
* @param address the address of the variable
|
||||
* @param size the size of the variable. Ignored for memory
|
||||
* @return true if the variable can be mapped to the target
|
||||
|
@ -211,11 +389,35 @@ public interface Target {
|
|||
void writeVariable(TracePlatform platform, TraceThread thread, int frame, Address address,
|
||||
byte[] data);
|
||||
|
||||
/**
|
||||
* Get the kinds of breakpoints supported by the target.
|
||||
*
|
||||
* @return the set of kinds
|
||||
*/
|
||||
Set<TraceBreakpointKind> getSupportedBreakpointKinds();
|
||||
|
||||
/**
|
||||
* @see #placeBreakpoint(AddressRange, Set, String, String)
|
||||
*/
|
||||
CompletableFuture<Void> placeBreakpointAsync(AddressRange range,
|
||||
Set<TraceBreakpointKind> kinds, String condition, String commands);
|
||||
|
||||
/**
|
||||
* Place a new breakpoint of the given kind(s) over the given range
|
||||
*
|
||||
* <p>
|
||||
* If successful, this method must cause the breakpoint to be recorded into the trace.
|
||||
* Otherwise, it should throw an exception.
|
||||
*
|
||||
* @param range the range. NOTE: The target is only required to support length-1 execution
|
||||
* breakpoints.
|
||||
* @param kinds the kind(s) of the breakpoint.
|
||||
* @param condition optionally, a condition for the breakpoint, expressed in the back-end's
|
||||
* language. NOTE: May be silently ignored by the implementation, if not supported.
|
||||
* @param commands optionally, a command to execute upon hitting the breakpoint, expressed in
|
||||
* the back-end's language. NOTE: May be silently ignored by the implementation, if
|
||||
* not supported.
|
||||
*/
|
||||
void placeBreakpoint(AddressRange range, Set<TraceBreakpointKind> kinds, String condition,
|
||||
String commands);
|
||||
|
||||
|
@ -227,14 +429,61 @@ public interface Target {
|
|||
*/
|
||||
boolean isBreakpointValid(TraceBreakpoint breakpoint);
|
||||
|
||||
/**
|
||||
* @see #deleteBreakpoint(TraceBreakpoint)
|
||||
*/
|
||||
CompletableFuture<Void> deleteBreakpointAsync(TraceBreakpoint breakpoint);
|
||||
|
||||
/**
|
||||
* Delete the given breakpoint from the target
|
||||
*
|
||||
* <p>
|
||||
* If successful, this method must cause the breakpoint removal to be recorded in the trace.
|
||||
* Otherwise, it should throw an exception.
|
||||
*
|
||||
* @param breakpoint the breakpoint to delete
|
||||
*/
|
||||
void deleteBreakpoint(TraceBreakpoint breakpoint);
|
||||
|
||||
/**
|
||||
* @see #toggleBreakpoint(TraceBreakpoint, boolean)
|
||||
*/
|
||||
CompletableFuture<Void> toggleBreakpointAsync(TraceBreakpoint breakpoint, boolean enabled);
|
||||
|
||||
/**
|
||||
* Toggle the given breakpoint on the target
|
||||
*
|
||||
* <p>
|
||||
* If successful, this method must cause the breakpoint toggle to be recorded in the trace. If
|
||||
* the state is already as desired, this method may have no effect. If unsuccessful, this method
|
||||
* should throw an exception.
|
||||
*
|
||||
* @param breakpoint the breakpoint to toggle
|
||||
* @param enabled true to enable, false to disable
|
||||
*/
|
||||
void toggleBreakpoint(TraceBreakpoint breakpoint, boolean enabled);
|
||||
|
||||
/**
|
||||
* @see #forceTerminate()
|
||||
*/
|
||||
CompletableFuture<Void> forceTerminateAsync();
|
||||
|
||||
/**
|
||||
* Forcefully terminate the target
|
||||
*
|
||||
* <p>
|
||||
* This will first attempt to kill the target gracefully. In addition, and whether or not the
|
||||
* target is successfully terminated, the target will be dissociated from its trace, and the
|
||||
* target will be invalidated. To attempt only a graceful termination, check
|
||||
* {@link #collectActions(ActionName, ActionContext)} with {@link ActionName#KILL}.
|
||||
*/
|
||||
void forceTerminate();
|
||||
|
||||
/**
|
||||
* @see #disconnect()
|
||||
*/
|
||||
CompletableFuture<Void> disconnectAsync();
|
||||
|
||||
/**
|
||||
* Terminate the target and its connection
|
||||
*
|
||||
|
@ -244,13 +493,6 @@ public interface Target {
|
|||
* the debugger is configured to remain attached to both. Whether this is expected or acceptable
|
||||
* behavior has not been decided.
|
||||
*
|
||||
* @see #disconnect()
|
||||
*/
|
||||
CompletableFuture<Void> disconnectAsync();
|
||||
|
||||
/**
|
||||
* Terminate the target and its connection
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This method cannot be invoked on the Swing thread, because it may block on I/O.
|
||||
*
|
||||
|
|
|
@ -15,8 +15,21 @@
|
|||
*/
|
||||
package ghidra.debug.api.target;
|
||||
|
||||
/**
|
||||
* A listener for changes to the set of published targets
|
||||
*/
|
||||
public interface TargetPublicationListener {
|
||||
/**
|
||||
* The given target was published
|
||||
*
|
||||
* @param target the published target
|
||||
*/
|
||||
void targetPublished(Target target);
|
||||
|
||||
/**
|
||||
* The given target was withdrawn, usually because it's no longer valid
|
||||
*
|
||||
* @param target the withdrawn target
|
||||
*/
|
||||
void targetWithdrawn(Target target);
|
||||
}
|
||||
|
|
|
@ -658,13 +658,12 @@ public class DebuggerCoordinates {
|
|||
projData = new DefaultProjectData(projLoc, false, false);
|
||||
}
|
||||
catch (NotOwnerException e) {
|
||||
Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed",
|
||||
Msg.error(DebuggerCoordinates.class,
|
||||
"Not project owner: " + projLoc + "(" + pathname + ")");
|
||||
return null;
|
||||
}
|
||||
catch (IOException | LockException e) {
|
||||
Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed",
|
||||
"Project error: " + e.getMessage());
|
||||
Msg.error(DebuggerCoordinates.class, "Project error: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -676,8 +675,7 @@ public class DebuggerCoordinates {
|
|||
if (version != DomainFile.DEFAULT_VERSION) {
|
||||
message += " version " + version;
|
||||
}
|
||||
String title = df == null ? "Trace Not Found" : "Wrong File Type";
|
||||
Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), title, message);
|
||||
Msg.error(DebuggerCoordinates.class, message);
|
||||
return null;
|
||||
}
|
||||
return df;
|
||||
|
|
|
@ -13,21 +13,33 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.services;
|
||||
package ghidra.debug.api.tracermi;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
/**
|
||||
* A terminal with some back-end element attached to it
|
||||
*/
|
||||
public interface TerminalSession extends AutoCloseable {
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
|
||||
@ServiceInfo(
|
||||
defaultProviderName = "ghidra.app.plugin.core.debug.service.workflow.DebuggerWorkflowServicePlugin",
|
||||
description = "Service for managing automatic debugger actions and analysis")
|
||||
public interface DebuggerWorkflowFrontEndService extends DebuggerWorkflowService {
|
||||
/**
|
||||
* Get all the tools with the corresponding {@link DebuggerWorkflowToolService}
|
||||
*
|
||||
* @return the tools proxying this service
|
||||
* Terminate the session without closing the terminal
|
||||
*/
|
||||
Collection<PluginTool> getProxyingPluginTools();
|
||||
void terminate() throws IOException;
|
||||
|
||||
/**
|
||||
* Check whether the terminal session is terminated or still active
|
||||
*
|
||||
* @return true for terminated, false for active
|
||||
*/
|
||||
boolean isTerminated();
|
||||
|
||||
/**
|
||||
* Provide a human-readable description of the session
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
String description();
|
||||
}
|
|
@ -17,23 +17,124 @@ package ghidra.debug.api.tracermi;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
/**
|
||||
* A connection to a TraceRmi back end
|
||||
*
|
||||
* <p>
|
||||
* TraceRmi is a two-way request-reply channel, usually over TCP. The back end, i.e., the trace-rmi
|
||||
* plugin hosted in the target platform's actual debugger, is granted a fixed set of
|
||||
* methods/messages for creating and populating a {@link Trace}. Each such trace is designated as a
|
||||
* target. The back end provides a set of methods for the front-end to use to control the connection
|
||||
* and its targets. For a given connection, the methods are fixed, but each back end may provide a
|
||||
* different set of methods to best describe/model its command set. The same methods are applicable
|
||||
* to all of the back end's target. While uncommon, one back end may create several targets. E.g.,
|
||||
* if a target creates a child process, and the back-end debugger is configured to remain attached
|
||||
* to both parent and child, then it should create and publish a second target.
|
||||
*/
|
||||
public interface TraceRmiConnection extends AutoCloseable {
|
||||
/**
|
||||
* Get the address of the back end debugger
|
||||
*
|
||||
* @return the address, usually IP of the host and port for the trace-rmi plugin.
|
||||
*/
|
||||
SocketAddress getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Get the methods provided by the back end
|
||||
*
|
||||
* @return the method registry
|
||||
*/
|
||||
RemoteMethodRegistry getMethods();
|
||||
|
||||
/**
|
||||
* Wait for the first trace created by the back end.
|
||||
*
|
||||
* <p>
|
||||
* Typically, a connection handles only a single target. A shell script handles launching the
|
||||
* back-end debugger, creating its first target, and connecting back to the front end via
|
||||
* TraceRmi. If a secondary target does appear, it usually happens only after the initial target
|
||||
* has run. Thus, this method is useful for waiting on and getting and handle to that initial
|
||||
* target.
|
||||
*
|
||||
* @param timeoutMillis the number of milliseconds to wait for the target
|
||||
* @return the trace
|
||||
* @throws TimeoutException if no trace is created after the given timeout. This usually
|
||||
* indicates there was an error launching the initial target, e.g., the target's
|
||||
* binary was not found on the target's host.
|
||||
*/
|
||||
Trace waitForTrace(long timeoutMillis) throws TimeoutException;
|
||||
|
||||
/**
|
||||
* Get the last snapshot created by the back end for the given trace.
|
||||
*
|
||||
* <p>
|
||||
* Back ends that support timeless or time-travel debugging have not been integrated yet, but in
|
||||
* those cases, we anticipate this method returning the current snapshot (however the back end
|
||||
* defines that with respect to its own definition of time), whether or not it is the last
|
||||
* snapshot it created. If the back end has not created a snapshot yet, 0 is returned.
|
||||
*
|
||||
* @param trace
|
||||
* @return the snapshot number
|
||||
* @throws NoSuchElementException if the given trace is not a target for this connection
|
||||
*/
|
||||
long getLastSnapshot(Trace trace);
|
||||
|
||||
/**
|
||||
* Forcefully remove the given trace from the connection.
|
||||
*
|
||||
* <p>
|
||||
* This removes the back end's access to the given trace and removes this connection from the
|
||||
* trace's list of consumers (thus, freeing it if this was the only remaining consumer.) For all
|
||||
* intents and purposes, the given trace is no longer a target for this connection.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This method should only be used if gracefully killing the target has failed. In
|
||||
* some cases, it may be better to terminate the entire connection (See {@link #close()}) or to
|
||||
* terminate the back end debugger. The back end gets no notification that its trace was
|
||||
* forcefully removed. However, subsequent requests involving that trace will result in errors.
|
||||
*
|
||||
* @param trace the trace to remove
|
||||
*/
|
||||
void forceCloseTrace(Trace trace);
|
||||
|
||||
/**
|
||||
* Close the TraceRmi connection.
|
||||
*
|
||||
* <p>
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* Upon closing, all the connection's targets (there's usually only one) will be withdrawn and
|
||||
* invalidated.
|
||||
*/
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Check if the connection has been closed
|
||||
*
|
||||
* @return true if closed, false if still open/valid
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
/**
|
||||
* Wait for the connection to become closed.
|
||||
*
|
||||
* <p>
|
||||
* This is usually just for clean-up purposes during automated testing.
|
||||
*/
|
||||
void waitClosed();
|
||||
|
||||
/**
|
||||
* Check if the given trace represents one of this connection's targets.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @return true if the trace is a target, false otherwise.
|
||||
*/
|
||||
boolean isTarget(Trace trace);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package ghidra.debug.api.tracermi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -37,19 +36,6 @@ import ghidra.util.task.TaskMonitor;
|
|||
*/
|
||||
public interface TraceRmiLaunchOffer {
|
||||
|
||||
/**
|
||||
* A terminal with some back-end element attached to it
|
||||
*/
|
||||
interface TerminalSession extends AutoCloseable {
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* Terminate the session without closing the terminal
|
||||
*/
|
||||
void terminate() throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of launching a program
|
||||
*
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
/* ###
|
||||
* 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.debug.api.tracermi;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* A factory of launch offers
|
||||
*
|
||||
* <p>
|
||||
* Each factory is instantiated only once for the entire application, even when multiple tools are
|
||||
* open. Thus, {@link #init(PluginTool)} and {@link #dispose(PluginTool)} will be invoked for each
|
||||
* tool.
|
||||
*/
|
||||
public interface TraceRmiLaunchOpinion extends ExtensionPoint {
|
||||
/**
|
||||
* Register any options
|
||||
*
|
||||
* @param tool the tool
|
||||
*/
|
||||
default void registerOptions(Options options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a change in the given option requires a refresh of offers
|
||||
*
|
||||
* @param optionName the name of the option that changed
|
||||
* @return true to refresh, false otherwise
|
||||
*/
|
||||
default boolean requiresRefresh(String optionName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate or retrieve a collection of offers based on the current program.
|
||||
*
|
||||
* <p>
|
||||
* Take care trying to "validate" a particular mechanism. For example, it is <em>not</em>
|
||||
* appropriate to check that GDB exists, nor to execute it to derive its version.
|
||||
*
|
||||
* <ol>
|
||||
* <li>It's possible the user has dependencies installed in non-standard locations. I.e., the
|
||||
* user needs a chance to configure things <em>before</em> the UI decides whether or not to
|
||||
* display them.</li>
|
||||
* <li>The menus are meant to display <em>all</em> possibilities installed in Ghidra, even if
|
||||
* some dependencies are missing on the local system. Discovery of the feature is most
|
||||
* important. Knowing a feature exists may motivate a user to obtain the required dependencies
|
||||
* and try it out.</li>
|
||||
* <li>An offer is only promoted to the quick-launch menu upon <em>successful</em> connection.
|
||||
* I.e., the entries there are already validated; they've worked at least once before.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param program the current program. While this is not <em>always</em> used by the launcher,
|
||||
* it is implied that the user expects the debugger to do something with the current
|
||||
* program, even if it's just informing the back-end debugger of the target image.
|
||||
* @param tool the current tool for context and services
|
||||
* @return the offers. The order is not important since items are displayed alphabetically.
|
||||
*/
|
||||
public Collection<TraceRmiLaunchOffer> getOffers(Program program, PluginTool tool);
|
||||
}
|
|
@ -1,221 +0,0 @@
|
|||
/* ###
|
||||
* 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.debug.api.workflow;
|
||||
|
||||
import ghidra.app.services.DebuggerWorkflowFrontEndService;
|
||||
import ghidra.framework.options.AutoOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* A bot (or analyzer) that aids the user in the debugging workflow
|
||||
*
|
||||
* <p>
|
||||
* These are a sort of miniature front-end plugin (TODO: consider tool-only bots) with a number of
|
||||
* conveniences allowing the specification of automatic actions taken under given circumstances,
|
||||
* e.g., "Open the interpreter for new debugger connections." Such actions may include analysis of
|
||||
* open traces, e.g., "Disassemble memory at the Program Counter."
|
||||
*
|
||||
* <p>
|
||||
* Bots which react to target state changes should take care to act quickly in most, if not all,
|
||||
* circumstances. Otherwise, the UI could become sluggish. It is vitally important that the UI not
|
||||
* become sluggish when the user is stepping a target. Bots should also be wary of prompts. If too
|
||||
* many bots are prompting the user for input, they may collectively become a source of extreme
|
||||
* annoyance. In most cases, the bot should use its best judgment and just perform the action, so
|
||||
* long as it's not potentially destructive. That way, the user can undo the action and/or disable
|
||||
* the bot. For cases where the bot, in its best judgment, cannot make a decision, it's probably
|
||||
* best to simply log an informational message and do nothing. There are exceptions, just consider
|
||||
* them carefully, and be mindful of prompting the user unexpectedly or incessantly.
|
||||
*/
|
||||
public interface DebuggerBot extends ExtensionPoint {
|
||||
|
||||
/**
|
||||
* Log a missing-info-annotation error
|
||||
*
|
||||
* @param cls the bot's class missing the annotation
|
||||
* @param methodName the name of the method requesting the info
|
||||
*/
|
||||
@Internal
|
||||
static void noAnnot(Class<?> cls, String methodName) {
|
||||
Msg.error(DebuggerBot.class, "Debugger bot " + cls + " must apply @" +
|
||||
DebuggerBotInfo.class.getSimpleName() + " or override getDescription()");
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for obtaining and bot's info annotation
|
||||
*
|
||||
* <p>
|
||||
* If the annotation is not present, an error is logged for the developer's sake.
|
||||
*
|
||||
* @param cls the bot's class
|
||||
* @param methodName the name of the method requesting the info, for error-reporting purposes
|
||||
* @return the annotation, or {@code null}
|
||||
*/
|
||||
@Internal
|
||||
static DebuggerBotInfo getInfo(Class<?> cls, String methodName) {
|
||||
DebuggerBotInfo info = cls.getAnnotation(DebuggerBotInfo.class);
|
||||
if (info == null) {
|
||||
noAnnot(cls, methodName);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a description of the bot
|
||||
*
|
||||
* @see DebuggerBotInfo#description()
|
||||
* @return the description
|
||||
*/
|
||||
default String getDescription() {
|
||||
DebuggerBotInfo info = getInfo(getClass(), "getDescription");
|
||||
if (info == null) {
|
||||
return "<NO DESCRIPTION>";
|
||||
}
|
||||
return info.description();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a detailed description of the bot
|
||||
*
|
||||
* @see DebuggerBotInfo#details()
|
||||
* @return the details
|
||||
*/
|
||||
default String getDetails() {
|
||||
DebuggerBotInfo info = getInfo(getClass(), "getDetails");
|
||||
if (info == null) {
|
||||
return "";
|
||||
}
|
||||
return info.details();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the help location for information about the bot
|
||||
*
|
||||
* @see DebuggerBotInfo#help()
|
||||
* @return the help location
|
||||
*/
|
||||
default HelpLocation getHelpLocation() {
|
||||
DebuggerBotInfo info = getInfo(getClass(), "getHelpLocation");
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
return AutoOptions.getHelpLocation("DebuggerBots", info.help());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this bot is enabled by default
|
||||
*
|
||||
* <p>
|
||||
* Assuming the user has never configured this bot before, determine whether it should be
|
||||
* enabled.
|
||||
*
|
||||
* @return true if enabled by default, false otherwise
|
||||
*/
|
||||
default boolean isEnabledByDefault() {
|
||||
DebuggerBotInfo info = getInfo(getClass(), "isEnabledByDefault");
|
||||
if (info == null) {
|
||||
return false;
|
||||
}
|
||||
return info.enabledByDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this bot is enabled
|
||||
*
|
||||
* @return true if enabled, false otherwise
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Enable or disable the bot
|
||||
*
|
||||
* <p>
|
||||
* If {@link #isEnabled()} is already equal to the given -enabled- value, this method has no
|
||||
* effect.
|
||||
*
|
||||
* @param service the front-end service, required if -enabled- is set
|
||||
* @param enabled true to enable, false to disable
|
||||
*/
|
||||
default void setEnabled(DebuggerWorkflowFrontEndService service, boolean enabled) {
|
||||
if (isEnabled() == enabled) {
|
||||
return;
|
||||
}
|
||||
if (enabled) {
|
||||
enable(service);
|
||||
}
|
||||
else {
|
||||
disable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable and initialize the bot
|
||||
*
|
||||
* @param service the front-end service
|
||||
*/
|
||||
void enable(DebuggerWorkflowFrontEndService service);
|
||||
|
||||
/**
|
||||
* Disable and dispose the bot
|
||||
*
|
||||
* <p>
|
||||
* Note the bot must be prepared to be enabled again. In other words, it will not be
|
||||
* re-instantiated. It should return to the same state after construction but before being
|
||||
* enabled the first time.
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* A program has been opened in a tool
|
||||
*
|
||||
* @param tool the tool which opened the program
|
||||
* @param program the program that was opened
|
||||
*/
|
||||
default void programOpened(PluginTool tool, Program program) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A program has been closed in a tool
|
||||
*
|
||||
* @param tool the tool which closed the program
|
||||
* @param program the program that was closed
|
||||
*/
|
||||
default void programClosed(PluginTool tool, Program program) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A trace has been opened in a tool
|
||||
*
|
||||
* @param tool the tool which opened the trace
|
||||
* @param trace the trace that was opened
|
||||
*/
|
||||
default void traceOpened(PluginTool tool, Trace trace) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A trace has been closed in a tool
|
||||
*
|
||||
* @param tool the tool which closed the trace
|
||||
* @param trace the trace that was closed
|
||||
*/
|
||||
default void traceClosed(PluginTool tool, Trace trace) {
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
/* ###
|
||||
* 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.debug.api.workflow;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import ghidra.framework.options.annotation.HelpInfo;
|
||||
|
||||
/**
|
||||
* Required information annotation on {@link DebuggerBot}s
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface DebuggerBotInfo {
|
||||
/**
|
||||
* A quick one-line description of the actor
|
||||
*
|
||||
* This is used as the option name to enable and disable the actor, to please, keep it short.
|
||||
* Use {@link #details()} or {@link #help()} to provide more details.
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
String description();
|
||||
|
||||
/**
|
||||
* A longer description of this actor
|
||||
*
|
||||
* A one-to-three-sentence detailed description of the actor. Again, it should be relatively
|
||||
* short, as it used as the tool-tip popup in the plugin's options dialog. On some systems, such
|
||||
* tips only display for a short time.
|
||||
*
|
||||
* @return the detailed description
|
||||
*/
|
||||
String details();
|
||||
|
||||
/**
|
||||
* The location for help about this actor
|
||||
*
|
||||
* Help is the best place to put lengthy descriptions of the actor and/or describe the caveats
|
||||
* of using it. Since, in most cases, the actor is simply performing automatic actions, it is
|
||||
* useful to show the reader how to perform those same actions manually. This way, if/when the
|
||||
* actor takes an unreasonable action, the user can manually correct it.
|
||||
*
|
||||
* @return the link to detailed help about the actor
|
||||
*/
|
||||
HelpInfo help() default @HelpInfo(topic = {});
|
||||
|
||||
/**
|
||||
* Check whether the actor should be enabled by default
|
||||
*
|
||||
* For the stock plugin, a collection of actors should be enabled by default that make the
|
||||
* debugger most accessible, erring toward ease of use, rather than toward correctness. Advanced
|
||||
* users can always disable unwanted actors, tweak the options (TODO: Allow actors to present
|
||||
* additional options in the tool config), and/or write their own actors and scripts.
|
||||
*
|
||||
* For extensions, consider the user's expectations upon installing your extension. For example,
|
||||
* if the extension consists of just an actor and some supporting classes, it should probably be
|
||||
* enabled by default.
|
||||
*
|
||||
* @return true to enable by default, false to leave disabled by default
|
||||
*/
|
||||
boolean enabledByDefault() default false;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue