mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-23 20:59:58 +00:00
Merge remote-tracking branch 'origin/GP-4439_Dan_rawGdbConnector--SQUASHED'
This commit is contained in:
commit
11abf7553c
57
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh
Executable file
57
Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh
Executable file
@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env 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 raw gdb
|
||||
#@no-image
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Start <tt>gdb</tt></h3>
|
||||
#@desc <p>This will start <tt>gdb</tt> and connect to it. It will not launch
|
||||
#@desc a target, so you can (must) set up your target manually.
|
||||
#@desc 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.</p>
|
||||
#@desc </body></html>
|
||||
#@menu-group raw
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb
|
||||
#@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_ARCH:str="i386:x86-64" "Architecture" "Target architecture"
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$GHIDRA_HOME/ghidra/Ghidra/Debug/Debugger-agent-gdb/build/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$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:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-agent-gdb/pypkg/src:$PYTHONPATH
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
"$OPT_GDB_PATH" \
|
||||
-q \
|
||||
-ex "set pagination off" \
|
||||
-ex "set confirm off" \
|
||||
-ex "show version" \
|
||||
-ex "python import ghidragdb" \
|
||||
-ex "set architecture $OPT_ARCH" \
|
||||
-ex "ghidra trace connect \"$GHIDRA_TRACE_RMI_ADDR\"" \
|
||||
-ex "ghidra trace start" \
|
||||
-ex "ghidra trace sync-enable" \
|
||||
-ex "set confirm on" \
|
||||
-ex "set pagination on"
|
@ -48,8 +48,8 @@ class HookState(object):
|
||||
def end_batch(self):
|
||||
if self.batch is None:
|
||||
return
|
||||
commands.STATE.client.end_batch()
|
||||
self.batch = None
|
||||
commands.STATE.client.end_batch()
|
||||
|
||||
def check_skip_continue(self):
|
||||
skip = self.skip_continue
|
||||
|
@ -52,6 +52,7 @@ public interface TraceRmiLaunchOffer {
|
||||
* @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 acceptor the acceptor if waiting for a connection
|
||||
* @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
|
||||
@ -138,7 +139,7 @@ public interface TraceRmiLaunchOffer {
|
||||
/**
|
||||
* Re-write the launcher arguments, if desired
|
||||
*
|
||||
* @param launcher the launcher that will create the target
|
||||
* @param offer the offer 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
|
||||
@ -262,6 +263,8 @@ public interface TraceRmiLaunchOffer {
|
||||
* 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.
|
||||
*
|
||||
* @return the sub-group name for ordering in the menu
|
||||
*/
|
||||
default String getMenuOrder() {
|
||||
return "";
|
||||
@ -285,4 +288,11 @@ public interface TraceRmiLaunchOffer {
|
||||
* @return the parameters
|
||||
*/
|
||||
Map<String, ParameterDescription<?>> getParameters();
|
||||
|
||||
/**
|
||||
* Check if this offer requires an open program
|
||||
*
|
||||
* @return true if required
|
||||
*/
|
||||
boolean requiresImage();
|
||||
}
|
||||
|
43
Ghidra/Debug/Debugger-rmi-trace/data/debugger-launchers/raw-python3.sh
Executable file
43
Ghidra/Debug/Debugger-rmi-trace/data/debugger-launchers/raw-python3.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env 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 raw python
|
||||
#@no-image
|
||||
#@desc <html><body width="300px">
|
||||
#@desc <h3>Start <tt>gdb</tt></h3>
|
||||
#@desc <p>This will start <tt>python</tt>, import <tt>ghidratrace</tt> and connect to it.
|
||||
#@desc This connector is made for those wanting to explore the TraceRMI API and possibly develop
|
||||
#@desc a new connector. You will need <tt>protobuf</tt> installed for Python 3.</p>
|
||||
#@desc </body></html>
|
||||
#@menu-group raw
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb
|
||||
#@env OPT_PYTHON_EXE:str="python" "Path to python" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_LANG:str="DATA:LE:64:default" "Ghidra Language" "The Ghidra LanguageID for the trace"
|
||||
#@env OPT_COMP:str="pointer64" "Ghidra Compiler" "The Ghidra CompilerSpecID for the trace"
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
export PYTHONPATH=$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-rmi-trace/build/pypkg/src:$PYTHONPATH
|
||||
else
|
||||
export PYTHONPATH=$GHIDRA_HOME/Ghidra/Debug/Debugger-rmi-trace/pypkg/src:$PYTHONPATH
|
||||
fi
|
||||
|
||||
"$OPT_PYTHON_EXE" -i ../support/raw-python3.py
|
36
Ghidra/Debug/Debugger-rmi-trace/data/support/raw-python3.py
Normal file
36
Ghidra/Debug/Debugger-rmi-trace/data/support/raw-python3.py
Normal file
@ -0,0 +1,36 @@
|
||||
## ###
|
||||
# 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.
|
||||
##
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from ghidratrace import *
|
||||
from ghidratrace.client import *
|
||||
|
||||
|
||||
REGISTRY = MethodRegistry(ThreadPoolExecutor(max_workers=1))
|
||||
|
||||
host = os.getenv("GHIDRA_TRACE_RMI_HOST")
|
||||
port = int(os.getenv("GHIDRA_TRACE_RMI_PORT"))
|
||||
c = socket.socket()
|
||||
c.connect((host, port))
|
||||
client = Client(
|
||||
c, f"python-{sys.version_info.major}.{sys.version_info.minor}", REGISTRY)
|
||||
print(f"Connected to {client.description} at {host}:{port}")
|
||||
|
||||
trace = client.create_trace("noname", os.getenv(
|
||||
"OPT_LANG"), os.getenv("OPT_COMP"))
|
@ -102,7 +102,9 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||
List<String> commandLine = new ArrayList<>();
|
||||
Map<String, String> env = new HashMap<>(System.getenv());
|
||||
prepareSubprocess(commandLine, env, args, address);
|
||||
if (program != null) {
|
||||
env.put("GHIDRA_LANGUAGE_ID", program.getLanguageID().toString());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, TtyCondition> ent : attrs.extraTtys().entrySet()) {
|
||||
if (!ent.getValue().isActive(args)) {
|
||||
@ -116,4 +118,9 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||
sessions.put("Shell",
|
||||
runInTerminal(commandLine, env, script.getParentFile(), sessions.values()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresImage() {
|
||||
return !attrs.noImage();
|
||||
}
|
||||
}
|
||||
|
@ -26,13 +26,9 @@ import java.util.concurrent.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.jdom.Element;
|
||||
import org.jdom.JDOMException;
|
||||
|
||||
import db.Transaction;
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.LaunchFailureDialog.ErrPromptResponse;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.DefaultTraceRmiAcceptor;
|
||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||
import ghidra.app.plugin.core.terminal.TerminalListener;
|
||||
@ -48,21 +44,20 @@ 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.model.listing.InstructionIterator;
|
||||
import ghidra.program.model.listing.Program;
|
||||
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.*;
|
||||
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";
|
||||
public static final String PREFIX_PARAM_EXTTOOL = "env:GHIDRA_LANG_EXTTOOL_";
|
||||
|
||||
@ -146,7 +141,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
|
||||
public AbstractTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program) {
|
||||
this.plugin = Objects.requireNonNull(plugin);
|
||||
this.program = Objects.requireNonNull(program);
|
||||
this.program = program;
|
||||
this.tool = plugin.getTool();
|
||||
this.terminalService = Objects.requireNonNull(tool.getService(TerminalService.class));
|
||||
}
|
||||
@ -165,6 +160,9 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
|
||||
protected Address getMappingProbeAddress() {
|
||||
if (program == null) {
|
||||
return null;
|
||||
}
|
||||
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
|
||||
if (eepi.hasNext()) {
|
||||
return eepi.next();
|
||||
@ -220,6 +218,9 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
|
||||
protected Collection<ModuleMapEntry> invokeMapper(TaskMonitor monitor,
|
||||
DebuggerStaticMappingService mappingService, Trace trace) throws CancelledException {
|
||||
if (program == null) {
|
||||
return List.of();
|
||||
}
|
||||
Map<TraceModule, ModuleMapProposal> map = mappingService
|
||||
.proposeModuleMaps(trace.getModuleManager().getAllModules(), List.of(program));
|
||||
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
|
||||
@ -227,7 +228,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
return proposal;
|
||||
}
|
||||
|
||||
private void saveLauncherArgs(Map<String, ?> args,
|
||||
protected SaveState saveLauncherArgsToState(Map<String, ?> args,
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
SaveState state = new SaveState();
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
@ -235,17 +236,22 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
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));
|
||||
return state;
|
||||
}
|
||||
|
||||
protected void saveState(SaveState state) {
|
||||
if (program == null) {
|
||||
plugin.writeToolLaunchConfig(getConfigName(), state);
|
||||
return;
|
||||
}
|
||||
plugin.writeProgramLaunchConfig(program, getConfigName(), state);
|
||||
}
|
||||
|
||||
protected void saveLauncherArgs(Map<String, ?> args,
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
saveState(saveLauncherArgsToState(args, params));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,9 +267,6 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
@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()) {
|
||||
@ -285,7 +288,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paramImage != null) {
|
||||
if (paramImage != null && program != null) {
|
||||
File imageFile = TraceRmiLauncherServicePlugin.getProgramPath(program);
|
||||
if (imageFile != null) {
|
||||
paramImage.set(map, imageFile.getAbsolutePath());
|
||||
@ -354,56 +357,43 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
* 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);
|
||||
Map<String, ?> args = loadLauncherArgsFromState(loadState(forPrompt), params);
|
||||
saveLauncherArgs(args, params);
|
||||
return args;
|
||||
}
|
||||
|
||||
return new LinkedHashMap<>();
|
||||
protected Map<String, ?> loadLauncherArgsFromState(SaveState state,
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
Map<String, ?> defaultArgs = generateDefaultLauncherArgs(params);
|
||||
if (state == null) {
|
||||
return defaultArgs;
|
||||
}
|
||||
List<String> names = List.of(state.getNames());
|
||||
Map<String, Object> args = new LinkedHashMap<>();
|
||||
for (ParameterDescription<?> param : params.values()) {
|
||||
String key = "param_" + param.name;
|
||||
Object configState =
|
||||
names.contains(key) ? ConfigStateField.getState(state, param.type, key) : null;
|
||||
if (configState != null) {
|
||||
args.put(param.name, configState);
|
||||
}
|
||||
else {
|
||||
args.put(param.name, defaultArgs.get(param.name));
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
protected SaveState loadState(boolean forPrompt) {
|
||||
if (program == null) {
|
||||
return plugin.readToolLaunchConfig(getConfigName());
|
||||
}
|
||||
return plugin.readProgramLaunchConfig(program, getConfigName(), forPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -527,58 +517,24 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
|
||||
InternalTraceRmiService service = tool.getService(InternalTraceRmiService.class);
|
||||
protected void initializeMonitor(TaskMonitor monitor) {
|
||||
if (requiresImage()) {
|
||||
monitor.setMaximum(6);
|
||||
}
|
||||
else {
|
||||
monitor.setMaximum(5);
|
||||
}
|
||||
}
|
||||
|
||||
protected void waitForModuleMapping(TaskMonitor monitor, TraceRmiHandler connection,
|
||||
Trace trace) throws CancelledException, InterruptedException, ExecutionException,
|
||||
NoStaticMappingException {
|
||||
if (!requiresImage()) {
|
||||
return;
|
||||
}
|
||||
DebuggerStaticMappingService mappingService =
|
||||
tool.getService(DebuggerStaticMappingService.class);
|
||||
DebuggerTraceManagerService traceManager =
|
||||
tool.getService(DebuggerTraceManagerService.class);
|
||||
final PromptMode mode = configurator.getPromptMode();
|
||||
boolean prompt = mode == PromptMode.ALWAYS;
|
||||
|
||||
DefaultTraceRmiAcceptor acceptor = null;
|
||||
Map<String, TerminalSession> sessions = new LinkedHashMap<>();
|
||||
TraceRmiHandler 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, acceptor, connection, trace, lastExc);
|
||||
}
|
||||
acceptor = null;
|
||||
sessions.clear();
|
||||
connection = null;
|
||||
trace = null;
|
||||
lastExc = null;
|
||||
|
||||
try {
|
||||
monitor.setMessage("Listening for connection");
|
||||
monitor.increment();
|
||||
acceptor = service.acceptOne(new InetSocketAddress("127.0.0.1", 0));
|
||||
monitor.setMessage("Launching back-end");
|
||||
monitor.increment();
|
||||
launchBackEnd(monitor, sessions, args, acceptor.getAddress());
|
||||
monitor.setMessage("Waiting for connection");
|
||||
monitor.increment();
|
||||
acceptor.setTimeout(getConnectionTimeoutMillis());
|
||||
connection = acceptor.accept();
|
||||
connection.registerTerminals(sessions.values());
|
||||
monitor.setMessage("Waiting for trace");
|
||||
monitor.increment();
|
||||
trace = connection.waitForTrace(getTimeoutMillis());
|
||||
traceManager.openTrace(trace);
|
||||
traceManager.activate(traceManager.resolveTrace(trace),
|
||||
ActivationCause.START_RECORDING);
|
||||
monitor.setMessage("Waiting for module mapping");
|
||||
monitor.increment();
|
||||
try {
|
||||
listenForMapping(mappingService, connection, trace).get(getTimeoutMillis(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
@ -598,6 +554,80 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
"The resulting target process has no mapping to the static image.");
|
||||
}
|
||||
}
|
||||
monitor.increment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
|
||||
if (requiresImage() && program == null) {
|
||||
throw new IllegalStateException("Offer requires image, but no program given.");
|
||||
}
|
||||
InternalTraceRmiService service = tool.getService(InternalTraceRmiService.class);
|
||||
DebuggerTraceManagerService traceManager =
|
||||
tool.getService(DebuggerTraceManagerService.class);
|
||||
final PromptMode mode = configurator.getPromptMode();
|
||||
boolean prompt = mode == PromptMode.ALWAYS;
|
||||
|
||||
DefaultTraceRmiAcceptor acceptor = null;
|
||||
Map<String, TerminalSession> sessions = new LinkedHashMap<>();
|
||||
TraceRmiHandler connection = null;
|
||||
Trace trace = null;
|
||||
Throwable lastExc = null;
|
||||
|
||||
initializeMonitor(monitor);
|
||||
while (true) {
|
||||
try {
|
||||
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, acceptor, connection, trace,
|
||||
lastExc);
|
||||
}
|
||||
monitor.increment();
|
||||
|
||||
acceptor = null;
|
||||
sessions.clear();
|
||||
connection = null;
|
||||
trace = null;
|
||||
lastExc = null;
|
||||
|
||||
monitor.setMessage("Listening for connection");
|
||||
acceptor = service.acceptOne(new InetSocketAddress("127.0.0.1", 0));
|
||||
monitor.increment();
|
||||
|
||||
monitor.setMessage("Launching back-end");
|
||||
launchBackEnd(monitor, sessions, args, acceptor.getAddress());
|
||||
monitor.increment();
|
||||
|
||||
monitor.setMessage("Waiting for connection");
|
||||
acceptor.setTimeout(getConnectionTimeoutMillis());
|
||||
connection = acceptor.accept();
|
||||
connection.registerTerminals(sessions.values());
|
||||
monitor.increment();
|
||||
|
||||
monitor.setMessage("Waiting for trace");
|
||||
trace = connection.waitForTrace(getTimeoutMillis());
|
||||
traceManager.openTrace(trace);
|
||||
traceManager.activate(traceManager.resolveTrace(trace),
|
||||
ActivationCause.START_RECORDING);
|
||||
monitor.increment();
|
||||
|
||||
waitForModuleMapping(monitor, connection, trace);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
lastExc = e;
|
||||
LaunchResult result =
|
||||
new LaunchResult(program, sessions, acceptor, connection, trace, lastExc);
|
||||
try {
|
||||
result.close();
|
||||
}
|
||||
catch (Exception e1) {
|
||||
Msg.error(this, "Could not close", e1);
|
||||
}
|
||||
return new LaunchResult(program, Map.of(), null, null, null, lastExc);
|
||||
}
|
||||
catch (Exception e) {
|
||||
DebuggerConsoleService consoleService =
|
||||
@ -635,101 +665,11 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new LaunchResult(program, sessions, null, connection, trace, null);
|
||||
return new LaunchResult(program, sessions, acceptor, connection, trace, null);
|
||||
}
|
||||
}
|
||||
|
||||
enum ErrPromptResponse {
|
||||
KEEP, RETRY, TERMINATE;
|
||||
}
|
||||
|
||||
protected ErrPromptResponse promptError(LaunchResult result) {
|
||||
String message = """
|
||||
<html><body width="400px">
|
||||
<h3>Failed to launch %s due to an exception:</h3>
|
||||
|
||||
<tt>%s</tt>
|
||||
|
||||
<h3>Troubleshooting</h3>
|
||||
<p>
|
||||
<b>Check the Terminal!</b>
|
||||
If no terminal is visible, check the menus: <b>Window → Terminals →
|
||||
...</b>.
|
||||
A path or other configuration parameter may be incorrect.
|
||||
The back-end debugger may have paused for user input.
|
||||
There may be a missing dependency.
|
||||
There may be an incorrect version, etc.</p>
|
||||
|
||||
<h3>These resources remain after the failed launch:</h3>
|
||||
<ul>
|
||||
%s
|
||||
</ul>
|
||||
|
||||
<h3>Do you want to keep these resources?</h3>
|
||||
<ul>
|
||||
<li>Choose <b>Yes</b> to stop here and diagnose or complete the launch manually.
|
||||
</li>
|
||||
<li>Choose <b>No</b> to clean up and retry at the launch dialog.</li>
|
||||
<li>Choose <b>Cancel</b> to clean up without retrying.</li>
|
||||
""".formatted(
|
||||
htmlProgramName(result), htmlExceptionMessage(result), htmlResources(result));
|
||||
return LaunchFailureDialog.show(message);
|
||||
}
|
||||
|
||||
static class LaunchFailureDialog extends OptionDialog {
|
||||
public LaunchFailureDialog(String message) {
|
||||
super("Launch Failed", message, "&Yes", "&No", OptionDialog.ERROR_MESSAGE, null,
|
||||
true, "No");
|
||||
}
|
||||
|
||||
static ErrPromptResponse show(String message) {
|
||||
return switch (new LaunchFailureDialog(message).show()) {
|
||||
case OptionDialog.YES_OPTION -> ErrPromptResponse.KEEP;
|
||||
case OptionDialog.NO_OPTION -> ErrPromptResponse.RETRY;
|
||||
case OptionDialog.CANCEL_OPTION -> ErrPromptResponse.TERMINATE;
|
||||
default -> throw new AssertionError();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected String htmlProgramName(LaunchResult result) {
|
||||
if (result.program() == null) {
|
||||
return "";
|
||||
}
|
||||
return "<tt>" + HTMLUtilities.escapeHTML(result.program().getName()) + "</tt>";
|
||||
}
|
||||
|
||||
protected String htmlExceptionMessage(LaunchResult result) {
|
||||
if (result.exception() == null) {
|
||||
return "(No exception)";
|
||||
}
|
||||
return HTMLUtilities.escapeHTML(result.exception().toString());
|
||||
}
|
||||
|
||||
protected String htmlResources(LaunchResult result) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
|
||||
TerminalSession session = ent.getValue();
|
||||
sb.append("<li>Terminal: %s → <tt>%s</tt>".formatted(
|
||||
HTMLUtilities.escapeHTML(ent.getKey()),
|
||||
HTMLUtilities.escapeHTML(session.description())));
|
||||
if (session.isTerminated()) {
|
||||
sb.append(" (Terminated)");
|
||||
}
|
||||
sb.append("</li>\n");
|
||||
}
|
||||
if (result.acceptor() != null) {
|
||||
sb.append("<li>Acceptor: <tt>%s</tt></li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.acceptor().getAddress().toString())));
|
||||
}
|
||||
if (result.connection() != null) {
|
||||
sb.append("<li>Connection: <tt>%s</tt></li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.connection().getRemoteAddress().toString())));
|
||||
}
|
||||
if (result.trace() != null) {
|
||||
sb.append("<li>Trace: %s</li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.trace().getName())));
|
||||
}
|
||||
return sb.toString();
|
||||
return LaunchFailureDialog.show(result);
|
||||
}
|
||||
}
|
||||
|
@ -15,27 +15,22 @@
|
||||
*/
|
||||
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.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin.ConfigLast;
|
||||
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;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class LaunchAction extends MultiActionDockingAction {
|
||||
public static final String NAME = "Launch";
|
||||
@ -55,58 +50,10 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
|
||||
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;
|
||||
if (program == null) {
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
String title = program == null
|
||||
? "Configure and Launch ..."
|
||||
: "Configure and Launch %s using...".formatted(program.getName());
|
||||
return Stream.concat(Stream.of(title), menuPath.stream()).toArray(String[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -116,17 +63,7 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
|
||||
List<DockingActionIf> actions = new ArrayList<>();
|
||||
|
||||
Map<String, Long> saved = new HashMap<>();
|
||||
if (program != null) {
|
||||
ProgramUserData userData = program.getProgramUserData();
|
||||
for (String propName : userData.getStringPropertyNames()) {
|
||||
ConfigLast check = checkSavedConfig(userData, propName);
|
||||
if (check == null) {
|
||||
continue;
|
||||
}
|
||||
saved.put(check.configName, check.last);
|
||||
}
|
||||
}
|
||||
Map<String, Long> saved = plugin.loadSavedConfigs(program);
|
||||
|
||||
for (TraceRmiLaunchOffer offer : offers) {
|
||||
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
|
||||
@ -134,7 +71,7 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
.popupMenuGroup(offer.getMenuGroup(), offer.getMenuOrder())
|
||||
.popupMenuIcon(offer.getIcon())
|
||||
.helpLocation(offer.getHelpLocation())
|
||||
.enabledWhen(ctx -> true)
|
||||
.enabledWhen(ctx -> !offer.requiresImage() || program != null)
|
||||
.onAction(ctx -> plugin.configureAndLaunch(offer))
|
||||
.build());
|
||||
Long last = saved.get(offer.getConfigName());
|
||||
@ -143,8 +80,11 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
// Thus, no worries about program.getName() below.
|
||||
continue;
|
||||
}
|
||||
String title = program == null
|
||||
? "Re-launch " + offer.getTitle()
|
||||
: "Re-launch %s using %s".formatted(program.getName(), offer.getTitle());
|
||||
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
|
||||
.popupMenuPath("Re-launch " + program.getName() + " using " + offer.getTitle())
|
||||
.popupMenuPath(title)
|
||||
.popupMenuGroup("0", "%016x".formatted(Long.MAX_VALUE - last))
|
||||
.popupMenuIcon(offer.getIcon())
|
||||
.helpLocation(offer.getHelpLocation())
|
||||
@ -169,6 +109,7 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
MenuManager manager =
|
||||
new MenuManager("Launch", (char) 0, GROUP, true, handler, null);
|
||||
for (DockingActionIf action : actionList) {
|
||||
action.setEnabled(action.isEnabledForContext(context));
|
||||
manager.addAction(action);
|
||||
}
|
||||
return manager.getPopupMenu();
|
||||
@ -193,26 +134,14 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return plugin.currentProgram != null;
|
||||
}
|
||||
|
||||
protected TraceRmiLaunchOffer findOffer(ConfigLast last) {
|
||||
if (last == null) {
|
||||
return null;
|
||||
}
|
||||
for (TraceRmiLaunchOffer offer : plugin.getOffers(plugin.currentProgram)) {
|
||||
if (offer.getConfigName().equals(last.configName)) {
|
||||
return offer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return !plugin.getOffers(plugin.currentProgram).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
// See comment on super method about use of runLater
|
||||
ConfigLast last = findMostRecentConfig();
|
||||
TraceRmiLaunchOffer offer = findOffer(last);
|
||||
ConfigLast last = plugin.findMostRecentConfig(plugin.currentProgram);
|
||||
TraceRmiLaunchOffer offer = plugin.findOffer(last);
|
||||
if (offer == null) {
|
||||
Swing.runLater(() -> button.showPopup());
|
||||
return;
|
||||
@ -223,14 +152,17 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
@Override
|
||||
public String getDescription() {
|
||||
Program program = plugin.currentProgram;
|
||||
if (program == null) {
|
||||
return "Launch (program required)";
|
||||
ConfigLast last = plugin.findMostRecentConfig(program);
|
||||
TraceRmiLaunchOffer offer = plugin.findOffer(last);
|
||||
if (offer == null && program == null) {
|
||||
return "Configure and launch";
|
||||
}
|
||||
ConfigLast last = findMostRecentConfig();
|
||||
TraceRmiLaunchOffer offer = findOffer(last);
|
||||
if (offer == null) {
|
||||
return "Configure and launch " + program.getName();
|
||||
}
|
||||
return "Re-launch " + program.getName() + " using " + offer.getTitle();
|
||||
if (program == null) {
|
||||
return "Re-launch " + offer.getTitle();
|
||||
}
|
||||
return "Re-launch %s using %s".formatted(program.getName(), offer.getTitle());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
/* ###
|
||||
* 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.Map.Entry;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.debug.api.tracermi.TerminalSession;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchResult;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
|
||||
public class LaunchFailureDialog extends OptionDialog {
|
||||
private static final String MSGPAT_PART_TOP = """
|
||||
<html><body width="400px">
|
||||
<h3>Failed to launch %s due to an exception:</h3>
|
||||
|
||||
<tt>%s</tt>
|
||||
|
||||
<h3>Troubleshooting</h3>
|
||||
<p>
|
||||
<b>Check the Terminal!</b>
|
||||
If no terminal is visible, check the menus: <b>Window → Terminals →
|
||||
...</b>.
|
||||
A path or other configuration parameter may be incorrect.
|
||||
The back-end debugger may have paused for user input.
|
||||
There may be a missing dependency.
|
||||
There may be an incorrect version, etc.</p>
|
||||
|
||||
""";
|
||||
private static final String MSGPAT_PART_RESOURCES = """
|
||||
<h3>These resources remain after the failed launch:</h3>
|
||||
<ul>
|
||||
%s
|
||||
</ul>
|
||||
|
||||
<h3>How do you want to proceed?</h3>
|
||||
<ul>
|
||||
<li>Choose <b>Keep</b> to stop here and diagnose or complete the launch manually.</li>
|
||||
<li>Choose <b>Retry</b> to clean up and retry at the launch dialog.</li>
|
||||
<li>Choose <b>Cancel</b> to clean up without retrying.</li>
|
||||
</ul>
|
||||
""";
|
||||
private static final String MSGPAT_WITH_RESOURCES = MSGPAT_PART_TOP + MSGPAT_PART_RESOURCES;
|
||||
private static final String MSGPAT_WITHOUT_RESOURCES = MSGPAT_PART_TOP;
|
||||
|
||||
public enum ErrPromptResponse {
|
||||
KEEP, RETRY, TERMINATE;
|
||||
}
|
||||
|
||||
protected static String formatMessage(LaunchResult result) {
|
||||
return hasResources(result)
|
||||
? MSGPAT_WITH_RESOURCES.formatted(htmlProgramName(result),
|
||||
htmlExceptionMessage(result), htmlResources(result))
|
||||
: MSGPAT_WITHOUT_RESOURCES.formatted(htmlProgramName(result),
|
||||
htmlExceptionMessage(result));
|
||||
}
|
||||
|
||||
protected static String htmlProgramName(LaunchResult result) {
|
||||
if (result.program() == null) {
|
||||
return "";
|
||||
}
|
||||
return "<tt>" + HTMLUtilities.escapeHTML(result.program().getName()) + "</tt>";
|
||||
}
|
||||
|
||||
protected static String htmlExceptionMessage(LaunchResult result) {
|
||||
if (result.exception() == null) {
|
||||
return "(No exception)";
|
||||
}
|
||||
return HTMLUtilities.escapeHTML(result.exception().toString());
|
||||
}
|
||||
|
||||
protected static boolean hasResources(LaunchResult result) {
|
||||
return !result.sessions().isEmpty() ||
|
||||
result.acceptor() != null ||
|
||||
result.connection() != null ||
|
||||
result.trace() != null;
|
||||
}
|
||||
|
||||
protected static String htmlResources(LaunchResult result) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Entry<String, TerminalSession> ent : result.sessions().entrySet()) {
|
||||
TerminalSession session = ent.getValue();
|
||||
sb.append("<li>Terminal: %s → <tt>%s</tt>".formatted(
|
||||
HTMLUtilities.escapeHTML(ent.getKey()),
|
||||
HTMLUtilities.escapeHTML(session.description())));
|
||||
if (session.isTerminated()) {
|
||||
sb.append(" (Terminated)");
|
||||
}
|
||||
sb.append("</li>\n");
|
||||
}
|
||||
if (result.acceptor() != null) {
|
||||
sb.append("<li>Acceptor: <tt>%s</tt></li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.acceptor().getAddress().toString())));
|
||||
}
|
||||
if (result.connection() != null) {
|
||||
sb.append("<li>Connection: <tt>%s</tt></li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.connection().getRemoteAddress().toString())));
|
||||
}
|
||||
if (result.trace() != null) {
|
||||
sb.append("<li>Trace: %s</li>\n".formatted(
|
||||
HTMLUtilities.escapeHTML(result.trace().getName())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static ErrPromptResponse show(LaunchResult result) {
|
||||
return switch (new LaunchFailureDialog(result).show()) {
|
||||
case OptionDialog.YES_OPTION -> ErrPromptResponse.KEEP;
|
||||
case OptionDialog.NO_OPTION -> ErrPromptResponse.RETRY;
|
||||
case OptionDialog.CANCEL_OPTION -> ErrPromptResponse.TERMINATE;
|
||||
default -> throw new AssertionError();
|
||||
};
|
||||
}
|
||||
|
||||
protected LaunchFailureDialog(LaunchResult result) {
|
||||
super("Launch Failed", formatMessage(result), hasResources(result) ? "&Keep" : null,
|
||||
"&Retry", OptionDialog.ERROR_MESSAGE, null, true, "Retry");
|
||||
}
|
||||
}
|
@ -32,11 +32,7 @@ import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Some attributes are required. Others are optional:
|
||||
* <ul>
|
||||
* <li>{@code @menu-path}: <b>(Required)</b></li>
|
||||
* </ul>
|
||||
*
|
||||
* A parser for reading attributes from a script header
|
||||
*/
|
||||
public abstract class ScriptAttributesParser {
|
||||
public static final String AT_TITLE = "@title";
|
||||
@ -52,6 +48,7 @@ public abstract class ScriptAttributesParser {
|
||||
public static final String AT_ARGS = "@args";
|
||||
public static final String AT_TTY = "@tty";
|
||||
public static final String AT_TIMEOUT = "@timeout";
|
||||
public static final String AT_NOIMAGE = "@no-image";
|
||||
|
||||
public static final String PREFIX_ENV = "env:";
|
||||
public static final String PREFIX_ARG = "arg:";
|
||||
@ -277,7 +274,7 @@ public abstract class ScriptAttributesParser {
|
||||
public record ScriptAttributes(String title, String description, List<String> menuPath,
|
||||
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
|
||||
Map<String, ParameterDescription<?>> parameters, Map<String, TtyCondition> extraTtys,
|
||||
int timeoutMillis) {
|
||||
int timeoutMillis, boolean noImage) {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -301,7 +298,7 @@ public abstract class ScriptAttributesParser {
|
||||
if (address != null) {
|
||||
env.put("GHIDRA_TRACE_RMI_ADDR", sockToString(address));
|
||||
if (address instanceof InetSocketAddress tcp) {
|
||||
env.put("GHIDRA_TRACE_RMI_HOST", tcp.getAddress().toString());
|
||||
env.put("GHIDRA_TRACE_RMI_HOST", tcp.getAddress().getHostAddress());
|
||||
env.put("GHIDRA_TRACE_RMI_PORT", Integer.toString(tcp.getPort()));
|
||||
}
|
||||
}
|
||||
@ -337,6 +334,7 @@ public abstract class ScriptAttributesParser {
|
||||
private final Map<String, ParameterDescription<?>> parameters = new LinkedHashMap<>();
|
||||
private final Map<String, TtyCondition> extraTtys = new LinkedHashMap<>();
|
||||
private int timeoutMillis = AbstractTraceRmiLaunchOffer.DEFAULT_TIMEOUT_MILLIS;
|
||||
private boolean noImage = false;
|
||||
|
||||
/**
|
||||
* Check if a line should just be ignored, e.g., blank lines, or the "shebang" line on UNIX.
|
||||
@ -397,10 +395,13 @@ public abstract class ScriptAttributesParser {
|
||||
if (!parts[0].startsWith("@")) {
|
||||
return;
|
||||
}
|
||||
if (parts.length < 2) {
|
||||
Msg.error(this, "%s: Too few tokens: %s".formatted(loc, comment));
|
||||
return;
|
||||
if (parts.length == 1) {
|
||||
switch (parts[0].trim()) {
|
||||
case AT_NOIMAGE -> parseNoImage(loc);
|
||||
default -> parseUnrecognized(loc, comment);
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (parts[0].trim()) {
|
||||
case AT_TITLE -> parseTitle(loc, parts[1]);
|
||||
case AT_DESC -> parseDesc(loc, parts[1]);
|
||||
@ -418,6 +419,7 @@ public abstract class ScriptAttributesParser {
|
||||
default -> parseUnrecognized(loc, comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void parseTitle(Location loc, String str) {
|
||||
if (title != null) {
|
||||
@ -602,6 +604,10 @@ public abstract class ScriptAttributesParser {
|
||||
}
|
||||
}
|
||||
|
||||
protected void parseNoImage(Location loc) {
|
||||
noImage = true;
|
||||
}
|
||||
|
||||
protected void parseUnrecognized(Location loc, String line) {
|
||||
Msg.warn(this, "%s: Unrecognized metadata: %s".formatted(loc, line));
|
||||
}
|
||||
@ -626,7 +632,7 @@ public abstract class ScriptAttributesParser {
|
||||
return new ScriptAttributes(title, getDescription(), List.copyOf(menuPath), menuGroup,
|
||||
menuOrder, new GIcon(iconId), helpLocation,
|
||||
Collections.unmodifiableMap(new LinkedHashMap<>(parameters)),
|
||||
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis);
|
||||
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis, noImage);
|
||||
}
|
||||
|
||||
private String getDescription() {
|
||||
|
@ -18,8 +18,13 @@ package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jdom.Element;
|
||||
import org.jdom.JDOMException;
|
||||
|
||||
import db.Transaction;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
@ -32,17 +37,18 @@ import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchConfigurator;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.PromptMode;
|
||||
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.listing.ProgramUserData;
|
||||
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;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "GUI elements to launch targets using Trace RMI",
|
||||
@ -65,6 +71,10 @@ import ghidra.util.task.TaskMonitor;
|
||||
})
|
||||
public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
implements TraceRmiLauncherService, OptionsChangeListener {
|
||||
protected static final String KEY_DBGLAUNCH = "DBGLAUNCH";
|
||||
protected static final String PREFIX_DBGLAUNCH = "DBGLAUNCH_";
|
||||
protected static final String KEY_LAST = "last";
|
||||
|
||||
protected static final String OPTION_NAME_SCRIPT_PATHS = "Script Paths";
|
||||
|
||||
private final static LaunchConfigurator RELAUNCH = new LaunchConfigurator() {
|
||||
@ -139,6 +149,8 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
protected LaunchAction launchAction;
|
||||
protected List<DockingActionIf> currentLaunchers = new ArrayList<>();
|
||||
|
||||
protected SaveState toolLaunchConfigs = new SaveState();
|
||||
|
||||
public TraceRmiLauncherServicePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
this.options = tool.getOptions(DebuggerPluginPackage.NAME);
|
||||
@ -174,9 +186,6 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public Collection<TraceRmiLaunchOffer> getOffers(Program program) {
|
||||
if (program == null) {
|
||||
return List.of();
|
||||
}
|
||||
return ClassSearcher.getInstances(TraceRmiLaunchOpinion.class)
|
||||
.stream()
|
||||
.flatMap(op -> op.getOffers(this, program).stream())
|
||||
@ -253,4 +262,132 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState saveState) {
|
||||
super.readConfigState(saveState);
|
||||
SaveState read = saveState.getSaveState(KEY_DBGLAUNCH);
|
||||
if (read != null) {
|
||||
toolLaunchConfigs = read;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
super.writeConfigState(saveState);
|
||||
if (toolLaunchConfigs != null) {
|
||||
saveState.putSaveState(KEY_DBGLAUNCH, toolLaunchConfigs);
|
||||
}
|
||||
}
|
||||
|
||||
protected SaveState readProgramLaunchConfig(Program program, String name, boolean forPrompt) {
|
||||
/**
|
||||
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
|
||||
* Re-examine this if/when that gets merged
|
||||
*/
|
||||
ProgramUserData userData = program.getProgramUserData();
|
||||
String property = userData.getStringProperty(PREFIX_DBGLAUNCH + name, null);
|
||||
if (property == null) {
|
||||
return new SaveState();
|
||||
}
|
||||
try {
|
||||
Element element = XmlUtilities.fromString(property);
|
||||
return new SaveState(element);
|
||||
}
|
||||
catch (JDOMException | IOException e) {
|
||||
if (forPrompt) {
|
||||
Msg.error(this,
|
||||
"Saved launcher args are corrupt, or launcher parameters changed. Defaulting.",
|
||||
e);
|
||||
return new SaveState();
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"Saved launcher args are corrupt, or launcher parameters changed. Not launching.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
protected SaveState readToolLaunchConfig(String name) {
|
||||
if (!toolLaunchConfigs.hasValue(name)) {
|
||||
return new SaveState();
|
||||
}
|
||||
return toolLaunchConfigs.getSaveState(name);
|
||||
}
|
||||
|
||||
protected void writeProgramLaunchConfig(Program program, String name, SaveState state) {
|
||||
ProgramUserData userData = program.getProgramUserData();
|
||||
state.putLong(KEY_LAST, System.currentTimeMillis());
|
||||
try (Transaction tx = userData.openTransaction()) {
|
||||
Element element = state.saveToXml();
|
||||
userData.setStringProperty(PREFIX_DBGLAUNCH + name, XmlUtilities.toString(element));
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeToolLaunchConfig(String name, SaveState state) {
|
||||
state.putLong(KEY_LAST, System.currentTimeMillis());
|
||||
toolLaunchConfigs.putSaveState(name, state);
|
||||
}
|
||||
|
||||
protected record ConfigLast(String configName, long last, Program program) {
|
||||
}
|
||||
|
||||
protected ConfigLast checkSavedConfig(Program program, ProgramUserData userData,
|
||||
String propName) {
|
||||
if (!propName.startsWith(PREFIX_DBGLAUNCH)) {
|
||||
return null;
|
||||
}
|
||||
String configName = propName.substring(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;
|
||||
}
|
||||
return checkSavedConfig(program, configName, new SaveState(element));
|
||||
}
|
||||
|
||||
protected ConfigLast checkSavedConfig(Program program, String name, SaveState state) {
|
||||
if (!state.hasValue(KEY_LAST)) {
|
||||
return null;
|
||||
}
|
||||
return new ConfigLast(name, state.getLong(KEY_LAST, 0), program);
|
||||
}
|
||||
|
||||
protected Stream<ConfigLast> streamSavedConfigs(Program program) {
|
||||
if (program == null) {
|
||||
return Stream.of(toolLaunchConfigs.getNames())
|
||||
.map(n -> checkSavedConfig(null, n, toolLaunchConfigs.getSaveState(n)))
|
||||
.filter(c -> c != null);
|
||||
}
|
||||
ProgramUserData userData = program.getProgramUserData();
|
||||
return userData.getStringPropertyNames()
|
||||
.stream()
|
||||
.map(n -> checkSavedConfig(program, userData, n))
|
||||
.filter(c -> c != null);
|
||||
}
|
||||
|
||||
protected ConfigLast findMostRecentConfig(Program program) {
|
||||
return streamSavedConfigs(program).max(Comparator.comparing(c -> c.last)).orElse(null);
|
||||
}
|
||||
|
||||
protected TraceRmiLaunchOffer findOffer(ConfigLast last) {
|
||||
if (last == null) {
|
||||
return null;
|
||||
}
|
||||
for (TraceRmiLaunchOffer offer : getOffers(last.program)) {
|
||||
if (offer.getConfigName().equals(last.configName)) {
|
||||
return offer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Map<String, Long> loadSavedConfigs(Program program) {
|
||||
return streamSavedConfigs(program)
|
||||
.collect(Collectors.toMap(c -> c.configName(), c -> c.last()));
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import java.util.concurrent.*;
|
||||
import java.util.stream.*;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
@ -493,8 +492,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
return rep == null ? null : rep.build();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Exception caused by back end", e);
|
||||
return rep.setError(ReplyError.newBuilder()
|
||||
.setMessage(e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e)))
|
||||
.setMessage(e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +62,9 @@ class Receiver(Thread):
|
||||
Client._write_value(
|
||||
reply.xreply_invoke_method.return_value, result)
|
||||
except BaseException as e:
|
||||
reply.xreply_invoke_method.error = ''.join(
|
||||
traceback.format_exc())
|
||||
print("Error caused by front end")
|
||||
traceback.print_exc()
|
||||
reply.xreply_invoke_method.error = repr(e)
|
||||
self.client._send(reply)
|
||||
|
||||
def _handle_reply(self, reply):
|
||||
@ -552,8 +553,16 @@ class Batch(object):
|
||||
def append(self, fut):
|
||||
self.futures.append(fut)
|
||||
|
||||
@staticmethod
|
||||
def _get_result(f, timeout):
|
||||
try:
|
||||
return f.result(timeout)
|
||||
except BaseException as e:
|
||||
print(f"Exception in batch operation: {repr(e)}")
|
||||
return e
|
||||
|
||||
def results(self, timeout=None):
|
||||
return [f.result(timeout) for f in self.futures]
|
||||
return [self._get_result(f, timeout) for f in self.futures]
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
@ -681,7 +681,7 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||
* <p>
|
||||
* The terminal will no longer respond to the window resizing, and scrollbars are displayed as
|
||||
* needed. If the terminal size changes as a result of this call,
|
||||
* {@link TerminalListener#resized(int, int)} is invoked.
|
||||
* {@link TerminalListener#resized(short, short)} is invoked.
|
||||
*
|
||||
* @param cols the number of columns
|
||||
* @param rows the number of rows
|
||||
@ -699,7 +699,7 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||
* <p>
|
||||
* Immediately fit the terminal to the window. It will also respond to the window resizing by
|
||||
* recalculating the rows and columns and adjusting the buffer's contents to fit. Whenever the
|
||||
* terminal size changes {@link TerminalListener#resized(int, int)} is invoked. The bottom
|
||||
* terminal size changes {@link TerminalListener#resized(short, short)} is invoked. The bottom
|
||||
* scrollbar is disabled, and the vertical scrollbar is always displayed, to avoid frenetic
|
||||
* horizontal resizing.
|
||||
*/
|
||||
|
@ -365,6 +365,9 @@ public class TerminalProvider extends ComponentProviderAdapter {
|
||||
terminated = true;
|
||||
removeLocalAction(actionTerminate);
|
||||
panel.terminalListeners.clear();
|
||||
panel.setOutputCallback(buf -> {
|
||||
});
|
||||
panel.getFieldPanel().setCursorOn(false);
|
||||
setTitle("[Terminal]");
|
||||
setSubTitle("Terminated");
|
||||
if (!isVisible()) {
|
||||
|
@ -68,6 +68,8 @@ public class UnixPty implements Pty {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
child.closeStreams();
|
||||
parent.closeStreams();
|
||||
LIB_POSIX.close(achild);
|
||||
LIB_POSIX.close(aparent);
|
||||
closed = true;
|
||||
|
@ -15,8 +15,7 @@
|
||||
*/
|
||||
package ghidra.pty.unix;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
|
||||
import ghidra.pty.PtyEndpoint;
|
||||
import ghidra.pty.unix.PosixC.Ioctls;
|
||||
@ -43,4 +42,9 @@ public class UnixPtyEndpoint implements PtyEndpoint {
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
protected void closeStreams() throws IOException {
|
||||
outputStream.close();
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user