GP-2623: Improve connect dialog and factory descriptions

This commit is contained in:
Dan 2023-01-18 16:53:01 -05:00
parent 5195aaebc1
commit 8dbf2341b2
30 changed files with 565 additions and 333 deletions

View file

@ -23,15 +23,16 @@ import agent.dbgeng.model.impl.DbgModelImpl;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.program.model.listing.Program;
/** @FactoryDescription(
* Note this is in the testing source because it's not meant to be shipped in the release.... That brief = "MS dbgeng.dll (WinDbg)",
* may change if it proves stable, though, no? htmlDetails = """
*/ Connect to the Microsoft Debug Engine.
@FactoryDescription( // This is the same engine that powers WinDbg.
brief = "IN-VM MS dbgeng local debugger", // This is best for most Windows userspace and kernel targets.
htmlDetails = "Launch a dbgeng session in this same JVM" // Kernel debugging is still experimental.
) This will access the native API, which may put Ghidra's JVM at risk.""")
public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory { public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory {
protected String remote = "none"; // Require user to start server protected String remote = "none"; // Require user to start server
@ -53,8 +54,18 @@ public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory {
} }
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
return System.getProperty("os.name").toLowerCase().contains("windows"); // 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() { public String getAgentTransport() {

View file

@ -19,14 +19,17 @@ import java.util.List;
import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.util.classfinder.ExtensionPointProperties; import ghidra.program.model.listing.Program;
@FactoryDescription( // @FactoryDescription(
brief = "MS dbgeng.dll (WinDbg) local agent via GADP/TCP", // brief = "MS dbgeng.dll (WinDbg) via GADP",
htmlDetails = "Launch a new agent using the Microsoft Debug Engine." // htmlDetails = """
) Connect to the Microsoft Debug Engine.
@ExtensionPointProperties(priority = 100) This is the same engine that powers WinDbg.
public class DbgEngLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { 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 protected String remote = "none"; // Require user to start server
@FactoryOption("DebugConnect options (.server)") @FactoryOption("DebugConnect options (.server)")
@ -39,9 +42,18 @@ public class DbgEngLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerMo
Property.fromAccessors(String.class, this::getAgentTransport, this::setAgentTransport); Property.fromAccessors(String.class, this::getAgentTransport, this::setAgentTransport);
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
// TODO: Might instead look for the DLL // 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() { public String getAgentTransport() {

View file

@ -16,7 +16,7 @@
package agent.dbgeng.model.gadp; package agent.dbgeng.model.gadp;
import agent.dbgeng.dbgeng.DbgEngTest; import agent.dbgeng.dbgeng.DbgEngTest;
import agent.dbgeng.gadp.DbgEngLocalDebuggerModelFactory; import agent.dbgeng.gadp.DbgEngGadpDebuggerModelFactory;
import agent.dbgeng.model.AbstractDbgengModelHost; import agent.dbgeng.model.AbstractDbgengModelHost;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
@ -24,6 +24,6 @@ public class GadpDbgengModelHost extends AbstractDbgengModelHost {
@Override @Override
public DebuggerModelFactory getModelFactory() { public DebuggerModelFactory getModelFactory() {
DbgEngTest.assumeDbgengDLLLoadable(); DbgEngTest.assumeDbgengDLLLoadable();
return new DbgEngLocalDebuggerModelFactory(); return new DbgEngGadpDebuggerModelFactory();
} }
} }

View file

@ -23,15 +23,14 @@ import agent.dbgmodel.model.impl.DbgModel2Impl;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.program.model.listing.Program;
/** @FactoryDescription(
* Note this is in the testing source because it's not meant to be shipped in the release.... That brief = "MS dbgmodel.dll (WinDbg Preview)",
* may change if it proves stable, though, no? htmlDetails = """
*/ Connect to the Microsoft Debug Model.
@FactoryDescription( // This is the same engine that powers WinDbg 2.
brief = "IN-VM MS dbgmodel local debugger", // This will access the native API, which may put Ghidra's JVM at risk.""")
htmlDetails = "Launch a dbgmodel session in this same JVM" //
)
public class DbgModelInJvmDebuggerModelFactory implements DebuggerModelFactory { public class DbgModelInJvmDebuggerModelFactory implements DebuggerModelFactory {
protected String remote = "none"; // Require user to start server protected String remote = "none"; // Require user to start server
@ -53,8 +52,18 @@ public class DbgModelInJvmDebuggerModelFactory implements DebuggerModelFactory {
} }
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
return System.getProperty("os.name").toLowerCase().contains("windows"); // 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() { public String getAgentTransport() {

View file

@ -15,16 +15,17 @@
*/ */
package agent.dbgmodel.gadp; package agent.dbgmodel.gadp;
import agent.dbgeng.gadp.DbgEngLocalDebuggerModelFactory; import agent.dbgeng.gadp.DbgEngGadpDebuggerModelFactory;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.util.classfinder.ExtensionPointProperties; import ghidra.program.model.listing.Program;
@FactoryDescription( // @FactoryDescription(
brief = "MS dbgmodel.dll (WinDbg 2) local agent via GADP/TCP", // brief = "MS dbgmodel.dll (WinDbg Preview) via GADP/TCP",
htmlDetails = "Launch a new agent using the Microsoft Debug Model (best for WinDbg 2)." // htmlDetails = """
) Connect to the Microsoft Debug Model.
@ExtensionPointProperties(priority = 90) This is the same engine that powers WinDbg 2.
public class DbgModelLocalDebuggerModelFactory extends DbgEngLocalDebuggerModelFactory { This will protect Ghidra's JVM by using a subprocess to access the native API.""")
public class DbgModelGadpDebuggerModelFactory extends DbgEngGadpDebuggerModelFactory {
@Override @Override
protected String getThreadName() { protected String getThreadName() {
@ -35,4 +36,19 @@ public class DbgModelLocalDebuggerModelFactory extends DbgEngLocalDebuggerModelF
protected Class<?> getServerClass() { protected Class<?> getServerClass() {
return DbgModelGadpServer.class; 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;
}
} }

View file

@ -21,17 +21,14 @@ import agent.frida.model.impl.FridaModelImpl;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.util.classfinder.ExtensionPointProperties; import ghidra.program.model.listing.Program;
/** @FactoryDescription(
* Note this is in the testing source because it's not meant to be shipped in the release.... That brief = "PROTOTYPE: Frida",
* may change if it proves stable, though, no? htmlDetails = """
*/ Connect to Frida.
@FactoryDescription( // This is an experimental connector. Use at your own risk.
brief = "IN-VM Frida local debugger", // This will access the native API, which may put Ghidra's JVM at risk.""")
htmlDetails = "Launch a Frida session in this same JVM" //
)
@ExtensionPointProperties(priority = 80)
public class FridaInJvmDebuggerModelFactory implements DebuggerModelFactory { public class FridaInJvmDebuggerModelFactory implements DebuggerModelFactory {
@Override @Override
@ -41,9 +38,18 @@ public class FridaInJvmDebuggerModelFactory implements DebuggerModelFactory {
} }
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
String osname = System.getProperty("os.name"); String osname = System.getProperty("os.name").toLowerCase();
return osname.contains("Mac OS X") || osname.contains("Linux") || osname.contains("Windows"); 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;
} }
} }

View file

@ -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<String> cmd) {
cmd.add(getServerClass().getCanonicalName());
cmd.addAll(List.of("-H", host));
cmd.addAll(List.of("-p", Integer.toString(port)));
}
}

View file

@ -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<String> 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<String> 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<String> cmd) {
cmd.add(getServerClass().getCanonicalName());
cmd.addAll(List.of("-H", host));
cmd.addAll(List.of("-p", Integer.toString(port)));
}
}

View file

@ -17,7 +17,7 @@ package agent.frida.model.gadp;
import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeFalse;
import agent.frida.gadp.FridaLocalDebuggerModelFactory; import agent.frida.gadp.FridaGadpDebuggerModelFactory;
import agent.frida.model.AbstractFridaModelHost; import agent.frida.model.AbstractFridaModelHost;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
@ -26,6 +26,6 @@ class GadpFridaModelHost extends AbstractFridaModelHost {
@Override @Override
public DebuggerModelFactory getModelFactory() { public DebuggerModelFactory getModelFactory() {
assumeFalse("Not ready for CI", SystemUtilities.isInTestingBatchMode()); assumeFalse("Not ready for CI", SystemUtilities.isInTestingBatchMode());
return new FridaLocalDebuggerModelFactory(); return new FridaGadpDebuggerModelFactory();
} }
} }

View file

@ -25,15 +25,16 @@ import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.dbg.util.ShellUtils; import ghidra.dbg.util.ShellUtils;
import ghidra.program.model.listing.Program;
/** @FactoryDescription(
* Note this is in the testing source because it's not meant to be shipped in the release.... That brief = "gdb",
* may change if it proves stable, though, no? htmlDetails = """
*/ Connect to gdb.
@FactoryDescription( // This is best for most Linux and Unix userspace targets, and many embedded targets.
brief = "IN-VM GNU gdb local debugger", // It may also be used with gdbserver by connecting to gdb, then using <code>target remote
htmlDetails = "Launch a GDB session in this same JVM" // ...</code>.
) This will access the native API, which may put Ghidra's JVM at risk.""")
public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory { public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory {
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD; private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
@ -59,8 +60,17 @@ public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory {
} }
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
return GdbCompatibility.INSTANCE.isCompatible(gdbCmd); 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() { public String getGdbCommand() {

View file

@ -24,10 +24,14 @@ import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ShellUtils; import ghidra.dbg.util.ShellUtils;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.program.model.listing.Program;
@FactoryDescription( @FactoryDescription(
brief = "GNU gdb via SSH", brief = "gdb via SSH",
htmlDetails = "Launch a GDB session over an SSH connection") 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 { public class GdbOverSshDebuggerModelFactory implements DebuggerModelFactory {
private String gdbCmd = "/usr/bin/gdb"; private String gdbCmd = "/usr/bin/gdb";
@ -91,8 +95,14 @@ public class GdbOverSshDebuggerModelFactory implements DebuggerModelFactory {
} }
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
return true; if (program != null) {
String exe = program.getExecutablePath();
if (exe == null || exe.isBlank()) {
return -1;
}
}
return 75;
} }
public String getGdbCommand() { public String getGdbCommand() {

View file

@ -22,14 +22,17 @@ import agent.gdb.manager.GdbManager;
import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory; import ghidra.dbg.gadp.server.AbstractGadpLocalDebuggerModelFactory;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.dbg.util.ShellUtils; import ghidra.dbg.util.ShellUtils;
import ghidra.util.classfinder.ExtensionPointProperties; import ghidra.program.model.listing.Program;
@FactoryDescription( // @FactoryDescription(
brief = "GNU gdb local agent via GADP/TCP", // brief = "gdb via GADP",
htmlDetails = "Launch a new agent using GDB. This may start a new session or join an existing one." // htmlDetails = """
) Connect to gdb.
@ExtensionPointProperties(priority = 100) This is best for most Linux and Unix userspace targets, and many embedded targets.
public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory { This will protect Ghidra's JVM by using a subprocess to access the native API.
If you are using <b>gdbserver</b>, you must connect to gdb first (consider the non-GADP
connector), then use <code>target remote ...</code> to connect to your target.""")
public class GdbGadpDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory {
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD; private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
@FactoryOption("GDB launch command") @FactoryOption("GDB launch command")
@ -44,9 +47,17 @@ public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModel
// TODO: newLine option? // TODO: newLine option?
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
// TODO: Could potentially support GDB on Windows, but the pty thing would need porting. if (!GdbCompatibility.INSTANCE.isCompatible(gdbCmd)) {
return 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() { public String getGdbCommand() {

View file

@ -20,7 +20,7 @@ import static org.junit.Assume.assumeTrue;
import java.io.File; import java.io.File;
import agent.gdb.gadp.GdbLocalDebuggerModelFactory; import agent.gdb.gadp.GdbGadpDebuggerModelFactory;
import agent.gdb.model.AbstractGdbModelHost; import agent.gdb.model.AbstractGdbModelHost;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
@ -30,6 +30,6 @@ class GadpGdbModelHost extends AbstractGdbModelHost {
public DebuggerModelFactory getModelFactory() { public DebuggerModelFactory getModelFactory() {
assumeFalse("Not ready for CI", SystemUtilities.isInTestingBatchMode()); assumeFalse("Not ready for CI", SystemUtilities.isInTestingBatchMode());
assumeTrue("GDB cannot be found", new File("/usr/bin/gdb").canExecute()); assumeTrue("GDB cannot be found", new File("/usr/bin/gdb").canExecute());
return new GdbLocalDebuggerModelFactory(); return new GdbGadpDebuggerModelFactory();
} }
} }

View file

@ -21,21 +21,16 @@ import agent.lldb.model.impl.LldbModelImpl;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
import ghidra.util.classfinder.ExtensionPointProperties; import ghidra.program.model.listing.Program;
/** @FactoryDescription(
* Note this is in the testing source because it's not meant to be shipped in the release.... That brief = "lldb",
* may change if it proves stable, though, no? htmlDetails = """
*/ Connect to lldb.
@FactoryDescription( // This is best for most macOS and iOS targets, but supports many others.
brief = "IN-VM lldb local debugger", // This will access the native API, which may put Ghidra's JVM at risk.""")
htmlDetails = "Launch a lldb session in this same JVM" //
)
@ExtensionPointProperties(priority = 80)
public class LldbInJvmDebuggerModelFactory implements DebuggerModelFactory { public class LldbInJvmDebuggerModelFactory implements DebuggerModelFactory {
// TODO remoteTransport option?
@Override @Override
public CompletableFuture<? extends DebuggerObjectModel> build() { public CompletableFuture<? extends DebuggerObjectModel> build() {
LldbModelImpl model = new LldbModelImpl(); LldbModelImpl model = new LldbModelImpl();
@ -43,9 +38,19 @@ public class LldbInJvmDebuggerModelFactory implements DebuggerModelFactory {
} }
@Override @Override
public boolean isCompatible() { public int getPriority(Program program) {
String osname = System.getProperty("os.name"); String osname = System.getProperty("os.name").toLowerCase();
return osname.contains("Mac OS X") || osname.contains("Linux") || osname.contains("Windows"); 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;
} }
} }

View file

@ -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<String> cmd) {
cmd.add(getServerClass().getCanonicalName());
cmd.addAll(List.of("-H", host));
cmd.addAll(List.of("-p", Integer.toString(port)));
}
}

View file

@ -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<String> 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<String> 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<String> cmd) {
cmd.add(getServerClass().getCanonicalName());
cmd.addAll(List.of("-H", host));
cmd.addAll(List.of("-p", Integer.toString(port)));
}
}

View file

@ -26,10 +26,12 @@ import ghidra.async.TypeSpec;
import ghidra.dbg.DebuggerModelFactory; import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
@FactoryDescription( // @FactoryDescription(
brief = "GADP connection over TCP", // brief = "Ghidra debug agent (GADP)",
htmlDetails = "Connect to an optionally remote agent via GADP/TCP." // 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 { public class GadpTcpDebuggerModelFactory implements DebuggerModelFactory {
private String host = "localhost"; private String host = "localhost";

View file

@ -22,10 +22,12 @@ import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.jdi.model.JdiModelImpl; import ghidra.dbg.jdi.model.JdiModelImpl;
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription; import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
@FactoryDescription( // @FactoryDescription(
brief = "JDI debugger", // brief = "PROTOTYPE: JDWP (Java or Dalvik)",
htmlDetails = "Debug a Java or Dalvik VM (supports JDWP)" // 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 { public class JdiDebuggerModelFactory implements DebuggerModelFactory {
@Override @Override

View file

@ -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.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.DebuggerModelService; import ghidra.app.services.DebuggerModelService;
import ghidra.app.services.ProgramManager;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.listing.Program;
public class DebuggerTargetsProvider extends ComponentProviderAdapter { public class DebuggerTargetsProvider extends ComponentProviderAdapter {
@ -107,7 +109,9 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter {
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
// NB. Drop the future on the floor, because the UI will report issues. // NB. Drop the future on the floor, because the UI will report issues.
// Cancellation should be ignored. // Cancellation should be ignored.
modelService.showConnectDialog(); ProgramManager programManager = tool.getService(ProgramManager.class);
Program program = programManager == null ? null : programManager.getCurrentProgram();
modelService.showConnectDialog(program);
} }
@Override @Override

View file

@ -37,7 +37,6 @@ public class DbgDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
protected List<String> getLauncherPath() { protected List<String> getLauncherPath() {
return PathUtils.parse(""); return PathUtils.parse("");
} }
} }
protected class InVmDbgengDebuggerProgramLaunchOffer protected class InVmDbgengDebuggerProgramLaunchOffer
@ -133,7 +132,7 @@ public class DbgDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
} }
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>(); List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
for (DebuggerModelFactory factory : service.getModelFactories()) { for (DebuggerModelFactory factory : service.getModelFactories()) {
if (!factory.isCompatible()) { if (!factory.isCompatible(program)) {
continue; continue;
} }
String clsName = factory.getClass().getName(); String clsName = factory.getClass().getName();

View file

@ -95,7 +95,7 @@ public class FridaDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchO
} }
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>(); List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
for (DebuggerModelFactory factory : service.getModelFactories()) { for (DebuggerModelFactory factory : service.getModelFactories()) {
if (!factory.isCompatible()) { if (!factory.isCompatible(program)) {
continue; continue;
} }
String clsName = factory.getClass().getName(); String clsName = factory.getClass().getName();

View file

@ -139,7 +139,7 @@ public class GdbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
} }
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>(); List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
for (DebuggerModelFactory factory : service.getModelFactories()) { for (DebuggerModelFactory factory : service.getModelFactories()) {
if (!factory.isCompatible()) { if (!factory.isCompatible(program)) {
continue; continue;
} }
String clsName = factory.getClass().getName(); String clsName = factory.getClass().getName();

View file

@ -90,7 +90,7 @@ public class LldbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOp
} }
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>(); List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
for (DebuggerModelFactory factory : service.getModelFactories()) { for (DebuggerModelFactory factory : service.getModelFactories()) {
if (!factory.isCompatible()) { if (!factory.isCompatible(program)) {
continue; continue;
} }
String clsName = factory.getClass().getName(); String clsName = factory.getClass().getName();

View file

@ -26,6 +26,7 @@ import java.util.concurrent.CompletableFuture;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import javax.swing.text.View;
import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
@ -40,13 +41,14 @@ import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.util.ConfigurableFactory.Property; import ghidra.dbg.util.ConfigurableFactory.Property;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.program.model.listing.Program;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.layout.PairLayout;
public class DebuggerConnectDialog extends DialogComponentProvider public class DebuggerConnectDialog extends DialogComponentProvider
implements PropertyChangeListener { 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 = "<html><b>Description:</b> "; private static final String HTML_BOLD_DESCRIPTION = "<html><b>Description:</b> ";
protected class FactoriesChangedListener 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<String> {
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 DebuggerModelService modelService;
private DebuggerModelFactory factory; private DebuggerModelFactory currentFactory;
private DebuggerModelFactory successFactory;
private final Map<DebuggerModelFactory, FactoryEntry> factories = new HashMap<>(); private final Map<DebuggerModelFactory, FactoryEntry> factories = new HashMap<>();
private FactoriesChangedListener listener = new FactoriesChangedListener(); private FactoriesChangedListener listener = new FactoriesChangedListener();
@ -81,26 +114,12 @@ public class DebuggerConnectDialog extends DialogComponentProvider
private final Map<Property<?>, Component> components = new LinkedHashMap<>(); private final Map<Property<?>, Component> components = new LinkedHashMap<>();
protected JLabel description; protected JLabel description;
protected JPanel pairPanel; protected JPanel gridPanel;
private PairLayout layout;
protected JButton connectButton; protected JButton connectButton;
protected CompletableFuture<? extends DebuggerObjectModel> futureConnect; protected CompletableFuture<? extends DebuggerObjectModel> futureConnect;
protected CompletableFuture<DebuggerObjectModel> result; protected CompletableFuture<DebuggerObjectModel> result;
protected static class FactoryEntry {
DebuggerModelFactory factory;
public FactoryEntry(DebuggerModelFactory factory) {
this.factory = factory;
}
@Override
public String toString() {
return factory.getBrief();
}
}
public DebuggerConnectDialog() { public DebuggerConnectDialog() {
super(AbstractConnectAction.NAME, true, true, true, false); super(AbstractConnectAction.NAME, true, true, true, false);
@ -126,7 +145,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
toAdd.add(entry); toAdd.add(entry);
} }
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
toAdd.sort(Comparator.comparing(FactoryEntry::toString)); toAdd.sort(Comparator.comparing(FactoryEntry::toString, NameComparator.INSTANCE));
for (FactoryEntry entry : toAdd) { for (FactoryEntry entry : toAdd) {
dropdownModel.addElement(entry); dropdownModel.addElement(entry);
} }
@ -190,20 +209,20 @@ public class DebuggerConnectDialog extends DialogComponentProvider
JPanel inner = new JPanel(new BorderLayout()); JPanel inner = new JPanel(new BorderLayout());
description = new JLabel(HTML_BOLD_DESCRIPTION + "</html>"); description = new JLabel(HTML_BOLD_DESCRIPTION + "</html>");
description.setBorder(new EmptyBorder(10, 0, 10, 0)); description.setBorder(new EmptyBorder(10, 0, 10, 0));
description.setPreferredSize(new Dimension(400, 150));
inner.add(description); inner.add(description);
topBox.add(inner); topBox.add(inner);
panel.add(topBox, BorderLayout.NORTH); panel.add(topBox, BorderLayout.NORTH);
layout = new PairLayout(5, 5); gridPanel = new JPanel(new GridBagLayout());
pairPanel = new JPanel(layout);
JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER)); JPanel centering = new JPanel(new FlowLayout(FlowLayout.CENTER));
JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane scrolling = new JScrollPane(centering, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrolling.setPreferredSize(new Dimension(100, 130)); scrolling.setPreferredSize(new Dimension(100, 200));
panel.add(scrolling, BorderLayout.CENTER); panel.add(scrolling, BorderLayout.CENTER);
centering.add(pairPanel); centering.add(gridPanel);
addWorkPanel(panel); addWorkPanel(panel);
@ -219,13 +238,15 @@ public class DebuggerConnectDialog extends DialogComponentProvider
private void itemSelected(ItemEvent evt) { private void itemSelected(ItemEvent evt) {
if (evt.getStateChange() == ItemEvent.DESELECTED) { if (evt.getStateChange() == ItemEvent.DESELECTED) {
pairPanel.removeAll(); gridPanel.removeAll();
} }
else if (evt.getStateChange() == ItemEvent.SELECTED) { else if (evt.getStateChange() == ItemEvent.SELECTED) {
FactoryEntry ent = (FactoryEntry) evt.getItem(); FactoryEntry ent = (FactoryEntry) evt.getItem();
factory = ent.factory; currentFactory = ent.factory;
populateOptions(); 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..."); setStatusText("Connecting...");
synchronized (this) { synchronized (this) {
futureConnect = factory.build(); futureConnect = currentFactory.build();
} }
futureConnect.thenCompose(m -> m.fetchModelRoot()).thenAcceptAsync(r -> { futureConnect.thenCompose(m -> m.fetchModelRoot()).thenAcceptAsync(r -> {
DebuggerObjectModel m = r.getModel(); DebuggerObjectModel m = r.getModel();
@ -268,6 +289,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
synchronized (this) { synchronized (this) {
futureConnect = null; futureConnect = null;
} }
successFactory = currentFactory;
connectButton.setEnabled(true); connectButton.setEnabled(true);
}); });
} }
@ -284,7 +306,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
} }
protected synchronized CompletableFuture<DebuggerObjectModel> reset( protected synchronized CompletableFuture<DebuggerObjectModel> reset(
DebuggerModelFactory factory) { DebuggerModelFactory factory, Program program) {
if (factory != null) { if (factory != null) {
synchronized (factories) { synchronized (factories) {
dropdownModel.setSelectedItem(factories.get(factory)); dropdownModel.setSelectedItem(factories.get(factory));
@ -292,6 +314,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
dropdown.setEnabled(false); dropdown.setEnabled(false);
} }
else { else {
selectCompatibleFactory(program);
dropdown.setEnabled(true); dropdown.setEnabled(true);
} }
@ -311,18 +334,42 @@ public class DebuggerConnectDialog extends DialogComponentProvider
} }
protected void populateOptions() { protected void populateOptions() {
description.setText( description.setText(HTML_BOLD_DESCRIPTION + currentFactory.getHtmlDetails());
HTML_BOLD_DESCRIPTION + HTMLUtilities.friendlyEncodeHTML(factory.getHtmlDetails()));
propertyEditors.clear(); propertyEditors.clear();
components.clear(); components.clear();
Map<String, Property<?>> optsMap = factory.getOptions(); Map<String, Property<?>> optsMap = currentFactory.getOptions();
//layout.setRows(Math.max(1, optsMap.size())); gridPanel.removeAll();
pairPanel.removeAll(); GridBagConstraints constraints;
if (optsMap.isEmpty()) {
JLabel label =
new JLabel("<html>There are no configuration options for this connector.");
constraints = new GridBagConstraints();
gridPanel.add(label, constraints);
}
int i = 0;
for (Map.Entry<String, Property<?>> opt : optsMap.entrySet()) { for (Map.Entry<String, Property<?>> opt : optsMap.entrySet()) {
Property<?> property = opt.getValue(); Property<?> property = opt.getValue();
JLabel label = new JLabel(opt.getKey()); JLabel label = new JLabel("<html>" + HTMLUtilities.escapeHTML(opt.getKey())) {
pairPanel.add(label); @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(); Class<?> type = property.getValueClass();
PropertyEditor editor = PropertyEditorManager.findEditor(type); PropertyEditor editor = PropertyEditorManager.findEditor(type);
@ -331,11 +378,22 @@ public class DebuggerConnectDialog extends DialogComponentProvider
} }
editor.setValue(property.getValue()); editor.setValue(property.getValue());
editor.addPropertyChangeListener(this); editor.addPropertyChangeListener(this);
Component comp = MiscellaneousUtils.getEditorComponent(editor); Component editorComponent = MiscellaneousUtils.getEditorComponent(editor);
pairPanel.add(comp); 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); 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) { public void writeConfigState(SaveState saveState) {
if (factory != null) { if (currentFactory != null) {
saveState.putString(KEY_FACTORY_CLASSNAME, factory.getClass().getName()); 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) { protected FactoryEntry getByName(String className) {
String factoryName = saveState.getString(KEY_FACTORY_CLASSNAME, null);
if (factoryName == null) {
return;
}
synchronized (factories) { synchronized (factories) {
for (Map.Entry<DebuggerModelFactory, FactoryEntry> ent : factories.entrySet()) { for (FactoryEntry ent : factories.values()) {
String name = ent.getKey().getClass().getName(); String name = ent.factory.getClass().getName();
if (factoryName.equals(name)) { if (className.equals(name)) {
factory = ent.getKey(); return ent;
dropdown.setSelectedItem(ent.getValue());
} }
} }
return null;
} }
} }
protected Collection<PrioritizedFactory> 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;
}
} }

View file

@ -641,14 +641,24 @@ public class DebuggerModelServicePlugin extends Plugin
} }
protected CompletableFuture<DebuggerObjectModel> doShowConnectDialog(PluginTool tool, protected CompletableFuture<DebuggerObjectModel> doShowConnectDialog(PluginTool tool,
DebuggerModelFactory factory) { DebuggerModelFactory factory, Program program) {
CompletableFuture<DebuggerObjectModel> future = connectDialog.reset(factory); CompletableFuture<DebuggerObjectModel> future = connectDialog.reset(factory, program);
tool.showDialog(connectDialog); tool.showDialog(connectDialog);
return future; return future;
} }
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog() {
return doShowConnectDialog(tool, null, null);
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(Program program) {
return doShowConnectDialog(tool, null, program);
}
@Override @Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) { public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
return doShowConnectDialog(tool, factory); return doShowConnectDialog(tool, factory, null);
} }
} }

View file

@ -269,9 +269,19 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
closeAllModels(); closeAllModels();
} }
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog() {
return delegate.doShowConnectDialog(tool, null, null);
}
@Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(Program program) {
return delegate.doShowConnectDialog(tool, null, program);
}
@Override @Override
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) { public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
return delegate.doShowConnectDialog(tool, factory); return delegate.doShowConnectDialog(tool, factory, null);
} }
@Override @Override

View file

@ -353,14 +353,20 @@ public interface DebuggerModelService {
* *
* @return a future which completes with the new connection, possibly cancelled * @return a future which completes with the new connection, possibly cancelled
*/ */
default CompletableFuture<DebuggerObjectModel> showConnectDialog() { CompletableFuture<DebuggerObjectModel> showConnectDialog();
return showConnectDialog(null);
} /**
* 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<DebuggerObjectModel> showConnectDialog(Program program);
/** /**
* Prompt the user to create a new connection, optionally fixing the factory * 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 * @return a future which completes with the new connection, possible cancelled
*/ */
CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory); CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory);

View file

@ -473,15 +473,15 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
DebuggerConnectDialog dialog = waitForDialogComponent(DebuggerConnectDialog.class); DebuggerConnectDialog dialog = waitForDialogComponent(DebuggerConnectDialog.class);
FactoryEntry fe = (FactoryEntry) dialog.dropdownModel.getSelectedItem(); FactoryEntry fe = (FactoryEntry) dialog.dropdownModel.getSelectedItem();
assertEquals(mb.testFactory, fe.factory); assertEquals(mb.testFactory, fe.factory());
assertEquals(TestDebuggerModelFactory.FAKE_DETAILS_HTML, dialog.description.getText()); assertEquals(TestDebuggerModelFactory.FAKE_DETAILS_HTML, dialog.description.getText());
Component[] components = dialog.pairPanel.getComponents(); Component[] components = dialog.gridPanel.getComponents();
assertTrue(components[0] instanceof JLabel); assertTrue(components[0] instanceof JLabel);
JLabel label = (JLabel) components[0]; JLabel label = (JLabel) components[0];
assertEquals(TestDebuggerModelFactory.FAKE_OPTION_NAME, label.getText()); assertEquals("<html>" + TestDebuggerModelFactory.FAKE_OPTION_NAME, label.getText());
assertTrue(components[1] instanceof JTextField); assertTrue(components[1] instanceof JTextField);
JTextField field = (JTextField) components[1]; JTextField field = (JTextField) components[1];
@ -518,7 +518,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
DebuggerConnectDialog connectDialog = waitForDialogComponent(DebuggerConnectDialog.class); DebuggerConnectDialog connectDialog = waitForDialogComponent(DebuggerConnectDialog.class);
FactoryEntry fe = (FactoryEntry) connectDialog.dropdownModel.getSelectedItem(); FactoryEntry fe = (FactoryEntry) connectDialog.dropdownModel.getSelectedItem();
assertEquals(mb.testFactory, fe.factory); assertEquals(mb.testFactory, fe.factory());
pressButtonByText(connectDialog, AbstractConnectAction.NAME, true); pressButtonByText(connectDialog, AbstractConnectAction.NAME, true);
// NOTE: testModel is null. Don't use #createTestModel(), which adds to service // NOTE: testModel is null. Don't use #createTestModel(), which adds to service

View file

@ -16,22 +16,52 @@
package ghidra.dbg; package ghidra.dbg;
import ghidra.dbg.util.ConfigurableFactory; import ghidra.dbg.util.ConfigurableFactory;
import ghidra.program.model.listing.Program;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
/** /**
* A factory for a debugger model * A factory for a debugger model
* *
* <p> * <p>
* 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 public interface DebuggerModelFactory
extends ExtensionPoint, ConfigurableFactory<DebuggerObjectModel> { extends ExtensionPoint, ConfigurableFactory<DebuggerObjectModel> {
/** /**
* Check if this factory is compatible with the local system. * Get the priority for selecting this factory by default for the given program
* *
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* <b>WARNING:</b> 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 * @return true if compatible
*/ */
default boolean isCompatible() { default boolean isCompatible(Program program) {
return true; return getPriority(program) >= 0;
} }
} }

View file

@ -27,7 +27,7 @@ import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
public class TestDebuggerModelFactory implements DebuggerModelFactory { public class TestDebuggerModelFactory implements DebuggerModelFactory {
public static final String FAKE_DETAILS = "A 'connection' to a fake debugger"; public static final String FAKE_DETAILS = "A 'connection' to a fake debugger";
public static final String FAKE_DETAILS_HTML = public static final String FAKE_DETAILS_HTML =
"<html><b>Description:</b> A&nbsp;'connection'&nbsp;to&nbsp;a&nbsp;fake&nbsp;debugger"; "<html><b>Description:</b> A 'connection' to a fake debugger";
public static final String FAKE_OPTION_NAME = "Test String"; public static final String FAKE_OPTION_NAME = "Test String";
public static final String FAKE_DEFAULT = "Default test string"; public static final String FAKE_DEFAULT = "Default test string";