GP-4609: Add FileChoosers to launcher dialog.

This commit is contained in:
Dan 2024-05-28 13:28:47 -04:00
parent 221034599d
commit fb4807e1a2
23 changed files with 474 additions and 48 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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;
}

View File

@ -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());
}

View File

@ -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;

View File

@ -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);

View File

@ -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) {

View File

@ -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

View File

@ -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;
}
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 19 KiB