mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-2623: Improve connect dialog and factory descriptions
This commit is contained in:
parent
5195aaebc1
commit
8dbf2341b2
30 changed files with 565 additions and 333 deletions
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <code>target remote
|
||||
...</code>.
|
||||
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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 <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;
|
||||
@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() {
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<? extends DebuggerObjectModel> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -37,7 +37,6 @@ public class DbgDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
|
|||
protected List<String> getLauncherPath() {
|
||||
return PathUtils.parse("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected class InVmDbgengDebuggerProgramLaunchOffer
|
||||
|
@ -133,7 +132,7 @@ public class DbgDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
|
|||
}
|
||||
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
|
||||
for (DebuggerModelFactory factory : service.getModelFactories()) {
|
||||
if (!factory.isCompatible()) {
|
||||
if (!factory.isCompatible(program)) {
|
||||
continue;
|
||||
}
|
||||
String clsName = factory.getClass().getName();
|
||||
|
|
|
@ -95,7 +95,7 @@ public class FridaDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchO
|
|||
}
|
||||
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
|
||||
for (DebuggerModelFactory factory : service.getModelFactories()) {
|
||||
if (!factory.isCompatible()) {
|
||||
if (!factory.isCompatible(program)) {
|
||||
continue;
|
||||
}
|
||||
String clsName = factory.getClass().getName();
|
||||
|
|
|
@ -139,7 +139,7 @@ public class GdbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpi
|
|||
}
|
||||
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
|
||||
for (DebuggerModelFactory factory : service.getModelFactories()) {
|
||||
if (!factory.isCompatible()) {
|
||||
if (!factory.isCompatible(program)) {
|
||||
continue;
|
||||
}
|
||||
String clsName = factory.getClass().getName();
|
||||
|
|
|
@ -90,7 +90,7 @@ public class LldbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOp
|
|||
}
|
||||
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
|
||||
for (DebuggerModelFactory factory : service.getModelFactories()) {
|
||||
if (!factory.isCompatible()) {
|
||||
if (!factory.isCompatible(program)) {
|
||||
continue;
|
||||
}
|
||||
String clsName = factory.getClass().getName();
|
||||
|
|
|
@ -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 = "<html><b>Description:</b> ";
|
||||
|
||||
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 DebuggerModelFactory factory;
|
||||
private DebuggerModelFactory currentFactory;
|
||||
private DebuggerModelFactory successFactory;
|
||||
private final Map<DebuggerModelFactory, FactoryEntry> factories = new HashMap<>();
|
||||
private FactoriesChangedListener listener = new FactoriesChangedListener();
|
||||
|
||||
|
@ -81,26 +114,12 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
|||
private final Map<Property<?>, Component> components = new LinkedHashMap<>();
|
||||
|
||||
protected JLabel description;
|
||||
protected JPanel pairPanel;
|
||||
private PairLayout layout;
|
||||
protected JPanel gridPanel;
|
||||
|
||||
protected JButton connectButton;
|
||||
protected CompletableFuture<? extends DebuggerObjectModel> futureConnect;
|
||||
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() {
|
||||
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 + "</html>");
|
||||
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<DebuggerObjectModel> 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<String, Property<?>> optsMap = factory.getOptions();
|
||||
//layout.setRows(Math.max(1, optsMap.size()));
|
||||
pairPanel.removeAll();
|
||||
Map<String, Property<?>> optsMap = currentFactory.getOptions();
|
||||
gridPanel.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()) {
|
||||
Property<?> property = opt.getValue();
|
||||
JLabel label = new JLabel(opt.getKey());
|
||||
pairPanel.add(label);
|
||||
JLabel label = new JLabel("<html>" + 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<DebuggerModelFactory, FactoryEntry> 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<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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -641,14 +641,24 @@ public class DebuggerModelServicePlugin extends Plugin
|
|||
}
|
||||
|
||||
protected CompletableFuture<DebuggerObjectModel> doShowConnectDialog(PluginTool tool,
|
||||
DebuggerModelFactory factory) {
|
||||
CompletableFuture<DebuggerObjectModel> future = connectDialog.reset(factory);
|
||||
DebuggerModelFactory factory, Program program) {
|
||||
CompletableFuture<DebuggerObjectModel> future = connectDialog.reset(factory, program);
|
||||
tool.showDialog(connectDialog);
|
||||
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
|
||||
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
|
||||
return doShowConnectDialog(tool, factory);
|
||||
return doShowConnectDialog(tool, factory, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -269,9 +269,19 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
|||
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
|
||||
public CompletableFuture<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory) {
|
||||
return delegate.doShowConnectDialog(tool, factory);
|
||||
return delegate.doShowConnectDialog(tool, factory, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -353,14 +353,20 @@ public interface DebuggerModelService {
|
|||
*
|
||||
* @return a future which completes with the new connection, possibly cancelled
|
||||
*/
|
||||
default CompletableFuture<DebuggerObjectModel> showConnectDialog() {
|
||||
return showConnectDialog(null);
|
||||
}
|
||||
CompletableFuture<DebuggerObjectModel> 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<DebuggerObjectModel> 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<DebuggerObjectModel> showConnectDialog(DebuggerModelFactory factory);
|
||||
|
|
|
@ -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("<html>" + 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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* <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
|
||||
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
|
||||
*/
|
||||
default boolean isCompatible() {
|
||||
return true;
|
||||
default boolean isCompatible(Program program) {
|
||||
return getPriority(program) >= 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
"<html><b>Description:</b> A 'connection' to a fake 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_DEFAULT = "Default test string";
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue