mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-02-16 15:40:14 +00:00
GP-3818: Create TraceRMI launcher framework. Launch script for gdb.
This commit is contained in:
parent
4561e8335d
commit
eea90f49c9
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
58
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.sh
Executable file
58
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.sh
Executable 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"
|
@ -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
|
||||||
""")
|
""")
|
||||||
|
0
Ghidra/Debug/Debugger-api/Module.manifest
Normal file
0
Ghidra/Debug/Debugger-api/Module.manifest
Normal 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
2
Ghidra/Debug/Debugger-api/certification.manifest
Normal file
2
Ghidra/Debug/Debugger-api/certification.manifest
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
##VERSION: 2.0
|
||||||
|
Module.manifest||GHIDRA||||END|
|
@ -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 {
|
||||||
|
|
||||||
/**
|
/**
|
@ -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 {
|
@ -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 {
|
@ -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);
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -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 {
|
||||||
/**
|
/**
|
@ -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 {
|
||||||
/**
|
/**
|
@ -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;
|
||||||
|
|
@ -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;
|
@ -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 {
|
||||||
|
|
||||||
/**
|
/**
|
@ -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 {
|
||||||
/**
|
/**
|
@ -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();
|
||||||
|
}
|
@ -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();
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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
|
@ -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;
|
||||||
|
|
@ -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;
|
@ -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);
|
@ -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.*;
|
||||||
|
|
@ -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;
|
@ -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;
|
||||||
|
|
@ -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;
|
@ -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
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
@ -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;
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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.*;
|
||||||
|
|
@ -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.*;
|
||||||
|
|
@ -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
|
@ -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
|
||||||
*
|
*
|
@ -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);
|
||||||
}
|
}
|
@ -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;
|
@ -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
|
@ -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;
|
||||||
|
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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);
|
@ -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;
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
@ -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();
|
||||||
}
|
}
|
@ -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;
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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() {
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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
|
@ -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.*;
|
||||||
|
|
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
@ -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|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
TraceRmiLaunchOpinion
|
@ -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')"));
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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.*;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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')
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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.*;
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user