mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
GP-4439: Add raw-gdb.sh and raw-python.sh. Add @no-image tag.
This commit is contained in:
parent
523f6e4cbe
commit
f9bea7720a
17 changed files with 655 additions and 337 deletions
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):
|
def end_batch(self):
|
||||||
if self.batch is None:
|
if self.batch is None:
|
||||||
return
|
return
|
||||||
commands.STATE.client.end_batch()
|
|
||||||
self.batch = None
|
self.batch = None
|
||||||
|
commands.STATE.client.end_batch()
|
||||||
|
|
||||||
def check_skip_continue(self):
|
def check_skip_continue(self):
|
||||||
skip = self.skip_continue
|
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
|
* @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
|
* than one, they are distinguished by launcher-defined keys. If there are no
|
||||||
* sessions, then there was likely a catastrophic error in the launcher.
|
* 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 connection if the target connected back to Ghidra, that connection
|
||||||
* @param trace if the connection started a trace, the (first) trace it created
|
* @param trace if the connection started a trace, the (first) trace it created
|
||||||
* @param exception optional error, if failed
|
* @param exception optional error, if failed
|
||||||
|
@ -138,7 +139,7 @@ public interface TraceRmiLaunchOffer {
|
||||||
/**
|
/**
|
||||||
* Re-write the launcher arguments, if desired
|
* 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 arguments the arguments suggested by the offer or saved settings
|
||||||
* @param relPrompt describes the timing of this callback relative to prompting the user
|
* @param relPrompt describes the timing of this callback relative to prompting the user
|
||||||
* @return the adjusted arguments
|
* @return the adjusted arguments
|
||||||
|
@ -176,7 +177,7 @@ public interface TraceRmiLaunchOffer {
|
||||||
* memorized. The opinion will generate each offer fresh each time, so it's important that the
|
* 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
|
* "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
|
* the program name, but can depend on the model factory and program language and/or compiler
|
||||||
* spec. This name cannot contain semicolons ({@ code ;}).
|
* spec. This name cannot contain semicolons ({@code ;}).
|
||||||
*
|
*
|
||||||
* @return the configuration name
|
* @return the configuration name
|
||||||
*/
|
*/
|
||||||
|
@ -262,6 +263,8 @@ public interface TraceRmiLaunchOffer {
|
||||||
* The order of entries in the quick-launch drop-down menu is always most-recently to
|
* 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
|
* least-recently used. An entry that has never been used does not appear in the quick launch
|
||||||
* menu.
|
* menu.
|
||||||
|
*
|
||||||
|
* @return the sub-group name for ordering in the menu
|
||||||
*/
|
*/
|
||||||
default String getMenuOrder() {
|
default String getMenuOrder() {
|
||||||
return "";
|
return "";
|
||||||
|
@ -285,4 +288,11 @@ public interface TraceRmiLaunchOffer {
|
||||||
* @return the parameters
|
* @return the parameters
|
||||||
*/
|
*/
|
||||||
Map<String, ParameterDescription<?>> getParameters();
|
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<>();
|
List<String> commandLine = new ArrayList<>();
|
||||||
Map<String, String> env = new HashMap<>(System.getenv());
|
Map<String, String> env = new HashMap<>(System.getenv());
|
||||||
prepareSubprocess(commandLine, env, args, address);
|
prepareSubprocess(commandLine, env, args, address);
|
||||||
|
if (program != null) {
|
||||||
env.put("GHIDRA_LANGUAGE_ID", program.getLanguageID().toString());
|
env.put("GHIDRA_LANGUAGE_ID", program.getLanguageID().toString());
|
||||||
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, TtyCondition> ent : attrs.extraTtys().entrySet()) {
|
for (Map.Entry<String, TtyCondition> ent : attrs.extraTtys().entrySet()) {
|
||||||
if (!ent.getValue().isActive(args)) {
|
if (!ent.getValue().isActive(args)) {
|
||||||
|
@ -116,4 +118,9 @@ public abstract class AbstractScriptTraceRmiLaunchOffer extends AbstractTraceRmi
|
||||||
sessions.put("Shell",
|
sessions.put("Shell",
|
||||||
runInTerminal(commandLine, env, script.getParentFile(), sessions.values()));
|
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 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.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
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.DefaultTraceRmiAcceptor;
|
||||||
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
import ghidra.app.plugin.core.debug.service.tracermi.TraceRmiHandler;
|
||||||
import ghidra.app.plugin.core.terminal.TerminalListener;
|
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.AutoConfigState.ConfigStateField;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.*;
|
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.program.util.ProgramLocation;
|
||||||
import ghidra.pty.*;
|
import ghidra.pty.*;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.TraceLocation;
|
import ghidra.trace.model.TraceLocation;
|
||||||
import ghidra.trace.model.modules.TraceModule;
|
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.exception.CancelledException;
|
||||||
import ghidra.util.task.Task;
|
import ghidra.util.task.Task;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import ghidra.util.xml.XmlUtilities;
|
|
||||||
|
|
||||||
public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer {
|
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 PARAM_DISPLAY_IMAGE = "Image";
|
||||||
public static final String PREFIX_PARAM_EXTTOOL = "env:GHIDRA_LANG_EXTTOOL_";
|
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) {
|
public AbstractTraceRmiLaunchOffer(TraceRmiLauncherServicePlugin plugin, Program program) {
|
||||||
this.plugin = Objects.requireNonNull(plugin);
|
this.plugin = Objects.requireNonNull(plugin);
|
||||||
this.program = Objects.requireNonNull(program);
|
this.program = program;
|
||||||
this.tool = plugin.getTool();
|
this.tool = plugin.getTool();
|
||||||
this.terminalService = Objects.requireNonNull(tool.getService(TerminalService.class));
|
this.terminalService = Objects.requireNonNull(tool.getService(TerminalService.class));
|
||||||
}
|
}
|
||||||
|
@ -165,6 +160,9 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Address getMappingProbeAddress() {
|
protected Address getMappingProbeAddress() {
|
||||||
|
if (program == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
|
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
|
||||||
if (eepi.hasNext()) {
|
if (eepi.hasNext()) {
|
||||||
return eepi.next();
|
return eepi.next();
|
||||||
|
@ -220,6 +218,9 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
|
|
||||||
protected Collection<ModuleMapEntry> invokeMapper(TaskMonitor monitor,
|
protected Collection<ModuleMapEntry> invokeMapper(TaskMonitor monitor,
|
||||||
DebuggerStaticMappingService mappingService, Trace trace) throws CancelledException {
|
DebuggerStaticMappingService mappingService, Trace trace) throws CancelledException {
|
||||||
|
if (program == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
Map<TraceModule, ModuleMapProposal> map = mappingService
|
Map<TraceModule, ModuleMapProposal> map = mappingService
|
||||||
.proposeModuleMaps(trace.getModuleManager().getAllModules(), List.of(program));
|
.proposeModuleMaps(trace.getModuleManager().getAllModules(), List.of(program));
|
||||||
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
|
Collection<ModuleMapEntry> proposal = MapProposal.flatten(map.values());
|
||||||
|
@ -227,7 +228,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
return proposal;
|
return proposal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveLauncherArgs(Map<String, ?> args,
|
protected SaveState saveLauncherArgsToState(Map<String, ?> args,
|
||||||
Map<String, ParameterDescription<?>> params) {
|
Map<String, ParameterDescription<?>> params) {
|
||||||
SaveState state = new SaveState();
|
SaveState state = new SaveState();
|
||||||
for (ParameterDescription<?> param : params.values()) {
|
for (ParameterDescription<?> param : params.values()) {
|
||||||
|
@ -235,17 +236,22 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
ConfigStateField.putState(state, param.type.asSubclass(Object.class),
|
ConfigStateField.putState(state, param.type.asSubclass(Object.class),
|
||||||
"param_" + param.name, val);
|
"param_" + param.name, val);
|
||||||
state.putLong("last", System.currentTimeMillis());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (program != null) {
|
return state;
|
||||||
ProgramUserData userData = program.getProgramUserData();
|
|
||||||
try (Transaction tx = userData.openTransaction()) {
|
|
||||||
Element element = state.saveToXml();
|
|
||||||
userData.setStringProperty(PREFIX_DBGLAUNCH + getConfigName(),
|
|
||||||
XmlUtilities.toString(element));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
protected Map<String, ?> generateDefaultLauncherArgs(
|
protected Map<String, ?> generateDefaultLauncherArgs(
|
||||||
Map<String, ParameterDescription<?>> params) {
|
Map<String, ParameterDescription<?>> params) {
|
||||||
if (program == null) {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||||
ParameterDescription<String> paramImage = null;
|
ParameterDescription<String> paramImage = null;
|
||||||
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
|
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);
|
File imageFile = TraceRmiLauncherServicePlugin.getProgramPath(program);
|
||||||
if (imageFile != null) {
|
if (imageFile != null) {
|
||||||
paramImage.set(map, imageFile.getAbsolutePath());
|
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
|
* 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.
|
* 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
|
* @param forPrompt true if the user will be confirming the arguments
|
||||||
* @return the loaded arguments, or defaults
|
* @return the loaded arguments, or defaults
|
||||||
*/
|
*/
|
||||||
protected Map<String, ?> loadLastLauncherArgs(boolean forPrompt) {
|
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();
|
Map<String, ParameterDescription<?>> params = getParameters();
|
||||||
ProgramUserData userData = program.getProgramUserData();
|
Map<String, ?> args = loadLauncherArgsFromState(loadState(forPrompt), params);
|
||||||
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);
|
saveLauncherArgs(args, params);
|
||||||
return args;
|
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
|
protected void initializeMonitor(TaskMonitor monitor) {
|
||||||
public LaunchResult launchProgram(TaskMonitor monitor, LaunchConfigurator configurator) {
|
if (requiresImage()) {
|
||||||
InternalTraceRmiService service = tool.getService(InternalTraceRmiService.class);
|
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 =
|
DebuggerStaticMappingService mappingService =
|
||||||
tool.getService(DebuggerStaticMappingService.class);
|
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.setMessage("Waiting for module mapping");
|
||||||
monitor.increment();
|
|
||||||
try {
|
try {
|
||||||
listenForMapping(mappingService, connection, trace).get(getTimeoutMillis(),
|
listenForMapping(mappingService, connection, trace).get(getTimeoutMillis(),
|
||||||
TimeUnit.MILLISECONDS);
|
TimeUnit.MILLISECONDS);
|
||||||
|
@ -598,6 +554,80 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
"The resulting target process has no mapping to the static image.");
|
"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) {
|
catch (Exception e) {
|
||||||
DebuggerConsoleService consoleService =
|
DebuggerConsoleService consoleService =
|
||||||
|
@ -635,101 +665,11 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
}
|
}
|
||||||
return result;
|
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) {
|
protected ErrPromptResponse promptError(LaunchResult result) {
|
||||||
String message = """
|
return LaunchFailureDialog.show(result);
|
||||||
<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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,22 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
|
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import org.jdom.Element;
|
|
||||||
import org.jdom.JDOMException;
|
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.PopupMenuHandler;
|
import docking.PopupMenuHandler;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.action.builder.ActionBuilder;
|
import docking.action.builder.ActionBuilder;
|
||||||
import docking.menu.*;
|
import docking.menu.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
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.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||||
import ghidra.framework.options.SaveState;
|
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.listing.ProgramUserData;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.*;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.xml.XmlUtilities;
|
|
||||||
|
|
||||||
public class LaunchAction extends MultiActionDockingAction {
|
public class LaunchAction extends MultiActionDockingAction {
|
||||||
public static final String NAME = "Launch";
|
public static final String NAME = "Launch";
|
||||||
|
@ -55,58 +50,10 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||||
|
|
||||||
protected String[] prependConfigAndLaunch(List<String> menuPath) {
|
protected String[] prependConfigAndLaunch(List<String> menuPath) {
|
||||||
Program program = plugin.currentProgram;
|
Program program = plugin.currentProgram;
|
||||||
return Stream.concat(
|
String title = program == null
|
||||||
Stream.of("Configure and Launch " + program.getName() + " using..."),
|
? "Configure and Launch ..."
|
||||||
menuPath.stream()).toArray(String[]::new);
|
: "Configure and Launch %s using...".formatted(program.getName());
|
||||||
}
|
return Stream.concat(Stream.of(title), 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -116,17 +63,7 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||||
|
|
||||||
List<DockingActionIf> actions = new ArrayList<>();
|
List<DockingActionIf> actions = new ArrayList<>();
|
||||||
|
|
||||||
Map<String, Long> saved = new HashMap<>();
|
Map<String, Long> saved = plugin.loadSavedConfigs(program);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TraceRmiLaunchOffer offer : offers) {
|
for (TraceRmiLaunchOffer offer : offers) {
|
||||||
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
|
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
|
||||||
|
@ -134,7 +71,7 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||||
.popupMenuGroup(offer.getMenuGroup(), offer.getMenuOrder())
|
.popupMenuGroup(offer.getMenuGroup(), offer.getMenuOrder())
|
||||||
.popupMenuIcon(offer.getIcon())
|
.popupMenuIcon(offer.getIcon())
|
||||||
.helpLocation(offer.getHelpLocation())
|
.helpLocation(offer.getHelpLocation())
|
||||||
.enabledWhen(ctx -> true)
|
.enabledWhen(ctx -> !offer.requiresImage() || program != null)
|
||||||
.onAction(ctx -> plugin.configureAndLaunch(offer))
|
.onAction(ctx -> plugin.configureAndLaunch(offer))
|
||||||
.build());
|
.build());
|
||||||
Long last = saved.get(offer.getConfigName());
|
Long last = saved.get(offer.getConfigName());
|
||||||
|
@ -143,8 +80,11 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||||
// Thus, no worries about program.getName() below.
|
// Thus, no worries about program.getName() below.
|
||||||
continue;
|
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())
|
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))
|
.popupMenuGroup("0", "%016x".formatted(Long.MAX_VALUE - last))
|
||||||
.popupMenuIcon(offer.getIcon())
|
.popupMenuIcon(offer.getIcon())
|
||||||
.helpLocation(offer.getHelpLocation())
|
.helpLocation(offer.getHelpLocation())
|
||||||
|
@ -169,6 +109,7 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||||
MenuManager manager =
|
MenuManager manager =
|
||||||
new MenuManager("Launch", (char) 0, GROUP, true, handler, null);
|
new MenuManager("Launch", (char) 0, GROUP, true, handler, null);
|
||||||
for (DockingActionIf action : actionList) {
|
for (DockingActionIf action : actionList) {
|
||||||
|
action.setEnabled(action.isEnabledForContext(context));
|
||||||
manager.addAction(action);
|
manager.addAction(action);
|
||||||
}
|
}
|
||||||
return manager.getPopupMenu();
|
return manager.getPopupMenu();
|
||||||
|
@ -193,26 +134,14 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabledForContext(ActionContext context) {
|
public boolean isEnabledForContext(ActionContext context) {
|
||||||
return plugin.currentProgram != null;
|
return !plugin.getOffers(plugin.currentProgram).isEmpty();
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
// See comment on super method about use of runLater
|
// See comment on super method about use of runLater
|
||||||
ConfigLast last = findMostRecentConfig();
|
ConfigLast last = plugin.findMostRecentConfig(plugin.currentProgram);
|
||||||
TraceRmiLaunchOffer offer = findOffer(last);
|
TraceRmiLaunchOffer offer = plugin.findOffer(last);
|
||||||
if (offer == null) {
|
if (offer == null) {
|
||||||
Swing.runLater(() -> button.showPopup());
|
Swing.runLater(() -> button.showPopup());
|
||||||
return;
|
return;
|
||||||
|
@ -223,14 +152,17 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
Program program = plugin.currentProgram;
|
Program program = plugin.currentProgram;
|
||||||
if (program == null) {
|
ConfigLast last = plugin.findMostRecentConfig(program);
|
||||||
return "Launch (program required)";
|
TraceRmiLaunchOffer offer = plugin.findOffer(last);
|
||||||
|
if (offer == null && program == null) {
|
||||||
|
return "Configure and launch";
|
||||||
}
|
}
|
||||||
ConfigLast last = findMostRecentConfig();
|
|
||||||
TraceRmiLaunchOffer offer = findOffer(last);
|
|
||||||
if (offer == null) {
|
if (offer == null) {
|
||||||
return "Configure and launch " + program.getName();
|
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;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some attributes are required. Others are optional:
|
* A parser for reading attributes from a script header
|
||||||
* <ul>
|
|
||||||
* <li>{@code @menu-path}: <b>(Required)</b></li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public abstract class ScriptAttributesParser {
|
public abstract class ScriptAttributesParser {
|
||||||
public static final String AT_TITLE = "@title";
|
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_ARGS = "@args";
|
||||||
public static final String AT_TTY = "@tty";
|
public static final String AT_TTY = "@tty";
|
||||||
public static final String AT_TIMEOUT = "@timeout";
|
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_ENV = "env:";
|
||||||
public static final String PREFIX_ARG = "arg:";
|
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,
|
public record ScriptAttributes(String title, String description, List<String> menuPath,
|
||||||
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
|
String menuGroup, String menuOrder, Icon icon, HelpLocation helpLocation,
|
||||||
Map<String, ParameterDescription<?>> parameters, Map<String, TtyCondition> extraTtys,
|
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) {
|
if (address != null) {
|
||||||
env.put("GHIDRA_TRACE_RMI_ADDR", sockToString(address));
|
env.put("GHIDRA_TRACE_RMI_ADDR", sockToString(address));
|
||||||
if (address instanceof InetSocketAddress tcp) {
|
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()));
|
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, ParameterDescription<?>> parameters = new LinkedHashMap<>();
|
||||||
private final Map<String, TtyCondition> extraTtys = new LinkedHashMap<>();
|
private final Map<String, TtyCondition> extraTtys = new LinkedHashMap<>();
|
||||||
private int timeoutMillis = AbstractTraceRmiLaunchOffer.DEFAULT_TIMEOUT_MILLIS;
|
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.
|
* 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("@")) {
|
if (!parts[0].startsWith("@")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (parts.length < 2) {
|
if (parts.length == 1) {
|
||||||
Msg.error(this, "%s: Too few tokens: %s".formatted(loc, comment));
|
switch (parts[0].trim()) {
|
||||||
return;
|
case AT_NOIMAGE -> parseNoImage(loc);
|
||||||
|
default -> parseUnrecognized(loc, comment);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
switch (parts[0].trim()) {
|
switch (parts[0].trim()) {
|
||||||
case AT_TITLE -> parseTitle(loc, parts[1]);
|
case AT_TITLE -> parseTitle(loc, parts[1]);
|
||||||
case AT_DESC -> parseDesc(loc, parts[1]);
|
case AT_DESC -> parseDesc(loc, parts[1]);
|
||||||
|
@ -418,6 +419,7 @@ public abstract class ScriptAttributesParser {
|
||||||
default -> parseUnrecognized(loc, comment);
|
default -> parseUnrecognized(loc, comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void parseTitle(Location loc, String str) {
|
protected void parseTitle(Location loc, String str) {
|
||||||
if (title != null) {
|
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) {
|
protected void parseUnrecognized(Location loc, String line) {
|
||||||
Msg.warn(this, "%s: Unrecognized metadata: %s".formatted(loc, 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,
|
return new ScriptAttributes(title, getDescription(), List.copyOf(menuPath), menuGroup,
|
||||||
menuOrder, new GIcon(iconId), helpLocation,
|
menuOrder, new GIcon(iconId), helpLocation,
|
||||||
Collections.unmodifiableMap(new LinkedHashMap<>(parameters)),
|
Collections.unmodifiableMap(new LinkedHashMap<>(parameters)),
|
||||||
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis);
|
Collections.unmodifiableMap(new LinkedHashMap<>(extraTtys)), timeoutMillis, noImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDescription() {
|
private String getDescription() {
|
||||||
|
|
|
@ -18,8 +18,13 @@ package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.jdom.Element;
|
||||||
|
import org.jdom.JDOMException;
|
||||||
|
|
||||||
|
import db.Transaction;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.builder.ActionBuilder;
|
import docking.action.builder.ActionBuilder;
|
||||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
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.LaunchConfigurator;
|
||||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.PromptMode;
|
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.PromptMode;
|
||||||
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
||||||
import ghidra.framework.options.OptionsChangeListener;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.options.ToolOptions;
|
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.listing.ProgramUserData;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.Task;
|
import ghidra.util.task.Task;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import ghidra.util.xml.XmlUtilities;
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "GUI elements to launch targets using Trace RMI",
|
shortDescription = "GUI elements to launch targets using Trace RMI",
|
||||||
|
@ -65,6 +71,10 @@ import ghidra.util.task.TaskMonitor;
|
||||||
})
|
})
|
||||||
public class TraceRmiLauncherServicePlugin extends Plugin
|
public class TraceRmiLauncherServicePlugin extends Plugin
|
||||||
implements TraceRmiLauncherService, OptionsChangeListener {
|
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";
|
protected static final String OPTION_NAME_SCRIPT_PATHS = "Script Paths";
|
||||||
|
|
||||||
private final static LaunchConfigurator RELAUNCH = new LaunchConfigurator() {
|
private final static LaunchConfigurator RELAUNCH = new LaunchConfigurator() {
|
||||||
|
@ -139,6 +149,8 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||||
protected LaunchAction launchAction;
|
protected LaunchAction launchAction;
|
||||||
protected List<DockingActionIf> currentLaunchers = new ArrayList<>();
|
protected List<DockingActionIf> currentLaunchers = new ArrayList<>();
|
||||||
|
|
||||||
|
protected SaveState toolLaunchConfigs = new SaveState();
|
||||||
|
|
||||||
public TraceRmiLauncherServicePlugin(PluginTool tool) {
|
public TraceRmiLauncherServicePlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
this.options = tool.getOptions(DebuggerPluginPackage.NAME);
|
this.options = tool.getOptions(DebuggerPluginPackage.NAME);
|
||||||
|
@ -174,9 +186,6 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<TraceRmiLaunchOffer> getOffers(Program program) {
|
public Collection<TraceRmiLaunchOffer> getOffers(Program program) {
|
||||||
if (program == null) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
return ClassSearcher.getInstances(TraceRmiLaunchOpinion.class)
|
return ClassSearcher.getInstances(TraceRmiLaunchOpinion.class)
|
||||||
.stream()
|
.stream()
|
||||||
.flatMap(op -> op.getOffers(this, program).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 java.util.stream.*;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
@ -493,8 +492,9 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||||
return rep == null ? null : rep.build();
|
return rep == null ? null : rep.build();
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
|
Msg.error(this, "Exception caused by back end", e);
|
||||||
return rep.setError(ReplyError.newBuilder()
|
return rep.setError(ReplyError.newBuilder()
|
||||||
.setMessage(e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e)))
|
.setMessage(e.getMessage()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,9 @@ class Receiver(Thread):
|
||||||
Client._write_value(
|
Client._write_value(
|
||||||
reply.xreply_invoke_method.return_value, result)
|
reply.xreply_invoke_method.return_value, result)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
reply.xreply_invoke_method.error = ''.join(
|
print("Error caused by front end")
|
||||||
traceback.format_exc())
|
traceback.print_exc()
|
||||||
|
reply.xreply_invoke_method.error = repr(e)
|
||||||
self.client._send(reply)
|
self.client._send(reply)
|
||||||
|
|
||||||
def _handle_reply(self, reply):
|
def _handle_reply(self, reply):
|
||||||
|
@ -540,8 +541,16 @@ class Batch(object):
|
||||||
def append(self, fut):
|
def append(self, fut):
|
||||||
self.futures.append(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):
|
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):
|
class Client(object):
|
||||||
|
|
|
@ -681,7 +681,7 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
* <p>
|
* <p>
|
||||||
* The terminal will no longer respond to the window resizing, and scrollbars are displayed as
|
* 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,
|
* 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 cols the number of columns
|
||||||
* @param rows the number of rows
|
* @param rows the number of rows
|
||||||
|
@ -699,7 +699,7 @@ public class TerminalPanel extends JPanel implements FieldLocationListener, Fiel
|
||||||
* <p>
|
* <p>
|
||||||
* Immediately fit the terminal to the window. It will also respond to the window resizing by
|
* 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
|
* 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
|
* scrollbar is disabled, and the vertical scrollbar is always displayed, to avoid frenetic
|
||||||
* horizontal resizing.
|
* horizontal resizing.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -365,6 +365,9 @@ public class TerminalProvider extends ComponentProviderAdapter {
|
||||||
terminated = true;
|
terminated = true;
|
||||||
removeLocalAction(actionTerminate);
|
removeLocalAction(actionTerminate);
|
||||||
panel.terminalListeners.clear();
|
panel.terminalListeners.clear();
|
||||||
|
panel.setOutputCallback(buf -> {
|
||||||
|
});
|
||||||
|
panel.getFieldPanel().setCursorOn(false);
|
||||||
setTitle("[Terminal]");
|
setTitle("[Terminal]");
|
||||||
setSubTitle("Terminated");
|
setSubTitle("Terminated");
|
||||||
if (!isVisible()) {
|
if (!isVisible()) {
|
||||||
|
|
|
@ -68,6 +68,8 @@ public class UnixPty implements Pty {
|
||||||
if (closed) {
|
if (closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
child.closeStreams();
|
||||||
|
parent.closeStreams();
|
||||||
LIB_POSIX.close(achild);
|
LIB_POSIX.close(achild);
|
||||||
LIB_POSIX.close(aparent);
|
LIB_POSIX.close(aparent);
|
||||||
closed = true;
|
closed = true;
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.pty.unix;
|
package ghidra.pty.unix;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.*;
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import ghidra.pty.PtyEndpoint;
|
import ghidra.pty.PtyEndpoint;
|
||||||
import ghidra.pty.unix.PosixC.Ioctls;
|
import ghidra.pty.unix.PosixC.Ioctls;
|
||||||
|
@ -43,4 +42,9 @@ public class UnixPtyEndpoint implements PtyEndpoint {
|
||||||
public InputStream getInputStream() {
|
public InputStream getInputStream() {
|
||||||
return inputStream;
|
return inputStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void closeStreams() throws IOException {
|
||||||
|
outputStream.close();
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue