GP-3818: Create TraceRMI launcher framework. Launch script for gdb.

This commit is contained in:
Dan 2023-09-20 15:17:37 -04:00
parent 4561e8335d
commit eea90f49c9
379 changed files with 5180 additions and 1487 deletions

View File

@ -24,28 +24,27 @@ import agent.frida.manager.FridaThread;
import agent.frida.manager.evt.FridaStateChangedEvent; import agent.frida.manager.evt.FridaStateChangedEvent;
import agent.frida.manager.evt.FridaThreadSelectedEvent; import agent.frida.manager.evt.FridaThreadSelectedEvent;
import agent.frida.manager.impl.FridaManagerImpl; import agent.frida.manager.impl.FridaManagerImpl;
import ghidra.app.plugin.core.debug.service.workflow.DebuggerWorkflowServicePlugin; import ghidra.app.services.DebuggerWorkflowFrontEndService;
import ghidra.app.services.DebuggerBot;
import ghidra.app.services.DebuggerBotInfo;
import ghidra.dbg.*; import ghidra.dbg.*;
import ghidra.dbg.error.DebuggerMemoryAccessException; import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetConsole.Channel; import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.DebuggerCallbackReorderer; import ghidra.dbg.util.DebuggerCallbackReorderer;
import ghidra.debug.api.workflow.DebuggerBot;
import ghidra.debug.api.workflow.DebuggerBotInfo;
import ghidra.framework.options.annotation.HelpInfo; import ghidra.framework.options.annotation.HelpInfo;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
import ghidra.util.datastruct.PrivatelyQueuedListener; import ghidra.util.datastruct.PrivatelyQueuedListener;
@DebuggerBotInfo( // @DebuggerBotInfo(
description = "Link debugger to Frida", // description = "Link debugger to Frida",
details = "Listens for debuggers to add state to Frida.", // details = "Listens for debuggers to add state to Frida.",
help = @HelpInfo(anchor = "link_frida"), // help = @HelpInfo(anchor = "link_frida"),
enabledByDefault = true // enabledByDefault = true)
)
public class FridaDebuggerBot implements DebuggerBot { public class FridaDebuggerBot implements DebuggerBot {
private DebuggerWorkflowServicePlugin plugin; private DebuggerWorkflowFrontEndService service;
private FridaObjectListener listener = new FridaObjectListener(); private FridaObjectListener listener = new FridaObjectListener();
private List<DebuggerObjectModel> models = new ArrayList<>(); private List<DebuggerObjectModel> models = new ArrayList<>();
@ -53,18 +52,18 @@ public class FridaDebuggerBot implements DebuggerBot {
private FridaManagerImpl manager; private FridaManagerImpl manager;
@Override @Override
public void enable(DebuggerWorkflowServicePlugin wp) { public void enable(DebuggerWorkflowFrontEndService service) {
this.plugin = wp; this.service = service;
} }
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return plugin != null; return service != null;
} }
@Override @Override
public void disable() { public void disable() {
plugin = null; service = null;
} }
@Override @Override
@ -73,8 +72,9 @@ public class FridaDebuggerBot implements DebuggerBot {
if (model instanceof FridaModelImpl) { if (model instanceof FridaModelImpl) {
primary = (FridaModelImpl) model; primary = (FridaModelImpl) model;
manager = primary.getManager(); manager = primary.getManager();
} else { }
model.addModelListener(getListener(), true); else {
model.addModelListener(getListener(), true);
} }
} }
@ -84,15 +84,16 @@ public class FridaDebuggerBot implements DebuggerBot {
if (model instanceof FridaModelImpl) { if (model instanceof FridaModelImpl) {
primary = null; primary = null;
manager = null; manager = null;
} else { }
model.removeModelListener(getListener()); else {
model.removeModelListener(getListener());
} }
} }
public DebuggerModelListener getListener() { public DebuggerModelListener getListener() {
return listener.queue.in; return listener.queue.in;
} }
private Object findMatchingObject(TargetObject object) { private Object findMatchingObject(TargetObject object) {
if (object instanceof TargetProcess) { if (object instanceof TargetProcess) {
String id = Long.toHexString(((TargetProcess) object).getPid()); String id = Long.toHexString(((TargetProcess) object).getPid());
@ -109,7 +110,7 @@ public class FridaDebuggerBot implements DebuggerBot {
} }
return null; return null;
} }
class FridaObjectListener extends AnnotatedDebuggerAttributeListener { class FridaObjectListener extends AnnotatedDebuggerAttributeListener {
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this); protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
protected final PrivatelyQueuedListener<DebuggerModelListener> queue = protected final PrivatelyQueuedListener<DebuggerModelListener> queue =
@ -158,7 +159,7 @@ public class FridaDebuggerBot implements DebuggerBot {
if (localObject != null) { if (localObject != null) {
FridaThreadInfo info = new FridaThreadInfo((FridaThread) localObject); FridaThreadInfo info = new FridaThreadInfo((FridaThread) localObject);
manager.processEvent(new FridaThreadSelectedEvent(info)); manager.processEvent(new FridaThreadSelectedEvent(info));
} }
} }
} }
} }

View File

@ -0,0 +1,58 @@
#!/usr/bin/bash
## ###
# 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.
##
#@title gdb
#@desc <html><body width="300px">
#@desc <h3>Launch with <tt>gdb</tt></h3>
#@desc <p>This will launch the target on the local machine using <tt>gdb</tt>. GDB must already
#@desc be installed on your system, and it must embed the Python 3 interpreter. You will also
#@desc need <tt>protobuf</tt> and <tt>psutil</tt> installed for Python 3.
#@desc </body></html>
#@menu-group local
#@icon icon.debugger
#@help TraceRmiLauncherServicePlugin#gdb
#@enum StartCmd:str run start starti
#@env OPT_GDB_PATH:str="gdb" "Path to gdb" "The path to gdb. Omit the full path to resolve using the system PATH."
#@env OPT_START_CMD:StartCmd="start" "Run command" "The gdb command to actually run the target."
#@arg :str "Image" "The target binary executable image"
#@args "Arguments" "Command-line arguments to pass to the target"
#@tty TTY_TARGET
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
then
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
elif [ -d ${GHIDRA_HOME}/.git ]
then
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
else
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
fi
target_image="$1"
shift
target_args="$@"
"$OPT_GDB_PATH" \
-ex "set pagination off" \
-ex "python import ghidragdb" \
-ex "file \"$target_image\"" \
-ex "set args $target_args" \
-ex "set inferior-tty $TTY_TARGET" \
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
-ex "ghidra trace start" \
-ex "ghidra trace sync-enable" \
-ex "$OPT_START_CMD" \
-ex "set pagination on"

View File

@ -24,7 +24,7 @@ class GhidraHookPrefix(gdb.Command):
"""Commands for exporting data to a Ghidra trace""" """Commands for exporting data to a Ghidra trace"""
def __init__(self): def __init__(self):
super().__init__('ghidra-hook', gdb.COMMAND_NONE, prefix=True) super().__init__('hooks-ghidra', gdb.COMMAND_NONE, prefix=True)
GhidraHookPrefix() GhidraHookPrefix()
@ -386,7 +386,7 @@ def on_before_prompt():
# This will be called by a catchpoint # This will be called by a catchpoint
class GhidraTraceEventMemoryCommand(gdb.Command): class GhidraTraceEventMemoryCommand(gdb.Command):
def __init__(self): def __init__(self):
super().__init__('ghidra-hook event-memory', gdb.COMMAND_NONE) super().__init__('hooks-ghidra event-memory', gdb.COMMAND_NONE)
def invoke(self, argument, from_tty): def invoke(self, argument, from_tty):
self.dont_repeat() self.dont_repeat()
@ -401,10 +401,10 @@ def cmd_hook(name):
class _ActiveCommand(gdb.Command): class _ActiveCommand(gdb.Command):
def __init__(self): def __init__(self):
# It seems we can't hook commands using the Python API.... # It seems we can't hook commands using the Python API....
super().__init__(f"ghidra-hook def-{name}", gdb.COMMAND_USER) super().__init__(f"hooks-ghidra def-{name}", gdb.COMMAND_USER)
gdb.execute(f""" gdb.execute(f"""
define {name} define {name}
ghidra-hook def-{name} hooks-ghidra def-{name}
end end
""") """)
@ -474,7 +474,7 @@ def install_hooks():
catch syscall group:memory catch syscall group:memory
commands commands
silent silent
ghidra-hook event-memory hooks-ghidra event-memory
cont cont
end end
""") """)

View File

@ -13,22 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.utils;
import java.awt.Component; apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
import java.awt.KeyboardFocusManager; apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
import javax.swing.SwingUtilities; apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger-api'
public interface FocusUtils { dependencies {
static boolean isFocusIn(Component c) { api project(':SoftwareModeling')
Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); api project(':Framework-TraceModeling')
if (c == owner) { api project(':Emulation')
return true;
}
if (owner == null) {
return false;
}
return SwingUtilities.isDescendingFrom(owner, c);
}
} }

View File

@ -0,0 +1,2 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|

View File

@ -21,12 +21,11 @@ import javax.swing.Icon;
import docking.ActionContext; import docking.ActionContext;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.dbg.DebuggerConsoleLogger; import ghidra.dbg.DebuggerConsoleLogger;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.util.HTMLUtilities; import ghidra.util.HTMLUtilities;
@ServiceInfo(defaultProvider = DebuggerConsolePlugin.class) @ServiceInfo(defaultProviderName = "ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin")
public interface DebuggerConsoleService extends DebuggerConsoleLogger { public interface DebuggerConsoleService extends DebuggerConsoleLogger {
/** /**

View File

@ -17,18 +17,19 @@ package ghidra.app.services;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.debug.api.control.ControlMode;
import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.mem.LiveMemoryHandler; import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo( @ServiceInfo(
defaultProvider = DebuggerControlServicePlugin.class, defaultProviderName = "ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin",
description = "Centralized service for modifying machine states") description = "Centralized service for modifying machine states")
public interface DebuggerControlService { public interface DebuggerControlService {
interface StateEditor { interface StateEditor {

View File

@ -18,7 +18,8 @@ package ghidra.app.services;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.emulation.*; import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory;
import ghidra.debug.api.emulation.DebuggerPcodeMachine;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.guest.TracePlatform;
@ -36,7 +37,8 @@ import ghidra.util.task.TaskMonitor;
* user. Scripts may interact with these managed emulators, or they may instantiate their own * user. Scripts may interact with these managed emulators, or they may instantiate their own
* unmanaged emulators, without using this service. * unmanaged emulators, without using this service.
*/ */
@ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class) @ServiceInfo(
defaultProviderName = "ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin")
public interface DebuggerEmulationService { public interface DebuggerEmulationService {
interface EmulationResult extends RunResult { interface EmulationResult extends RunResult {

View File

@ -15,16 +15,14 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import ghidra.app.plugin.core.debug.gui.interpreters.DebuggerInterpreterConnection;
import ghidra.app.plugin.core.debug.gui.interpreters.DebuggerInterpreterPlugin;
import ghidra.dbg.target.TargetConsole; import ghidra.dbg.target.TargetConsole;
import ghidra.dbg.target.TargetInterpreter; import ghidra.dbg.target.TargetInterpreter;
import ghidra.debug.api.interpreter.DebuggerInterpreterConnection;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
@ServiceInfo( // @ServiceInfo(
defaultProvider = DebuggerInterpreterPlugin.class, // defaultProviderName = "ghidra.app.plugin.core.debug.gui.interpreters.DebuggerInterpreterPlugin",
description = "Service for managing debugger interpreter panels" // description = "Service for managing debugger interpreter panels")
)
public interface DebuggerInterpreterService { public interface DebuggerInterpreterService {
DebuggerInterpreterConnection showConsole(TargetConsole console); DebuggerInterpreterConnection showConsole(TargetConsole console);

View File

@ -15,10 +15,9 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.debug.api.action.LocationTrackingSpec;
import ghidra.debug.api.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
@ -27,7 +26,7 @@ import ghidra.program.util.ProgramSelection;
* A service providing access to the main listing panel * A service providing access to the main listing panel
*/ */
@ServiceInfo( @ServiceInfo(
defaultProvider = DebuggerListingPlugin.class, defaultProviderName = "ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin",
description = "Replacement CodeViewerService for Debugger") description = "Replacement CodeViewerService for Debugger")
public interface DebuggerListingService extends CodeViewerService { public interface DebuggerListingService extends CodeViewerService {

View File

@ -20,19 +20,21 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Supplier; import java.util.function.Supplier;
import ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin; import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.app.services.LogicalBreakpoint.State; import ghidra.debug.api.breakpoint.LogicalBreakpointsChangeListener;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.*; import ghidra.program.util.CodeUnitLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo( // @ServiceInfo(
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class, defaultProviderName = "ghidra.app.plugin.core.debug.service.breakpoint.DebuggerLogicalBreakpointServicePlugin",
description = "Aggregate breakpoints for programs and traces") description = "Aggregate breakpoints for programs and traces")
public interface DebuggerLogicalBreakpointService { public interface DebuggerLogicalBreakpointService {
/** /**

View File

@ -21,13 +21,11 @@ import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream; import java.util.stream.Stream;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.debug.api.action.ActionSource;
import ghidra.debug.api.model.*;
import ghidra.framework.plugintool.PluginEvent; import ghidra.framework.plugintool.PluginEvent;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -36,7 +34,7 @@ import ghidra.trace.model.thread.TraceThread;
import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.datastruct.CollectionChangeListener;
@ServiceInfo( @ServiceInfo(
defaultProvider = DebuggerModelServiceProxyPlugin.class, defaultProviderName = "ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin",
description = "Service for managing debug sessions and connections") description = "Service for managing debug sessions and connections")
public interface DebuggerModelService { public interface DebuggerModelService {
/** /**

View File

@ -15,7 +15,7 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper; import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;

View File

@ -18,9 +18,10 @@ package ghidra.app.services;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.*;
import ghidra.app.services.RegionMapProposal.RegionMapEntry; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
import ghidra.app.services.SectionMapProposal.SectionMapEntry; import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry;
import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;

View File

@ -18,9 +18,8 @@ package ghidra.app.services;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.async.AsyncReference; import ghidra.async.AsyncReference;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -35,7 +34,8 @@ import ghidra.util.TriConsumer;
/** /**
* The interface for managing open traces and navigating among them and their contents * The interface for managing open traces and navigating among them and their contents
*/ */
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class) @ServiceInfo(
defaultProviderName = "ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin")
public interface DebuggerTraceManagerService { public interface DebuggerTraceManagerService {
/** /**

View File

@ -17,15 +17,14 @@ package ghidra.app.services;
import java.util.Collection; import java.util.Collection;
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin; import ghidra.debug.api.watch.WatchRow;
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
/** /**
* A service interface for controlling the Watches window * A service interface for controlling the Watches window
*/ */
@ServiceInfo( @ServiceInfo(
defaultProvider = DebuggerWatchesPlugin.class, defaultProviderName = "ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin",
description = "Service for managing watches") description = "Service for managing watches")
public interface DebuggerWatchesService { public interface DebuggerWatchesService {
/** /**

View File

@ -0,0 +1,33 @@
/* ###
* 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.Collection;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceInfo;
@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
*/
Collection<PluginTool> getProxyingPluginTools();
}

View File

@ -17,14 +17,12 @@ package ghidra.app.services;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.service.workflow.DebuggerWorkflowServiceProxyPlugin; import ghidra.debug.api.workflow.DebuggerBot;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.PluginTool;
@ServiceInfo( //
defaultProvider = DebuggerWorkflowServiceProxyPlugin.class, //
description = "Service for managing automatic debugger actions and analysis" //
)
public interface DebuggerWorkflowService { public interface DebuggerWorkflowService {
PluginTool getTool();
Set<DebuggerBot> getAllBots(); Set<DebuggerBot> getAllBots();
Set<DebuggerBot> getEnabledBots(); Set<DebuggerBot> getEnabledBots();

View File

@ -0,0 +1,25 @@
/* ###
* 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

@ -0,0 +1,46 @@
/* ###
* 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.Collection;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.tracermi.TraceRmiLaunchOpinion;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Program;
/**
* The service for launching Trace RMI targets in the GUI.
*/
@ServiceInfo(
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
*
* @param program the program
* @return the offers
*/
Collection<TraceRmiLaunchOffer> getOffers(Program program);
}

View File

@ -0,0 +1,108 @@
/* ###
* 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.io.IOException;
import java.net.SocketAddress;
import java.util.Collection;
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
import ghidra.debug.api.tracermi.TraceRmiConnection;
import ghidra.framework.plugintool.ServiceInfo;
/**
* A service (both in the Ghidra framework sense, and in the network sense) for connecting Trace
* RMI-based back-end debuggers.
*
* <p>
* This service connects to back-end debuggers, and/or allows back-end debuggers to connect to it.
* Either way, Ghidra becomes the front-end, acting as the Trace RMI server, and the back-end
* debugger acts as the Trace RMI client. The Ghidra front-end may also send control commands to the
* back-end, e.g., to step, resume, or suspend the target.
*/
@ServiceInfo(
description = "A service for accepting and creating Trace RMI connections",
defaultProviderName = "ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin")
public interface TraceRmiService {
/**
* Get the address (and port) of the Trace RMI TCP server
*
* @return
*/
SocketAddress getServerAddress();
/**
* Set the address (and port) of the Trace RMI TCP server
*
* @param serverAddress may be null to bind to ephemeral port
*/
void setServerAddress(SocketAddress serverAddress);
/**
* Start the Trace RMI TCP server
*
* @throws IOException
*/
void startServer() throws IOException;
/**
* Stop the Trace RMI TCP server
*/
void stopServer();
/**
* Check if the service is listening for inbound connections (other than those expected by
* {@link #acceptOne(SocketAddress)}).
*
* @return true if listening, false otherwise
*/
boolean isServerStarted();
/**
* Assuming a back-end debugger is listening, connect to it.
*
* @param address the address (and port) of the back-end system
* @return the connection
* @throws IOException if the connection failed
*/
TraceRmiConnection connect(SocketAddress address) throws IOException;
/**
* Prepare to accept a single connection by listening on the given address.
*
* <p>
* This essentially starts a server (separate from the one creased by {@link #startServer()})
* that will accept a single connection. The server is started by this method. The caller can
* then invoke (on the same thread) whatever back-end system or agent that is expected to
* connect back to this service. Assuming that system runs in the background, this thread can
* then invoke {@link TraceRmiAcceptor#accept()} to actually accept that connection. Once
* accepted, the service is terminated, and the server socket is closed. The client socket
* remains open.
*
* @param address the socket address to bind, or null for ephemeral
* @return the acceptor, which can be used to retrieve the ephemeral address and accept the
* actual connection
* @throws IOException on error
*/
TraceRmiAcceptor acceptOne(SocketAddress address) throws IOException;
/**
* Get all of the active connections
*
* @return the connections
*/
Collection<TraceRmiConnection> getAllConnections();
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.action;
/** /**
* Possible sources that drive actions or method invocations * Possible sources that drive actions or method invocations

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.debug.api.action;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;

View File

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.debug.api.action;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;

View File

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.debug.api.action;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec; import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec;
import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange;
@ -73,6 +73,9 @@ public interface LocationTrackingSpec {
return false; return false;
} }
if (!space.getAddressSpace().isMemorySpace()) { if (!space.getAddressSpace().isMemorySpace()) {
if (current.getThread() == null) {
return false;
}
TraceMemorySpace memSpace = current.getTrace() TraceMemorySpace memSpace = current.getTrace()
.getMemoryManager() .getMemoryManager()
.getMemoryRegisterSpace(current.getThread(), current.getFrame(), false); .getMemoryRegisterSpace(current.getThread(), current.getFrame(), false);

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.debug.api.action;
import java.util.*; import java.util.*;

View File

@ -13,14 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.breakpoint;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import generic.theme.GIcon;
import ghidra.app.services.DebuggerLogicalBreakpointService;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark; import ghidra.program.model.listing.Bookmark;
@ -29,6 +30,7 @@ import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import resources.MultiIcon;
/** /**
* A logical breakpoint * A logical breakpoint
@ -55,8 +57,31 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
* {@link LogicalBreakpointsChangeListener#breakpointRemoved(LogicalBreakpoint)}. * {@link LogicalBreakpointsChangeListener#breakpointRemoved(LogicalBreakpoint)}.
*/ */
public interface LogicalBreakpoint { public interface LogicalBreakpoint {
String BREAKPOINT_ENABLED_BOOKMARK_TYPE = "BreakpointEnabled"; String ENABLED_BOOKMARK_TYPE = "BreakpointEnabled";
String BREAKPOINT_DISABLED_BOOKMARK_TYPE = "BreakpointDisabled"; String DISABLED_BOOKMARK_TYPE = "BreakpointDisabled";
String NAME_MARKER_ENABLED = "Enabled Breakpoint";
String NAME_MARKER_DISABLED = "Disabled Breakpoint";
String NAME_MARKER_MIXED = "Mixed Breakpoint";
String NAME_MARKER_INEFF_EN = "Ineffective Enabled Breakpoint";
String NAME_MARKER_INEFF_DIS = "Ineffective Disabled Breakpoint";
String NAME_MARKER_INEFF_MIX = "Ineffective Mixed Breakpoint";
String NAME_MARKER_INCON_EN = "Inconsistent Enabled Breakpoint";
String NAME_MARKER_INCON_DIS = "Inconsistent Disabled Breakpoint";
String NAME_MARKER_INCON_MIX = "Inconsistent Mixed Breakpoint";
Icon ICON_OVERLAY_INCONSISTENT = new GIcon("icon.debugger.breakpoint.overlay.inconsistent");
Icon ICON_MARKER_ENABLED = new GIcon("icon.debugger.breakpoint.marker.enabled");
Icon ICON_MARKER_DISABLED = new GIcon("icon.debugger.breakpoint.marker.disabled");
Icon ICON_MARKER_MIXED = new GIcon("icon.debugger.breakpoint.marker.mixed");
Icon ICON_MARKER_INEFF_EN = new GIcon("icon.debugger.breakpoint.marker.ineffective.enabled");
Icon ICON_MARKER_INEFF_DIS = new GIcon("icon.debugger.breakpoint.marker.ineffective.disabled");
Icon ICON_MARKER_INEFF_MIX = new GIcon("icon.debugger.breakpoint.marker.ineffective.mixed");
Icon ICON_MARKER_INCON_EN = new MultiIcon(ICON_MARKER_ENABLED, ICON_OVERLAY_INCONSISTENT);
Icon ICON_MARKER_INCON_DIS = new MultiIcon(ICON_MARKER_DISABLED, ICON_OVERLAY_INCONSISTENT);
Icon ICON_MARKER_INCON_MIX = new MultiIcon(ICON_MARKER_MIXED, ICON_OVERLAY_INCONSISTENT);
/** /**
* The state of a logical breakpoint's program bookmark * The state of a logical breakpoint's program bookmark
@ -407,44 +432,44 @@ public interface LogicalBreakpoint {
/** /**
* The breakpoint is enabled, and all locations and its bookmark agree * The breakpoint is enabled, and all locations and its bookmark agree
*/ */
ENABLED(Mode.ENABLED, Consistency.NORMAL, DebuggerResources.NAME_BREAKPOINT_MARKER_ENABLED, DebuggerResources.ICON_BREAKPOINT_MARKER_ENABLED), ENABLED(Mode.ENABLED, Consistency.NORMAL, NAME_MARKER_ENABLED, ICON_MARKER_ENABLED),
/** /**
* The breakpoint is disabled, and all locations and its bookmark agree * The breakpoint is disabled, and all locations and its bookmark agree
*/ */
DISABLED(Mode.DISABLED, Consistency.NORMAL, DebuggerResources.NAME_BREAKPOINT_MARKER_DISABLED, DebuggerResources.ICON_BREAKPOINT_MARKER_DISABLED), DISABLED(Mode.DISABLED, Consistency.NORMAL, NAME_MARKER_DISABLED, ICON_MARKER_DISABLED),
/** /**
* There are multiple logical breakpoints at this address, and they are all saved and * There are multiple logical breakpoints at this address, and they are all saved and
* effective, but some are enabled, and some are disabled. * effective, but some are enabled, and some are disabled.
*/ */
MIXED(Mode.MIXED, Consistency.NORMAL, DebuggerResources.NAME_BREAKPOINT_MARKER_MIXED, DebuggerResources.ICON_BREAKPOINT_MARKER_MIXED), MIXED(Mode.MIXED, Consistency.NORMAL, NAME_MARKER_MIXED, ICON_MARKER_MIXED),
/** /**
* The breakpoint is saved as enabled, but one or more trace locations are absent. * The breakpoint is saved as enabled, but one or more trace locations are absent.
*/ */
INEFFECTIVE_ENABLED(Mode.ENABLED, Consistency.INEFFECTIVE, DebuggerResources.NAME_BREAKPOINT_MARKER_INEFF_EN, DebuggerResources.ICON_BREAKPOINT_MARKER_INEFF_EN), INEFFECTIVE_ENABLED(Mode.ENABLED, Consistency.INEFFECTIVE, NAME_MARKER_INEFF_EN, ICON_MARKER_INEFF_EN),
/** /**
* The breakpoint is saved as disabled, and one or more trace locations are absent. * The breakpoint is saved as disabled, and one or more trace locations are absent.
*/ */
INEFFECTIVE_DISABLED(Mode.DISABLED, Consistency.INEFFECTIVE, DebuggerResources.NAME_BREAKPOINT_MARKER_INEFF_DIS, DebuggerResources.ICON_BREAKPOINT_MARKER_INEFF_DIS), INEFFECTIVE_DISABLED(Mode.DISABLED, Consistency.INEFFECTIVE, NAME_MARKER_INEFF_DIS, ICON_MARKER_INEFF_DIS),
/** /**
* There are multiple logical breakpoints at this address, and they are all saved, but at * There are multiple logical breakpoints at this address, and they are all saved, but at
* least one is ineffective; furthermore, some are enabled, and some are disabled. * least one is ineffective; furthermore, some are enabled, and some are disabled.
*/ */
INEFFECTIVE_MIXED(Mode.MIXED, Consistency.INEFFECTIVE, DebuggerResources.NAME_BREAKPOINT_MARKER_INEFF_MIX, DebuggerResources.ICON_BREAKPOINT_MARKER_INEFF_MIX), INEFFECTIVE_MIXED(Mode.MIXED, Consistency.INEFFECTIVE, NAME_MARKER_INEFF_MIX, ICON_MARKER_INEFF_MIX),
/** /**
* The breakpoint is enabled, and all locations agree, but the bookmark is absent or * The breakpoint is enabled, and all locations agree, but the bookmark is absent or
* disagrees. * disagrees.
*/ */
INCONSISTENT_ENABLED(Mode.ENABLED, Consistency.INCONSISTENT, DebuggerResources.NAME_BREAKPOINT_MARKER_INCON_EN, DebuggerResources.ICON_BREAKPOINT_MARKER_INCON_EN), INCONSISTENT_ENABLED(Mode.ENABLED, Consistency.INCONSISTENT, NAME_MARKER_INCON_EN, ICON_MARKER_INCON_EN),
/** /**
* The breakpoint is disabled, and all locations agree, but the bookmark is absent or * The breakpoint is disabled, and all locations agree, but the bookmark is absent or
* disagrees. * disagrees.
*/ */
INCONSISTENT_DISABLED(Mode.DISABLED, Consistency.INCONSISTENT, DebuggerResources.NAME_BREAKPOINT_MARKER_INCON_DIS, DebuggerResources.ICON_BREAKPOINT_MARKER_INCON_DIS), INCONSISTENT_DISABLED(Mode.DISABLED, Consistency.INCONSISTENT, NAME_MARKER_INCON_DIS, ICON_MARKER_INCON_DIS),
/** /**
* The breakpoint is terribly inconsistent: its locations disagree, and the bookmark may be * The breakpoint is terribly inconsistent: its locations disagree, and the bookmark may be
* absent. * absent.
*/ */
INCONSISTENT_MIXED(Mode.MIXED, Consistency.INCONSISTENT, DebuggerResources.NAME_BREAKPOINT_MARKER_INCON_MIX, DebuggerResources.ICON_BREAKPOINT_MARKER_INCON_MIX); INCONSISTENT_MIXED(Mode.MIXED, Consistency.INCONSISTENT, NAME_MARKER_INCON_MIX, ICON_MARKER_INCON_MIX);
public final Mode mode; public final Mode mode;
public final Consistency consistency; public final Consistency consistency;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.breakpoint;
import java.util.Collection; import java.util.Collection;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.control;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
@ -24,9 +24,11 @@ import javax.swing.Icon;
import db.Transaction; import db.Transaction;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;

View File

@ -13,11 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.emulation; package ghidra.debug.api.emulation;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.debug.api.model.TraceRecorder;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.guest.TracePlatform;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
@ -48,10 +46,8 @@ public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint {
* @param recorder if applicable, the recorder for the trace's live target * @param recorder if applicable, the recorder for the trace's live target
* @return the emulator * @return the emulator
*/ */
default DebuggerPcodeMachine<?> create(PluginTool tool, TracePlatform platform, long snap, DebuggerPcodeMachine<?> create(PluginTool tool, TracePlatform platform, long snap,
TraceRecorder recorder) { TraceRecorder recorder);
return create(new DefaultPcodeDebuggerAccess(tool, recorder, platform, snap));
}
/** /**
* Create the emulator * Create the emulator

View File

@ -13,10 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.emulation; package ghidra.debug.api.emulation;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeMachine; import ghidra.pcode.exec.trace.TracePcodeMachine;
/** /**

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.emulation.data; package ghidra.debug.api.emulation;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.trace.data.PcodeTraceAccess; import ghidra.pcode.exec.trace.data.PcodeTraceAccess;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.emulation.data; package ghidra.debug.api.emulation;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.emulation.data; package ghidra.debug.api.emulation;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.emulation.data; package ghidra.debug.api.emulation;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.gui.interpreters; package ghidra.debug.api.interpreter;
import ghidra.app.plugin.core.interpreter.InterpreterConnection; import ghidra.app.plugin.core.interpreter.InterpreterConnection;
import ghidra.app.plugin.core.interpreter.InterpreterConsole; import ghidra.app.plugin.core.interpreter.InterpreterConsole;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.gui.listing; package ghidra.debug.api.listing;
import java.awt.Color; import java.awt.Color;
import java.math.BigInteger; import java.math.BigInteger;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.debug.api.model;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.debug.api.model;
import java.util.*; import java.util.*;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.debug.api.model;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;

View File

@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.model.launch; package ghidra.debug.api.model;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetLauncher; import ghidra.dbg.target.TargetLauncher;
@ -170,9 +168,7 @@ public interface DebuggerProgramLaunchOffer {
* *
* @return the icon * @return the icon
*/ */
default Icon getIcon() { Icon getIcon();
return DebuggerResources.ICON_DEBUGGER;
}
/** /**
* Get the text display on the parent menu for this offer * Get the text display on the parent menu for this offer

View File

@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.debug.api.model;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import ghidra.app.plugin.core.debug.register.RegisterTypeInfo;
import ghidra.dbg.target.TargetRegister; import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.util.ConversionUtils; import ghidra.dbg.util.ConversionUtils;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
@ -159,17 +158,6 @@ public interface DebuggerRegisterMapper {
*/ */
Register targetToTrace(TargetRegister tReg); Register targetToTrace(TargetRegister tReg);
/**
* Get suggested type information for a given trace register
*
* <P>
* TODO: The recorder should apply this type when recording register values
*
* @param lReg the name of the trace register
* @return the default type information
*/
RegisterTypeInfo getDefaultTypeInfo(Register lReg);
/** /**
* Get the (base) registers on target that can be mapped * Get the (base) registers on target that can be mapped
* *

View File

@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.debug.api.model;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin; import ghidra.framework.plugintool.PluginTool;
import ghidra.app.services.TraceRecorder;
import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
@ -27,5 +26,5 @@ public interface DebuggerTargetTraceMapper {
CompilerSpec getTraceCompilerSpec(); CompilerSpec getTraceCompilerSpec();
TraceRecorder startRecording(DebuggerModelServicePlugin service, Trace trace); TraceRecorder startRecording(PluginTool tool, Trace trace);
} }

View File

@ -13,14 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.model;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.model;
/** /**
* A listener for state changes in a recorded target, or in the recorder itself * A listener for state changes in a recorded target, or in the recorder itself

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.modules;
import java.util.Set; import java.util.Set;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.modules;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;

View File

@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.modules;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.TraceStaticMappingManager; import ghidra.trace.model.modules.TraceStaticMappingManager;

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.modules;
import ghidra.app.services.ModuleMapProposal.ModuleMapEntry; import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceModule;

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.modules;
import ghidra.app.services.RegionMapProposal.RegionMapEntry; import ghidra.debug.api.modules.RegionMapProposal.RegionMapEntry;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.modules;
import ghidra.app.services.SectionMapProposal.SectionMapEntry; import ghidra.debug.api.modules.SectionMapProposal.SectionMapEntry;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceModule;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.debug.api.platform;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.mapping; package ghidra.debug.api.platform;
public class DisassemblyResult { public class DisassemblyResult {
public static final DisassemblyResult SUCCESS = new DisassemblyResult(true, null); public static final DisassemblyResult SUCCESS = new DisassemblyResult(true, null);

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug; package ghidra.debug.api.tracemgr;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@ -21,8 +21,8 @@ import java.util.*;
import org.jdom.Element; import org.jdom.Element;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.framework.data.DefaultProjectData; import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;

View File

@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.rmi.trace; package ghidra.debug.api.tracermi;
import java.util.concurrent.*; import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.util.Swing;
/** /**
* The future result of invoking a {@link RemoteMethod}. * The future result of invoking a {@link RemoteMethod}.
@ -40,31 +40,6 @@ import ghidra.util.Swing;
* a non-swing thread, e.g., a task thread or script thread, to wait for results, or chain * a non-swing thread, e.g., a task thread or script thread, to wait for results, or chain
* callbacks. * callbacks.
*/ */
public class RemoteAsyncResult extends CompletableFuture<Object> { public interface RemoteAsyncResult extends CompletionStage<Object>, Future<Object> {
final ValueDecoder decoder;
public RemoteAsyncResult() {
this.decoder = ValueDecoder.DEFAULT;
}
public RemoteAsyncResult(OpenTrace open) {
this.decoder = open;
}
@Override
public Object get() throws InterruptedException, ExecutionException {
if (Swing.isSwingThread()) {
throw new AssertionError("Refusing indefinite wait on Swing thread");
}
return super.get();
}
@Override
public Object get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (Swing.isSwingThread() && unit.toSeconds(timeout) > 1) {
throw new AssertionError("Refusing a timeout > 1 second on Swing thread");
}
return super.get(timeout, unit);
}
} }

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.rmi.trace; package ghidra.debug.api.tracermi;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -316,15 +316,4 @@ public interface RemoteMethod {
throw new TraceRmiError(e); throw new TraceRmiError(e);
} }
} }
record RecordRemoteMethod(TraceRmiHandler handler, String name, Action action,
String description, Map<String, RemoteParameter> parameters, SchemaName retType)
implements RemoteMethod {
@Override
public RemoteAsyncResult invokeAsync(Map<String, Object> arguments) {
Trace trace = validate(arguments);
OpenTrace open = handler.getOpenTrace(trace);
return handler.invoke(open, name, arguments);
}
}
} }

View File

@ -13,14 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.register; package ghidra.debug.api.tracermi;
import ghidra.program.model.data.DataType; import java.util.Map;
import ghidra.program.model.lang.Register; import java.util.Set;
import ghidra.program.model.lang.RegisterValue;
public interface DynamicRegisterChangeListener { import ghidra.debug.api.tracermi.RemoteMethod.Action;
void updateRegisterValue(RegisterValue registerValue);
void updateRegisterDataType(Register register, DataType dataType); public interface RemoteMethodRegistry {
Map<String, RemoteMethod> all();
RemoteMethod get(String name);
Set<RemoteMethod> getByAction(Action action);
} }

View File

@ -13,10 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.rmi.trace; package ghidra.debug.api.tracermi;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
public record RemoteParameter(String name, SchemaName type, boolean required, public interface RemoteParameter {
ValueSupplier defaultValue, String display, String description) { SchemaName type();
boolean required();
Object getDefaultValue();
} }

View File

@ -0,0 +1,51 @@
/* ###
* 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.io.IOException;
import java.net.SocketAddress;
import java.net.SocketException;
/**
* An acceptor to receive a single Trace RMI connection from a back-end
*/
public interface TraceRmiAcceptor {
/**
* Accept a single connection
*
* <p>
* This acceptor is no longer valid after the connection is accepted.
*
* @return the connection, if successful
* @throws IOException if there was an error
*/
TraceRmiConnection accept() throws IOException;
/**
* Get the address (and port) where the acceptor is listening
*
* @return the socket address
*/
SocketAddress getAddress();
/**
* Set the timeout
*
* @param millis the number of milliseconds after which an {@link #accept()} will time out.
* @throws SocketException if there's a protocol error
*/
void setTimeout(int millis) throws SocketException;
}

View File

@ -0,0 +1,39 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.debug.api.tracermi;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.concurrent.TimeoutException;
import ghidra.trace.model.Trace;
public interface TraceRmiConnection extends AutoCloseable {
SocketAddress getRemoteAddress();
RemoteMethodRegistry getMethods();
Trace waitForTrace(long timeoutMillis) throws TimeoutException;
long getLastSnapshot(Trace trace);
@Override
void close() throws IOException;
boolean isClosed();
void waitClosed();
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.plugin.core.debug.service.rmi.trace; package ghidra.debug.api.tracermi;
public class TraceRmiError extends RuntimeException { public class TraceRmiError extends RuntimeException {
public TraceRmiError() { public TraceRmiError() {

View File

@ -0,0 +1,288 @@
/* ###
* 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.io.IOException;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.util.HelpLocation;
import ghidra.util.task.TaskMonitor;
/**
* An offer to launch a program with a given mechanism
*
* <p>
* Typically each offer is configured with the program it's going to launch, and knows how to work a
* specific connector and platform to obtain a target executing the program's image. The mechanisms
* may vary wildly from platform to platform.
*/
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
*
* <p>
* The launch may not always be completely successful. Instead of tearing things down, partial
* launches are left in place, in case the user wishes to repair/complete the steps manually. If
* the result includes a connection, then at least that was successful. If not, then the caller
* can choose how to treat the terminal sessions. If the cause of failure was an exception, it
* is included. If the launch succeeded, but module mapping failed, the result will include a
* trace and the exception. If an error occurred in the shell script, it may not be communicated
* here, but instead displayed only in the terminal.
*
* @param program the program associated with the launch attempt
* @param sessions any terminal sessions created while launching the back-end. If there are more
* than one, they are distinguished by launcher-defined keys. If there are no
* sessions, then there was likely a catastrophic error in the launcher.
* @param connection if the target connected back to Ghidra, that connection
* @param trace if the connection started a trace, the (first) trace it created
* @param exception optional error, if failed
*/
public record LaunchResult(Program program, Map<String, TerminalSession> sessions,
TraceRmiConnection connection, Trace trace, Throwable exception)
implements AutoCloseable {
@Override
public void close() throws Exception {
for (TerminalSession s : sessions.values()) {
s.close();
}
if (connection != null) {
connection.close();
}
}
}
/**
* When programmatically customizing launch configuration, describes callback timing relative to
* prompting the user.
*/
public enum RelPrompt {
/**
* The user is not prompted for parameters. This will be the only callback.
*/
NONE,
/**
* The user will be prompted. This callback can pre-populate suggested parameters. Another
* callback will be issued if the user does not cancel.
*/
BEFORE,
/**
* The user has confirmed the parameters. This callback can validate or override the users
* parameters. Overriding the user is discouraged. This is the final callback.
*/
AFTER;
}
public enum PromptMode {
/**
* The user is always prompted for parameters.
*/
ALWAYS,
/**
* The user is never prompted for parameters.
*/
NEVER,
/**
* The user is prompted after an error.
*/
ON_ERROR;
}
/**
* Callbacks for custom configuration when launching a program
*/
public interface LaunchConfigurator {
LaunchConfigurator NOP = new LaunchConfigurator() {};
/**
* Determine whether the user should be prompted to confirm launch parameters
*
* @return the prompt mode
*/
default PromptMode getPromptMode() {
return PromptMode.NEVER;
}
/**
* Re-write the launcher arguments, if desired
*
* @param launcher the launcher that will create the target
* @param arguments the arguments suggested by the offer or saved settings
* @param relPrompt describes the timing of this callback relative to prompting the user
* @return the adjusted arguments
*/
default Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
Map<String, ?> arguments, RelPrompt relPrompt) {
return arguments;
}
}
/**
* Launch the program using the offered mechanism
*
* @param monitor a monitor for progress and cancellation
* @param configurator the configuration callback
* @return the launch result
*/
LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator);
/**
* Launch the program using the offered mechanism
*
* @param monitor a monitor for progress and cancellation
* @return the launch result
*/
default LaunchResult launchProgram(TaskMonitor monitor) {
return launchProgram(monitor, LaunchConfigurator.NOP);
}
/**
* A name so that this offer can be recognized later
*
* <p>
* The name is saved to configuration files, so that user preferences and priorities can be
* memorized. The opinion will generate each offer fresh each time, so it's important that the
* "same offer" have the same configuration name. Note that the name <em>cannot</em> depend on
* the program name, but can depend on the model factory and program language and/or compiler
* spec. This name cannot contain semicolons ({@ code ;}).
*
* @return the configuration name
*/
String getConfigName();
/**
* Get the icon displayed in the UI for this offer
*
* <p>
* Please take care when overriding this that the icon still clearly indicates the target will
* be executed. Changing it, e.g., to the same icon as "Step" would be an unwelcome prank. A
* more reasonable choice would be the standard {@code "icon.debugger"} plus an overlay, or the
* branding of the underlying technology, e.g., QEMU or GDB.
*
* @return the icon
*/
Icon getIcon();
/**
* Get the menu path subordinate to "Debugger.Debug [imagePath]" for this offer.
*
* <p>
* By default, this is just the title, i.e., the same as in the quick-launch drop-down menu. A
* package that introduces a large number of offers should override this method to organize
* them. A general rule of thumb is "no more than seven." Except at the level immediately under
* "Debug [imagePath]," no more than seven items should be presented to the user. In some cases,
* it may be more appropriate to group things using {@link #getMenuGroup() menu groups} rather
* than sub menus.
*
* <p>
* Organization is very much a matter of taste, but consider that you are cooperating with other
* packages to populate the launcher menu. The top level is especially contentious, but sub
* menus, if named appropriately, are presumed to belong to a single package.
*
* @return the path
*/
default List<String> getMenuPath() {
return List.of(getTitle());
}
/**
* Get the text displayed in the quick-launch drop-down menu.
*
* <p>
* No two offers should ever have the same title, even if they appear in different sub-menus.
* Otherwise, the user cannot distinguish the offers in the quick-launch drop-down menu.
*
* @return the menu title
*/
public String getTitle();
/**
* Get an HTML description of the connector
*
* @return the description
*/
public String getDescription();
/**
* Get the menu group for the offer
*
* <p>
* Especially for entries immediately under to "Debugger.Debug [imagePath]", specifies the menu
* group. A package that introduces a large number of offers should instead consider
* {@link #getMenuPath() sub menus}.
*
* @return the menu group
*/
default String getMenuGroup() {
return "";
}
/**
* Controls the position in the menu (within its group) of the entry
*
* <p>
* The menus will always be presented in the same order, barring any changes to the plugins or
* launcher properties. Groups are alphabetized and visually separated. Then sub groups are
* alphabetized, but <em>not</em> visually separated. Finally, offers are alphabetized by their
* final path element, usually the title.
*
* <p>
* The order of entries in the quick-launch drop-down menu is always most-recently to
* least-recently used. An entry that has never been used does not appear in the quick launch
* menu.
*/
default String getMenuOrder() {
return "";
}
/**
* Get the location for additional help about this specific offer
*
* <p>
* The default is just the entry on Trace RMI launchers in general.
*
* @return the location
*/
default HelpLocation getHelpLocation() {
return new HelpLocation("TraceRmiPlugin", "launch");
}
/**
* Get the parameter descriptions for the launcher
*
* @return the parameters
*/
Map<String, ParameterDescription<?>> getParameters();
}

View File

@ -0,0 +1,78 @@
/* ###
* 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

@ -0,0 +1,233 @@
/* ###
* 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.watch;
import ghidra.docking.settings.Settings;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.symbol.Symbol;
/**
* A row in the Watches table
*/
public interface WatchRow {
/**
* Get the Sleigh expression
*
* @return the expression
*/
String getExpression();
/**
* Set the Sleigh expression
*
* @param expression the expression
*/
void setExpression(String expression);
/**
* Get the data type for interpreting the value
*
* @return the data type
*/
DataType getDataType();
/**
* Set the data type for interpreting the value
*
* @param dataType the data type
*/
void setDataType(DataType dataType);
/**
* Get the settings on the data type
*
* <p>
* The returned settings may be modified, after which {@link #settingsChanged()} must be called.
* There is no {@code setSettings} method.
*
* @return the settings
*/
Settings getSettings();
/**
* Notify the row that the settings were changed
*
* @see #getSettings()
*/
void settingsChanged();
/**
* Get the address of the value, if it exists at one (memory or register)
*
* @return the address, or null
*/
Address getAddress();
/**
* Get the address range of the value, if it exists at an address (memory or register)
*
* @return the range, or null
*/
AddressRange getRange();
/**
* Get the complete set of all addresses read to evaluate the expression
*
* @return the address set, or null
*/
AddressSetView getReads();
/**
* Get the nearest symbol before the value's address, if applicable
*
* @return the symbol, or null
*/
Symbol getSymbol();
/**
* Get the raw value
*
* @return the value, or null
*/
byte[] getValue();
/**
* Get the raw value displayed as a string
*
* <p>
* For values in memory, this is a list of hex bytes. For others, it is a hex integer subject to
* the platform's endian.
*
* @return the value, or null
*/
String getRawValueString();
/**
* Get the number of bytes in the value
*
* @return the length, or 0 if evaluation failed
*/
int getValueLength();
/**
* Patch memory or register values such that the expression evaluates to the given raw value
*
* <p>
* This is only supported when {@link #isRawValueEditable} returns true. The given value must be
* a list of hex bytes (as returned by {@link #getRawValueString()}), or a hex integer subject
* to the platform's endian. Either is accepted, regardless of whether the value resides in
* memory.
*
* @see #getAddress()
* @param value the raw value as returned by {@link #getRawValueString()}
*/
void setRawValueString(String value);
/**
* Check if {@link #setRawValueString(String)} is supported
*
* <p>
* Setting the value may not be supported for many reasons: 1) The expression is not valid, 2)
* The expression could not be evaluated, 3) The value has no address or register. Reason 3 is
* somewhat strict, but reasonable, lest we have to implement a solver.
*
* @return whether or not the value can be modified
*/
boolean isRawValueEditable();
/**
* Get the value as returned by the data type
*
* @return the data-type defined value
*/
Object getValueObject();
/**
* Get the value as represented by the data type
*
* @return the value's data-type-defined representation
*/
String getValueString();
/**
* Patch memory or register values such that the expression evaluates to the given value
*
* <p>
* This is only supported when {@link #isValueEditable()} returns true. The given value must be
* encodable by the data type.
*
* @param value the desired value, as returned by {@link #getValueString()}
*/
void setValueString(String value);
/**
* Check if {@link #setValueString(String)} is supported
*
* <p>
* In addition to those reasons given in {@link #isRawValueEditable()}, setting the value may
* not be supported because: 1) No data type is set, or 2) The selected data type does not
* support encoding.
*
* @return whether or not the data-type interpreted value can be modified
*/
boolean isValueEditable();
/**
* If the watch could not be evaluated, get the cause
*
* @return the error
*/
Throwable getError();
/**
* If the watch could not be evaluated, get a message explaining why
*
* <p>
* This is essentially the message given by {@link #getError()}. If the exception does not
* provide a message, this will at least give the name of the exception class.
*
* @return the error message, or an empty string
*/
String getErrorMessage();
/**
* Check if the value given is actually known to be the value
*
* <p>
* If the value itself or any value encountered during the evaluation of the expression is
* stale, then the final value is considered stale, i.e., not known.
*
* @return true all memory and registers involved in the evaluation are known, false otherwise.
*/
boolean isKnown();
/**
* Check if the value has changed
*
* <p>
* "Changed" technically deals in navigation. In the case of a step, resume-and-break, patch,
* etc. This will detect the changes as expected. When manually navigating, this compares the
* two most recent times visited. Only the value itself is compared, without consideration for
* any intermediate values encountered during evaluation. Consider an array whose elements are
* all currently 0. An expression that dereferences an index in that array will be considered
* unchanged, even if the index did change.
*
* @return true if the value changed, false otherwise.
*/
boolean isChanged();
}

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.workflow;
import ghidra.app.plugin.core.debug.service.workflow.DebuggerWorkflowServicePlugin; import ghidra.app.services.DebuggerModelService;
import ghidra.app.services.DebuggerWorkflowFrontEndService;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.framework.options.AutoOptions; import ghidra.framework.options.AutoOptions;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -153,15 +154,15 @@ public interface DebuggerBot extends ExtensionPoint {
* If {@link #isEnabled()} is already equal to the given -enabled- value, this method has no * If {@link #isEnabled()} is already equal to the given -enabled- value, this method has no
* effect. * effect.
* *
* @param plugin the front-end plugin, required if -enabled- is set * @param service the front-end service, required if -enabled- is set
* @param enabled true to enable, false to disable * @param enabled true to enable, false to disable
*/ */
default void setEnabled(DebuggerWorkflowServicePlugin plugin, boolean enabled) { default void setEnabled(DebuggerWorkflowFrontEndService service, boolean enabled) {
if (isEnabled() == enabled) { if (isEnabled() == enabled) {
return; return;
} }
if (enabled) { if (enabled) {
enable(plugin); enable(service);
} }
else { else {
disable(); disable();
@ -171,9 +172,9 @@ public interface DebuggerBot extends ExtensionPoint {
/** /**
* Enable and initialize the bot * Enable and initialize the bot
* *
* @param plugin the front-end plugin * @param service the front-end service
*/ */
void enable(DebuggerWorkflowServicePlugin plugin); void enable(DebuggerWorkflowFrontEndService service);
/** /**
* Disable and dispose the bot * Disable and dispose the bot

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.app.services; package ghidra.debug.api.workflow;
import java.lang.annotation.*; import java.lang.annotation.*;

View File

@ -278,3 +278,10 @@ I wouldn't expect the user to manage multiple transaction objects.
The recommendation is that the CLI can have at most one active transaction. The recommendation is that the CLI can have at most one active transaction.
For the user to open a second transaction may be considered an error. For the user to open a second transaction may be considered an error.
Take care as you're coding (and likely re-using command logic) that you don't accidentally take or otherwise conflict with the CLI's transaction manager when processing an event. Take care as you're coding (and likely re-using command logic) that you don't accidentally take or otherwise conflict with the CLI's transaction manager when processing an event.
# Regarding launcher shell scripts:
Need to document all the @metadata stuff
In particular, "Image" is a special parameter that will get the program executable by default.

View File

@ -27,6 +27,7 @@ eclipse.project.name = 'Debug Debugger-rmi-trace'
dependencies { dependencies {
api project(':Pty') api project(':Pty')
api project(':Debugger') api project(':Debugger')
api project(':Debugger-api')
} }
task generateProtoPy { task generateProtoPy {

View File

@ -1,6 +1,7 @@
##VERSION: 2.0 ##VERSION: 2.0
DEVNOTES.txt||GHIDRA||||END| DEVNOTES.txt||GHIDRA||||END|
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
src/main/py/LICENSE||GHIDRA||||END| src/main/py/LICENSE||GHIDRA||||END|
src/main/py/README.md||GHIDRA||||END| src/main/py/README.md||GHIDRA||||END|
src/main/py/pyproject.toml||GHIDRA||||END| src/main/py/pyproject.toml||GHIDRA||||END|

View File

@ -0,0 +1 @@
TraceRmiLaunchOpinion

View File

@ -14,10 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin; import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.TraceRmiService; import ghidra.app.services.TraceRmiService;
@ -36,10 +34,9 @@ public class ConnectTraceRmiScript extends GhidraScript {
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
TraceRmiService service = getService(); TraceRmiService service = getService();
TraceRmiHandler handler = service.connect( service.connect(new InetSocketAddress(askString("Trace RMI", "hostname", "localhost"),
new InetSocketAddress(askString("Trace RMI", "hostname", "localhost"), askInt("Trace RMI", "port"))); askInt("Trace RMI", "port")));
println("Connected"); println("Connected");
handler.start();
// if (askYesNo("Execute?", "Execute 'echo test'?")) { // if (askYesNo("Execute?", "Execute 'echo test'?")) {
// handler.getMethods().get("execute").invoke(Map.of("cmd", "script print('test')")); // handler.getMethods().get("execute").invoke(Map.of("cmd", "script print('test')"));

View File

@ -16,9 +16,11 @@
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import ghidra.app.plugin.core.debug.service.rmi.trace.*; import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiPlugin;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.TraceRmiService; import ghidra.app.services.TraceRmiService;
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
import ghidra.debug.api.tracermi.TraceRmiConnection;
public class ListenTraceRmiScript extends GhidraScript { public class ListenTraceRmiScript extends GhidraScript {
@ -37,12 +39,11 @@ public class ListenTraceRmiScript extends GhidraScript {
TraceRmiAcceptor acceptor = service.acceptOne(null); TraceRmiAcceptor acceptor = service.acceptOne(null);
println("Listening at " + acceptor.getAddress()); println("Listening at " + acceptor.getAddress());
TraceRmiHandler handler = acceptor.accept(); TraceRmiConnection connection = acceptor.accept();
println("Connection from " + handler.getRemoteAddress()); println("Connection from " + connection.getRemoteAddress());
handler.start();
while (askYesNo("Execute?", "Execute 'echo test'?")) { while (askYesNo("Execute?", "Execute 'echo test'?")) {
handler.getMethods().get("execute").invoke(Map.of("cmd", "echo test")); connection.getMethods().get("execute").invoke(Map.of("cmd", "echo test"));
} }
} }
} }

View File

@ -0,0 +1,42 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.connection;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.services.TraceRmiService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo(
shortDescription = "GUI elements to manage Trace RMI connections",
description = """
Provides a panel for managing Trace RMI connections. The panel also allows users to
control the Trace RMI server and/or create manual connections.
""",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
servicesRequired = {
TraceRmiService.class,
})
public class TraceRmiConnectionManagerPlugin extends Plugin {
public TraceRmiConnectionManagerPlugin(PluginTool tool) {
super(tool);
}
// TODO: Add the actual provider. This will probably replace DebuggerTargetsPlugin.
}

View File

@ -0,0 +1,553 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import javax.swing.Icon;
import org.jdom.Element;
import org.jdom.JDOMException;
import db.Transaction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
import ghidra.app.plugin.core.terminal.TerminalListener;
import ghidra.app.services.*;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.util.ShellUtils;
import ghidra.debug.api.modules.*;
import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
import ghidra.debug.api.tracermi.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
import ghidra.pty.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceLocation;
import ghidra.trace.model.modules.TraceModule;
import ghidra.util.MessageType;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.XmlUtilities;
public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer {
public static final String PREFIX_DBGLAUNCH = "DBGLAUNCH_";
public static final String PARAM_DISPLAY_IMAGE = "Image";
protected record PtyTerminalSession(Terminal terminal, Pty pty, PtySession session,
Thread waiter) implements TerminalSession {
@Override
public void close() throws IOException {
terminate();
terminal.close();
}
@Override
public void terminate() throws IOException {
terminal.terminated();
session.destroyForcibly();
pty.close();
waiter.interrupt();
}
}
protected record NullPtyTerminalSession(Terminal terminal, Pty pty, String name)
implements TerminalSession {
@Override
public void close() throws IOException {
terminate();
terminal.close();
}
@Override
public void terminate() throws IOException {
terminal.terminated();
pty.close();
}
}
static class TerminateSessionTask extends Task {
private final TerminalSession session;
public TerminateSessionTask(TerminalSession session) {
super("Terminate Session", false, false, false);
this.session = session;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
session.close();
}
catch (IOException e) {
Msg.error(this, "Could not terminate: " + e, e);
}
}
}
protected final Program program;
protected final PluginTool tool;
protected final TerminalService terminalService;
public AbstractTraceRmiLaunchOffer(Program program, PluginTool tool) {
this.program = Objects.requireNonNull(program);
this.tool = Objects.requireNonNull(tool);
this.terminalService = Objects.requireNonNull(tool.getService(TerminalService.class));
}
protected int getTimeoutMillis() {
return 10000;
}
@Override
public Icon getIcon() {
return DebuggerResources.ICON_DEBUGGER;
}
protected Address getMappingProbeAddress() {
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
if (eepi.hasNext()) {
return eepi.next();
}
InstructionIterator ii = program.getListing().getInstructions(true);
if (ii.hasNext()) {
return ii.next().getAddress();
}
AddressSetView es = program.getMemory().getExecuteSet();
if (!es.isEmpty()) {
return es.getMinAddress();
}
if (!program.getMemory().isEmpty()) {
return program.getMinAddress();
}
return null; // I guess we won't wait for a mapping, then
}
protected CompletableFuture<Void> listenForMapping(
DebuggerStaticMappingService mappingService, TraceRmiConnection connection,
Trace trace) {
Address probeAddress = getMappingProbeAddress();
if (probeAddress == null) {
return AsyncUtils.NIL; // No need to wait on mapping of nothing
}
ProgramLocation probe = new ProgramLocation(program, probeAddress);
var result = new CompletableFuture<Void>() {
DebuggerStaticMappingChangeListener listener = (affectedTraces, affectedPrograms) -> {
if (!affectedPrograms.contains(program) &&
!affectedTraces.contains(trace)) {
return;
}
check();
};
protected void check() {
long snap = connection.getLastSnapshot(trace);
TraceLocation result = mappingService.getOpenMappedLocation(trace, probe, snap);
if (result == null) {
return;
}
complete(null);
mappingService.removeChangeListener(listener);
}
};
mappingService.addChangeListener(result.listener);
result.check();
result.exceptionally(ex -> {
mappingService.removeChangeListener(result.listener);
return null;
});
return result;
}
protected Collection<ModuleMapEntry> invokeMapper(TaskMonitor monitor,
DebuggerStaticMappingService mappingService, Trace trace) throws CancelledException {
Map<TraceModule, ModuleMapProposal> map = mappingService
.proposeModuleMaps(trace.getModuleManager().getAllModules(), List.of(program));
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
mappingService.addModuleMappings(proposal, monitor, true);
return proposal;
}
private void saveLauncherArgs(Map<String, ?> args,
Map<String, ParameterDescription<?>> params) {
SaveState state = new SaveState();
for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
if (val != null) {
ConfigStateField.putState(state, param.type.asSubclass(Object.class),
"param_" + param.name, val);
state.putLong("last", System.currentTimeMillis());
}
}
if (program != null) {
ProgramUserData userData = program.getProgramUserData();
try (Transaction tx = userData.openTransaction()) {
Element element = state.saveToXml();
userData.setStringProperty(PREFIX_DBGLAUNCH + getConfigName(),
XmlUtilities.toString(element));
}
}
}
/**
* Generate the default launcher arguments
*
* <p>
* It is not sufficient to simply take the defaults specified in the parameters. This must
* populate the arguments necessary to launch the requested program.
*
* @param params the parameters
* @return the default arguments
*/
@SuppressWarnings("unchecked")
protected Map<String, ?> generateDefaultLauncherArgs(
Map<String, ParameterDescription<?>> params) {
if (program == null) {
return Map.of();
}
Map<String, Object> map = new LinkedHashMap<String, Object>();
ParameterDescription<String> paramImage = null;
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
ParameterDescription<?> param = entry.getValue();
map.put(entry.getKey(), param.defaultValue);
if (PARAM_DISPLAY_IMAGE.equals(param.display)) {
if (param.type != String.class) {
Msg.warn(this, "'Image' parameter has unexpected type: " + paramImage.type);
}
paramImage = (ParameterDescription<String>) param;
}
}
if (paramImage != null) {
File imageFile = TraceRmiLauncherServicePlugin.getProgramPath(program);
if (imageFile != null) {
paramImage.set(map, imageFile.getAbsolutePath());
}
}
return map;
}
/**
* Prompt the user for arguments, showing those last used or defaults
*
* @param lastExc
*
* @param params the parameters of the model's launcher
* @param lastExc if re-prompting, an error to display
* @return the arguments given by the user, or null if cancelled
*/
protected Map<String, ?> promptLauncherArgs(LaunchConfigurator configurator,
Throwable lastExc) {
Map<String, ParameterDescription<?>> params = getParameters();
DebuggerMethodInvocationDialog dialog =
new DebuggerMethodInvocationDialog(tool, getTitle(), "Launch", getIcon());
dialog.setDescription(getDescription());
// NB. Do not invoke read/writeConfigState
Map<String, ?> args;
boolean reset = false;
do {
args =
configurator.configureLauncher(this, loadLastLauncherArgs(true), RelPrompt.BEFORE);
for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
if (val != null) {
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
val);
}
}
if (lastExc != null) {
dialog.setStatusText(lastExc.toString(), MessageType.ERROR);
}
else {
dialog.setStatusText("");
}
args = dialog.promptArguments(params);
if (args == null) {
// Cancelled
return null;
}
reset = dialog.isResetRequested();
if (reset) {
args = generateDefaultLauncherArgs(params);
}
saveLauncherArgs(args, params);
}
while (reset);
return args;
}
/**
* Load the arguments last used for this offer, or give the defaults
*
* <p>
* If there are no saved "last used" arguments, then this will return the defaults. If there are
* saved arguments, but they cannot be loaded, then this will behave differently depending on
* whether the user will be confirming the arguments. If there will be no prompt/confirmation,
* then this method must throw an exception in order to avoid launching with defaults, when the
* user may be expecting a customized launch. If there will be a prompt, then this may safely
* return the defaults, since the user will be given a chance to correct them.
*
* @param params the parameters of the model's launcher
* @param forPrompt true if the user will be confirming the arguments
* @return the loaded arguments, or defaults
*/
protected Map<String, ?> loadLastLauncherArgs(boolean forPrompt) {
/**
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
* Re-examine this if/when that gets merged
*/
if (program != null) {
Map<String, ParameterDescription<?>> params = getParameters();
ProgramUserData userData = program.getProgramUserData();
String property =
userData.getStringProperty(PREFIX_DBGLAUNCH + getConfigName(), null);
if (property != null) {
try {
Element element = XmlUtilities.fromString(property);
SaveState state = new SaveState(element);
List<String> names = List.of(state.getNames());
Map<String, Object> args = new LinkedHashMap<>();
for (ParameterDescription<?> param : params.values()) {
String key = "param_" + param.name;
if (names.contains(key)) {
Object configState = ConfigStateField.getState(state, param.type, key);
if (configState != null) {
args.put(param.name, configState);
}
}
}
if (!args.isEmpty()) {
return args;
}
}
catch (JDOMException | IOException e) {
if (!forPrompt) {
throw new RuntimeException(
"Saved launcher args are corrupt, or launcher parameters changed. Not launching.",
e);
}
Msg.error(this,
"Saved launcher args are corrupt, or launcher parameters changed. Defaulting.",
e);
}
}
Map<String, ?> args = generateDefaultLauncherArgs(params);
saveLauncherArgs(args, params);
return args;
}
return new LinkedHashMap<>();
}
/**
* Obtain the launcher args
*
* <p>
* This should either call {@link #promptLauncherArgs(Map))} or
* {@link #loadLastLauncherArgs(Map, boolean))}. Note if choosing the latter, the user will not
* be prompted to confirm.
*
* @param params the parameters of the model's launcher
* @param configurator the rules for configuring the launcher
* @param lastExc if retrying, the last exception to display as an error message
* @return the chosen arguments, or null if the user cancels at the prompt
*/
public Map<String, ?> getLauncherArgs(boolean prompt, LaunchConfigurator configurator,
Throwable lastExc) {
return prompt
? configurator.configureLauncher(this, promptLauncherArgs(configurator, lastExc),
RelPrompt.AFTER)
: configurator.configureLauncher(this, loadLastLauncherArgs(false), RelPrompt.NONE);
}
public Map<String, ?> getLauncherArgs(boolean prompt) {
return getLauncherArgs(prompt, LaunchConfigurator.NOP, null);
}
protected PtyFactory getPtyFactory() {
return PtyFactory.local();
}
protected PtyTerminalSession runInTerminal(List<String> commandLine, Map<String, String> env,
Collection<TerminalSession> subordinates)
throws IOException {
PtyFactory factory = getPtyFactory();
Pty pty = factory.openpty();
PtyParent parent = pty.getParent();
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
parent.getInputStream(), parent.getOutputStream());
terminal.setSubTitle(ShellUtils.generateLine(commandLine));
TerminalListener resizeListener = new TerminalListener() {
@Override
public void resized(short cols, short rows) {
parent.setWindowSize(cols, rows);
}
};
terminal.addTerminalListener(resizeListener);
env.put("TERM", "xterm-256color");
PtySession session = pty.getChild().session(commandLine.toArray(String[]::new), env);
Thread waiter = new Thread(() -> {
try {
session.waitExited();
terminal.terminated();
pty.close();
for (TerminalSession ss : subordinates) {
ss.terminate();
}
}
catch (InterruptedException | IOException e) {
Msg.error(this, e);
}
}, "Waiter: " + getConfigName());
waiter.start();
PtyTerminalSession terminalSession =
new PtyTerminalSession(terminal, pty, session, waiter);
terminal.setTerminateAction(() -> {
tool.execute(new TerminateSessionTask(terminalSession));
});
return terminalSession;
}
protected NullPtyTerminalSession nullPtyTerminal() throws IOException {
PtyFactory factory = getPtyFactory();
Pty pty = factory.openpty();
PtyParent parent = pty.getParent();
Terminal terminal = terminalService.createWithStreams(Charset.forName("UTF-8"),
parent.getInputStream(), parent.getOutputStream());
TerminalListener resizeListener = new TerminalListener() {
@Override
public void resized(short cols, short rows) {
parent.setWindowSize(cols, rows);
}
};
terminal.addTerminalListener(resizeListener);
String name = pty.getChild().nullSession();
terminal.setSubTitle(name);
NullPtyTerminalSession terminalSession = new NullPtyTerminalSession(terminal, pty, name);
terminal.setTerminateAction(() -> {
tool.execute(new TerminateSessionTask(terminalSession));
});
return terminalSession;
}
protected abstract void launchBackEnd(TaskMonitor monitor,
Map<String, TerminalSession> sessions, Map<String, ?> args, SocketAddress address)
throws Exception;
@Override
public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
TraceRmiService service = tool.getService(TraceRmiService.class);
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
final PromptMode mode = configurator.getPromptMode();
boolean prompt = mode == PromptMode.ALWAYS;
TraceRmiAcceptor acceptor = null;
Map<String, TerminalSession> sessions = new LinkedHashMap<>();
TraceRmiConnection connection = null;
Trace trace = null;
Throwable lastExc = null;
monitor.setMaximum(5);
while (true) {
monitor.setMessage("Gathering arguments");
Map<String, ?> args = getLauncherArgs(prompt, configurator, lastExc);
if (args == null) {
if (lastExc == null) {
lastExc = new CancelledException();
}
return new LaunchResult(program, sessions, connection, trace, lastExc);
}
acceptor = null;
sessions.clear();
connection = null;
trace = null;
lastExc = null;
try {
monitor.setMessage("Listening for connection");
acceptor = service.acceptOne(new InetSocketAddress("127.0.0.1", 0));
monitor.setMessage("Launching back-end");
launchBackEnd(monitor, sessions, args, acceptor.getAddress());
monitor.setMessage("Waiting for connection");
acceptor.setTimeout(getTimeoutMillis());
connection = acceptor.accept();
monitor.setMessage("Waiting for trace");
trace = connection.waitForTrace(getTimeoutMillis());
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
monitor.setMessage("Waiting for module mapping");
try {
listenForMapping(mappingService, connection, trace).get(getTimeoutMillis(),
TimeUnit.MILLISECONDS);
}
catch (TimeoutException e) {
monitor.setMessage(
"Timed out waiting for module mapping. Invoking the mapper.");
Collection<ModuleMapEntry> mapped;
try {
mapped = invokeMapper(monitor, mappingService, trace);
}
catch (CancelledException ce) {
throw new CancellationException(e.getMessage());
}
if (mapped.isEmpty()) {
monitor.setMessage(
"Could not formulate a mapping with the target program. " +
"Continuing without one.");
Msg.showWarn(this, null, "Launch " + program,
"The resulting target process has no mapping to the static image " +
program + ". Intervention is required before static and dynamic " +
"addresses can be translated. Check the target's module list.");
}
}
}
catch (Exception e) {
lastExc = e;
prompt = mode != PromptMode.NEVER;
if (prompt) {
continue;
}
return new LaunchResult(program, sessions, connection, trace, lastExc);
}
return new LaunchResult(program, sessions, connection, trace, null);
}
}
}

View File

@ -0,0 +1,198 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;
import javax.swing.*;
import org.jdom.Element;
import org.jdom.JDOMException;
import docking.ActionContext;
import docking.PopupMenuHandler;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.menu.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.framework.options.SaveState;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.ProgramUserData;
import ghidra.util.*;
import ghidra.util.xml.XmlUtilities;
public class LaunchAction extends MultiActionDockingAction {
public static final String NAME = "Launch";
public static final Icon ICON = DebuggerResources.ICON_DEBUGGER;
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
public static final String HELP_ANCHOR = "launch_tracermi";
private final TraceRmiLauncherServicePlugin plugin;
private MenuActionDockingToolbarButton button;
public LaunchAction(TraceRmiLauncherServicePlugin plugin) {
super(NAME, plugin.getName());
this.plugin = plugin;
setToolBarData(new ToolBarData(ICON, GROUP, "A"));
setHelpLocation(new HelpLocation(plugin.getName(), HELP_ANCHOR));
}
protected String[] prependConfigAndLaunch(List<String> menuPath) {
Program program = plugin.currentProgram;
return Stream.concat(
Stream.of("Configure and Launch " + program.getName() + " using..."),
menuPath.stream()).toArray(String[]::new);
}
record ConfigLast(String configName, long last) {
}
ConfigLast checkSavedConfig(ProgramUserData userData, String propName) {
if (!propName.startsWith(AbstractTraceRmiLaunchOffer.PREFIX_DBGLAUNCH)) {
return null;
}
String configName =
propName.substring(AbstractTraceRmiLaunchOffer.PREFIX_DBGLAUNCH.length());
String propVal = Objects.requireNonNull(
userData.getStringProperty(propName, null));
Element element;
try {
element = XmlUtilities.fromString(propVal);
}
catch (JDOMException | IOException e) {
Msg.error(this, "Could not load launcher config for " + configName + ": " + e, e);
return null;
}
SaveState state = new SaveState(element);
if (!state.hasValue("last")) {
return null;
}
return new ConfigLast(configName, state.getLong("last", 0));
}
ConfigLast findMostRecentConfig() {
Program program = plugin.currentProgram;
ConfigLast best = null;
ProgramUserData userData = program.getProgramUserData();
for (String propName : userData.getStringPropertyNames()) {
ConfigLast candidate = checkSavedConfig(userData, propName);
if (candidate == null) {
continue;
}
else if (best == null) {
best = candidate;
}
else if (candidate.last > best.last) {
best = candidate;
}
}
return best;
}
@Override
public List<DockingActionIf> getActionList(ActionContext context) {
Program program = plugin.currentProgram;
Collection<TraceRmiLaunchOffer> offers = plugin.getOffers(program);
List<DockingActionIf> actions = new ArrayList<>();
ProgramUserData userData = program.getProgramUserData();
Map<String, Long> saved = new HashMap<>();
for (String propName : userData.getStringPropertyNames()) {
ConfigLast check = checkSavedConfig(userData, propName);
if (check == null) {
continue;
}
saved.put(check.configName, check.last);
}
for (TraceRmiLaunchOffer offer : offers) {
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
.popupMenuPath(prependConfigAndLaunch(offer.getMenuPath()))
.popupMenuGroup(offer.getMenuGroup(), offer.getMenuOrder())
.popupMenuIcon(offer.getIcon())
.helpLocation(offer.getHelpLocation())
.enabledWhen(ctx -> true)
.onAction(ctx -> plugin.configureAndLaunch(offer))
.build());
Long last = saved.get(offer.getConfigName());
if (last == null) {
continue;
}
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
.popupMenuPath("Re-launch " + program.getName() + " using " + offer.getTitle())
.popupMenuGroup("0", "%016x".formatted(Long.MAX_VALUE - last))
.popupMenuIcon(offer.getIcon())
.helpLocation(offer.getHelpLocation())
.enabledWhen(ctx -> true)
.onAction(ctx -> plugin.relaunch(offer))
.build());
}
return actions;
}
class MenuActionDockingToolbarButton extends MultipleActionDockingToolbarButton {
public MenuActionDockingToolbarButton(MultiActionDockingActionIf action) {
super(action);
}
@Override
protected JPopupMenu doCreateMenu() {
ActionContext context = getActionContext();
List<DockingActionIf> actionList = getActionList(context);
MenuHandler handler =
new PopupMenuHandler(plugin.getTool().getWindowManager(), context);
MenuManager manager =
new MenuManager("Launch", (char) 0, GROUP, true, handler, null);
for (DockingActionIf action : actionList) {
manager.addAction(action);
}
return manager.getPopupMenu();
}
@Override
protected JPopupMenu showPopup() {
// Make accessible to this file
return super.showPopup();
}
}
@Override
public JButton doCreateButton() {
return button = new MenuActionDockingToolbarButton(this);
}
@Override
public void actionPerformed(ActionContext context) {
// See comment on super method about use of runLater
ConfigLast last = findMostRecentConfig();
if (last == null) {
Swing.runLater(() -> button.showPopup());
return;
}
for (TraceRmiLaunchOffer offer : plugin.getOffers(plugin.currentProgram)) {
if (offer.getConfigName().equals(last.configName)) {
plugin.relaunch(offer);
return;
}
}
Swing.runLater(() -> button.showPopup());
}
}

View File

@ -0,0 +1,107 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JButton;
import javax.swing.JPanel;
import org.apache.commons.text.StringEscapeUtils;
import docking.DockingWindowManager;
import docking.widgets.OptionDialog;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.pathmanager.*;
import utility.function.Callback;
public class ScriptPathsPropertyEditor extends AbstractTypedPropertyEditor<String> {
@Override
protected String fromText(String text) {
return text;
}
@Override
public String getJavaInitializationString() {
return "\"" + StringEscapeUtils.escapeJava(getValue()) + "\"";
}
@Override
public Component getCustomEditor() {
return new ScriptPathsEditor();
}
protected class ScriptPathsEditor extends JPanel {
public ScriptPathsEditor() {
super(new BorderLayout());
JButton button = new JButton("Edit Paths");
button.addActionListener(this::showDialog);
add(button);
}
protected void showDialog(ActionEvent evt) {
DockingWindowManager.showDialog(this, new ScriptPathsDialog());
}
}
protected class ScriptPathsDialog extends AbstractPathsDialog {
protected ScriptPathsDialog() {
super("Debugger Launch Script Paths");
}
@Override
protected String[] loadPaths() {
return getValue().lines().filter(d -> !d.isBlank()).toArray(String[]::new);
}
@Override
protected void savePaths(String[] paths) {
setValue(Stream.of(paths).collect(Collectors.joining("\n")));
}
@Override
protected PathnameTablePanel newPathnameTablePanel() {
PathnameTablePanel tablePanel = new ScriptPathsPanel(this::reset);
tablePanel.setFileChooserProperties(getTitle(), "DebuggerLaunchScriptDirectory",
GhidraFileChooserMode.DIRECTORIES_ONLY, true, null);
return tablePanel;
}
}
protected class ScriptPathsPanel extends PathnameTablePanel {
public ScriptPathsPanel(Callback resetCallback) {
// disable edits, top/bottom irrelevant, unordered
super(null, resetCallback, false, false, false);
}
@Override
protected int promptConfirmReset() {
String confirmation = """
<html><body width="200px">
Are you sure you would like to reload the Debugger's launcher script paths?
This will reset any changes you've made so far.
</html>""";
String header = "Reset Script Paths?";
return OptionDialog.showYesNoDialog(this, header, confirmation);
}
}
}

View File

@ -0,0 +1,244 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;
import docking.action.DockingActionIf;
import docking.action.builder.ActionBuilder;
import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction;
import ghidra.app.services.*;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchConfigurator;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.PromptMode;
import ghidra.debug.api.tracermi.TraceRmiLaunchOpinion;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.bean.opteditor.OptionsVetoException;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
@PluginInfo(
shortDescription = "GUI elements to launch targets using Trace RMI",
description = """
Provides menus and toolbar actions to launch Trace RMI targets.
""",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.UNSTABLE,
eventsConsumed = {
ProgramActivatedPluginEvent.class,
ProgramClosedPluginEvent.class,
},
servicesRequired = {
TraceRmiService.class,
TerminalService.class,
},
servicesProvided = {
TraceRmiLauncherService.class,
})
public class TraceRmiLauncherServicePlugin extends Plugin
implements TraceRmiLauncherService, OptionsChangeListener {
protected static final String OPTION_NAME_SCRIPT_PATHS = "Script Paths";
private final static LaunchConfigurator PROMPT = new LaunchConfigurator() {
@Override
public PromptMode getPromptMode() {
return PromptMode.ALWAYS;
}
};
private static abstract class AbstractLaunchTask extends Task {
final TraceRmiLaunchOffer offer;
public AbstractLaunchTask(TraceRmiLaunchOffer offer) {
super(offer.getTitle(), true, true, true);
this.offer = offer;
}
}
private static class ReLaunchTask extends AbstractLaunchTask {
public ReLaunchTask(TraceRmiLaunchOffer offer) {
super(offer);
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
offer.launchProgram(monitor);
}
}
private static class ConfigureAndLaunchTask extends AbstractLaunchTask {
public ConfigureAndLaunchTask(TraceRmiLaunchOffer offer) {
super(offer);
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
offer.launchProgram(monitor, PROMPT);
}
}
public static File getProgramPath(Program program) {
if (program == null) {
return null;
}
String path = program.getExecutablePath();
if (path == null) {
return null;
}
File file = new File(path);
try {
if (!file.canExecute()) {
return null;
}
return file.getCanonicalFile();
}
catch (SecurityException | IOException e) {
Msg.error(TraceRmiLauncherServicePlugin.class, "Cannot examine file " + path, e);
return null;
}
}
protected final ToolOptions options;
protected Program currentProgram;
protected LaunchAction launchAction;
protected List<DockingActionIf> currentLaunchers = new ArrayList<>();
public TraceRmiLauncherServicePlugin(PluginTool tool) {
super(tool);
this.options = tool.getOptions(DebuggerPluginPackage.NAME);
this.options.addOptionsChangeListener(this);
createActions();
}
@Override
protected void init() {
super.init();
for (TraceRmiLaunchOpinion opinion : ClassSearcher
.getInstances(TraceRmiLaunchOpinion.class)) {
opinion.registerOptions(options);
}
}
protected void createActions() {
launchAction = new LaunchAction(this);
tool.addAction(launchAction);
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) throws OptionsVetoException {
for (TraceRmiLaunchOpinion opinion : ClassSearcher
.getInstances(TraceRmiLaunchOpinion.class)) {
if (opinion.requiresRefresh(optionName)) {
updateLauncherMenu();
return;
}
}
}
@Override
public Collection<TraceRmiLaunchOpinion> getOpinions() {
return ClassSearcher.getInstances(TraceRmiLaunchOpinion.class);
}
@Override
public Collection<TraceRmiLaunchOffer> getOffers(Program program) {
if (program == null) {
return List.of();
}
return ClassSearcher.getInstances(TraceRmiLaunchOpinion.class)
.stream()
.flatMap(op -> op.getOffers(program, getTool()).stream())
.toList();
}
protected void relaunch(TraceRmiLaunchOffer offer) {
tool.execute(new ReLaunchTask(offer));
}
protected void configureAndLaunch(TraceRmiLaunchOffer offer) {
tool.execute(new ConfigureAndLaunchTask(offer));
}
protected String[] constructLaunchMenuPrefix() {
return new String[] {
DebuggerPluginPackage.NAME,
"Configure and Launch " + currentProgram.getName() + " using..." };
}
protected String[] prependConfigAndLaunch(List<String> menuPath) {
return Stream.concat(
Stream.of(constructLaunchMenuPrefix()),
menuPath.stream()).toArray(String[]::new);
}
private void updateLauncherMenu() {
Collection<TraceRmiLaunchOffer> offers = currentProgram == null
? List.of()
: getOffers(currentProgram);
synchronized (currentLaunchers) {
for (DockingActionIf launcher : currentLaunchers) {
tool.removeAction(launcher);
}
currentLaunchers.clear();
if (!offers.isEmpty()) {
tool.setMenuGroup(constructLaunchMenuPrefix(), DebugProgramAction.GROUP, "zz");
}
for (TraceRmiLaunchOffer offer : offers) {
currentLaunchers.add(new ActionBuilder(offer.getConfigName(), getName())
.menuPath(prependConfigAndLaunch(offer.getMenuPath()))
.menuGroup(offer.getMenuGroup(), offer.getMenuOrder())
.menuIcon(offer.getIcon())
.helpLocation(offer.getHelpLocation())
.enabledWhen(ctx -> true)
.onAction(ctx -> configureAndLaunch(offer))
.buildAndInstall(tool));
}
}
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof ProgramActivatedPluginEvent evt) {
currentProgram = evt.getActiveProgram();
updateLauncherMenu();
}
if (event instanceof ProgramClosedPluginEvent evt) {
if (currentProgram == evt.getProgram()) {
currentProgram = null;
updateLauncherMenu();
}
}
}
}

View File

@ -0,0 +1,633 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.io.*;
import java.math.BigInteger;
import java.net.*;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.Icon;
import generic.theme.GIcon;
import generic.theme.Gui;
import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.util.ShellUtils;
import ghidra.framework.Application;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* A launcher implemented by a simple UNIX shell script.
*
* <p>
* The script must start with an attributes header in a comment block. Some attributes are required.
* Others are optional:
* <ul>
* <li>{@code @menu-path}: <b>(Required)</b></li>
* </ul>
*/
public class UnixShellScriptTraceRmiLaunchOffer extends AbstractTraceRmiLaunchOffer {
public static final String SHEBANG = "#!";
public static final String AT_TITLE = "@title";
public static final String AT_DESC = "@desc";
public static final String AT_MENU_PATH = "@menu-path";
public static final String AT_MENU_GROUP = "@menu-group";
public static final String AT_MENU_ORDER = "@menu-order";
public static final String AT_ICON = "@icon";
public static final String AT_HELP = "@help";
public static final String AT_ENUM = "@enum";
public static final String AT_ENV = "@env";
public static final String AT_ARG = "@arg";
public static final String AT_ARGS = "@args";
public static final String AT_TTY = "@tty";
public static final String PREFIX_ENV = "env:";
public static final String PREFIX_ARG = "arg:";
public static final String KEY_ARGS = "args";
public static final String MSGPAT_INVALID_HELP_SYNTAX =
"%s: Invalid %s syntax. Use Topic#anchor";
public static final String MSGPAT_INVALID_ENUM_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type Choice1 [ChoiceN...]";
public static final String MSGPAT_INVALID_ENV_SYNTAX =
"%s: Invalid %s syntax. Use NAME:type=default \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_ARG_SYNTAX =
"%s: Invalid %s syntax. Use :type \"Display\" \"Tool Tip\"";
public static final String MSGPAT_INVALID_ARGS_SYNTAX =
"%s: Invalid %s syntax. Use \"Display\" \"Tool Tip\"";
protected record Location(String fileName, int lineNo) {
@Override
public String toString() {
return "%s:%d".formatted(fileName, lineNo);
}
}
protected interface OptType<T> {
static OptType<?> parse(Location loc, String typeName,
Map<String, UserType<?>> userEnums) {
OptType<?> type = switch (typeName) {
case "str" -> BaseType.STRING;
case "int" -> BaseType.INT;
case "bool" -> BaseType.BOOL;
default -> userEnums.get(typeName);
};
if (type == null) {
Msg.error(AttributesParser.class, "%s: Invalid type %s".formatted(loc, typeName));
return null;
}
return type;
}
default TypeAndDefault<T> withCastDefault(Object defaultValue) {
return new TypeAndDefault<>(this, cls().cast(defaultValue));
}
Class<T> cls();
T decode(Location loc, String str);
ParameterDescription<T> createParameter(String name, T defaultValue, String display,
String description);
}
protected interface BaseType<T> extends OptType<T> {
static BaseType<?> parse(Location loc, String typeName) {
BaseType<?> type = switch (typeName) {
case "str" -> BaseType.STRING;
case "int" -> BaseType.INT;
case "bool" -> BaseType.BOOL;
default -> null;
};
if (type == null) {
Msg.error(AttributesParser.class,
"%s: Invalid base type %s".formatted(loc, typeName));
return null;
}
return type;
}
public static final BaseType<String> STRING = new BaseType<>() {
@Override
public Class<String> cls() {
return String.class;
}
@Override
public String decode(Location loc, String str) {
return str;
}
};
public static final BaseType<BigInteger> INT = new BaseType<>() {
@Override
public Class<BigInteger> cls() {
return BigInteger.class;
}
@Override
public BigInteger decode(Location loc, String str) {
try {
if (str.startsWith("0x")) {
return new BigInteger(str.substring(2), 16);
}
return new BigInteger(str);
}
catch (NumberFormatException e) {
Msg.error(AttributesParser.class,
("%s: Invalid int for %s: %s. You may prefix with 0x for hexadecimal. " +
"Otherwise, decimal is used.").formatted(loc, AT_ENV, str));
return null;
}
}
};
public static final BaseType<Boolean> BOOL = new BaseType<>() {
@Override
public Class<Boolean> cls() {
return Boolean.class;
}
@Override
public Boolean decode(Location loc, String str) {
Boolean result = switch (str) {
case "true" -> true;
case "false" -> false;
default -> null;
};
if (result == null) {
Msg.error(AttributesParser.class,
"%s: Invalid bool for %s: %s. Only true or false (in lower case) is allowed."
.formatted(loc, AT_ENV, str));
return null;
}
return result;
}
};
default UserType<T> withCastChoices(List<?> choices) {
return new UserType<>(this, choices.stream().map(cls()::cast).toList());
}
@Override
default ParameterDescription<T> createParameter(String name, T defaultValue, String display,
String description) {
return ParameterDescription.create(cls(), name, false, defaultValue, display,
description);
}
}
protected record UserType<T> (BaseType<T> base, List<T> choices) implements OptType<T> {
@Override
public Class<T> cls() {
return base.cls();
}
@Override
public T decode(Location loc, String str) {
return base.decode(loc, str);
}
@Override
public ParameterDescription<T> createParameter(String name, T defaultValue, String display,
String description) {
return ParameterDescription.choices(cls(), name, choices, defaultValue, display,
description);
}
}
protected record TypeAndDefault<T> (OptType<T> type, T defaultValue) {
public static TypeAndDefault<?> parse(Location loc, String typeName, String defaultString,
Map<String, UserType<?>> userEnums) {
OptType<?> tac = OptType.parse(loc, typeName, userEnums);
if (tac == null) {
return null;
}
Object value = tac.decode(loc, defaultString);
if (value == null) {
return null;
}
return tac.withCastDefault(value);
}
public ParameterDescription<T> createParameter(String name, String display,
String description) {
return type.createParameter(name, defaultValue, display, description);
}
}
protected static class AttributesParser {
protected int argc = 0;
protected String title;
protected StringBuilder description;
protected List<String> menuPath;
protected String menuGroup;
protected String menuOrder;
protected String iconId;
protected HelpLocation helpLocation;
protected final Map<String, UserType<?>> userTypes = new HashMap<>();
protected final Map<String, ParameterDescription<?>> parameters = new LinkedHashMap<>();
protected final Set<String> extraTtys = new LinkedHashSet<>();
/**
* Process a line in the metadata comment block
*
* @param line the line, excluding any comment delimiters
*/
public void parseLine(Location loc, String line) {
String afterHash = line.stripLeading().substring(1);
if (afterHash.isBlank()) {
return;
}
String[] parts = afterHash.split("\\s+", 2);
if (!parts[0].startsWith("@")) {
return;
}
if (parts.length < 2) {
Msg.error(this, "%s: Too few tokens: %s".formatted(loc, line));
return;
}
switch (parts[0].trim()) {
case AT_TITLE -> parseTitle(loc, parts[1]);
case AT_DESC -> parseDesc(loc, parts[1]);
case AT_MENU_PATH -> parseMenuPath(loc, parts[1]);
case AT_MENU_GROUP -> parseMenuGroup(loc, parts[1]);
case AT_MENU_ORDER -> parseMenuOrder(loc, parts[1]);
case AT_ICON -> parseIcon(loc, parts[1]);
case AT_HELP -> parseHelp(loc, parts[1]);
case AT_ENUM -> parseEnum(loc, parts[1]);
case AT_ENV -> parseEnv(loc, parts[1]);
case AT_ARG -> parseArg(loc, parts[1], ++argc);
case AT_ARGS -> parseArgs(loc, parts[1]);
case AT_TTY -> parseTty(loc, parts[1]);
default -> parseUnrecognized(loc, line);
}
}
protected void parseTitle(Location loc, String str) {
if (title != null) {
Msg.warn(this, "%s: Duplicate @title".formatted(loc));
}
title = str;
}
protected void parseDesc(Location loc, String str) {
if (description == null) {
description = new StringBuilder();
}
description.append(str);
description.append("\n");
}
protected void parseMenuPath(Location loc, String str) {
if (menuPath != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_PATH));
}
menuPath = List.of(str.trim().split("\\."));
if (menuPath.isEmpty()) {
Msg.error(this,
"%s: Empty %s. Ignoring.".formatted(loc, AT_MENU_PATH));
}
}
protected void parseMenuGroup(Location loc, String str) {
if (menuGroup != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_GROUP));
}
menuGroup = str;
}
protected void parseMenuOrder(Location loc, String str) {
if (menuOrder != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_MENU_ORDER));
}
menuOrder = str;
}
protected void parseIcon(Location loc, String str) {
if (iconId != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_ICON));
}
iconId = str.trim();
if (!Gui.hasIcon(iconId)) {
Msg.error(this,
"%s: Icon id %s not registered in the theme".formatted(loc, iconId));
}
}
protected void parseHelp(Location loc, String str) {
if (helpLocation != null) {
Msg.warn(this, "%s: Duplicate %s".formatted(loc, AT_HELP));
}
String[] parts = str.trim().split("#", 2);
if (parts.length != 2) {
Msg.error(this, MSGPAT_INVALID_HELP_SYNTAX.formatted(loc, AT_HELP));
return;
}
helpLocation = new HelpLocation(parts[0].trim(), parts[1].trim());
}
protected void parseEnum(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() < 2) {
Msg.error(this, MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM));
return;
}
String[] nameParts = parts.get(0).split(":", 2);
if (nameParts.length != 2) {
Msg.error(this, MSGPAT_INVALID_ENUM_SYNTAX.formatted(loc, AT_ENUM));
return;
}
String name = nameParts[0].trim();
BaseType<?> baseType = BaseType.parse(loc, nameParts[1]);
if (baseType == null) {
return;
}
List<?> choices = parts.stream().skip(1).map(s -> baseType.decode(loc, s)).toList();
if (choices.contains(null)) {
return;
}
UserType<?> userType = baseType.withCastChoices(choices);
if (userTypes.put(name, userType) != null) {
Msg.warn(this, "%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENUM, name));
}
}
protected void parseEnv(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
String[] nameParts = parts.get(0).split(":", 2);
if (nameParts.length != 2) {
Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
String trimmed = nameParts[0].trim();
String name = PREFIX_ENV + trimmed;
String[] tadParts = nameParts[1].split("=", 2);
if (tadParts.length != 2) {
Msg.error(this, MSGPAT_INVALID_ENV_SYNTAX.formatted(loc, AT_ENV));
return;
}
TypeAndDefault<?> tad =
TypeAndDefault.parse(loc, tadParts[0].trim(), tadParts[1].trim(), userTypes);
ParameterDescription<?> param = tad.createParameter(name, parts.get(1), parts.get(2));
if (parameters.put(name, param) != null) {
Msg.warn(this, "%s: Duplicate %s %s. Replaced.".formatted(loc, AT_ENV, trimmed));
}
}
protected void parseArg(Location loc, String str, int argNum) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 3) {
Msg.error(this, MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
String colonType = parts.get(0).trim();
if (!colonType.startsWith(":")) {
Msg.error(this, MSGPAT_INVALID_ARG_SYNTAX.formatted(loc, AT_ARG));
return;
}
OptType<?> type = OptType.parse(loc, colonType.substring(1), userTypes);
if (type == null) {
return;
}
String name = PREFIX_ARG + argNum;
parameters.put(name, ParameterDescription.create(type.cls(), name, true, null,
parts.get(1), parts.get(2)));
}
protected void parseArgs(Location loc, String str) {
List<String> parts = ShellUtils.parseArgs(str);
if (parts.size() != 2) {
Msg.error(this, MSGPAT_INVALID_ARGS_SYNTAX.formatted(loc, AT_ARGS));
return;
}
ParameterDescription<String> parameter = ParameterDescription.create(String.class,
"args", false, "", parts.get(0), parts.get(1));
if (parameters.put(KEY_ARGS, parameter) != null) {
Msg.warn(this, "%s: Duplicate %s. Replaced".formatted(loc, AT_ARGS));
}
}
protected void parseTty(Location loc, String str) {
if (!extraTtys.add(str)) {
Msg.warn(this, "%s: Duplicate %s. Ignored".formatted(loc, AT_TTY));
}
}
protected void parseUnrecognized(Location loc, String line) {
Msg.warn(this, "%s: Unrecognized metadata: %s".formatted(loc, line));
}
protected void validate(String fileName) {
if (title == null) {
Msg.error(this, "%s is required. Using script file name.".formatted(AT_TITLE));
title = fileName;
}
if (menuPath == null) {
menuPath = List.of(title);
}
if (menuGroup == null) {
menuGroup = "";
}
if (menuOrder == null) {
menuOrder = "";
}
if (iconId == null) {
iconId = "icon.debugger";
}
}
public String getDescription() {
return description == null ? null : description.toString();
}
}
/**
* Create a launch offer from the given shell script.
*
* @param program the current program, usually the target image. In general, this should be used
* for at least two purposes. 1) To populate the default command line. 2) To ensure
* the target image is mapped in the resulting target trace.
* @throws FileNotFoundException
*/
public static UnixShellScriptTraceRmiLaunchOffer create(Program program, PluginTool tool,
File script) throws FileNotFoundException {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(script)))) {
AttributesParser attrs = new AttributesParser();
String line;
for (int lineNo = 1; (line = reader.readLine()) != null; lineNo++) {
if (line.startsWith(SHEBANG) && lineNo == 1) {
}
else if (line.isBlank()) {
continue;
}
else if (line.stripLeading().startsWith("#")) {
attrs.parseLine(new Location(script.getName(), lineNo), line);
}
else {
break;
}
}
attrs.validate(script.getName());
return new UnixShellScriptTraceRmiLaunchOffer(program, tool, script,
"UNIX_SHELL:" + script.getName(), attrs.title, attrs.getDescription(),
attrs.menuPath, attrs.menuGroup, attrs.menuOrder, new GIcon(attrs.iconId),
attrs.helpLocation, attrs.parameters, attrs.extraTtys);
}
catch (FileNotFoundException e) {
// Avoid capture by IOException
throw e;
}
catch (IOException e) {
throw new AssertionError(e);
}
}
protected final File script;
protected final String configName;
protected final String title;
protected final String description;
protected final List<String> menuPath;
protected final String menuGroup;
protected final String menuOrder;
protected final Icon icon;
protected final HelpLocation helpLocation;
protected final Map<String, ParameterDescription<?>> parameters;
protected final List<String> extraTtys;
public UnixShellScriptTraceRmiLaunchOffer(Program program, PluginTool tool, File script,
String configName, String title, String description, List<String> menuPath,
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
Map<String, ParameterDescription<?>> parameters, Collection<String> extraTtys) {
super(program, tool);
this.script = script;
this.configName = configName;
this.title = title;
this.description = description;
this.menuPath = List.copyOf(menuPath);
this.menuGroup = menuGroup;
this.menuOrder = menuOrder;
this.icon = icon;
this.helpLocation = helpLocation;
this.parameters = Collections.unmodifiableMap(new LinkedHashMap<>(parameters));
this.extraTtys = List.copyOf(extraTtys);
}
@Override
public String getConfigName() {
return configName;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getDescription() {
return description;
}
@Override
public List<String> getMenuPath() {
return menuPath;
}
@Override
public String getMenuGroup() {
return menuGroup;
}
@Override
public String getMenuOrder() {
return menuOrder;
}
@Override
public Icon getIcon() {
return icon;
}
@Override
public HelpLocation getHelpLocation() {
return helpLocation;
}
@Override
public Map<String, ParameterDescription<?>> getParameters() {
return parameters;
}
protected static String addrToString(InetAddress address) {
if (address.isAnyLocalAddress()) {
return "127.0.0.1"; // Can't connect to 0.0.0.0 as such. Choose localhost.
}
return address.getHostAddress();
}
protected static String sockToString(SocketAddress address) {
if (address instanceof InetSocketAddress tcp) {
return addrToString(tcp.getAddress()) + ":" + tcp.getPort();
}
throw new AssertionError("Unhandled address type " + address);
}
@Override
protected void launchBackEnd(TaskMonitor monitor, Map<String, TerminalSession> sessions,
Map<String, ?> args, SocketAddress address) throws Exception {
List<String> commandLine = new ArrayList<>();
Map<String, String> env = new HashMap<>(System.getenv());
commandLine.add(script.getAbsolutePath());
env.put("GHIDRA_HOME", Application.getInstallationDirectory().getAbsolutePath());
env.put("GHIDRA_TRACE_RMI_ADDR", sockToString(address));
ParameterDescription<?> paramDesc;
for (int i = 1; (paramDesc = parameters.get("arg:" + i)) != null; i++) {
commandLine.add(Objects.toString(paramDesc.get(args)));
}
paramDesc = parameters.get("args");
if (paramDesc != null) {
commandLine.addAll(ShellUtils.parseArgs((String) paramDesc.get(args)));
}
for (Entry<String, ParameterDescription<?>> ent : parameters.entrySet()) {
String key = ent.getKey();
if (key.startsWith(PREFIX_ENV)) {
String varName = key.substring(PREFIX_ENV.length());
env.put(varName, Objects.toString(ent.getValue().get(args)));
}
}
for (String tty : extraTtys) {
NullPtyTerminalSession ns = nullPtyTerminal();
env.put(tty, ns.name());
sessions.put(ns.name(), ns);
}
sessions.put("Shell", runInTerminal(commandLine, env, sessions.values()));
}
}

View File

@ -0,0 +1,81 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.debug.api.tracermi.TraceRmiLaunchOpinion;
import ghidra.framework.Application;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
public class UnixShellScriptTraceRmiLaunchOpinion implements TraceRmiLaunchOpinion {
@Override
public void registerOptions(Options options) {
String pluginName = PluginUtils.getPluginNameFromClass(TraceRmiLauncherServicePlugin.class);
options.registerOption(TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS,
OptionType.STRING_TYPE, "", new HelpLocation(pluginName, "options"),
"Paths to search for user-created debugger launchers", new ScriptPathsPropertyEditor());
}
@Override
public boolean requiresRefresh(String optionName) {
return TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS.equals(optionName);
}
protected Stream<ResourceFile> getModuleScriptPaths() {
return Application.findModuleSubDirectories("data/debugger-launchers").stream();
}
protected Stream<ResourceFile> getUserScriptPaths(PluginTool tool) {
Options options = tool.getOptions(DebuggerPluginPackage.NAME);
String scriptPaths =
options.getString(TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS, "");
return scriptPaths.lines().filter(d -> !d.isBlank()).map(ResourceFile::new);
}
protected Stream<ResourceFile> getScriptPaths(PluginTool tool) {
return Stream.concat(getModuleScriptPaths(), getUserScriptPaths(tool));
}
@Override
public Collection<TraceRmiLaunchOffer> getOffers(Program program, PluginTool tool) {
return getScriptPaths(tool)
.flatMap(rf -> Stream.of(rf.listFiles(crf -> crf.getName().endsWith(".sh"))))
.flatMap(sf -> {
try {
return Stream.of(UnixShellScriptTraceRmiLaunchOffer.create(program, tool,
sf.getFile(false)));
}
catch (Exception e) {
Msg.error(this, "Could not offer " + sf + ":" + e.getMessage(), e);
return Stream.of();
}
})
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,51 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.rmi.trace;
import java.util.concurrent.*;
import ghidra.debug.api.tracermi.RemoteAsyncResult;
import ghidra.util.Swing;
public class DefaultRemoteAsyncResult extends CompletableFuture<Object>
implements RemoteAsyncResult {
final ValueDecoder decoder;
public DefaultRemoteAsyncResult() {
this.decoder = ValueDecoder.DEFAULT;
}
public DefaultRemoteAsyncResult(OpenTrace open) {
this.decoder = open;
}
@Override
public Object get() throws InterruptedException, ExecutionException {
if (Swing.isSwingThread()) {
throw new AssertionError("Refusing indefinite wait on Swing thread");
}
return super.get();
}
@Override
public Object get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (Swing.isSwingThread() && unit.toSeconds(timeout) > 1) {
throw new AssertionError("Refusing a timeout > 1 second on Swing thread");
}
return super.get(timeout, unit);
}
}

View File

@ -17,9 +17,11 @@ package ghidra.app.plugin.core.debug.service.rmi.trace;
import java.util.*; import java.util.*;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod.Action; import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.debug.api.tracermi.RemoteMethodRegistry;
import ghidra.debug.api.tracermi.RemoteMethod.Action;
public class RemoteMethodRegistry { public class DefaultRemoteMethodRegistry implements RemoteMethodRegistry {
private final Map<String, RemoteMethod> map = new HashMap<>(); private final Map<String, RemoteMethod> map = new HashMap<>();
private final Map<Action, Set<RemoteMethod>> byAction = new HashMap<>(); private final Map<Action, Set<RemoteMethod>> byAction = new HashMap<>();
@ -30,18 +32,21 @@ public class RemoteMethodRegistry {
} }
} }
@Override
public Map<String, RemoteMethod> all() { public Map<String, RemoteMethod> all() {
synchronized (map) { synchronized (map) {
return Map.copyOf(map); return Map.copyOf(map);
} }
} }
@Override
public RemoteMethod get(String name) { public RemoteMethod get(String name) {
synchronized (map) { synchronized (map) {
return map.get(name); return map.get(name);
} }
} }
@Override
public Set<RemoteMethod> getByAction(Action action) { public Set<RemoteMethod> getByAction(Action action) {
synchronized (map) { synchronized (map) {
return byAction.getOrDefault(action, Set.of()); return byAction.getOrDefault(action, Set.of());

View File

@ -19,9 +19,11 @@ import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.SocketAddress; import java.net.SocketAddress;
public class TraceRmiAcceptor extends TraceRmiServer { import ghidra.debug.api.tracermi.TraceRmiAcceptor;
public TraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) { public class DefaultTraceRmiAcceptor extends TraceRmiServer implements TraceRmiAcceptor {
public DefaultTraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) {
super(plugin, address); super(plugin, address);
} }

View File

@ -16,6 +16,7 @@
package ghidra.app.plugin.core.debug.service.rmi.trace; package ghidra.app.plugin.core.debug.service.rmi.trace;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler.*; import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler.*;
import ghidra.debug.api.tracermi.TraceRmiError;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.rmi.trace.TraceRmi.*; import ghidra.rmi.trace.TraceRmi.*;

View File

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.rmi.trace;
import java.util.Map;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.debug.api.tracermi.RemoteMethod;
import ghidra.debug.api.tracermi.RemoteParameter;
import ghidra.debug.api.tracermi.RemoteMethod.Action;
import ghidra.trace.model.Trace;
public record RecordRemoteMethod(TraceRmiHandler handler, String name, Action action,
String description, Map<String, RemoteParameter> parameters, SchemaName retType)
implements RemoteMethod {
@Override
public DefaultRemoteAsyncResult invokeAsync(Map<String, Object> arguments) {
Trace trace = validate(arguments);
OpenTrace open = handler.getOpenTrace(trace);
return handler.invoke(open, name, arguments);
}
}

View File

@ -0,0 +1,49 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.service.rmi.trace;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.debug.api.tracermi.RemoteParameter;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.trace.model.Trace;
public record RecordRemoteParameter(TraceRmiHandler handler, String name, SchemaName type,
boolean required, ValueSupplier defaultValue, String display, String description)
implements RemoteParameter {
public Object getDefaultValue(Trace trace) {
OpenTrace open = handler.getOpenTrace(trace);
if (open == null) {
throw new IllegalArgumentException("Trace is not from this connection");
}
try {
return defaultValue().get(open);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
@Override
public Object getDefaultValue() {
try {
return defaultValue().get(ValueDecoder.DEFAULT);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
}

View File

@ -25,8 +25,7 @@ import java.nio.file.Paths;
import java.time.Instant; import java.time.Instant;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.*;
import java.util.concurrent.ExecutionException;
import java.util.stream.*; import java.util.stream.*;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
@ -35,16 +34,16 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import db.Transaction; import db.Transaction;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin; import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin;
import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod.Action;
import ghidra.app.plugin.core.debug.service.rmi.trace.RemoteMethod.RecordRemoteMethod;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathPattern;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.debug.api.tracermi.*;
import ghidra.debug.api.tracermi.RemoteMethod.Action;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.AutoService.Wiring; import ghidra.framework.plugintool.AutoService.Wiring;
@ -69,7 +68,7 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException; import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class TraceRmiHandler { public class TraceRmiHandler implements TraceRmiConnection {
public static final String VERSION = "10.4"; public static final String VERSION = "10.4";
protected static class VersionMismatchError extends TraceRmiError { protected static class VersionMismatchError extends TraceRmiError {
@ -139,6 +138,7 @@ public class TraceRmiHandler {
protected static class OpenTraceMap { protected static class OpenTraceMap {
private final Map<DoId, OpenTrace> byId = new HashMap<>(); private final Map<DoId, OpenTrace> byId = new HashMap<>();
private final Map<Trace, OpenTrace> byTrace = new HashMap<>(); private final Map<Trace, OpenTrace> byTrace = new HashMap<>();
private final CompletableFuture<OpenTrace> first = new CompletableFuture<>();
public synchronized boolean isEmpty() { public synchronized boolean isEmpty() {
return byId.isEmpty(); return byId.isEmpty();
@ -168,6 +168,11 @@ public class TraceRmiHandler {
public synchronized void put(OpenTrace openTrace) { public synchronized void put(OpenTrace openTrace) {
byId.put(openTrace.doId, openTrace); byId.put(openTrace.doId, openTrace);
byTrace.put(openTrace.trace, openTrace); byTrace.put(openTrace.trace, openTrace);
first.complete(openTrace);
}
public CompletableFuture<OpenTrace> getFirstAsync() {
return first;
} }
} }
@ -181,9 +186,9 @@ public class TraceRmiHandler {
private final OpenTraceMap openTraces = new OpenTraceMap(); private final OpenTraceMap openTraces = new OpenTraceMap();
private final Map<Tid, OpenTx> openTxes = new HashMap<>(); private final Map<Tid, OpenTx> openTxes = new HashMap<>();
private final RemoteMethodRegistry methodRegistry = new RemoteMethodRegistry(); private final DefaultRemoteMethodRegistry methodRegistry = new DefaultRemoteMethodRegistry();
// The remote must service requests and reply in the order received. // The remote must service requests and reply in the order received.
private final Deque<RemoteAsyncResult> xReqQueue = new ArrayDeque<>(); private final Deque<DefaultRemoteAsyncResult> xReqQueue = new ArrayDeque<>();
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
@ -201,6 +206,7 @@ public class TraceRmiHandler {
*/ */
public TraceRmiHandler(TraceRmiPlugin plugin, Socket socket) throws IOException { public TraceRmiHandler(TraceRmiPlugin plugin, Socket socket) throws IOException {
this.plugin = plugin; this.plugin = plugin;
plugin.addHandler(this);
this.socket = socket; this.socket = socket;
this.in = socket.getInputStream(); this.in = socket.getInputStream();
this.out = socket.getOutputStream(); this.out = socket.getOutputStream();
@ -211,17 +217,18 @@ public class TraceRmiHandler {
} }
protected void flushXReqQueue(Throwable exc) { protected void flushXReqQueue(Throwable exc) {
List<RemoteAsyncResult> copy; List<DefaultRemoteAsyncResult> copy;
synchronized (xReqQueue) { synchronized (xReqQueue) {
copy = List.copyOf(xReqQueue); copy = List.copyOf(xReqQueue);
xReqQueue.clear(); xReqQueue.clear();
} }
for (RemoteAsyncResult result : copy) { for (DefaultRemoteAsyncResult result : copy) {
result.completeExceptionally(exc); result.completeExceptionally(exc);
} }
} }
public void dispose() throws IOException { public void dispose() throws IOException {
plugin.removeHandler(this);
flushXReqQueue(new TraceRmiError("Socket closed")); flushXReqQueue(new TraceRmiError("Socket closed"));
socket.close(); socket.close();
while (!openTxes.isEmpty()) { while (!openTxes.isEmpty()) {
@ -248,12 +255,24 @@ public class TraceRmiHandler {
closed.complete(null); closed.complete(null);
} }
@Override
public void close() throws IOException {
dispose();
}
@Override
public boolean isClosed() { public boolean isClosed() {
return socket.isClosed() && closed.isDone(); return socket.isClosed() && closed.isDone();
} }
public void waitClosed() throws InterruptedException, ExecutionException { @Override
closed.get(); public void waitClosed() {
try {
closed.get();
}
catch (InterruptedException | ExecutionException e) {
throw new TraceRmiError(e);
}
} }
protected DomainFolder getOrCreateNewTracesFolder() protected DomainFolder getOrCreateNewTracesFolder()
@ -442,8 +461,9 @@ public class TraceRmiHandler {
req.getRequestActivate().getObject().getPath().getPath()); req.getRequestActivate().getObject().getPath().getPath());
case REQUEST_END_TX -> "endTx(%d)".formatted( case REQUEST_END_TX -> "endTx(%d)".formatted(
req.getRequestEndTx().getTxid().getId()); req.getRequestEndTx().getTxid().getId());
case REQUEST_START_TX -> "startTx(%d)".formatted( case REQUEST_START_TX -> "startTx(%d,%s)".formatted(
req.getRequestStartTx().getTxid().getId()); req.getRequestStartTx().getTxid().getId(),
req.getRequestStartTx().getDescription());
default -> null; default -> null;
}; };
} }
@ -728,7 +748,12 @@ public class TraceRmiHandler {
OpenTrace open = requireOpenTrace(req.getOid()); OpenTrace open = requireOpenTrace(req.getOid());
TraceObject object = open.getObject(req.getObject(), true); TraceObject object = open.getObject(req.getObject(), true);
DebuggerCoordinates coords = traceManager.getCurrent(); DebuggerCoordinates coords = traceManager.getCurrent();
coords = coords.object(object); if (coords.getTrace() == object.getTrace()) {
coords = coords.object(object);
}
else {
coords = DebuggerCoordinates.NOWHERE.object(object);
}
if (open.lastSnapshot != null) { if (open.lastSnapshot != null) {
coords = coords.snap(open.lastSnapshot.getKey()); coords = coords.snap(open.lastSnapshot.getKey());
} }
@ -738,7 +763,7 @@ public class TraceRmiHandler {
} }
else { else {
Trace currentTrace = traceManager.getCurrentTrace(); Trace currentTrace = traceManager.getCurrentTrace();
if (currentTrace == null || currentTrace == open.trace) { if (currentTrace == null || openTraces.getByTrace(currentTrace) != null) {
traceManager.activate(coords); traceManager.activate(coords);
} }
} }
@ -939,8 +964,7 @@ public class TraceRmiHandler {
RemoteMethod rm = new RecordRemoteMethod(this, m.getName(), new Action(m.getAction()), RemoteMethod rm = new RecordRemoteMethod(this, m.getName(), new Action(m.getAction()),
m.getDescription(), m.getParametersList() m.getDescription(), m.getParametersList()
.stream() .stream()
.collect(Collectors.toMap(MethodParameter::getName, .collect(Collectors.toMap(MethodParameter::getName, this::makeParameter)),
TraceRmiHandler::makeParameter)),
new SchemaName(m.getReturnType().getName())); new SchemaName(m.getReturnType().getName()));
methodRegistry.add(rm); methodRegistry.add(rm);
} }
@ -978,8 +1002,8 @@ public class TraceRmiHandler {
return ReplyPutRegisterValue.getDefaultInstance(); return ReplyPutRegisterValue.getDefaultInstance();
} }
protected static RemoteParameter makeParameter(MethodParameter mp) { protected RecordRemoteParameter makeParameter(MethodParameter mp) {
return new RemoteParameter(mp.getName(), new SchemaName(mp.getType().getName()), return new RecordRemoteParameter(this, mp.getName(), new SchemaName(mp.getType().getName()),
mp.getRequired(), ot -> ot.toValue(mp.getDefaultValue()), mp.getDisplay(), mp.getRequired(), ot -> ot.toValue(mp.getDefaultValue()), mp.getDisplay(),
mp.getDescription()); mp.getDescription());
} }
@ -1084,7 +1108,7 @@ public class TraceRmiHandler {
protected RootMessage.Builder handleXInvokeMethod(XReplyInvokeMethod xrep) { protected RootMessage.Builder handleXInvokeMethod(XReplyInvokeMethod xrep) {
String error = xrep.getError(); String error = xrep.getError();
RemoteAsyncResult result; DefaultRemoteAsyncResult result;
synchronized (xReqQueue) { synchronized (xReqQueue) {
result = xReqQueue.poll(); result = xReqQueue.poll();
} }
@ -1102,11 +1126,13 @@ public class TraceRmiHandler {
return null; return null;
} }
@Override
public SocketAddress getRemoteAddress() { public SocketAddress getRemoteAddress() {
return socket.getRemoteSocketAddress(); return socket.getRemoteSocketAddress();
} }
public RemoteMethodRegistry getMethods() { @Override
public DefaultRemoteMethodRegistry getMethods() {
return methodRegistry; return methodRegistry;
} }
@ -1121,20 +1147,20 @@ public class TraceRmiHandler {
return open; return open;
} }
protected RemoteAsyncResult invoke(OpenTrace open, String methodName, protected DefaultRemoteAsyncResult invoke(OpenTrace open, String methodName,
Map<String, Object> arguments) { Map<String, Object> arguments) {
RootMessage.Builder req = RootMessage.newBuilder(); RootMessage.Builder req = RootMessage.newBuilder();
XRequestInvokeMethod.Builder invoke = XRequestInvokeMethod.newBuilder() XRequestInvokeMethod.Builder invoke = XRequestInvokeMethod.newBuilder()
.setName(methodName) .setName(methodName)
.addAllArguments( .addAllArguments(
arguments.entrySet().stream().map(TraceRmiHandler::makeArgument).toList()); arguments.entrySet().stream().map(TraceRmiHandler::makeArgument).toList());
RemoteAsyncResult result; DefaultRemoteAsyncResult result;
if (open != null) { if (open != null) {
result = new RemoteAsyncResult(open); result = new DefaultRemoteAsyncResult(open);
invoke.setOid(open.doId.toDomObjId()); invoke.setOid(open.doId.toDomObjId());
} }
else { else {
result = new RemoteAsyncResult(); result = new DefaultRemoteAsyncResult();
} }
req.setXrequestInvokeMethod(invoke); req.setXrequestInvokeMethod(invoke);
synchronized (xReqQueue) { synchronized (xReqQueue) {
@ -1151,6 +1177,7 @@ public class TraceRmiHandler {
} }
} }
@Override
@Internal @Internal
public long getLastSnapshot(Trace trace) { public long getLastSnapshot(Trace trace) {
TraceSnapshot lastSnapshot = openTraces.getByTrace(trace).lastSnapshot; TraceSnapshot lastSnapshot = openTraces.getByTrace(trace).lastSnapshot;
@ -1159,4 +1186,14 @@ public class TraceRmiHandler {
} }
return lastSnapshot.getKey(); return lastSnapshot.getKey();
} }
@Override
public Trace waitForTrace(long timeoutMillis) throws TimeoutException {
try {
return openTraces.getFirstAsync().get(timeoutMillis, TimeUnit.MILLISECONDS).trace;
}
catch (InterruptedException | ExecutionException e) {
throw new TraceRmiError(e);
}
}
} }

View File

@ -17,12 +17,14 @@ package ghidra.app.plugin.core.debug.service.rmi.trace;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.*;
import java.util.*;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.services.TraceRmiService; import ghidra.app.services.TraceRmiService;
import ghidra.debug.api.tracermi.TraceRmiConnection;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.util.task.ConsoleTaskMonitor; import ghidra.util.task.ConsoleTaskMonitor;
@ -31,11 +33,8 @@ import ghidra.util.task.TaskMonitor;
@PluginInfo( @PluginInfo(
shortDescription = "Connect to back-end debuggers via Trace RMI", shortDescription = "Connect to back-end debuggers via Trace RMI",
description = """ description = """
Provides an alternative for connecting to back-end debuggers. The DebuggerModel has Provides a means for connecting to back-end debuggers.
become a bit onerous to implement. Despite its apparent flexibility, the recorder at NOTE this is an alternative to the DebuggerModel and is meant to replace it.
the front-end imposes many restrictions, and getting it to work turns into a lot of
guess work and frustration. Trace RMI should offer a more direct means of recording a
trace from a back-end.
""", """,
category = PluginCategoryNames.DEBUGGER, category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME, packageName = DebuggerPluginPackage.NAME,
@ -54,6 +53,8 @@ public class TraceRmiPlugin extends Plugin implements TraceRmiService {
private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT); private SocketAddress serverAddress = new InetSocketAddress("0.0.0.0", DEFAULT_PORT);
private TraceRmiServer server; private TraceRmiServer server;
private final Set<TraceRmiHandler> handlers = new LinkedHashSet<>();
public TraceRmiPlugin(PluginTool tool) { public TraceRmiPlugin(PluginTool tool) {
super(tool); super(tool);
} }
@ -107,13 +108,28 @@ public class TraceRmiPlugin extends Plugin implements TraceRmiService {
public TraceRmiHandler connect(SocketAddress address) throws IOException { public TraceRmiHandler connect(SocketAddress address) throws IOException {
Socket socket = new Socket(); Socket socket = new Socket();
socket.connect(address); socket.connect(address);
return new TraceRmiHandler(this, socket); TraceRmiHandler handler = new TraceRmiHandler(this, socket);
handler.start();
return handler;
} }
@Override @Override
public TraceRmiAcceptor acceptOne(SocketAddress address) throws IOException { public DefaultTraceRmiAcceptor acceptOne(SocketAddress address) throws IOException {
TraceRmiAcceptor acceptor = new TraceRmiAcceptor(this, address); DefaultTraceRmiAcceptor acceptor = new DefaultTraceRmiAcceptor(this, address);
acceptor.start(); acceptor.start();
return acceptor; return acceptor;
} }
void addHandler(TraceRmiHandler handler) {
handlers.add(handler);
}
void removeHandler(TraceRmiHandler handler) {
handlers.remove(handler);
}
@Override
public Collection<TraceRmiConnection> getAllConnections() {
return List.copyOf(handlers);
}
} }

View File

@ -1,51 +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.io.IOException;
import java.net.SocketAddress;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiAcceptor;
import ghidra.app.plugin.core.debug.service.rmi.trace.TraceRmiHandler;
public interface TraceRmiService {
SocketAddress getServerAddress();
/**
* Set the server address and port
*
* @param serverAddress may be null to bind to ephemeral port
*/
void setServerAddress(SocketAddress serverAddress);
void startServer() throws IOException;
void stopServer();
boolean isServerStarted();
TraceRmiHandler connect(SocketAddress address) throws IOException;
/**
* Accept a single connection by listening on the given address
*
* @param address the socket address to bind, or null for ephemeral
* @return the acceptor, which can be used to retrieve the ephemeral address and accept the
* actual connection
* @throws IOException on error
*/
TraceRmiAcceptor acceptOne(SocketAddress address) throws IOException;
}

View File

@ -23,6 +23,7 @@ apply plugin: 'eclipse'
eclipse.project.name = 'Debug Debugger' eclipse.project.name = 'Debug Debugger'
dependencies { dependencies {
api project(':Debugger-api')
api project(':Framework-AsyncComm') api project(':Framework-AsyncComm')
api project(':Framework-Debugging') api project(':Framework-Debugging')
api project(':Framework-TraceModeling') api project(':Framework-TraceModeling')

View File

@ -25,9 +25,9 @@
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer.LaunchResult;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.LogicalBreakpoint; import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.LaunchResult;
import ghidra.debug.flatapi.FlatDebuggerAPI; import ghidra.debug.flatapi.FlatDebuggerAPI;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;

View File

@ -15,7 +15,6 @@
*/ */
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
@ -23,6 +22,8 @@ import ghidra.dbg.DebuggerObjectModel.RefreshBehavior;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetRegisterBank; import ghidra.dbg.target.TargetRegisterBank;
import ghidra.dbg.util.PathMatcher; import ghidra.dbg.util.PathMatcher;
import ghidra.debug.api.model.TraceRecorder;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
public class RefreshRegistersScript extends GhidraScript { public class RefreshRegistersScript extends GhidraScript {
@Override @Override

View File

@ -18,11 +18,11 @@ package ghidra.app.plugin.core.debug.disassemble;
import docking.ActionContext; import docking.ActionContext;
import docking.action.*; import docking.action.*;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin.Reqs; import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin.Reqs;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper; import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.app.plugin.core.debug.mapping.DisassemblyResult; import ghidra.debug.api.platform.DisassemblyResult;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.cmd.TypedBackgroundCommand; import ghidra.framework.cmd.TypedBackgroundCommand;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;

View File

@ -25,9 +25,9 @@ import generic.jar.ResourceFile;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingActionContext;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.services.DebuggerPlatformService; import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.AutoService.Wiring; import ghidra.framework.plugintool.AutoService.Wiring;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;

View File

@ -15,7 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.event; package ghidra.app.plugin.core.debug.event;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper; import ghidra.debug.api.platform.DebuggerPlatformMapper;
import ghidra.framework.plugintool.PluginEvent; import ghidra.framework.plugintool.PluginEvent;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;

View File

@ -15,8 +15,8 @@
*/ */
package ghidra.app.plugin.core.debug.event; package ghidra.app.plugin.core.debug.event;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginEvent; import ghidra.framework.plugintool.PluginEvent;
public class TraceActivatedPluginEvent extends PluginEvent { public class TraceActivatedPluginEvent extends PluginEvent {

View File

@ -15,7 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.event; package ghidra.app.plugin.core.debug.event;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginEvent; import ghidra.framework.plugintool.PluginEvent;
public class TraceInactiveCoordinatesPluginEvent extends PluginEvent { public class TraceInactiveCoordinatesPluginEvent extends PluginEvent {

View File

@ -15,7 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.event; package ghidra.app.plugin.core.debug.event;
import ghidra.app.services.TraceRecorder; import ghidra.debug.api.model.TraceRecorder;
import ghidra.framework.plugintool.PluginEvent; import ghidra.framework.plugintool.PluginEvent;
public class TraceRecorderAdvancedPluginEvent extends PluginEvent { public class TraceRecorderAdvancedPluginEvent extends PluginEvent {

View File

@ -21,9 +21,9 @@ import javax.swing.JLabel;
import org.apache.commons.collections4.ComparatorUtils; import org.apache.commons.collections4.ComparatorUtils;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.async.AsyncDebouncer; import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer; import ghidra.async.AsyncTimer;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.*; import ghidra.trace.model.Trace.*;

View File

@ -49,9 +49,9 @@ import ghidra.app.plugin.core.debug.gui.target.DebuggerTargetsPlugin;
import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin; import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin; import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin; import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter; import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.debug.api.model.DebuggerProgramLaunchOffer;
import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.util.PluginUtils; import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.program.database.ProgramContentHandler; import ghidra.program.database.ProgramContentHandler;
@ -275,37 +275,6 @@ public interface DebuggerResources {
Color COLOR_VALUE_CHANGED_SEL = Color COLOR_VALUE_CHANGED_SEL =
new GColor("color.debugger.plugin.resources.value.changed.selected"); new GColor("color.debugger.plugin.resources.value.changed.selected");
String NAME_BREAKPOINT_MARKER_ENABLED = "Enabled Breakpoint";
String NAME_BREAKPOINT_MARKER_DISABLED = "Disabled Breakpoint";
String NAME_BREAKPOINT_MARKER_MIXED = "Mixed Breakpoint";
String NAME_BREAKPOINT_MARKER_INEFF_EN = "Ineffective Enabled Breakpoint";
String NAME_BREAKPOINT_MARKER_INEFF_DIS = "Ineffective Disabled Breakpoint";
String NAME_BREAKPOINT_MARKER_INEFF_MIX = "Ineffective Mixed Breakpoint";
String NAME_BREAKPOINT_MARKER_INCON_EN = "Inconsistent Enabled Breakpoint";
String NAME_BREAKPOINT_MARKER_INCON_DIS = "Inconsistent Disabled Breakpoint";
String NAME_BREAKPOINT_MARKER_INCON_MIX = "Inconsistent Mixed Breakpoint";
Icon ICON_BREAKPOINT_OVERLAY_INCONSISTENT =
new GIcon("icon.debugger.breakpoint.overlay.inconsistent");
Icon ICON_BREAKPOINT_MARKER_ENABLED = new GIcon("icon.debugger.breakpoint.marker.enabled");
Icon ICON_BREAKPOINT_MARKER_DISABLED = new GIcon("icon.debugger.breakpoint.marker.disabled");
Icon ICON_BREAKPOINT_MARKER_MIXED =
new GIcon("icon.debugger.breakpoint.marker.mixed");
Icon ICON_BREAKPOINT_MARKER_INEFF_EN =
new GIcon("icon.debugger.breakpoint.marker.ineffective.enabled");
Icon ICON_BREAKPOINT_MARKER_INEFF_DIS =
new GIcon("icon.debugger.breakpoint.marker.ineffective.disabled");
Icon ICON_BREAKPOINT_MARKER_INEFF_MIX =
new GIcon("icon.debugger.breakpoint.marker.ineffective.mixed");
Icon ICON_BREAKPOINT_MARKER_INCON_EN =
new MultiIcon(ICON_BREAKPOINT_MARKER_ENABLED, ICON_BREAKPOINT_OVERLAY_INCONSISTENT);
Icon ICON_BREAKPOINT_MARKER_INCON_DIS =
new MultiIcon(ICON_BREAKPOINT_MARKER_DISABLED, ICON_BREAKPOINT_OVERLAY_INCONSISTENT);
Icon ICON_BREAKPOINT_MARKER_INCON_MIX =
new MultiIcon(ICON_BREAKPOINT_MARKER_MIXED, ICON_BREAKPOINT_OVERLAY_INCONSISTENT);
Icon ICON_UNIQUE_REF_READ = new GIcon("icon.debugger.unique.ref.read"); // TODO Icon ICON_UNIQUE_REF_READ = new GIcon("icon.debugger.unique.ref.read"); // TODO
Icon ICON_UNIQUE_REF_WRITE = new GIcon("icon.debugger.unique.ref.write"); // TODO Icon ICON_UNIQUE_REF_WRITE = new GIcon("icon.debugger.unique.ref.write"); // TODO
Icon ICON_UNIQUE_REF_RW = new MultiIcon(ICON_UNIQUE_REF_READ, ICON_UNIQUE_REF_WRITE); // TODO Icon ICON_UNIQUE_REF_RW = new MultiIcon(ICON_UNIQUE_REF_READ, ICON_UNIQUE_REF_WRITE); // TODO
@ -1147,19 +1116,6 @@ public interface DebuggerResources {
} }
} }
abstract class AbstractToggleBreakpointAction extends DockingAction {
public static final String NAME = "Toggle Breakpoint";
// TODO: A "toggle breakpoint" icon
public static final Icon ICON = ICON_BREAKPOINT_MARKER_MIXED;
public static final String HELP_ANCHOR = "toggle_breakpoint";
public AbstractToggleBreakpointAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Set, enable, or disable a breakpoint");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
abstract class AbstractSetBreakpointAction extends DockingAction { abstract class AbstractSetBreakpointAction extends DockingAction {
public static final String NAME = "Set Breakpoint"; public static final String NAME = "Set Breakpoint";
public static final Icon ICON = ICON_SET_BREAKPOINT; public static final Icon ICON = ICON_SET_BREAKPOINT;

Some files were not shown because too many files have changed in this diff Show More