diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/DbgEngInJvmDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/DbgEngInJvmDebuggerModelFactory.java index 91f990d289..4961be1aa1 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/DbgEngInJvmDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/DbgEngInJvmDebuggerModelFactory.java @@ -23,15 +23,16 @@ import agent.dbgeng.model.impl.DbgModelImpl; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; +import ghidra.program.model.listing.Program; -/** - * Note this is in the testing source because it's not meant to be shipped in the release.... That - * may change if it proves stable, though, no? - */ -@FactoryDescription( // - brief = "IN-VM MS dbgeng local debugger", // - htmlDetails = "Launch a dbgeng session in this same JVM" // -) +@FactoryDescription( + brief = "MS dbgeng.dll (WinDbg)", + htmlDetails = """ + Connect to the Microsoft Debug Engine. + This is the same engine that powers WinDbg. + This is best for most Windows userspace and kernel targets. + Kernel debugging is still experimental. + This will access the native API, which may put Ghidra's JVM at risk.""") public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory { protected String remote = "none"; // Require user to start server @@ -53,8 +54,18 @@ public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory { } @Override - public boolean isCompatible() { - return System.getProperty("os.name").toLowerCase().contains("windows"); + public int getPriority(Program program) { + // TODO: Might instead look for the DLL + if (!System.getProperty("os.name").toLowerCase().contains("windows")) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 80; } public String getAgentTransport() { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngGadpDebuggerModelFactory.java similarity index 73% rename from Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngLocalDebuggerModelFactory.java rename to Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngGadpDebuggerModelFactory.java index 62c57012f6..facfe630ee 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngLocalDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/gadp/DbgEngGadpDebuggerModelFactory.java @@ -19,14 +19,17 @@ import java.util.List; import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -import ghidra.util.classfinder.ExtensionPointProperties; +import ghidra.program.model.listing.Program; -@FactoryDescription( // - brief = "MS dbgeng.dll (WinDbg) local agent via GADP/TCP", // - htmlDetails = "Launch a new agent using the Microsoft Debug Engine." // -) -@ExtensionPointProperties(priority = 100) -public class DbgEngLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { +@FactoryDescription( + brief = "MS dbgeng.dll (WinDbg) via GADP", + htmlDetails = """ + Connect to the Microsoft Debug Engine. + This is the same engine that powers WinDbg. + This is best for most Windows userspace and kernel targets. + Kernel debugging is still experimental. + This will protect Ghidra's JVM by using a subprocess to access the native API.""") +public class DbgEngGadpDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { protected String remote = "none"; // Require user to start server @FactoryOption("DebugConnect options (.server)") @@ -39,9 +42,18 @@ public class DbgEngLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerMo Property.fromAccessors(String.class, this::getAgentTransport, this::setAgentTransport); @Override - public boolean isCompatible() { + public int getPriority(Program program) { // TODO: Might instead look for the DLL - return System.getProperty("os.name").toLowerCase().contains("windows"); + if (!System.getProperty("os.name").toLowerCase().contains("windows")) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 60; } public String getAgentTransport() { diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpDbgengModelHost.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpDbgengModelHost.java index ad0bd6ca67..8f5434ff73 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpDbgengModelHost.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/gadp/GadpDbgengModelHost.java @@ -16,7 +16,7 @@ package agent.dbgeng.model.gadp; import agent.dbgeng.dbgeng.DbgEngTest; -import agent.dbgeng.gadp.DbgEngLocalDebuggerModelFactory; +import agent.dbgeng.gadp.DbgEngGadpDebuggerModelFactory; import agent.dbgeng.model.AbstractDbgengModelHost; import ghidra.dbg.DebuggerModelFactory; @@ -24,6 +24,6 @@ public class GadpDbgengModelHost extends AbstractDbgengModelHost { @Override public DebuggerModelFactory getModelFactory() { DbgEngTest.assumeDbgengDLLLoadable(); - return new DbgEngLocalDebuggerModelFactory(); + return new DbgEngGadpDebuggerModelFactory(); } } diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/DbgModelInJvmDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/DbgModelInJvmDebuggerModelFactory.java index 8fa875f30d..5f8cb98ae3 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/DbgModelInJvmDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/DbgModelInJvmDebuggerModelFactory.java @@ -23,15 +23,14 @@ import agent.dbgmodel.model.impl.DbgModel2Impl; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; +import ghidra.program.model.listing.Program; -/** - * Note this is in the testing source because it's not meant to be shipped in the release.... That - * may change if it proves stable, though, no? - */ -@FactoryDescription( // - brief = "IN-VM MS dbgmodel local debugger", // - htmlDetails = "Launch a dbgmodel session in this same JVM" // -) +@FactoryDescription( + brief = "MS dbgmodel.dll (WinDbg Preview)", + htmlDetails = """ + Connect to the Microsoft Debug Model. + This is the same engine that powers WinDbg 2. + This will access the native API, which may put Ghidra's JVM at risk.""") public class DbgModelInJvmDebuggerModelFactory implements DebuggerModelFactory { protected String remote = "none"; // Require user to start server @@ -53,8 +52,18 @@ public class DbgModelInJvmDebuggerModelFactory implements DebuggerModelFactory { } @Override - public boolean isCompatible() { - return System.getProperty("os.name").toLowerCase().contains("windows"); + public int getPriority(Program program) { + // TODO: Might instead look for the DLL + if (!System.getProperty("os.name").toLowerCase().contains("windows")) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 70; } public String getAgentTransport() { diff --git a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/gadp/DbgModelLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/gadp/DbgModelGadpDebuggerModelFactory.java similarity index 51% rename from Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/gadp/DbgModelLocalDebuggerModelFactory.java rename to Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/gadp/DbgModelGadpDebuggerModelFactory.java index a8eadae2f6..cd57a8654a 100644 --- a/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/gadp/DbgModelLocalDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-dbgmodel/src/main/java/agent/dbgmodel/gadp/DbgModelGadpDebuggerModelFactory.java @@ -15,16 +15,17 @@ */ package agent.dbgmodel.gadp; -import agent.dbgeng.gadp.DbgEngLocalDebuggerModelFactory; +import agent.dbgeng.gadp.DbgEngGadpDebuggerModelFactory; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -import ghidra.util.classfinder.ExtensionPointProperties; +import ghidra.program.model.listing.Program; -@FactoryDescription( // - brief = "MS dbgmodel.dll (WinDbg 2) local agent via GADP/TCP", // - htmlDetails = "Launch a new agent using the Microsoft Debug Model (best for WinDbg 2)." // -) -@ExtensionPointProperties(priority = 90) -public class DbgModelLocalDebuggerModelFactory extends DbgEngLocalDebuggerModelFactory { +@FactoryDescription( + brief = "MS dbgmodel.dll (WinDbg Preview) via GADP/TCP", + htmlDetails = """ + Connect to the Microsoft Debug Model. + This is the same engine that powers WinDbg 2. + This will protect Ghidra's JVM by using a subprocess to access the native API.""") +public class DbgModelGadpDebuggerModelFactory extends DbgEngGadpDebuggerModelFactory { @Override protected String getThreadName() { @@ -35,4 +36,19 @@ public class DbgModelLocalDebuggerModelFactory extends DbgEngLocalDebuggerModelF protected Class getServerClass() { return DbgModelGadpServer.class; } + + @Override + public int getPriority(Program program) { + // TODO: Might instead look for the DLL + if (!System.getProperty("os.name").toLowerCase().contains("windows")) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 50; + } } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/FridaInJvmDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/FridaInJvmDebuggerModelFactory.java index fa7017795b..a319117896 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/FridaInJvmDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/FridaInJvmDebuggerModelFactory.java @@ -21,17 +21,14 @@ import agent.frida.model.impl.FridaModelImpl; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -import ghidra.util.classfinder.ExtensionPointProperties; +import ghidra.program.model.listing.Program; -/** - * Note this is in the testing source because it's not meant to be shipped in the release.... That - * may change if it proves stable, though, no? - */ -@FactoryDescription( // - brief = "IN-VM Frida local debugger", // - htmlDetails = "Launch a Frida session in this same JVM" // -) -@ExtensionPointProperties(priority = 80) +@FactoryDescription( + brief = "PROTOTYPE: Frida", + htmlDetails = """ + Connect to Frida. + This is an experimental connector. Use at your own risk. + This will access the native API, which may put Ghidra's JVM at risk.""") public class FridaInJvmDebuggerModelFactory implements DebuggerModelFactory { @Override @@ -41,9 +38,18 @@ public class FridaInJvmDebuggerModelFactory implements DebuggerModelFactory { } @Override - public boolean isCompatible() { - String osname = System.getProperty("os.name"); - return osname.contains("Mac OS X") || osname.contains("Linux") || osname.contains("Windows"); + public int getPriority(Program program) { + String osname = System.getProperty("os.name").toLowerCase(); + if (!(osname.contains("mac os x") || osname.contains("linux") || + osname.contains("windows"))) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 30; } - } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/gadp/FridaGadpDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/gadp/FridaGadpDebuggerModelFactory.java new file mode 100644 index 0000000000..8222f6dc80 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/gadp/FridaGadpDebuggerModelFactory.java @@ -0,0 +1,63 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package agent.frida.gadp; + +import java.util.List; + +import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; +import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; +import ghidra.program.model.listing.Program; + +@FactoryDescription( + brief = "PROTOTYPE: Frida via GADP", + htmlDetails = """ + Connect to Frida. + This is an experimental connector. Use at your own risk. + This will protect Ghidra's JVM by using a subprocess to access the native API.""") +public class FridaGadpDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { + + @Override + public int getPriority(Program program) { + String osname = System.getProperty("os.name").toLowerCase(); + if (!(osname.contains("mac os x") || osname.contains("linux") || + osname.contains("windows"))) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 25; + } + + @Override + protected String getThreadName() { + return "Local Frida Agent stdout"; + } + + protected Class getServerClass() { + return FridaGadpServer.class; + } + + @Override + protected void completeCommandLine(List cmd) { + cmd.add(getServerClass().getCanonicalName()); + cmd.addAll(List.of("-H", host)); + cmd.addAll(List.of("-p", Integer.toString(port))); + } +} diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/gadp/FridaLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/gadp/FridaLocalDebuggerModelFactory.java deleted file mode 100644 index ba16d00595..0000000000 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/gadp/FridaLocalDebuggerModelFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package agent.frida.gadp; - -import java.util.List; - -import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; -import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -import ghidra.util.classfinder.ExtensionPointProperties; - -@FactoryDescription( // - brief = "Frida local agent via GADP/TCP", // - htmlDetails = "Launch a new agent using Frida." // -) -@ExtensionPointProperties(priority = 100) -public class FridaLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { - - protected String remote = "none"; // Require user to start server - @FactoryOption("DebugConnect options (.server)") - public final Property agentRemoteOption = - Property.fromAccessors(String.class, this::getAgentRemote, this::setAgentRemote); - - protected String transport = "none"; // Require user to start server - @FactoryOption("Remote process server options (untested)") - public final Property agentTransportOption = - Property.fromAccessors(String.class, this::getAgentTransport, this::setAgentTransport); - - @Override - public boolean isCompatible() { - String osname = System.getProperty("os.name"); - return osname.contains("Mac OS X") || osname.contains("Linux") || osname.contains("Windows"); - } - - public String getAgentTransport() { - return transport; - } - - public void setAgentTransport(String transport) { - this.transport = transport; - } - - public String getAgentRemote() { - return remote; - } - - public void setAgentRemote(String remote) { - this.remote = remote; - } - - @Override - protected String getThreadName() { - return "Local Frida Agent stdout"; - } - - protected Class getServerClass() { - return FridaGadpServer.class; - } - - @Override - protected void completeCommandLine(List cmd) { - cmd.add(getServerClass().getCanonicalName()); - cmd.addAll(List.of("-H", host)); - cmd.addAll(List.of("-p", Integer.toString(port))); - } -} diff --git a/Ghidra/Debug/Debugger-agent-frida/src/test/java/agent/frida/model/gadp/GadpFridaModelHost.java b/Ghidra/Debug/Debugger-agent-frida/src/test/java/agent/frida/model/gadp/GadpFridaModelHost.java index 92b85bcf8d..84496f9b87 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/test/java/agent/frida/model/gadp/GadpFridaModelHost.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/test/java/agent/frida/model/gadp/GadpFridaModelHost.java @@ -17,7 +17,7 @@ package agent.frida.model.gadp; import static org.junit.Assume.assumeFalse; -import agent.frida.gadp.FridaLocalDebuggerModelFactory; +import agent.frida.gadp.FridaGadpDebuggerModelFactory; import agent.frida.model.AbstractFridaModelHost; import ghidra.dbg.DebuggerModelFactory; import ghidra.util.SystemUtilities; @@ -26,6 +26,6 @@ class GadpFridaModelHost extends AbstractFridaModelHost { @Override public DebuggerModelFactory getModelFactory() { assumeFalse("Not ready for CI", SystemUtilities.isInTestingBatchMode()); - return new FridaLocalDebuggerModelFactory(); + return new FridaGadpDebuggerModelFactory(); } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbInJvmDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbInJvmDebuggerModelFactory.java index eeb9181ce3..16a41d0c33 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbInJvmDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbInJvmDebuggerModelFactory.java @@ -25,15 +25,16 @@ import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ShellUtils; +import ghidra.program.model.listing.Program; -/** - * Note this is in the testing source because it's not meant to be shipped in the release.... That - * may change if it proves stable, though, no? - */ -@FactoryDescription( // - brief = "IN-VM GNU gdb local debugger", // - htmlDetails = "Launch a GDB session in this same JVM" // -) +@FactoryDescription( + brief = "gdb", + htmlDetails = """ + Connect to gdb. + This is best for most Linux and Unix userspace targets, and many embedded targets. + It may also be used with gdbserver by connecting to gdb, then using target remote + .... + This will access the native API, which may put Ghidra's JVM at risk.""") public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory { private String gdbCmd = GdbManager.DEFAULT_GDB_CMD; @@ -59,8 +60,17 @@ public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory { } @Override - public boolean isCompatible() { - return GdbCompatibility.INSTANCE.isCompatible(gdbCmd); + public int getPriority(Program program) { + if (!GdbCompatibility.INSTANCE.isCompatible(gdbCmd)) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 80; } public String getGdbCommand() { diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbOverSshDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbOverSshDebuggerModelFactory.java index 7c1140290c..cec59fdc5d 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbOverSshDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/GdbOverSshDebuggerModelFactory.java @@ -24,10 +24,14 @@ import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.util.ShellUtils; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; +import ghidra.program.model.listing.Program; @FactoryDescription( - brief = "GNU gdb via SSH", - htmlDetails = "Launch a GDB session over an SSH connection") + brief = "gdb via SSH", + htmlDetails = """ + Connect to gdb using SSH. + This is best for remote Linux and Unix userspace targets when gdb is installed on the + remote host.""") public class GdbOverSshDebuggerModelFactory implements DebuggerModelFactory { private String gdbCmd = "/usr/bin/gdb"; @@ -91,8 +95,14 @@ public class GdbOverSshDebuggerModelFactory implements DebuggerModelFactory { } @Override - public boolean isCompatible() { - return true; + public int getPriority(Program program) { + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 75; } public String getGdbCommand() { diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbGadpDebuggerModelFactory.java similarity index 73% rename from Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbLocalDebuggerModelFactory.java rename to Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbGadpDebuggerModelFactory.java index d44496fb30..5fb2c8802d 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbLocalDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/gadp/GdbGadpDebuggerModelFactory.java @@ -22,14 +22,17 @@ import agent.gdb.manager.GdbManager; import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ShellUtils; -import ghidra.util.classfinder.ExtensionPointProperties; +import ghidra.program.model.listing.Program; -@FactoryDescription( // - brief = "GNU gdb local agent via GADP/TCP", // - htmlDetails = "Launch a new agent using GDB. This may start a new session or join an existing one." // -) -@ExtensionPointProperties(priority = 100) -public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { +@FactoryDescription( + brief = "gdb via GADP", + htmlDetails = """ + Connect to gdb. + This is best for most Linux and Unix userspace targets, and many embedded targets. + This will protect Ghidra's JVM by using a subprocess to access the native API. + If you are using gdbserver, you must connect to gdb first (consider the non-GADP + connector), then use target remote ... to connect to your target.""") +public class GdbGadpDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { private String gdbCmd = GdbManager.DEFAULT_GDB_CMD; @FactoryOption("GDB launch command") @@ -44,9 +47,17 @@ public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModel // TODO: newLine option? @Override - public boolean isCompatible() { - // TODO: Could potentially support GDB on Windows, but the pty thing would need porting. - return GdbCompatibility.INSTANCE.isCompatible(gdbCmd); + public int getPriority(Program program) { + if (!GdbCompatibility.INSTANCE.isCompatible(gdbCmd)) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 60; } public String getGdbCommand() { diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/gadp/GadpGdbModelHost.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/gadp/GadpGdbModelHost.java index 81af549451..96186a740f 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/gadp/GadpGdbModelHost.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/gadp/GadpGdbModelHost.java @@ -20,7 +20,7 @@ import static org.junit.Assume.assumeTrue; import java.io.File; -import agent.gdb.gadp.GdbLocalDebuggerModelFactory; +import agent.gdb.gadp.GdbGadpDebuggerModelFactory; import agent.gdb.model.AbstractGdbModelHost; import ghidra.dbg.DebuggerModelFactory; import ghidra.util.SystemUtilities; @@ -30,6 +30,6 @@ class GadpGdbModelHost extends AbstractGdbModelHost { public DebuggerModelFactory getModelFactory() { assumeFalse("Not ready for CI", SystemUtilities.isInTestingBatchMode()); assumeTrue("GDB cannot be found", new File("/usr/bin/gdb").canExecute()); - return new GdbLocalDebuggerModelFactory(); + return new GdbGadpDebuggerModelFactory(); } } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/LldbInJvmDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/LldbInJvmDebuggerModelFactory.java index 4cab78a4f4..199e16f295 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/LldbInJvmDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/LldbInJvmDebuggerModelFactory.java @@ -21,21 +21,16 @@ import agent.lldb.model.impl.LldbModelImpl; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -import ghidra.util.classfinder.ExtensionPointProperties; +import ghidra.program.model.listing.Program; -/** - * Note this is in the testing source because it's not meant to be shipped in the release.... That - * may change if it proves stable, though, no? - */ -@FactoryDescription( // - brief = "IN-VM lldb local debugger", // - htmlDetails = "Launch a lldb session in this same JVM" // -) -@ExtensionPointProperties(priority = 80) +@FactoryDescription( + brief = "lldb", + htmlDetails = """ + Connect to lldb. + This is best for most macOS and iOS targets, but supports many others. + This will access the native API, which may put Ghidra's JVM at risk.""") public class LldbInJvmDebuggerModelFactory implements DebuggerModelFactory { - // TODO remoteTransport option? - @Override public CompletableFuture build() { LldbModelImpl model = new LldbModelImpl(); @@ -43,9 +38,19 @@ public class LldbInJvmDebuggerModelFactory implements DebuggerModelFactory { } @Override - public boolean isCompatible() { - String osname = System.getProperty("os.name"); - return osname.contains("Mac OS X") || osname.contains("Linux") || osname.contains("Windows"); + public int getPriority(Program program) { + String osname = System.getProperty("os.name").toLowerCase(); + if (!(osname.contains("mac os x") || osname.contains("linux") || + osname.contains("windows"))) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 40; } } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/gadp/LldbGadpDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/gadp/LldbGadpDebuggerModelFactory.java new file mode 100644 index 0000000000..db4099f302 --- /dev/null +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/gadp/LldbGadpDebuggerModelFactory.java @@ -0,0 +1,63 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package agent.lldb.gadp; + +import java.util.List; + +import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; +import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; +import ghidra.program.model.listing.Program; + +@FactoryDescription( + brief = "lldb via GADP", + htmlDetails = """ + Connect to lldb. + This is best for most macOS and iOS targets, but supports many others. + This will protect Ghidra's JVM by using a subprocess to access the native API.""") +public class LldbGadpDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { + + @Override + public int getPriority(Program program) { + String osname = System.getProperty("os.name").toLowerCase(); + if (!(osname.contains("mac os x") || osname.contains("linux") || + osname.contains("windows"))) { + return -1; + } + if (program != null) { + String exe = program.getExecutablePath(); + if (exe == null || exe.isBlank()) { + return -1; + } + } + return 35; + } + + @Override + protected String getThreadName() { + return "Local LLDB Agent stdout"; + } + + protected Class getServerClass() { + return LldbGadpServer.class; + } + + @Override + protected void completeCommandLine(List cmd) { + cmd.add(getServerClass().getCanonicalName()); + cmd.addAll(List.of("-H", host)); + cmd.addAll(List.of("-p", Integer.toString(port))); + } +} diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/gadp/LldbLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/gadp/LldbLocalDebuggerModelFactory.java deleted file mode 100644 index 08764d2bde..0000000000 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/gadp/LldbLocalDebuggerModelFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package agent.lldb.gadp; - -import java.util.List; - -import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; -import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -import ghidra.util.classfinder.ExtensionPointProperties; - -@FactoryDescription( // - brief = "LLVM lldb local agent via GADP/TCP", // - htmlDetails = "Launch a new agent using LLVM's lldb." // -) -@ExtensionPointProperties(priority = 100) -public class LldbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { - - protected String remote = "none"; // Require user to start server - @FactoryOption("DebugConnect options (.server)") - public final Property agentRemoteOption = - Property.fromAccessors(String.class, this::getAgentRemote, this::setAgentRemote); - - protected String transport = "none"; // Require user to start server - @FactoryOption("Remote process server options (untested)") - public final Property agentTransportOption = - Property.fromAccessors(String.class, this::getAgentTransport, this::setAgentTransport); - - @Override - public boolean isCompatible() { - String osname = System.getProperty("os.name"); - return osname.contains("Mac OS X") || osname.contains("Linux") || osname.contains("Windows"); - } - - public String getAgentTransport() { - return transport; - } - - public void setAgentTransport(String transport) { - this.transport = transport; - } - - public String getAgentRemote() { - return remote; - } - - public void setAgentRemote(String remote) { - this.remote = remote; - } - - @Override - protected String getThreadName() { - return "Local LLDB Agent stdout"; - } - - protected Class getServerClass() { - return LldbGadpServer.class; - } - - @Override - protected void completeCommandLine(List cmd) { - cmd.add(getServerClass().getCanonicalName()); - cmd.addAll(List.of("-H", host)); - cmd.addAll(List.of("-p", Integer.toString(port))); - } -} diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java index a57d52298d..ca0d551ac7 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java @@ -26,10 +26,12 @@ import ghidra.async.TypeSpec; import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -@FactoryDescription( // - brief = "GADP connection over TCP", // - htmlDetails = "Connect to an optionally remote agent via GADP/TCP." // -) +@FactoryDescription( + brief = "Ghidra debug agent (GADP)", + htmlDetails = """ + Connect to a Ghidra debug agent using GADP. + This is Ghidra's debugging protocol. + Use this if launching a (usually remote) agent manually.""") public class GadpTcpDebuggerModelFactory implements DebuggerModelFactory { private String host = "localhost"; diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/JdiDebuggerModelFactory.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/JdiDebuggerModelFactory.java index bbcdcc5aba..e70195beba 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/JdiDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/JdiDebuggerModelFactory.java @@ -22,10 +22,12 @@ import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.jdi.model.JdiModelImpl; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; -@FactoryDescription( // - brief = "JDI debugger", // - htmlDetails = "Debug a Java or Dalvik VM (supports JDWP)" // -) +@FactoryDescription( + brief = "PROTOTYPE: JDWP (Java or Dalvik)", + htmlDetails = """ + Connect to a Java or Dalvik VM via JDWP. + This is the same debugging protocol used by most Java IDEs. + Support for debugging Java and Dalvik is still experimental.""") public class JdiDebuggerModelFactory implements DebuggerModelFactory { @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java index fc4bd1a0e3..7360b1e16a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java @@ -34,10 +34,12 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.services.DebuggerModelService; +import ghidra.app.services.ProgramManager; import ghidra.dbg.DebuggerObjectModel; import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.program.model.listing.Program; public class DebuggerTargetsProvider extends ComponentProviderAdapter { @@ -107,7 +109,9 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter { public void actionPerformed(ActionContext context) { // NB. Drop the future on the floor, because the UI will report issues. // Cancellation should be ignored. - modelService.showConnectDialog(); + ProgramManager programManager = tool.getService(ProgramManager.class); + Program program = programManager == null ? null : programManager.getCurrentProgram(); + modelService.showConnectDialog(program); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgDebuggerProgramLaunchOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgDebuggerProgramLaunchOpinion.java index 7570809ca5..dac7182ce5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgDebuggerProgramLaunchOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgDebuggerProgramLaunchOpinion.java @@ -37,7 +37,6 @@ public class DbgDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi protected List getLauncherPath() { return PathUtils.parse(""); } - } protected class InVmDbgengDebuggerProgramLaunchOffer @@ -133,7 +132,7 @@ public class DbgDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi } List offers = new ArrayList<>(); for (DebuggerModelFactory factory : service.getModelFactories()) { - if (!factory.isCompatible()) { + if (!factory.isCompatible(program)) { continue; } String clsName = factory.getClass().getName(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerProgramLaunchOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerProgramLaunchOpinion.java index 3ca2e389c7..cac028347c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerProgramLaunchOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerProgramLaunchOpinion.java @@ -95,7 +95,7 @@ public class FridaDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchO } List offers = new ArrayList<>(); for (DebuggerModelFactory factory : service.getModelFactories()) { - if (!factory.isCompatible()) { + if (!factory.isCompatible(program)) { continue; } String clsName = factory.getClass().getName(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerProgramLaunchOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerProgramLaunchOpinion.java index 23871201d4..20715f64f6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerProgramLaunchOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerProgramLaunchOpinion.java @@ -139,7 +139,7 @@ public class GdbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi } List offers = new ArrayList<>(); for (DebuggerModelFactory factory : service.getModelFactories()) { - if (!factory.isCompatible()) { + if (!factory.isCompatible(program)) { continue; } String clsName = factory.getClass().getName(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerProgramLaunchOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerProgramLaunchOpinion.java index 3557eb1418..a1c9db454d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerProgramLaunchOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerProgramLaunchOpinion.java @@ -90,7 +90,7 @@ public class LldbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOp } List offers = new ArrayList<>(); for (DebuggerModelFactory factory : service.getModelFactories()) { - if (!factory.isCompatible()) { + if (!factory.isCompatible(program)) { continue; } String clsName = factory.getClass().getName(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java index ed3fb4b896..f03b345218 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java @@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture; import javax.swing.*; import javax.swing.border.EmptyBorder; +import javax.swing.text.View; import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; @@ -40,13 +41,14 @@ import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.util.ConfigurableFactory.Property; import ghidra.framework.options.SaveState; +import ghidra.program.model.listing.Program; import ghidra.util.*; import ghidra.util.datastruct.CollectionChangeListener; -import ghidra.util.layout.PairLayout; public class DebuggerConnectDialog extends DialogComponentProvider implements PropertyChangeListener { - private static final String KEY_FACTORY_CLASSNAME = "factoryClassname"; + private static final String KEY_CURRENT_FACTORY_CLASSNAME = "currentFactoryCls"; + private static final String KEY_SUCCESS_FACTORY_CLASSNAME = "successFactoryCls"; private static final String HTML_BOLD_DESCRIPTION = "Description: "; protected class FactoriesChangedListener @@ -67,9 +69,40 @@ public class DebuggerConnectDialog extends DialogComponentProvider } } + protected record FactoryEntry(DebuggerModelFactory factory) { + @Override + public String toString() { + return factory.getBrief(); + } + } + + protected record PrioritizedFactory(FactoryEntry entry, int priority) { + public PrioritizedFactory(FactoryEntry ent, Program program) { + this(ent, ent.factory.getPriority(program)); + } + } + + protected enum NameComparator implements Comparator { + INSTANCE; + + @Override + public int compare(String o1, String o2) { + boolean p1 = o1.startsWith("PROTOTYPE:"); + boolean p2 = o2.startsWith("PROTOTYPE:"); + if (p1 && !p2) { + return 1; + } + if (!p1 && p2) { + return -1; + } + return o1.toLowerCase().compareTo(o2.toLowerCase()); + } + } + private DebuggerModelService modelService; - private DebuggerModelFactory factory; + private DebuggerModelFactory currentFactory; + private DebuggerModelFactory successFactory; private final Map factories = new HashMap<>(); private FactoriesChangedListener listener = new FactoriesChangedListener(); @@ -81,26 +114,12 @@ public class DebuggerConnectDialog extends DialogComponentProvider private final Map, Component> components = new LinkedHashMap<>(); protected JLabel description; - protected JPanel pairPanel; - private PairLayout layout; + protected JPanel gridPanel; protected JButton connectButton; protected CompletableFuture futureConnect; protected CompletableFuture result; - protected static class FactoryEntry { - DebuggerModelFactory factory; - - public FactoryEntry(DebuggerModelFactory factory) { - this.factory = factory; - } - - @Override - public String toString() { - return factory.getBrief(); - } - } - public DebuggerConnectDialog() { super(AbstractConnectAction.NAME, true, true, true, false); @@ -126,7 +145,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider toAdd.add(entry); } SwingUtilities.invokeLater(() -> { - toAdd.sort(Comparator.comparing(FactoryEntry::toString)); + toAdd.sort(Comparator.comparing(FactoryEntry::toString, NameComparator.INSTANCE)); for (FactoryEntry entry : toAdd) { dropdownModel.addElement(entry); } @@ -190,20 +209,20 @@ public class DebuggerConnectDialog extends DialogComponentProvider JPanel inner = new JPanel(new BorderLayout()); description = new JLabel(HTML_BOLD_DESCRIPTION + ""); description.setBorder(new EmptyBorder(10, 0, 10, 0)); + description.setPreferredSize(new Dimension(400, 150)); inner.add(description); topBox.add(inner); panel.add(topBox, BorderLayout.NORTH); - layout = new PairLayout(5, 5); - pairPanel = new JPanel(layout); + gridPanel = new JPanel(new GridBagLayout()); JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER)); JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - scrolling.setPreferredSize(new Dimension(100, 130)); + scrolling.setPreferredSize(new Dimension(100, 200)); panel.add(scrolling, BorderLayout.CENTER); - centering.add(pairPanel); + centering.add(gridPanel); addWorkPanel(panel); @@ -219,13 +238,15 @@ public class DebuggerConnectDialog extends DialogComponentProvider private void itemSelected(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.DESELECTED) { - pairPanel.removeAll(); + gridPanel.removeAll(); } else if (evt.getStateChange() == ItemEvent.SELECTED) { FactoryEntry ent = (FactoryEntry) evt.getItem(); - factory = ent.factory; + currentFactory = ent.factory; populateOptions(); - //repack(); + /** + * Don't repack here. It can shrink the dialog, which may not be what the user wants. + */ } } @@ -239,7 +260,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider } setStatusText("Connecting..."); synchronized (this) { - futureConnect = factory.build(); + futureConnect = currentFactory.build(); } futureConnect.thenCompose(m -> m.fetchModelRoot()).thenAcceptAsync(r -> { DebuggerObjectModel m = r.getModel(); @@ -268,6 +289,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider synchronized (this) { futureConnect = null; } + successFactory = currentFactory; connectButton.setEnabled(true); }); } @@ -284,7 +306,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider } protected synchronized CompletableFuture reset( - DebuggerModelFactory factory) { + DebuggerModelFactory factory, Program program) { if (factory != null) { synchronized (factories) { dropdownModel.setSelectedItem(factories.get(factory)); @@ -292,6 +314,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider dropdown.setEnabled(false); } else { + selectCompatibleFactory(program); dropdown.setEnabled(true); } @@ -311,18 +334,42 @@ public class DebuggerConnectDialog extends DialogComponentProvider } protected void populateOptions() { - description.setText( - HTML_BOLD_DESCRIPTION + HTMLUtilities.friendlyEncodeHTML(factory.getHtmlDetails())); + description.setText(HTML_BOLD_DESCRIPTION + currentFactory.getHtmlDetails()); propertyEditors.clear(); components.clear(); - Map> optsMap = factory.getOptions(); - //layout.setRows(Math.max(1, optsMap.size())); - pairPanel.removeAll(); + Map> optsMap = currentFactory.getOptions(); + gridPanel.removeAll(); + GridBagConstraints constraints; + + if (optsMap.isEmpty()) { + JLabel label = + new JLabel("There are no configuration options for this connector."); + constraints = new GridBagConstraints(); + gridPanel.add(label, constraints); + } + + int i = 0; for (Map.Entry> opt : optsMap.entrySet()) { Property property = opt.getValue(); - JLabel label = new JLabel(opt.getKey()); - pairPanel.add(label); + JLabel label = new JLabel("" + HTMLUtilities.escapeHTML(opt.getKey())) { + @Override + public Dimension getPreferredSize() { + View v = (View) getClientProperty("html"); + if (v == null) { + return super.getPreferredSize(); + } + v.setSize(200, 0); + float height = v.getPreferredSpan(View.Y_AXIS); + return new Dimension(200, (int) height); + } + }; + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.BOTH; + constraints.gridx = 0; + constraints.gridy = i; + constraints.insets = new Insets(i == 0 ? 0 : 5, 0, 0, 5); + gridPanel.add(label, constraints); Class type = property.getValueClass(); PropertyEditor editor = PropertyEditorManager.findEditor(type); @@ -331,11 +378,22 @@ public class DebuggerConnectDialog extends DialogComponentProvider } editor.setValue(property.getValue()); editor.addPropertyChangeListener(this); - Component comp = MiscellaneousUtils.getEditorComponent(editor); - pairPanel.add(comp); + Component editorComponent = MiscellaneousUtils.getEditorComponent(editor); + if (editorComponent instanceof JTextField textField) { + textField.setColumns(13); + } + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.WEST; + constraints.gridx = 1; + constraints.gridy = i; + constraints.insets = new Insets(i == 0 ? 0 : 5, 0, 0, 0); + gridPanel.add(editorComponent, constraints); propertyEditors.put(property, editor); - components.put(property, comp); + components.put(property, editorComponent); + + i++; } } @@ -350,24 +408,75 @@ public class DebuggerConnectDialog extends DialogComponentProvider } public void writeConfigState(SaveState saveState) { - if (factory != null) { - saveState.putString(KEY_FACTORY_CLASSNAME, factory.getClass().getName()); + if (currentFactory != null) { + saveState.putString(KEY_CURRENT_FACTORY_CLASSNAME, currentFactory.getClass().getName()); + } + if (successFactory != null) { + saveState.putString(KEY_SUCCESS_FACTORY_CLASSNAME, successFactory.getClass().getName()); } } - public void readConfigState(SaveState saveState) { - String factoryName = saveState.getString(KEY_FACTORY_CLASSNAME, null); - if (factoryName == null) { - return; - } + protected FactoryEntry getByName(String className) { synchronized (factories) { - for (Map.Entry ent : factories.entrySet()) { - String name = ent.getKey().getClass().getName(); - if (factoryName.equals(name)) { - factory = ent.getKey(); - dropdown.setSelectedItem(ent.getValue()); + for (FactoryEntry ent : factories.values()) { + String name = ent.factory.getClass().getName(); + if (className.equals(name)) { + return ent; } } + return null; } } + + protected Collection getByPriority(Program program) { + synchronized (factories) { + return factories.values() + .stream() + .map(e -> new PrioritizedFactory(e, program)) + .sorted(Comparator.comparing(pf -> -pf.priority())) + .toList(); + } + } + + protected PrioritizedFactory getFirstCompatibleByPriority(Program program) { + for (PrioritizedFactory pf : getByPriority(program)) { + if (pf.priority >= 0) { + return pf; + } + return null; + } + return null; + } + + protected void selectCompatibleFactory(Program program) { + if (currentFactory != null && currentFactory.isCompatible(program)) { + return; + } + if (successFactory != null && successFactory.isCompatible(program)) { + currentFactory = successFactory; + synchronized (factories) { + dropdown.setSelectedItem(factories.get(successFactory)); + } + return; + } + PrioritizedFactory compat = getFirstCompatibleByPriority(program); + if (compat == null) { + return; + } + currentFactory = compat.entry.factory; + dropdown.setSelectedItem(compat.entry); + } + + public void readConfigState(SaveState saveState) { + String currentFactoryName = saveState.getString(KEY_CURRENT_FACTORY_CLASSNAME, null); + FactoryEntry restoreCurrent = + currentFactoryName == null ? null : getByName(currentFactoryName); + currentFactory = restoreCurrent == null ? null : restoreCurrent.factory; + dropdown.setSelectedItem(restoreCurrent); + + String successFactoryName = saveState.getString(KEY_SUCCESS_FACTORY_CLASSNAME, null); + FactoryEntry restoreSuccess = + successFactoryName == null ? null : getByName(successFactoryName); + successFactory = restoreSuccess == null ? null : restoreSuccess.factory; + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 9d06e75f10..0b3e89f598 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -641,14 +641,24 @@ public class DebuggerModelServicePlugin extends Plugin } protected CompletableFuture doShowConnectDialog(PluginTool tool, - DebuggerModelFactory factory) { - CompletableFuture future = connectDialog.reset(factory); + DebuggerModelFactory factory, Program program) { + CompletableFuture future = connectDialog.reset(factory, program); tool.showDialog(connectDialog); return future; } + @Override + public CompletableFuture showConnectDialog() { + return doShowConnectDialog(tool, null, null); + } + + @Override + public CompletableFuture showConnectDialog(Program program) { + return doShowConnectDialog(tool, null, program); + } + @Override public CompletableFuture showConnectDialog(DebuggerModelFactory factory) { - return doShowConnectDialog(tool, factory); + return doShowConnectDialog(tool, factory, null); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java index d0e21222be..84cb8a0434 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java @@ -269,9 +269,19 @@ public class DebuggerModelServiceProxyPlugin extends Plugin closeAllModels(); } + @Override + public CompletableFuture showConnectDialog() { + return delegate.doShowConnectDialog(tool, null, null); + } + + @Override + public CompletableFuture showConnectDialog(Program program) { + return delegate.doShowConnectDialog(tool, null, program); + } + @Override public CompletableFuture showConnectDialog(DebuggerModelFactory factory) { - return delegate.doShowConnectDialog(tool, factory); + return delegate.doShowConnectDialog(tool, factory, null); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java index d34446e812..e78f3edd60 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java @@ -353,14 +353,20 @@ public interface DebuggerModelService { * * @return a future which completes with the new connection, possibly cancelled */ - default CompletableFuture showConnectDialog() { - return showConnectDialog(null); - } + CompletableFuture showConnectDialog(); + + /** + * Prompt the user to create a new connection, hinting at the program to launch + * + * @param program the current program used to help select a default + * @return a future which completes with the new connection, possibly cancelled + */ + CompletableFuture showConnectDialog(Program program); /** * Prompt the user to create a new connection, optionally fixing the factory * - * @param factory the required factory, or null for user selection + * @param factory the required factory * @return a future which completes with the new connection, possible cancelled */ CompletableFuture showConnectDialog(DebuggerModelFactory factory); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java index af51803531..89b1018790 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java @@ -473,15 +473,15 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes DebuggerConnectDialog dialog = waitForDialogComponent(DebuggerConnectDialog.class); FactoryEntry fe = (FactoryEntry) dialog.dropdownModel.getSelectedItem(); - assertEquals(mb.testFactory, fe.factory); + assertEquals(mb.testFactory, fe.factory()); assertEquals(TestDebuggerModelFactory.FAKE_DETAILS_HTML, dialog.description.getText()); - Component[] components = dialog.pairPanel.getComponents(); + Component[] components = dialog.gridPanel.getComponents(); assertTrue(components[0] instanceof JLabel); JLabel label = (JLabel) components[0]; - assertEquals(TestDebuggerModelFactory.FAKE_OPTION_NAME, label.getText()); + assertEquals("" + TestDebuggerModelFactory.FAKE_OPTION_NAME, label.getText()); assertTrue(components[1] instanceof JTextField); JTextField field = (JTextField) components[1]; @@ -518,7 +518,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes DebuggerConnectDialog connectDialog = waitForDialogComponent(DebuggerConnectDialog.class); FactoryEntry fe = (FactoryEntry) connectDialog.dropdownModel.getSelectedItem(); - assertEquals(mb.testFactory, fe.factory); + assertEquals(mb.testFactory, fe.factory()); pressButtonByText(connectDialog, AbstractConnectAction.NAME, true); // NOTE: testModel is null. Don't use #createTestModel(), which adds to service diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java index c876b26de7..ecf1dc0883 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerModelFactory.java @@ -16,22 +16,52 @@ package ghidra.dbg; import ghidra.dbg.util.ConfigurableFactory; +import ghidra.program.model.listing.Program; import ghidra.util.classfinder.ExtensionPoint; /** * A factory for a debugger model * *

- * This provides a discoverable means of creating a debug model. + * This provides a discoverable means of configuring and creating a debug model. */ public interface DebuggerModelFactory extends ExtensionPoint, ConfigurableFactory { + /** - * Check if this factory is compatible with the local system. + * Get the priority for selecting this factory by default for the given program * + *

+ * A default factory is selected when the current factory and the last successful factory are + * incompatible with the current program, or if this is the very first time connecting. Of those + * factories compatible with the current program, the one with the highest priority (larger + * numerical value) is selected. If none are compatible, then the current selection is left as + * is. + * + *

+ * Note that negative priorities imply the factory is not compatible with the given program or + * local system. + * + * @param program the current program, or null + * @return the priority, higher values mean higher priority + */ + default int getPriority(Program program) { + return 0; + } + + /** + * Check if this factory is compatible with the local system and given program. + * + *

+ * WARNING: Implementations should not likely override this method. If one does, it must + * behave in the same manner as given in this default implementation: If + * {@link #getPriority(Program)} would return a non-negative result for the program, then this + * factory is compatible with that program. If negative, this factory is not compatible. + * + * @param program the current program, or null * @return true if compatible */ - default boolean isCompatible() { - return true; + default boolean isCompatible(Program program) { + return getPriority(program) >= 0; } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelFactory.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelFactory.java index 30f6d050c8..dd088f5e59 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelFactory.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestDebuggerModelFactory.java @@ -27,7 +27,7 @@ import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; public class TestDebuggerModelFactory implements DebuggerModelFactory { public static final String FAKE_DETAILS = "A 'connection' to a fake debugger"; public static final String FAKE_DETAILS_HTML = - "Description: A 'connection' to a fake debugger"; + "Description: A 'connection' to a fake debugger"; public static final String FAKE_OPTION_NAME = "Test String"; public static final String FAKE_DEFAULT = "Default test string";