GP-3857: Port most Debugger components to TraceRmi.

This commit is contained in:
Dan 2023-11-02 10:43:31 -04:00
parent 7e4d2bcfaa
commit fd4380c07a
222 changed files with 7241 additions and 3752 deletions

View file

@ -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);
}

View file

@ -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 {
}

View file

@ -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
*

View file

@ -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();
}

View file

@ -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");
}

View file

@ -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.
*

View file

@ -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);
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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
*

View file

@ -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);
}

View file

@ -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) {
}
}

View file

@ -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;
}