diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng.bat index 171590c9613..328e45d93f6 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng.bat +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-dbgeng.bat @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat index 693f7c67c88..70a316ad7bc 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat +++ b/Ghidra/Debug/Debugger-agent-dbgeng/data/debugger-launchers/local-ttd.bat @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.sh index 9f9f91374a3..bb245940bd9 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/local-gdb.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/qemu-gdb.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/qemu-gdb.sh index 924a151d0e6..f60c051f8bc 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/qemu-gdb.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/qemu-gdb.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh index 5b572354e57..0ed99c10119 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/raw-gdb.sh @@ -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 ] diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh index 29d80336abe..4ef8f370a90 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/remote-gdb.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh index 9c455de2277..99a71b6dd6b 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdb.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh index cf47498d806..c6bd71b6dfb 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/ssh-gdbserver.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh index 5a37621166e..b8187b81f45 100755 --- a/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh +++ b/Ghidra/Debug/Debugger-agent-gdb/data/debugger-launchers/wine-gdb.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh b/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh index d94d836d103..9177b54717f 100755 --- a/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh +++ b/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/local-lldb.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/remote-lldb.sh b/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/remote-lldb.sh index c7ac062620f..d77a6d981a4 100755 --- a/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/remote-lldb.sh +++ b/Ghidra/Debug/Debugger-agent-lldb/data/debugger-launchers/remote-lldb.sh @@ -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 diff --git a/Ghidra/Debug/Debugger-rmi-trace/data/debugger-launchers/raw-python3.sh b/Ghidra/Debug/Debugger-rmi-trace/data/debugger-launchers/raw-python3.sh index bdc3eb8ac10..3827f22cd2f 100755 --- a/Ghidra/Debug/Debugger-rmi-trace/data/debugger-launchers/raw-python3.sh +++ b/Ghidra/Debug/Debugger-rmi-trace/data/debugger-launchers/raw-python3.sh @@ -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" diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/images/GdbLauncher.png b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/images/GdbLauncher.png index 408cd4a2dd2..e463e809aa4 100644 Binary files a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/images/GdbLauncher.png and b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/images/GdbLauncher.png differ diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java index 2478d173e01..1857819ac12 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java @@ -20,6 +20,7 @@ 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.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 @@ protected void saveLauncherArgs(Map args, saveState(saveLauncherArgsToState(args, params)); } + interface ImageParamSetter { + @SuppressWarnings("unchecked") + static ImageParamSetter get(ParameterDescription param) { + if (param.type == String.class) { + return new StringImageParamSetter((ParameterDescription) param); + } + if (param.type == PathIsFile.class) { + return new FileImageParamSetter((ParameterDescription) param); + } + Msg.warn(ImageParamSetter.class, + "'Image' parameter has unsupported type: " + param.type); + return null; + } + + void setImage(Map map, Program program); + } + + static class StringImageParamSetter implements ImageParamSetter { + private final ParameterDescription param; + + public StringImageParamSetter(ParameterDescription param) { + this.param = param; + } + + @Override + public void setImage(Map 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 param; + + public FileImageParamSetter(ParameterDescription param) { + this.param = param; + } + + @Override + public void setImage(Map 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 @@ protected void saveLauncherArgs(Map args, protected Map generateDefaultLauncherArgs( Map> params) { Map map = new LinkedHashMap(); - ParameterDescription paramImage = null; + ImageParamSetter imageSetter = null; for (Entry> 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) 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 @@ else if (param.name.startsWith(PREFIX_PARAM_EXTTOOL)) { } } } - 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; } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java index 6ddc255ac76..2722740ec37 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptAttributesParser.java @@ -18,6 +18,8 @@ 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 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 String toString() { protected interface OptType { static OptType parse(Location loc, String typeName, Map> 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 @@ ParameterDescription createParameter(String name, T defaultValue, String disp } protected interface BaseType extends OptType { - 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 Boolean decode(Location loc, String str) { } }; + public static final BaseType PATH = new BaseType<>() { + @Override + public Class cls() { + return Path.class; + } + + @Override + public Path decode(Location loc, String str) { + return Paths.get(str); + } + }; + + public static final BaseType DIR = new BaseType<>() { + @Override + public Class cls() { + return PathIsDir.class; + } + + @Override + public PathIsDir decode(Location loc, String str) { + return new PathIsDir(Paths.get(str)); + } + }; + + public static final BaseType FILE = new BaseType<>() { + @Override + public Class cls() { + return PathIsFile.class; + } + + @Override + public PathIsFile decode(Location loc, String str) { + return new PathIsFile(Paths.get(str)); + } + }; + default UserType withCastChoices(List choices) { return new UserType<>(this, choices.stream().map(cls()::cast).toList()); } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java index 9a32f67f597..e502f02c9e5 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java @@ -150,15 +150,23 @@ public static String extractFirstFsrl(Program program) { 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; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java index b91171aabb7..090a717c3f0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerMethodInvocationDialog.java @@ -18,12 +18,17 @@ 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.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 void setAsText(String text) throws IllegalArgumentException { } } + 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 @@ void populateOptions() { Object val = computeMemorizedValue(param); if (val == null) { editor.setValue(""); - } else { + } + else { editor.setValue(val); } editor.addPropertyChangeListener(this); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoConfigState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoConfigState.java index 84bb0704dc0..49a56d95a99 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoConfigState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/AutoConfigState.java @@ -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 void write(SaveState state, String name, BigInteger value) { } } + static class FileConfigFieldCodec implements ConfigFieldCodec { + 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 { + 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 { + 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 { + 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> { public static final EnumConfigFieldCodec INSTANCE = new EnumConfigFieldCodec(); @@ -318,6 +395,10 @@ class ConfigStateField { 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 void addCodec(Class cls, ConfigFieldCodec codec) { diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java index 845ba3ea7c2..22f8a7be08b 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginScreenShots.java @@ -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 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 @@ protected void captureLauncherByTitle(String title, Map args) throws @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 diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java index 8d90b7050a2..719b30a6f31 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java @@ -19,6 +19,7 @@ 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.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 @@ protected LaunchConfigurator gdbFileOnly(String file) { public Map configureLauncher(TraceRmiLaunchOffer offer, Map arguments, RelPrompt relPrompt) { Map 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 @@ protected LaunchConfigurator dbgengFileOnly(String file) { public Map configureLauncher(TraceRmiLaunchOffer offer, Map arguments, RelPrompt relPrompt) { Map args = new HashMap<>(arguments); - args.put("env:OPT_TARGET_IMG", file); + args.put("env:OPT_TARGET_IMG", new PathIsFile(Paths.get(file))); return args; } }; diff --git a/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_LaunchGDBDialog.png b/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_LaunchGDBDialog.png index 96852010853..0f1e49d3b78 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_LaunchGDBDialog.png and b/GhidraDocs/GhidraClass/Debugger/images/GettingStarted_LaunchGDBDialog.png differ diff --git a/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbPlusGdbserverViaSsh.png b/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbPlusGdbserverViaSsh.png index 855e12f7949..a2a67193799 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbPlusGdbserverViaSsh.png and b/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbPlusGdbserverViaSsh.png differ diff --git a/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbViaSsh.png b/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbViaSsh.png index e33fd082854..5ced77f1c2d 100644 Binary files a/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbViaSsh.png and b/GhidraDocs/GhidraClass/Debugger/images/RemoteTargets_GdbViaSsh.png differ