GP-4609: Add FileChoosers to launcher dialog.
@ -9,12 +9,12 @@
|
||||
::@menu-group local
|
||||
::@icon icon.debugger
|
||||
::@help TraceRmiLauncherServicePlugin#dbgeng
|
||||
::@env OPT_PYTHON_EXE:str="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
::@env OPT_PYTHON_EXE:file="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
:: Use env instead of args, because "all args except first" is terrible to implement in batch
|
||||
::@env OPT_TARGET_IMG:str="" "Image" "The target binary executable image"
|
||||
::@env OPT_TARGET_IMG:file="" "Image" "The target binary executable image"
|
||||
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
|
||||
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."
|
||||
::@env WINDBG_DIR:str="" "Path to dbgeng.dll directory" "Path containing dbgeng and associated DLLS (if not Windows Kits)."
|
||||
::@env WINDBG_DIR:dir="" "Path to dbgeng.dll directory" "Path containing dbgeng and associated DLLS (if not Windows Kits)."
|
||||
|
||||
@echo off
|
||||
|
||||
|
@ -9,12 +9,12 @@
|
||||
::@menu-group local
|
||||
::@icon icon.debugger
|
||||
::@help TraceRmiLauncherServicePlugin#dbgeng_ttd
|
||||
::@env OPT_PYTHON_EXE:str="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
::@env OPT_PYTHON_EXE:file="python" "Python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
:: Use env instead of args, because "all args except first" is terrible to implement in batch
|
||||
::@env OPT_TARGET_IMG:str="" "Trace (.run)" "A trace associated with the target binary executable"
|
||||
::@env OPT_TARGET_IMG:file="" "Trace (.run)" "A trace associated with the target binary executable"
|
||||
::@env OPT_TARGET_ARGS:str="" "Arguments" "Command-line arguments to pass to the target"
|
||||
::@env OPT_USE_DBGMODEL:bool=true "Use dbgmodel" "Load and use dbgmodel.dll if it is available."
|
||||
::@env OPT_DBGMODEL_PATH:str="" "Path to dbgeng.dll & \\ttd" "Path containing dbgeng and associated DLLS (if not Windows Kits)."
|
||||
::@env OPT_DBGMODEL_PATH:dir="" "Path to dbgeng.dll & \\ttd" "Path containing dbgeng and associated DLLS (if not Windows Kits)."
|
||||
|
||||
@echo off
|
||||
|
||||
|
@ -26,9 +26,9 @@
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb
|
||||
#@enum StartCmd:str run start starti
|
||||
#@arg :str "Image" "The target binary executable image"
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target."
|
||||
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
@ -26,12 +26,12 @@
|
||||
#@menu-group cross
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_qemu
|
||||
#@arg :str "Image" "The target binary executable image"
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env GHIDRA_LANG_EXTTOOL_qemu:str="" "QEMU command" "The path to qemu for the target architecture."
|
||||
#@env GHIDRA_LANG_EXTTOOL_qemu:file="" "QEMU command" "The path to qemu for the target architecture."
|
||||
#@env QEMU_GDB:int=12345 "QEMU Port" "Port for gdb connection to qemu"
|
||||
#@env OPT_EXTRA_QEMU_ARGS:str="" "Extra qemu arguments" "Extra arguments to pass to qemu. Use with care."
|
||||
#@env OPT_GDB_PATH:str="gdb-multiarch" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_GDB_PATH:file="gdb-multiarch" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_EXTRA_TTY:bool=false "QEMU TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
#@menu-group raw
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_raw
|
||||
#@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "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 ]
|
||||
|
@ -31,7 +31,7 @@
|
||||
#@env OPT_HOST:str="localhost" "Host" "The hostname of the target"
|
||||
#@env OPT_PORT:str="9999" "Port" "The host's listening port"
|
||||
#@env OPT_ARCH:str="" "Architecture (optional)" "Target architecture override"
|
||||
#@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
|
@ -33,7 +33,7 @@
|
||||
#@env OPT_REMOTE_PORT:int=12345 "Remote Trace RMI Port" "A free port on the remote end to receive and forward the Trace RMI connection."
|
||||
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
|
||||
#@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb on the remote system. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_START_CMD:StartCmd="start" "Run command" "The gdb command to actually run the target."
|
||||
#@env OPT_START_CMD:StartCmd="starti" "Run command" "The gdb command to actually run the target."
|
||||
|
||||
target_image="$1"
|
||||
shift
|
||||
|
@ -32,7 +32,7 @@
|
||||
#@env OPT_EXTRA_SSH_ARGS:str="" "Extra ssh arguments" "Extra arguments to pass to ssh. Use with care."
|
||||
#@env OPT_GDBSERVER_PATH:str="gdbserver" "gdbserver command (remote)" "The path to gdbserver on the remote system. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_EXTRA_GDBSERVER_ARGS:str="" "Extra gdbserver arguments" "Extra arguments to pass to gdbserver. Use with care."
|
||||
#@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb on the local system. Omit the full path to resolve using the system PATH."
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
|
@ -25,10 +25,10 @@
|
||||
#@menu-group cross
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#gdb_wine
|
||||
#@arg :str "Image" "The target binary executable image"
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_WINE_PATH:str="/usr/lib/wine/wine64" "Path to wine binary" "The path to the wine executable for your target architecture."
|
||||
#@env OPT_GDB_PATH:str="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_WINE_PATH:file="/usr/lib/wine/wine64" "Path to wine binary" "The path to the wine executable for your target architecture."
|
||||
#@env OPT_GDB_PATH:file="gdb" "gdb command" "The path to gdb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_EXTRA_TTY:bool=false "Inferior TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
||||
|
@ -26,9 +26,9 @@
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#lldb
|
||||
#@enum StartCmd:str "process launch" "process launch --stop-at-entry"
|
||||
#@arg :str "Image" "The target binary executable image"
|
||||
#@arg :file "Image" "The target binary executable image"
|
||||
#@args "Arguments" "Command-line arguments to pass to the target"
|
||||
#@env OPT_LLDB_PATH:str="lldb" "lldb command" "The path to lldb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_LLDB_PATH:file="lldb" "lldb command" "The path to lldb. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_START_CMD:StartCmd="process launch" "Run command" "The lldb command to actually run the target."
|
||||
#@env OPT_EXTRA_TTY:bool=false "Target TTY" "Provide a separate terminal emulator for the target."
|
||||
#@tty TTY_TARGET if env:OPT_EXTRA_TTY
|
||||
|
@ -29,7 +29,7 @@
|
||||
#@env OPT_HOST:str="localhost" "Host" "The hostname of the target"
|
||||
#@env OPT_PORT:str="9999" "Port" "The host's listening port"
|
||||
#@env OPT_ARCH:str="" "Architecture" "Target architecture override"
|
||||
#@env OPT_LLDB_PATH:str="lldb" "lldb command" "The path to lldb on the local system. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_LLDB_PATH:file="lldb" "lldb command" "The path to lldb on the local system. Omit the full path to resolve using the system PATH."
|
||||
|
||||
if [ -d ${GHIDRA_HOME}/ghidra/.git ]
|
||||
then
|
||||
|
@ -27,7 +27,7 @@
|
||||
#@menu-group raw
|
||||
#@icon icon.debugger
|
||||
#@help TraceRmiLauncherServicePlugin#python_raw
|
||||
#@env OPT_PYTHON_EXE:str="python" "python command" "The path to the Python 3 interpreter. Omit the full path to resolve using the system PATH."
|
||||
#@env OPT_PYTHON_EXE:file="python" "python command" "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"
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.*;
|
||||
@ -42,6 +43,7 @@ import ghidra.debug.api.modules.ModuleMapProposal.ModuleMapEntry;
|
||||
import ghidra.debug.api.tracermi.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
|
||||
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.InstructionIterator;
|
||||
@ -263,6 +265,54 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
saveState(saveLauncherArgsToState(args, params));
|
||||
}
|
||||
|
||||
interface ImageParamSetter {
|
||||
@SuppressWarnings("unchecked")
|
||||
static ImageParamSetter get(ParameterDescription<?> param) {
|
||||
if (param.type == String.class) {
|
||||
return new StringImageParamSetter((ParameterDescription<String>) param);
|
||||
}
|
||||
if (param.type == PathIsFile.class) {
|
||||
return new FileImageParamSetter((ParameterDescription<PathIsFile>) param);
|
||||
}
|
||||
Msg.warn(ImageParamSetter.class,
|
||||
"'Image' parameter has unsupported type: " + param.type);
|
||||
return null;
|
||||
}
|
||||
|
||||
void setImage(Map<String, Object> map, Program program);
|
||||
}
|
||||
|
||||
static class StringImageParamSetter implements ImageParamSetter {
|
||||
private final ParameterDescription<String> param;
|
||||
|
||||
public StringImageParamSetter(ParameterDescription<String> param) {
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(Map<String, Object> map, Program program) {
|
||||
// str-type Image is a hint that the launcher is remote
|
||||
String value = TraceRmiLauncherServicePlugin.getProgramPath(program, false);
|
||||
param.set(map, value);
|
||||
}
|
||||
}
|
||||
|
||||
static class FileImageParamSetter implements ImageParamSetter {
|
||||
private final ParameterDescription<PathIsFile> param;
|
||||
|
||||
public FileImageParamSetter(ParameterDescription<PathIsFile> param) {
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(Map<String, Object> map, Program program) {
|
||||
// file-type Image is a hint that the launcher is local
|
||||
String str = TraceRmiLauncherServicePlugin.getProgramPath(program, true);
|
||||
PathIsFile value = str == null ? null : new PathIsFile(Paths.get(str));
|
||||
param.set(map, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the default launcher arguments
|
||||
*
|
||||
@ -277,15 +327,13 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
protected Map<String, ?> generateDefaultLauncherArgs(
|
||||
Map<String, ParameterDescription<?>> params) {
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
ParameterDescription<String> paramImage = null;
|
||||
ImageParamSetter imageSetter = null;
|
||||
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
|
||||
ParameterDescription<?> param = entry.getValue();
|
||||
map.put(entry.getKey(), param.defaultValue);
|
||||
if (PARAM_DISPLAY_IMAGE.equals(param.display)) {
|
||||
if (param.type != String.class) {
|
||||
Msg.warn(this, "'Image' parameter has unexpected type: " + paramImage.type);
|
||||
}
|
||||
paramImage = (ParameterDescription<String>) param;
|
||||
imageSetter = ImageParamSetter.get(param);
|
||||
// May still be null if type is not supported
|
||||
}
|
||||
else if (param.name.startsWith(PREFIX_PARAM_EXTTOOL)) {
|
||||
String tool = param.name.substring(PREFIX_PARAM_EXTTOOL.length());
|
||||
@ -297,11 +345,8 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paramImage != null && program != null) {
|
||||
File imageFile = TraceRmiLauncherServicePlugin.getProgramPath(program);
|
||||
if (imageFile != null) {
|
||||
paramImage.set(map, imageFile.getAbsolutePath());
|
||||
}
|
||||
if (imageSetter != null && program != null) {
|
||||
imageSetter.setImage(map, program);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.net.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@ -28,6 +30,8 @@ import generic.theme.Gui;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.dbg.util.ShellUtils;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.plugintool.AutoConfigState.PathIsDir;
|
||||
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@ -79,13 +83,11 @@ public abstract class ScriptAttributesParser {
|
||||
protected interface OptType<T> {
|
||||
static OptType<?> parse(Location loc, String typeName,
|
||||
Map<String, UserType<?>> userEnums) {
|
||||
OptType<?> type = switch (typeName) {
|
||||
case "str" -> BaseType.STRING;
|
||||
case "int" -> BaseType.INT;
|
||||
case "bool" -> BaseType.BOOL;
|
||||
default -> userEnums.get(typeName);
|
||||
};
|
||||
OptType<?> type = BaseType.parseNoErr(typeName);
|
||||
if (type == null) {
|
||||
type = userEnums.get(typeName);
|
||||
}
|
||||
if (type == null) { // still
|
||||
Msg.error(ScriptAttributesParser.class,
|
||||
"%s: Invalid type %s".formatted(loc, typeName));
|
||||
return null;
|
||||
@ -106,13 +108,20 @@ public abstract class ScriptAttributesParser {
|
||||
}
|
||||
|
||||
protected interface BaseType<T> extends OptType<T> {
|
||||
public static BaseType<?> parse(Location loc, String typeName) {
|
||||
BaseType<?> type = switch (typeName) {
|
||||
public static BaseType<?> parseNoErr(String typeName) {
|
||||
return switch (typeName) {
|
||||
case "str" -> BaseType.STRING;
|
||||
case "int" -> BaseType.INT;
|
||||
case "bool" -> BaseType.BOOL;
|
||||
case "path" -> BaseType.PATH;
|
||||
case "dir" -> BaseType.DIR;
|
||||
case "file" -> BaseType.FILE;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static BaseType<?> parse(Location loc, String typeName) {
|
||||
BaseType<?> type = parseNoErr(typeName);
|
||||
if (type == null) {
|
||||
Msg.error(ScriptAttributesParser.class,
|
||||
"%s: Invalid base type %s".formatted(loc, typeName));
|
||||
@ -179,6 +188,42 @@ public abstract class ScriptAttributesParser {
|
||||
}
|
||||
};
|
||||
|
||||
public static final BaseType<Path> PATH = new BaseType<>() {
|
||||
@Override
|
||||
public Class<Path> cls() {
|
||||
return Path.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path decode(Location loc, String str) {
|
||||
return Paths.get(str);
|
||||
}
|
||||
};
|
||||
|
||||
public static final BaseType<PathIsDir> DIR = new BaseType<>() {
|
||||
@Override
|
||||
public Class<PathIsDir> cls() {
|
||||
return PathIsDir.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathIsDir decode(Location loc, String str) {
|
||||
return new PathIsDir(Paths.get(str));
|
||||
}
|
||||
};
|
||||
|
||||
public static final BaseType<PathIsFile> FILE = new BaseType<>() {
|
||||
@Override
|
||||
public Class<PathIsFile> cls() {
|
||||
return PathIsFile.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathIsFile decode(Location loc, String str) {
|
||||
return new PathIsFile(Paths.get(str));
|
||||
}
|
||||
};
|
||||
|
||||
default UserType<T> withCastChoices(List<?> choices) {
|
||||
return new UserType<>(this, choices.stream().map(cls()::cast).toList());
|
||||
}
|
||||
|
@ -150,15 +150,23 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
return first.getPath();
|
||||
}
|
||||
|
||||
public static File getProgramPath(Program program) {
|
||||
public static String getProgramPath(Program program, boolean isLocal) {
|
||||
if (program == null) {
|
||||
return null;
|
||||
}
|
||||
File exec = tryProgramPath(program.getExecutablePath());
|
||||
if (exec != null) {
|
||||
return exec;
|
||||
return exec.getAbsolutePath();
|
||||
}
|
||||
return tryProgramPath(extractFirstFsrl(program));
|
||||
String first = extractFirstFsrl(program);
|
||||
if (!isLocal) {
|
||||
return first;
|
||||
}
|
||||
exec = tryProgramPath(first);
|
||||
if (exec != null) {
|
||||
return exec.getAbsolutePath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final ToolOptions options;
|
||||
|
@ -18,12 +18,17 @@ package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.beans.*;
|
||||
import java.io.File;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
|
||||
@ -33,12 +38,16 @@ import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.jdom.Element;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.options.editor.FileChooserEditor;
|
||||
import docking.widgets.button.BrowseButton;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
|
||||
import ghidra.dbg.target.TargetMethod;
|
||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
|
||||
import ghidra.framework.plugintool.AutoConfigState.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
@ -63,8 +72,240 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileChooserPanel extends JPanel {
|
||||
private final static int NUMBER_OF_COLUMNS = 20;
|
||||
|
||||
private final JTextField textField = new JTextField(NUMBER_OF_COLUMNS);
|
||||
private final JButton browseButton = new BrowseButton();
|
||||
private final Runnable propertyChange;
|
||||
|
||||
private GhidraFileChooser fileChooser; // lazy
|
||||
|
||||
public FileChooserPanel(Runnable propertyChange) {
|
||||
this.propertyChange = propertyChange;
|
||||
|
||||
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
||||
add(textField);
|
||||
add(Box.createHorizontalStrut(5));
|
||||
add(browseButton);
|
||||
setBorder(BorderFactory.createEmptyBorder());
|
||||
|
||||
textField.addActionListener(e -> propertyChange.run());
|
||||
textField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
propertyChange.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
propertyChange.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
propertyChange.run();
|
||||
}
|
||||
});
|
||||
|
||||
browseButton.addActionListener(e -> displayFileChooser());
|
||||
}
|
||||
|
||||
public void setValue(File file) {
|
||||
textField.setText(file == null ? "" : file.getAbsolutePath());
|
||||
}
|
||||
|
||||
private void displayFileChooser() {
|
||||
if (fileChooser == null) {
|
||||
fileChooser = createFileChooser();
|
||||
}
|
||||
|
||||
String path = textField.getText().trim();
|
||||
if (!path.isEmpty()) {
|
||||
File f = new File(path);
|
||||
if (f.isDirectory()) {
|
||||
fileChooser.setCurrentDirectory(f);
|
||||
}
|
||||
else {
|
||||
File pf = f.getParentFile();
|
||||
if (pf != null && pf.isDirectory()) {
|
||||
fileChooser.setSelectedFile(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File chosen = fileChooser.getSelectedFile(true);
|
||||
if (chosen != null) {
|
||||
textField.setText(chosen.getAbsolutePath());
|
||||
propertyChange.run();
|
||||
}
|
||||
}
|
||||
|
||||
protected String getTitle() {
|
||||
return "Choose Path";
|
||||
}
|
||||
|
||||
protected GhidraFileChooserMode getSelectionMode() {
|
||||
return GhidraFileChooserMode.FILES_AND_DIRECTORIES;
|
||||
}
|
||||
|
||||
private GhidraFileChooser createFileChooser() {
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(browseButton);
|
||||
chooser.setTitle(getTitle());
|
||||
chooser.setApproveButtonText(getTitle());
|
||||
chooser.setFileSelectionMode(getSelectionMode());
|
||||
// No way for script to specify filter....
|
||||
|
||||
return chooser;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compared to {@link FileChooserEditor}, this does not require the user to enter a full path.
|
||||
* Nor will it resolve file names against the working directory. It's just a text box with a
|
||||
* file browser assist.
|
||||
*/
|
||||
public static class PathEditor extends PropertyEditorSupport {
|
||||
private final FileChooserPanel panel = newChooserPanel();
|
||||
|
||||
protected FileChooserPanel newChooserPanel() {
|
||||
return new FileChooserPanel(this::firePropertyChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsText() {
|
||||
return panel.textField.getText().trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
String text = panel.textField.getText().trim();
|
||||
if (text.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return Paths.get(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsText(String text) throws IllegalArgumentException {
|
||||
if (text == null || text.isBlank()) {
|
||||
panel.textField.setText("");
|
||||
}
|
||||
else {
|
||||
panel.textField.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object value) {
|
||||
if (value == null) {
|
||||
panel.textField.setText("");
|
||||
}
|
||||
else if (value instanceof String s) {
|
||||
panel.textField.setText(s);
|
||||
}
|
||||
else if (value instanceof Path p) {
|
||||
panel.textField.setText(p.toString());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("value=" + value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCustomEditor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getCustomEditor() {
|
||||
return panel;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PathIsDirEditor extends PathEditor {
|
||||
@Override
|
||||
protected FileChooserPanel newChooserPanel() {
|
||||
return new FileChooserPanel(this::firePropertyChange) {
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return "Choose Directory";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GhidraFileChooserMode getSelectionMode() {
|
||||
return GhidraFileChooserMode.DIRECTORIES_ONLY;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
Object value = super.getValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Path p) {
|
||||
return new PathIsDir(p);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object value) {
|
||||
if (value instanceof PathIsDir dir) {
|
||||
super.setValue(dir.path());
|
||||
}
|
||||
else {
|
||||
super.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PathIsFileEditor extends PathEditor {
|
||||
@Override
|
||||
protected FileChooserPanel newChooserPanel() {
|
||||
return new FileChooserPanel(this::firePropertyChange) {
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return "Choose File";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GhidraFileChooserMode getSelectionMode() {
|
||||
return GhidraFileChooserMode.FILES_ONLY;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
Object value = super.getValue();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Path p) {
|
||||
return new PathIsFile(p);
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object value) {
|
||||
if (value instanceof PathIsFile file) {
|
||||
super.setValue(file.path());
|
||||
}
|
||||
else {
|
||||
super.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
PropertyEditorManager.registerEditor(BigInteger.class, BigIntEditor.class);
|
||||
PropertyEditorManager.registerEditor(Path.class, PathEditor.class);
|
||||
PropertyEditorManager.registerEditor(PathIsDir.class, PathIsDirEditor.class);
|
||||
PropertyEditorManager.registerEditor(PathIsFile.class, PathIsFileEditor.class);
|
||||
}
|
||||
|
||||
private static final String KEY_MEMORIZED_ARGUMENTS = "memorizedArguments";
|
||||
@ -335,7 +576,8 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||
Object val = computeMemorizedValue(param);
|
||||
if (val == null) {
|
||||
editor.setValue("");
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
editor.setValue(val);
|
||||
}
|
||||
editor.addPropertyChangeListener(this);
|
||||
|
@ -15,10 +15,13 @@
|
||||
*/
|
||||
package ghidra.framework.plugintool;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.reflect.*;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.framework.options.SaveState;
|
||||
@ -273,6 +276,80 @@ public interface AutoConfigState {
|
||||
}
|
||||
}
|
||||
|
||||
static class FileConfigFieldCodec implements ConfigFieldCodec<File> {
|
||||
public static final FileConfigFieldCodec INSTANCE = new FileConfigFieldCodec();
|
||||
|
||||
@Override
|
||||
public File read(SaveState state, String name, File current) {
|
||||
return state.getFile(name, current);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(SaveState state, String name, File value) {
|
||||
state.putFile(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
static class PathConfigFieldCodec implements ConfigFieldCodec<Path> {
|
||||
public static final PathConfigFieldCodec INSTANCE = new PathConfigFieldCodec();
|
||||
|
||||
@Override
|
||||
public Path read(SaveState state, String name, Path current) {
|
||||
return Paths.get(state.getString(name, current == null ? null : current.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(SaveState state, String name, Path value) {
|
||||
state.putString(name, value == null ? null : value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
record PathIsDir(Path path) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return path.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class PathIsDirConfigFieldCodec implements ConfigFieldCodec<PathIsDir> {
|
||||
public static final PathIsDirConfigFieldCodec INSTANCE = new PathIsDirConfigFieldCodec();
|
||||
|
||||
@Override
|
||||
public PathIsDir read(SaveState state, String name, PathIsDir current) {
|
||||
Path path = PathConfigFieldCodec.INSTANCE.read(state, name,
|
||||
current == null ? null : current.path);
|
||||
return path == null ? null : new PathIsDir(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(SaveState state, String name, PathIsDir value) {
|
||||
PathConfigFieldCodec.INSTANCE.write(state, name, value == null ? null : value.path);
|
||||
}
|
||||
}
|
||||
|
||||
record PathIsFile(Path path) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return path.toString();
|
||||
}
|
||||
}
|
||||
|
||||
static class PathIsFileConfigFieldCodec implements ConfigFieldCodec<PathIsFile> {
|
||||
public static final PathIsFileConfigFieldCodec INSTANCE = new PathIsFileConfigFieldCodec();
|
||||
|
||||
@Override
|
||||
public PathIsFile read(SaveState state, String name, PathIsFile current) {
|
||||
Path path = PathConfigFieldCodec.INSTANCE.read(state, name,
|
||||
current == null ? null : current.path);
|
||||
return path == null ? null : new PathIsFile(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(SaveState state, String name, PathIsFile value) {
|
||||
PathConfigFieldCodec.INSTANCE.write(state, name, value == null ? null : value.path);
|
||||
}
|
||||
}
|
||||
|
||||
static class EnumConfigFieldCodec implements ConfigFieldCodec<Enum<?>> {
|
||||
public static final EnumConfigFieldCodec INSTANCE = new EnumConfigFieldCodec();
|
||||
|
||||
@ -318,6 +395,10 @@ public interface AutoConfigState {
|
||||
addCodec(String[].class, StringArrayConfigFieldCodec.INSTANCE);
|
||||
|
||||
addCodec(BigInteger.class, BigIntegerConfigFieldCodec.INSTANCE);
|
||||
addCodec(File.class, FileConfigFieldCodec.INSTANCE);
|
||||
addCodec(Path.class, PathConfigFieldCodec.INSTANCE);
|
||||
addCodec(PathIsDir.class, PathIsDirConfigFieldCodec.INSTANCE);
|
||||
addCodec(PathIsFile.class, PathIsFileConfigFieldCodec.INSTANCE);
|
||||
}
|
||||
|
||||
private static <T> void addCodec(Class<T> cls, ConfigFieldCodec<T> codec) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.tracermi.launcher;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
@ -22,6 +23,7 @@ import org.junit.Test;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||
import ghidra.app.plugin.core.terminal.TerminalProvider;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import help.screenshot.GhidraScreenShotGenerator;
|
||||
|
||||
@ -49,7 +51,8 @@ public class TraceRmiLauncherServicePluginScreenShots extends GhidraScreenShotGe
|
||||
|
||||
@Test
|
||||
public void testCaptureGdbLauncher() throws Throwable {
|
||||
captureLauncherByTitle("gdb", Map.of("arg:1", "/home/user/demo"));
|
||||
captureLauncherByTitle("gdb",
|
||||
Map.of("arg:1", new PathIsFile(Paths.get("/home/user/demo"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Before;
|
||||
@ -30,6 +31,7 @@ import ghidra.app.services.TraceRmiLauncherService;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.*;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.plugintool.AutoConfigState.PathIsFile;
|
||||
import ghidra.util.task.ConsoleTaskMonitor;
|
||||
|
||||
public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
@ -58,7 +60,7 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
|
||||
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
Map<String, Object> args = new HashMap<>(arguments);
|
||||
args.put("arg:1", file);
|
||||
args.put("arg:1", new PathIsFile(Paths.get(file)));
|
||||
args.put("env:OPT_START_CMD", "starti");
|
||||
return args;
|
||||
}
|
||||
@ -94,7 +96,7 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug
|
||||
public Map<String, ?> configureLauncher(TraceRmiLaunchOffer offer,
|
||||
Map<String, ?> arguments, RelPrompt relPrompt) {
|
||||
Map<String, Object> args = new HashMap<>(arguments);
|
||||
args.put("env:OPT_TARGET_IMG", file);
|
||||
args.put("env:OPT_TARGET_IMG", new PathIsFile(Paths.get(file)));
|
||||
return args;
|
||||
}
|
||||
};
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 19 KiB |