mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-380: Better quick launch using opinions and offers.
This commit is contained in:
parent
93b2f5cc64
commit
808c20ab7f
28 changed files with 1210 additions and 310 deletions
|
@ -18,21 +18,19 @@ package agent.dbgeng;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.dbgeng.model.impl.DbgModelImpl;
|
import agent.dbgeng.model.impl.DbgModelImpl;
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
|
||||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note this is in the testing source because it's not meant to be shipped in
|
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
||||||
* the release.... That may change if it proves stable, though, no?
|
* may change if it proves stable, though, no?
|
||||||
*/
|
*/
|
||||||
@FactoryDescription( //
|
@FactoryDescription( //
|
||||||
brief = "IN-VM MS dbgeng local debugger", //
|
brief = "IN-VM MS dbgeng local debugger", //
|
||||||
htmlDetails = "Launch a dbgeng session in this same JVM" //
|
htmlDetails = "Launch a dbgeng session in this same JVM" //
|
||||||
)
|
)
|
||||||
@ExtensionPointProperties(priority = 80)
|
public class DbgEngInJvmDebuggerModelFactory implements DebuggerModelFactory {
|
||||||
public class DbgEngInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
|
||||||
|
|
||||||
// TODO remoteTransport option?
|
// TODO remoteTransport option?
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,9 @@ package agent.dbgmodel;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.dbgmodel.model.impl.DbgModel2Impl;
|
import agent.dbgmodel.model.impl.DbgModel2Impl;
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
|
||||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
||||||
|
@ -31,8 +30,7 @@ import ghidra.util.classfinder.ExtensionPointProperties;
|
||||||
brief = "IN-VM MS dbgmodel local debugger", //
|
brief = "IN-VM MS dbgmodel local debugger", //
|
||||||
htmlDetails = "Launch a dbgmodel session in this same JVM" //
|
htmlDetails = "Launch a dbgmodel session in this same JVM" //
|
||||||
)
|
)
|
||||||
@ExtensionPointProperties(priority = 70)
|
public class DbgModelInJvmDebuggerModelFactory implements DebuggerModelFactory {
|
||||||
public class DbgModelInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* ###
|
||||||
|
* 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.gdb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ProcessBuilder.Redirect;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.dbg.util.ShellUtils;
|
||||||
|
|
||||||
|
public enum GdbCompatibility {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public static boolean checkGdbPresent(String path) {
|
||||||
|
try {
|
||||||
|
ProcessBuilder builder = new ProcessBuilder(path, "--version");
|
||||||
|
builder.redirectError(Redirect.INHERIT);
|
||||||
|
builder.redirectOutput(Redirect.INHERIT);
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
Process gdb = builder.start();
|
||||||
|
// TODO: Once supported versions are decided, check the version.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Boolean> cache = new HashMap<>();
|
||||||
|
|
||||||
|
public boolean isCompatible(String gdbCmd) {
|
||||||
|
List<String> args = ShellUtils.parseArgs(gdbCmd);
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return cache.computeIfAbsent(gdbCmd, p -> checkGdbPresent(args.get(0)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,14 +17,12 @@ package agent.gdb;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.gdb.gadp.GdbLocalDebuggerModelFactory;
|
|
||||||
import agent.gdb.manager.GdbManager;
|
import agent.gdb.manager.GdbManager;
|
||||||
import agent.gdb.model.impl.GdbModelImpl;
|
import agent.gdb.model.impl.GdbModelImpl;
|
||||||
import agent.gdb.pty.linux.LinuxPtyFactory;
|
import agent.gdb.pty.linux.LinuxPtyFactory;
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
|
||||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
* Note this is in the testing source because it's not meant to be shipped in the release.... That
|
||||||
|
@ -34,8 +32,7 @@ import ghidra.util.classfinder.ExtensionPointProperties;
|
||||||
brief = "IN-VM GNU gdb local debugger", //
|
brief = "IN-VM GNU gdb local debugger", //
|
||||||
htmlDetails = "Launch a GDB session in this same JVM" //
|
htmlDetails = "Launch a GDB session in this same JVM" //
|
||||||
)
|
)
|
||||||
@ExtensionPointProperties(priority = 80)
|
public class GdbInJvmDebuggerModelFactory implements DebuggerModelFactory {
|
||||||
public class GdbInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
|
||||||
|
|
||||||
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
|
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
|
||||||
@FactoryOption("GDB launch command")
|
@FactoryOption("GDB launch command")
|
||||||
|
@ -56,7 +53,7 @@ public class GdbInJvmDebuggerModelFactory implements LocalDebuggerModelFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCompatible() {
|
public boolean isCompatible() {
|
||||||
return GdbLocalDebuggerModelFactory.checkGdbPresent(gdbCmd);
|
return GdbCompatibility.INSTANCE.isCompatible(gdbCmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGdbCommand() {
|
public String getGdbCommand() {
|
||||||
|
|
|
@ -19,16 +19,15 @@ import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import agent.gdb.model.impl.GdbModelImpl;
|
import agent.gdb.model.impl.GdbModelImpl;
|
||||||
import agent.gdb.pty.ssh.GhidraSshPtyFactory;
|
import agent.gdb.pty.ssh.GhidraSshPtyFactory;
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
|
||||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
import ghidra.dbg.util.ConfigurableFactory.FactoryOption;
|
||||||
|
|
||||||
@FactoryDescription(
|
@FactoryDescription(
|
||||||
brief = "GNU gdb via SSH",
|
brief = "GNU gdb via SSH",
|
||||||
htmlDetails = "Launch a GDB session over an SSH connection")
|
htmlDetails = "Launch a GDB session over an SSH connection")
|
||||||
@ExtensionPointProperties(priority = 60)
|
public class GdbOverSshDebuggerModelFactory implements DebuggerModelFactory {
|
||||||
public class GdbOverSshDebuggerModelFactory implements LocalDebuggerModelFactory {
|
|
||||||
|
|
||||||
private String gdbCmd = "gdb";
|
private String gdbCmd = "gdb";
|
||||||
@FactoryOption("GDB launch command")
|
@FactoryOption("GDB launch command")
|
||||||
|
|
|
@ -15,10 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package agent.gdb.gadp;
|
package agent.gdb.gadp;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.ProcessBuilder.Redirect;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import agent.gdb.GdbCompatibility;
|
||||||
import agent.gdb.manager.GdbManager;
|
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;
|
||||||
|
@ -31,26 +30,6 @@ import ghidra.util.classfinder.ExtensionPointProperties;
|
||||||
)
|
)
|
||||||
@ExtensionPointProperties(priority = 100)
|
@ExtensionPointProperties(priority = 100)
|
||||||
public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory {
|
public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModelFactory {
|
||||||
public static boolean checkGdbPresent(String gdbCmd) {
|
|
||||||
List<String> args = ShellUtils.parseArgs(gdbCmd);
|
|
||||||
if (args.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(args.get(0), "--version");
|
|
||||||
builder.redirectError(Redirect.INHERIT);
|
|
||||||
builder.redirectOutput(Redirect.INHERIT);
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
Process gdb = builder.start();
|
|
||||||
// TODO: Once supported versions are decided, check the version.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Boolean isSuitable;
|
|
||||||
|
|
||||||
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
|
private String gdbCmd = GdbManager.DEFAULT_GDB_CMD;
|
||||||
@FactoryOption("GDB launch command")
|
@FactoryOption("GDB launch command")
|
||||||
|
@ -62,15 +41,10 @@ public class GdbLocalDebuggerModelFactory extends AbstractGadpLocalDebuggerModel
|
||||||
public final Property<Boolean> useExistingOption =
|
public final Property<Boolean> useExistingOption =
|
||||||
Property.fromAccessors(boolean.class, this::isUseExisting, this::setUseExisting);
|
Property.fromAccessors(boolean.class, this::isUseExisting, this::setUseExisting);
|
||||||
|
|
||||||
// TODO: A factory which connects to GDB via SSH. Would need to refactor manager.
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCompatible() {
|
public boolean isCompatible() {
|
||||||
// TODO: Could potentially support GDB on Windows, but the pty thing would need porting.
|
// TODO: Could potentially support GDB on Windows, but the pty thing would need porting.
|
||||||
if (isSuitable != null) {
|
return GdbCompatibility.INSTANCE.isCompatible(gdbCmd);
|
||||||
return isSuitable;
|
|
||||||
}
|
|
||||||
return isSuitable = checkGdbPresent(gdbCmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGdbCommand() {
|
public String getGdbCommand() {
|
||||||
|
|
|
@ -23,12 +23,13 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.gadp.client.GadpClient;
|
import ghidra.dbg.gadp.client.GadpClient;
|
||||||
import ghidra.dbg.gadp.client.GadpTcpDebuggerModelFactory;
|
import ghidra.dbg.gadp.client.GadpTcpDebuggerModelFactory;
|
||||||
|
import ghidra.dbg.util.ConfigurableFactory.FactoryOption;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public abstract class AbstractGadpLocalDebuggerModelFactory implements LocalDebuggerModelFactory {
|
public abstract class AbstractGadpLocalDebuggerModelFactory implements DebuggerModelFactory {
|
||||||
public static final boolean LOG_AGENT_STDOUT = true;
|
public static final boolean LOG_AGENT_STDOUT = true;
|
||||||
|
|
||||||
protected String host = "localhost";
|
protected String host = "localhost";
|
||||||
|
|
|
@ -17,18 +17,16 @@ package ghidra.dbg.jdi;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
|
||||||
import ghidra.dbg.jdi.model.JdiModelImpl;
|
import ghidra.dbg.jdi.model.JdiModelImpl;
|
||||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
||||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
|
||||||
|
|
||||||
@FactoryDescription( //
|
@FactoryDescription( //
|
||||||
brief = "JDI debugger", //
|
brief = "JDI debugger", //
|
||||||
htmlDetails = "Debug a Java or Dalvik VM (supports JDWP)" //
|
htmlDetails = "Debug a Java or Dalvik VM (supports JDWP)" //
|
||||||
)
|
)
|
||||||
@ExtensionPointProperties(priority = 50)
|
public class JdiDebuggerModelFactory implements DebuggerModelFactory {
|
||||||
public class JdiDebuggerModelFactory implements LocalDebuggerModelFactory {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
||||||
|
|
|
@ -2,5 +2,6 @@ AutoReadMemorySpec
|
||||||
DebuggerBot
|
DebuggerBot
|
||||||
DebuggerMappingOpinion
|
DebuggerMappingOpinion
|
||||||
DebuggerModelFactory
|
DebuggerModelFactory
|
||||||
|
DebuggerProgramLaunchOpinion
|
||||||
DisassemblyInject
|
DisassemblyInject
|
||||||
LocationTrackingSpec
|
LocationTrackingSpec
|
||||||
|
|
|
@ -50,8 +50,8 @@
|
||||||
|
|
||||||
<LI>Trace manipulation - those used for viewing and manipulating the trace database,
|
<LI>Trace manipulation - those used for viewing and manipulating the trace database,
|
||||||
including machine state inspection. Most of these behave differently when the view is "at the
|
including machine state inspection. Most of these behave differently when the view is "at the
|
||||||
present," i.e., corresponds to a live target machine state. They may direct modifications to
|
present," i.e., corresponds to a live target machine state. They may directly command and/or
|
||||||
the target, and/or request additional information from the target.</LI>
|
request additional information from the target.</LI>
|
||||||
|
|
||||||
<LI>Global manipulation - those which aggregate information from several targets or traces,
|
<LI>Global manipulation - those which aggregate information from several targets or traces,
|
||||||
presenting a comprehensive picture. Modifications in these views may be directed to any
|
presenting a comprehensive picture. Modifications in these views may be directed to any
|
||||||
|
|
|
@ -24,15 +24,31 @@
|
||||||
|
|
||||||
<H3><A name="debug_program"></A> Debug Program</H3>
|
<H3><A name="debug_program"></A> Debug Program</H3>
|
||||||
|
|
||||||
<P>This action is available whenever a program is opened, and the current program indicates an
|
<P>This group of actions is available whenever there exists a debug launcher that knows how to
|
||||||
"executable path" that exists on the local file system and is marked executable by the host
|
run the current program. Various launchers may all make offers to run the current program, each
|
||||||
operating system. It will launch a suitable connection for debugging local applications, and
|
of which is presented as a item in this group. Not all offers are guaranteed to work. For
|
||||||
then run the current program in that debugger. If <A href=
|
example, an offer to launch the program remotely via SSH depends on the host's availability and
|
||||||
|
the user's credentials. The offers are ordered by most recent activation. The most recent offer
|
||||||
|
used is the default one-click launcher for the current program. Each launcher may check various
|
||||||
|
conditions before making an offer. Most commonly, it will check that there is a suitable
|
||||||
|
debugger for the current program's architecture (language) on the local system, that the
|
||||||
|
program's original executable image still exists on disk, and that the user has permission to
|
||||||
|
execute it. A launcher may take any arbitrary action to run the program. Most commonly, it
|
||||||
|
starts a new connection suitable for the target, and then launches the program on that
|
||||||
|
connection. If <A href=
|
||||||
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html#record_automatically">Record
|
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html#record_automatically">Record
|
||||||
Automatically</A> is enabled, this will provide a one-click action to debug the current
|
Automatically</A> is enabled, this will provide a one-click action to debug the current
|
||||||
program. This is similar to the <A href=
|
program. This is similar to the <A href=
|
||||||
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html#quick_launch">Quick Launch</A>
|
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html#quick_launch">Quick Launch</A>
|
||||||
action in the Commands and Objects window, except this one creates a new connection.</P>
|
action in the Commands and Objects window, except this one does not require an existing
|
||||||
|
connection.</P>
|
||||||
|
|
||||||
|
<P>The launch offers are presented in two places. First, they are listed as drop-down items
|
||||||
|
from the "Debug Program" action in the main toolbar. When activated here, there are typically
|
||||||
|
no further prompts. One notable exception is SSH, where authentication may be required. Second,
|
||||||
|
they are listed under the <SPAN class="menu">Debugger → Debug Program</SPAN> menu. When
|
||||||
|
activated here, the launcher should prompt for arguments. The chosen arguments are saved as the
|
||||||
|
default for future launches of the current program.</P>
|
||||||
|
|
||||||
<H3><A name="disconnect_all"></A> Disconnect All</H3>
|
<H3><A name="disconnect_all"></A> Disconnect All</H3>
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ import ghidra.app.plugin.core.debug.gui.target.DebuggerTargetsPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin;
|
import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
|
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
|
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
||||||
import ghidra.app.services.MarkerService;
|
import ghidra.app.services.MarkerService;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
@ -403,17 +404,24 @@ public interface DebuggerResources {
|
||||||
|
|
||||||
interface DebugProgramAction {
|
interface DebugProgramAction {
|
||||||
String NAME = "Debug Program";
|
String NAME = "Debug Program";
|
||||||
String DESCRIPTION_PREFIX = "Debug ";
|
|
||||||
Icon ICON = ICON_DEBUGGER;
|
Icon ICON = ICON_DEBUGGER;
|
||||||
String GROUP = GROUP_GENERAL;
|
String GROUP = GROUP_GENERAL;
|
||||||
String HELP_ANCHOR = "debug_program";
|
String HELP_ANCHOR = "debug_program";
|
||||||
|
|
||||||
static ActionBuilder builder(Plugin owner, Plugin helpOwner) {
|
static <T> MultiStateActionBuilder<T> buttonBuilder(Plugin owner, Plugin helpOwner) {
|
||||||
return new ActionBuilder(NAME, owner.getName()).description(DESCRIPTION_PREFIX)
|
return new MultiStateActionBuilder<T>(NAME, owner.getName())
|
||||||
.toolBarIcon(ICON)
|
.toolBarIcon(ICON)
|
||||||
.toolBarGroup(GROUP)
|
.toolBarGroup(GROUP)
|
||||||
.menuPath(DebuggerPluginPackage.NAME, DESCRIPTION_PREFIX)
|
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
|
||||||
.menuIcon(ICON)
|
}
|
||||||
|
|
||||||
|
static ActionBuilder menuBuilder(DebuggerProgramLaunchOffer offer, Plugin owner,
|
||||||
|
Plugin helpOwner) {
|
||||||
|
return new ActionBuilder(offer.getConfigName(), owner.getName())
|
||||||
|
.description(offer.getButtonTitle())
|
||||||
|
.menuPath(DebuggerPluginPackage.NAME, offer.getMenuParentTitle(),
|
||||||
|
offer.getMenuTitle())
|
||||||
|
.menuIcon(offer.getIcon())
|
||||||
.menuGroup(GROUP)
|
.menuGroup(GROUP)
|
||||||
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
|
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ import docking.DialogComponentProvider;
|
||||||
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
|
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
|
||||||
import ghidra.dbg.target.TargetMethod;
|
import ghidra.dbg.target.TargetMethod;
|
||||||
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
|
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -95,7 +94,7 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||||
protected JButton invokeButton;
|
protected JButton invokeButton;
|
||||||
|
|
||||||
private final PluginTool tool;
|
private final PluginTool tool;
|
||||||
private TargetParameterMap parameters;
|
private Map<String, ParameterDescription<?>> parameters;
|
||||||
|
|
||||||
// TODO: Not sure this is the best keying, but I think it works.
|
// TODO: Not sure this is the best keying, but I think it works.
|
||||||
private Map<NameTypePair, Object> memorized = new HashMap<>();
|
private Map<NameTypePair, Object> memorized = new HashMap<>();
|
||||||
|
@ -115,14 +114,14 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||||
ntp -> parameter.defaultValue);
|
ntp -> parameter.defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, ?> promptArguments(TargetParameterMap parameterMap) {
|
public Map<String, ?> promptArguments(Map<String, ParameterDescription<?>> parameterMap) {
|
||||||
setParameters(parameterMap);
|
setParameters(parameterMap);
|
||||||
tool.showDialog(this);
|
tool.showDialog(this);
|
||||||
|
|
||||||
return getArguments();
|
return getArguments();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParameters(TargetParameterMap parameterMap) {
|
public void setParameters(Map<String, ParameterDescription<?>> parameterMap) {
|
||||||
this.parameters = parameterMap;
|
this.parameters = parameterMap;
|
||||||
populateOptions();
|
populateOptions();
|
||||||
}
|
}
|
||||||
|
@ -210,13 +209,12 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
|
||||||
memorized.put(NameTypePair.fromParameter(param), editor.getValue());
|
memorized.put(NameTypePair.fromParameter(param), editor.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
||||||
public void writeConfigState(SaveState saveState) {
|
public void writeConfigState(SaveState saveState) {
|
||||||
SaveState subState = new SaveState();
|
SaveState subState = new SaveState();
|
||||||
for (Map.Entry<NameTypePair, Object> ent : memorized.entrySet()) {
|
for (Map.Entry<NameTypePair, Object> ent : memorized.entrySet()) {
|
||||||
NameTypePair ntp = ent.getKey();
|
NameTypePair ntp = ent.getKey();
|
||||||
ConfigStateField.putState(subState, (Class) ntp.getType(), ntp.getName(),
|
ConfigStateField.putState(subState, ntp.getType().asSubclass(Object.class),
|
||||||
ent.getValue());
|
ntp.getName(), ent.getValue());
|
||||||
}
|
}
|
||||||
saveState.putXmlElement(KEY_MEMORIZED_ARGUMENTS, subState.saveToXml());
|
saveState.putXmlElement(KEY_MEMORIZED_ARGUMENTS, subState.saveToXml());
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,13 +236,12 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
futureConnect = factory.build();
|
futureConnect = factory.build();
|
||||||
}
|
}
|
||||||
futureConnect.thenCompose(model -> {
|
futureConnect.thenAcceptAsync(model -> {
|
||||||
modelService.addModel(model);
|
modelService.addModel(model);
|
||||||
setStatusText("");
|
setStatusText("");
|
||||||
close();
|
close();
|
||||||
return CompletableFuture.runAsync(() -> modelService.activateModel(model),
|
modelService.activateModel(model);
|
||||||
SwingExecutorService.INSTANCE);
|
}, SwingExecutorService.INSTANCE).exceptionally(e -> {
|
||||||
}).exceptionally(e -> {
|
|
||||||
e = AsyncUtils.unwrapThrowable(e);
|
e = AsyncUtils.unwrapThrowable(e);
|
||||||
if (!(e instanceof CancellationException)) {
|
if (!(e instanceof CancellationException)) {
|
||||||
Msg.showError(this, getComponent(), "Could not connect", e);
|
Msg.showError(this, getComponent(), "Could not connect", e);
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.app.plugin.core.debug.platform;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.*;
|
||||||
|
import ghidra.app.services.DebuggerModelService;
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
|
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||||
|
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||||
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
|
||||||
|
public class DbgDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpinion {
|
||||||
|
protected static abstract class AbstractDbgDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractDebuggerProgramLaunchOffer {
|
||||||
|
|
||||||
|
public AbstractDbgDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuParentTitle() {
|
||||||
|
return "Debug " + program.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getLauncherPath() {
|
||||||
|
return PathUtils.parse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, ?> generateDefaultLauncherArgs(
|
||||||
|
Map<String, ParameterDescription<?>> params) {
|
||||||
|
return Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, program.getExecutablePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class InVmDbgengDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractDbgDebuggerProgramLaunchOffer {
|
||||||
|
private static final String FACTORY_CLS_NAME =
|
||||||
|
"agent.dbgeng.DbgEngInJvmDebuggerModelFactory";
|
||||||
|
|
||||||
|
public InVmDbgengDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "IN-VM dbgeng";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return "in dbgeng locally IN-VM";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class GadpDbgengDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractDbgDebuggerProgramLaunchOffer {
|
||||||
|
private static final String FACTORY_CLS_NAME =
|
||||||
|
"agent.dbgeng.gadp.DbgEngLocalDebuggerModelFactory";
|
||||||
|
|
||||||
|
public GadpDbgengDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "GADP dbgeng";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return "in dbgeng locally via GADP";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class InVmDbgmodelDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractDbgDebuggerProgramLaunchOffer {
|
||||||
|
private static final String FACTORY_CLS_NAME =
|
||||||
|
"agent.dbgmodel.DbgModelInJvmDebuggerModelFactory";
|
||||||
|
|
||||||
|
public InVmDbgmodelDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "IN-VM dbgmodel";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return "in dbgmodel locally IN-VM";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class GadpDbgmodelDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractDbgDebuggerProgramLaunchOffer {
|
||||||
|
private static final String FACTORY_CLS_NAME =
|
||||||
|
"agent.dbgmodel.gadp.DbgModelLocalDebuggerModelFactory";
|
||||||
|
|
||||||
|
public GadpDbgmodelDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "GADP dbgmodel";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return "in dbgmodel locally via GADP";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<DebuggerProgramLaunchOffer> getOffers(Program program, PluginTool tool,
|
||||||
|
DebuggerModelService service) {
|
||||||
|
String exe = program.getExecutablePath();
|
||||||
|
if (exe == null || "".equals(exe.trim())) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
|
||||||
|
for (DebuggerModelFactory factory : service.getModelFactories()) {
|
||||||
|
if (!factory.isCompatible()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String clsName = factory.getClass().getName();
|
||||||
|
if (clsName.equals(InVmDbgengDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||||
|
offers.add(new InVmDbgengDebuggerProgramLaunchOffer(program, tool, factory));
|
||||||
|
}
|
||||||
|
else if (clsName.equals(GadpDbgengDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||||
|
offers.add(new GadpDbgengDebuggerProgramLaunchOffer(program, tool, factory));
|
||||||
|
}
|
||||||
|
else if (clsName.equals(InVmDbgmodelDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||||
|
offers.add(new InVmDbgmodelDebuggerProgramLaunchOffer(program, tool, factory));
|
||||||
|
}
|
||||||
|
else if (clsName.equals(GadpDbgmodelDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||||
|
offers.add(new GadpDbgmodelDebuggerProgramLaunchOffer(program, tool, factory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offers;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.app.plugin.core.debug.platform;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.*;
|
||||||
|
import ghidra.app.services.DebuggerModelService;
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
|
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
||||||
|
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||||
|
import ghidra.dbg.util.ConfigurableFactory.Property;
|
||||||
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
|
||||||
|
public class GdbDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpinion {
|
||||||
|
protected static abstract class AbstractGdbDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractDebuggerProgramLaunchOffer {
|
||||||
|
|
||||||
|
public AbstractGdbDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuParentTitle() {
|
||||||
|
return "Debug " + program.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getLauncherPath() {
|
||||||
|
return PathUtils.parse("Inferiors[1]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, ?> generateDefaultLauncherArgs(
|
||||||
|
Map<String, ParameterDescription<?>> params) {
|
||||||
|
return Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, program.getExecutablePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class InVmGdbDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractGdbDebuggerProgramLaunchOffer {
|
||||||
|
private static final String FACTORY_CLS_NAME = "agent.gdb.GdbInJvmDebuggerModelFactory";
|
||||||
|
|
||||||
|
public InVmGdbDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "IN-VM GDB";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return "in GDB locally IN-VM";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class GadpGdbDebuggerProgramLaunchOffer
|
||||||
|
extends AbstractGdbDebuggerProgramLaunchOffer {
|
||||||
|
private static final String FACTORY_CLS_NAME =
|
||||||
|
"agent.gdb.gadp.GdbLocalDebuggerModelFactory";
|
||||||
|
|
||||||
|
public GadpGdbDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "GADP GDB";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return "in GDB locally via GADP";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class SshGdbDebuggerProgramLaunchOffer extends AbstractGdbDebuggerProgramLaunchOffer {
|
||||||
|
private static final String FACTORY_CLS_NAME = "agent.gdb.GdbOverSshDebuggerModelFactory";
|
||||||
|
|
||||||
|
public SshGdbDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
super(program, tool, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "SSH GDB";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
Map<String, Property<?>> opts = factory.getOptions();
|
||||||
|
return String.format("in GDB via ssh:%s@%s",
|
||||||
|
opts.get("SSH username").getValue(),
|
||||||
|
opts.get("SSH hostname").getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<DebuggerProgramLaunchOffer> getOffers(Program program, PluginTool tool,
|
||||||
|
DebuggerModelService service) {
|
||||||
|
String exe = program.getExecutablePath();
|
||||||
|
if (exe == null || "".equals(exe.trim())) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<DebuggerProgramLaunchOffer> offers = new ArrayList<>();
|
||||||
|
for (DebuggerModelFactory factory : service.getModelFactories()) {
|
||||||
|
if (!factory.isCompatible()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String clsName = factory.getClass().getName();
|
||||||
|
if (clsName.equals(InVmGdbDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||||
|
offers.add(new InVmGdbDebuggerProgramLaunchOffer(program, tool, factory));
|
||||||
|
}
|
||||||
|
else if (clsName.equals(GadpGdbDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||||
|
offers.add(new GadpGdbDebuggerProgramLaunchOffer(program, tool, factory));
|
||||||
|
}
|
||||||
|
else if (clsName.equals(SshGdbDebuggerProgramLaunchOffer.FACTORY_CLS_NAME)) {
|
||||||
|
offers.add(new SshGdbDebuggerProgramLaunchOffer(program, tool, factory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offers;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.service.model;
|
package ghidra.app.plugin.core.debug.service.model;
|
||||||
|
|
||||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.showError;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
|
@ -24,6 +24,7 @@ import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
|
@ -36,6 +37,8 @@ import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
|
||||||
import ghidra.app.plugin.core.debug.mapping.*;
|
import ghidra.app.plugin.core.debug.mapping.*;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.async.AsyncFence;
|
import ghidra.async.AsyncFence;
|
||||||
import ghidra.dbg.*;
|
import ghidra.dbg.*;
|
||||||
|
@ -48,6 +51,7 @@ import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
import ghidra.lifecycle.Internal;
|
import ghidra.lifecycle.Internal;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.database.DBTrace;
|
import ghidra.trace.database.DBTrace;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
@ -57,7 +61,14 @@ import ghidra.util.classfinder.ClassSearcher;
|
||||||
import ghidra.util.datastruct.CollectionChangeListener;
|
import ghidra.util.datastruct.CollectionChangeListener;
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
|
|
||||||
@PluginInfo(shortDescription = "Debugger models manager service", description = "Manage debug sessions, connections, and trace recording", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.HIDDEN, servicesRequired = {}, servicesProvided = {
|
@PluginInfo(
|
||||||
|
shortDescription = "Debugger models manager service",
|
||||||
|
description = "Manage debug sessions, connections, and trace recording",
|
||||||
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
|
status = PluginStatus.HIDDEN,
|
||||||
|
servicesRequired = {},
|
||||||
|
servicesProvided = {
|
||||||
DebuggerModelService.class, })
|
DebuggerModelService.class, })
|
||||||
public class DebuggerModelServicePlugin extends Plugin
|
public class DebuggerModelServicePlugin extends Plugin
|
||||||
implements DebuggerModelServiceInternal, FrontEndOnly {
|
implements DebuggerModelServiceInternal, FrontEndOnly {
|
||||||
|
@ -298,28 +309,6 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LocalDebuggerModelFactory getDefaultLocalDebuggerModelFactory() {
|
|
||||||
return factories.stream()
|
|
||||||
.filter(LocalDebuggerModelFactory.class::isInstance)
|
|
||||||
.map(LocalDebuggerModelFactory.class::cast)
|
|
||||||
.sorted(Comparator.comparing(f -> -f.getPriority()))
|
|
||||||
.filter(LocalDebuggerModelFactory::isCompatible)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<? extends DebuggerObjectModel> startLocalSession() {
|
|
||||||
LocalDebuggerModelFactory factory = getDefaultLocalDebuggerModelFactory();
|
|
||||||
if (factory == null) {
|
|
||||||
return CompletableFuture.failedFuture(
|
|
||||||
new NoSuchElementException("No suitable launcher for the local platform"));
|
|
||||||
}
|
|
||||||
CompletableFuture<? extends DebuggerObjectModel> future = factory.build();
|
|
||||||
future.thenAccept(this::addModel);
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper)
|
public TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
@ -677,4 +666,11 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program) {
|
||||||
|
return ClassSearcher.getInstances(DebuggerProgramLaunchOpinion.class)
|
||||||
|
.stream()
|
||||||
|
.flatMap(opinion -> opinion.getOffers(program, tool, this).stream());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,15 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
|
import docking.action.builder.MultiStateActionBuilder;
|
||||||
|
import docking.menu.ActionState;
|
||||||
|
import docking.menu.MultiStateDockingAction;
|
||||||
|
import docking.widgets.EventTrigger;
|
||||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
|
@ -29,20 +35,26 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
|
||||||
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||||
import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
|
import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.async.SwingExecutorService;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.dbg.*;
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.dbg.target.TargetThread;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.main.FrontEndTool;
|
import ghidra.framework.main.FrontEndTool;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.*;
|
import ghidra.framework.plugintool.util.*;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.listing.ProgramUserData;
|
||||||
|
import ghidra.program.model.util.StringPropertyMap;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
import ghidra.util.datastruct.CollectionChangeListener;
|
import ghidra.util.datastruct.CollectionChangeListener;
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -66,6 +78,39 @@ import ghidra.util.task.TaskMonitor;
|
||||||
public class DebuggerModelServiceProxyPlugin extends Plugin
|
public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
implements DebuggerModelServiceInternal {
|
implements DebuggerModelServiceInternal {
|
||||||
|
|
||||||
|
private static final String KEY_MOST_RECENT_LAUNCHES = "mostRecentLaunches";
|
||||||
|
|
||||||
|
private static final DebuggerProgramLaunchOffer DUMMY_LAUNCH_OFFER =
|
||||||
|
new DebuggerProgramLaunchOffer() {
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
|
||||||
|
throw new AssertionError("Who clicked me?");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "DUMMY";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuParentTitle() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getButtonTitle() {
|
||||||
|
return "No quick launcher for the current program";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final ActionState<DebuggerProgramLaunchOffer> DUMMY_LAUNCH_STATE =
|
||||||
|
new ActionState<>(DUMMY_LAUNCH_OFFER.getButtonTitle(), DUMMY_LAUNCH_OFFER.getIcon(),
|
||||||
|
DUMMY_LAUNCH_OFFER);
|
||||||
|
|
||||||
protected static DebuggerModelServicePlugin getOrCreateFrontEndDelegate() {
|
protected static DebuggerModelServicePlugin getOrCreateFrontEndDelegate() {
|
||||||
FrontEndTool frontEnd = AppInfo.getFrontEndTool();
|
FrontEndTool frontEnd = AppInfo.getFrontEndTool();
|
||||||
for (Plugin plugin : frontEnd.getManagedPlugins()) {
|
for (Plugin plugin : frontEnd.getManagedPlugins()) {
|
||||||
|
@ -161,7 +206,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
protected final ProxiedRecorderChangeListener recorderChangeListener =
|
protected final ProxiedRecorderChangeListener recorderChangeListener =
|
||||||
new ProxiedRecorderChangeListener();
|
new ProxiedRecorderChangeListener();
|
||||||
|
|
||||||
DockingAction actionDebugProgram;
|
MultiStateDockingAction<DebuggerProgramLaunchOffer> actionDebugProgram;
|
||||||
|
Set<DockingAction> actionDebugProgramMenus = new HashSet<>();
|
||||||
DockingAction actionDisconnectAll;
|
DockingAction actionDisconnectAll;
|
||||||
|
|
||||||
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
|
protected final ListenerSet<CollectionChangeListener<DebuggerModelFactory>> factoryListeners =
|
||||||
|
@ -189,9 +235,14 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
// Note, I have to give an enabledWhen, otherwise any context change re-enables it
|
// Note, I have to give an enabledWhen, otherwise any context change re-enables it
|
||||||
actionDebugProgram = DebugProgramAction.builder(this, delegate)
|
MultiStateActionBuilder<DebuggerProgramLaunchOffer> builderDebugProgram =
|
||||||
.enabledWhen(ctx -> currentProgramPath != null)
|
DebugProgramAction.buttonBuilder(this, delegate);
|
||||||
.onAction(this::debugProgramActivated)
|
actionDebugProgram = builderDebugProgram
|
||||||
|
.enabledWhen(ctx -> currentProgram != null)
|
||||||
|
.onAction(this::debugProgramButtonActivated)
|
||||||
|
.onActionStateChanged(this::debugProgramStateActivated)
|
||||||
|
.performActionOnButtonClick(true)
|
||||||
|
.addState(DUMMY_LAUNCH_STATE)
|
||||||
.buildAndInstall(tool);
|
.buildAndInstall(tool);
|
||||||
actionDisconnectAll = DisconnectAllAction.builder(this, delegate)
|
actionDisconnectAll = DisconnectAllAction.builder(this, delegate)
|
||||||
.menuPath("Debugger", DisconnectAllAction.NAME)
|
.menuPath("Debugger", DisconnectAllAction.NAME)
|
||||||
|
@ -201,58 +252,139 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
updateActionDebugProgram();
|
updateActionDebugProgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void debugProgramActivated(ActionContext ctx) {
|
|
||||||
if (currentProgramPath == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Note the background task must have an object for a "transaction", even though this
|
|
||||||
* particular task doesn't actually touch the program. Annoying.
|
|
||||||
*/
|
|
||||||
BackgroundUtils.async(tool, currentProgram, actionDebugProgram.getDescription(), true, true,
|
|
||||||
true, this::debugProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activatedDisconnectAll(ActionContext context) {
|
private void activatedDisconnectAll(ActionContext context) {
|
||||||
closeAllModels();
|
closeAllModels();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Void> debugProgram(Program __, TaskMonitor monitor) {
|
@Override
|
||||||
monitor.initialize(3);
|
public Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program) {
|
||||||
monitor.setMessage("Starting local session");
|
return orderOffers(delegate.getProgramLaunchOffers(program), program);
|
||||||
return startLocalSession().thenCompose(model -> {
|
}
|
||||||
CompletableFuture<Void> swing = CompletableFuture.runAsync(() -> {
|
|
||||||
// Needed to auto-record via objects provider
|
protected List<String> readMostRecentLaunches(Program program) {
|
||||||
activateModel(model);
|
StringPropertyMap prop = program.getProgramUserData()
|
||||||
}, SwingExecutorService.INSTANCE);
|
.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, false);
|
||||||
return swing.thenCompose(___ -> model.fetchModelRoot());
|
if (prop == null) {
|
||||||
}).thenCompose(root -> {
|
return List.of();
|
||||||
monitor.incrementProgress(1);
|
}
|
||||||
monitor.setMessage("Finding launcher");
|
Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
|
||||||
CompletableFuture<? extends TargetLauncher> futureLauncher =
|
String str = prop.getString(min);
|
||||||
DebugModelConventions.findSuitable(TargetLauncher.class, root);
|
if (str == null) {
|
||||||
return futureLauncher;
|
return List.of();
|
||||||
}).thenCompose(launcher -> {
|
}
|
||||||
monitor.incrementProgress(1);
|
return List.of(str.split(";"));
|
||||||
monitor.setMessage("Launching " + currentProgramPath);
|
}
|
||||||
// TODO: Pluggable ways to populate this
|
|
||||||
// TODO: Maybe still prompt the user?
|
protected void writeMostRecentLaunches(Program program, List<String> mrl) {
|
||||||
// TODO: Launch configurations, like Eclipse?
|
ProgramUserData userData = program.getProgramUserData();
|
||||||
// TODO: Maybe just let the pluggable thing invoke launch itself
|
try (UndoableTransaction tid = UndoableTransaction.start(userData)) {
|
||||||
return launcher.launch(
|
StringPropertyMap prop = userData
|
||||||
Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, currentProgramPath.toString()));
|
.getStringProperty(getName(), KEY_MOST_RECENT_LAUNCHES, true);
|
||||||
|
Address min = program.getAddressFactory().getDefaultAddressSpace().getMinAddress();
|
||||||
|
prop.add(min, mrl.stream().collect(Collectors.joining(";")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OfferComparator implements Comparator<DebuggerProgramLaunchOffer> {
|
||||||
|
Map<String, Integer> fastIndex = new HashMap<>();
|
||||||
|
|
||||||
|
public OfferComparator(List<String> mostRecentNames) {
|
||||||
|
int i = 0;
|
||||||
|
for (String name : mostRecentNames) {
|
||||||
|
fastIndex.put(name, i++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(DebuggerProgramLaunchOffer o1, DebuggerProgramLaunchOffer o2) {
|
||||||
|
int i1 = fastIndex.getOrDefault(o1, -1);
|
||||||
|
int i2 = fastIndex.getOrDefault(o2, -1);
|
||||||
|
int result = i1 - i2; // reversed, yes. Most recent is last in list
|
||||||
|
if (result != 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return o1.defaultPriority() - o2.defaultPriority(); // Greater is higher priority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Stream<DebuggerProgramLaunchOffer> orderOffers(
|
||||||
|
Stream<DebuggerProgramLaunchOffer> offers, Program program) {
|
||||||
|
List<String> mrl = readMostRecentLaunches(program);
|
||||||
|
return offers.sorted(Comparator.comparingInt(o -> -mrl.indexOf(o.getConfigName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void debugProgram(DebuggerProgramLaunchOffer offer, Program program, boolean prompt) {
|
||||||
|
BackgroundUtils.async(tool, program, offer.getButtonTitle(), true, true, true, (p, m) -> {
|
||||||
|
List<String> mrl = new ArrayList<>(readMostRecentLaunches(program));
|
||||||
|
mrl.remove(offer.getConfigName());
|
||||||
|
mrl.add(offer.getConfigName());
|
||||||
|
writeMostRecentLaunches(program, mrl);
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
updateActionDebugProgram();
|
||||||
|
}, AsyncUtils.SWING_EXECUTOR).exceptionally(ex -> {
|
||||||
|
Msg.error(this, "Trouble writing recent launches to program user data");
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
return offer.launchProgram(m, prompt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void debugProgramButtonActivated(ActionContext ctx) {
|
||||||
|
DebuggerProgramLaunchOffer offer = actionDebugProgram.getCurrentUserData();
|
||||||
|
Program program = currentProgram;
|
||||||
|
if (offer == null || program == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugProgram(offer, program, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void debugProgramStateActivated(ActionState<DebuggerProgramLaunchOffer> offer,
|
||||||
|
EventTrigger trigger) {
|
||||||
|
if (trigger == EventTrigger.GUI_ACTION) {
|
||||||
|
debugProgramButtonActivated(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void debugProgramMenuActivated(DebuggerProgramLaunchOffer offer) {
|
||||||
|
Program program = currentProgram;
|
||||||
|
if (program == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugProgram(offer, program, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateActionDebugProgram() {
|
private void updateActionDebugProgram() {
|
||||||
if (actionDebugProgram == null) {
|
if (actionDebugProgram == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
actionDebugProgram.setEnabled(currentProgramPath != null);
|
Program program = currentProgram;
|
||||||
String desc = currentProgramPath == null ? DebugProgramAction.DESCRIPTION_PREFIX.trim()
|
List<DebuggerProgramLaunchOffer> offers = program == null ? List.of()
|
||||||
: DebugProgramAction.DESCRIPTION_PREFIX + currentProgramPath;
|
: getProgramLaunchOffers(program).collect(Collectors.toList());
|
||||||
actionDebugProgram.setDescription(desc);
|
List<ActionState<DebuggerProgramLaunchOffer>> states = offers.stream()
|
||||||
actionDebugProgram.getMenuBarData().setMenuItemName(desc);
|
.map(o -> new ActionState<DebuggerProgramLaunchOffer>(o.getButtonTitle(),
|
||||||
|
o.getIcon(), o))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!states.isEmpty()) {
|
||||||
|
actionDebugProgram.setActionStates(states);
|
||||||
|
actionDebugProgram.setEnabled(true);
|
||||||
|
actionDebugProgram.setCurrentActionState(states.get(0));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
actionDebugProgram.setActionStates(List.of(DUMMY_LAUNCH_STATE));
|
||||||
|
actionDebugProgram.setEnabled(false);
|
||||||
|
actionDebugProgram.setCurrentActionState(DUMMY_LAUNCH_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Iterator<DockingAction> it = actionDebugProgramMenus.iterator(); it.hasNext();) {
|
||||||
|
DockingAction action = it.next();
|
||||||
|
it.remove();
|
||||||
|
tool.removeAction(action);
|
||||||
|
}
|
||||||
|
for (DebuggerProgramLaunchOffer offer : offers) {
|
||||||
|
actionDebugProgramMenus.add(DebugProgramAction.menuBuilder(offer, this, delegate)
|
||||||
|
.onAction(ctx -> debugProgramMenuActivated(offer))
|
||||||
|
.buildAndInstall(tool));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -349,11 +481,6 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
return delegate.removeModel(model);
|
return delegate.removeModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<? extends DebuggerObjectModel> startLocalSession() {
|
|
||||||
return delegate.startLocalSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper)
|
public TraceRecorder recordTarget(TargetObject target, DebuggerTargetTraceMapper mapper)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.app.plugin.core.debug.service.model.launch;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jdom.Element;
|
||||||
|
import org.jdom.JDOMException;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.gui.objects.components.DebuggerMethodInvocationDialog;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin;
|
||||||
|
import ghidra.app.services.DebuggerModelService;
|
||||||
|
import ghidra.async.SwingExecutorService;
|
||||||
|
import ghidra.dbg.*;
|
||||||
|
import ghidra.dbg.target.TargetLauncher;
|
||||||
|
import ghidra.dbg.target.TargetMethod.ParameterDescription;
|
||||||
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||||
|
import ghidra.dbg.util.PathUtils;
|
||||||
|
import ghidra.framework.options.SaveState;
|
||||||
|
import ghidra.framework.plugintool.AutoConfigState.ConfigStateField;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.framework.plugintool.util.PluginUtils;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.listing.ProgramUserData;
|
||||||
|
import ghidra.program.model.util.StringPropertyMap;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import ghidra.util.xml.XmlUtilities;
|
||||||
|
|
||||||
|
public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
|
||||||
|
protected final Program program;
|
||||||
|
protected final PluginTool tool;
|
||||||
|
protected final DebuggerModelFactory factory;
|
||||||
|
|
||||||
|
public AbstractDebuggerProgramLaunchOffer(Program program, PluginTool tool,
|
||||||
|
DebuggerModelFactory factory) {
|
||||||
|
this.program = program;
|
||||||
|
this.tool = tool;
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<String> getLauncherPath() {
|
||||||
|
return PathUtils.parse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveLauncherArgs(Map<String, ?> args,
|
||||||
|
Map<String, ParameterDescription<?>> params) {
|
||||||
|
SaveState state = new SaveState();
|
||||||
|
for (ParameterDescription<?> param : params.values()) {
|
||||||
|
Object val = args.get(param.name);
|
||||||
|
if (val != null) {
|
||||||
|
ConfigStateField.putState(state, param.type.asSubclass(Object.class), param.name,
|
||||||
|
val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String owner = PluginUtils.getPluginNameFromClass(DebuggerModelServicePlugin.class);
|
||||||
|
ProgramUserData userData = program.getProgramUserData();
|
||||||
|
try (UndoableTransaction tid = UndoableTransaction.start(userData)) {
|
||||||
|
StringPropertyMap stringProperty =
|
||||||
|
userData.getStringProperty(owner, getConfigName(), true);
|
||||||
|
Element element = state.saveToXml();
|
||||||
|
stringProperty.add(Address.NO_ADDRESS, XmlUtilities.toString(element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, ?> takeDefaultsForParameters(
|
||||||
|
Map<String, ParameterDescription<?>> params) {
|
||||||
|
return params.values().stream().collect(Collectors.toMap(p -> p.name, p -> p.defaultValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the default launcher arguments
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* It is not sufficient to simply take the defaults specified in the parameters. This must
|
||||||
|
* populate the arguments necessary to launch the requested program.
|
||||||
|
*
|
||||||
|
* @param params the parameters
|
||||||
|
* @return the default arguments
|
||||||
|
*/
|
||||||
|
protected abstract Map<String, ?> generateDefaultLauncherArgs(
|
||||||
|
Map<String, ParameterDescription<?>> params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user for arguments, showing those last used or defaults
|
||||||
|
*
|
||||||
|
* @param params the parameters of the model's launcher
|
||||||
|
* @return the arguments given by the user
|
||||||
|
*/
|
||||||
|
protected Map<String, ?> promptLauncherArgs(Map<String, ParameterDescription<?>> params) {
|
||||||
|
DebuggerMethodInvocationDialog dialog =
|
||||||
|
new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon());
|
||||||
|
// NB. Do not invoke read/writeConfigState
|
||||||
|
Map<String, ?> args = loadLastLauncherArgs(params, true);
|
||||||
|
for (ParameterDescription<?> param : params.values()) {
|
||||||
|
Object val = args.get(param.name);
|
||||||
|
if (val != null) {
|
||||||
|
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args = dialog.promptArguments(params);
|
||||||
|
saveLauncherArgs(args, params);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the arguments last used for this offer, or give the defaults
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If there are no saved "last used" arguments, then this will return the defaults. If there are
|
||||||
|
* saved arguments, but they cannot be loaded, then this will behave differently depending on
|
||||||
|
* whether the user will be confirming the arguments. If there will be no prompt/confirmation,
|
||||||
|
* then this method must throw an exception in order to avoid launching with defaults, when the
|
||||||
|
* user may be expecting a customized launch. If there will be a prompt, then this may safely
|
||||||
|
* return the defaults, since the user will be given a chance to correct them.
|
||||||
|
*
|
||||||
|
* @param params the parameters of the model's launcher
|
||||||
|
* @param forPrompt true if the user will be confirming the arguments
|
||||||
|
* @return the loaded arguments, or defaults
|
||||||
|
*/
|
||||||
|
protected Map<String, ?> loadLastLauncherArgs(
|
||||||
|
Map<String, ParameterDescription<?>> params, boolean forPrompt) {
|
||||||
|
/**
|
||||||
|
* TODO: Supposedly, per-program, per-user config stuff is being generalized for analyzers.
|
||||||
|
* Re-examine this if/when that gets merged
|
||||||
|
*/
|
||||||
|
String owner = PluginUtils.getPluginNameFromClass(DebuggerModelServicePlugin.class);
|
||||||
|
ProgramUserData userData = program.getProgramUserData();
|
||||||
|
StringPropertyMap property =
|
||||||
|
userData.getStringProperty(owner, getConfigName(), false);
|
||||||
|
if (property != null) {
|
||||||
|
String xml = property.getString(Address.NO_ADDRESS);
|
||||||
|
if (xml != null) {
|
||||||
|
try {
|
||||||
|
Element element = XmlUtilities.fromString(xml);
|
||||||
|
SaveState state = new SaveState(element);
|
||||||
|
Map<String, Object> args = new LinkedHashMap<>();
|
||||||
|
for (ParameterDescription<?> param : params.values()) {
|
||||||
|
args.put(param.name,
|
||||||
|
ConfigStateField.getState(state, param.type, param.name));
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
catch (JDOMException | IOException e) {
|
||||||
|
if (!forPrompt) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Saved launcher args are corrupt, or launcher parameters changed. Not launching.",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
Msg.error(this,
|
||||||
|
"Saved launcher args are corrup, or launcher parameters changed. Defaulting.",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ?> args = generateDefaultLauncherArgs(params);
|
||||||
|
saveLauncherArgs(args, params);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the launcher args
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This should either call {@link #promptLauncherArgs(Map))} or
|
||||||
|
* {@link #loadLastLauncherArgs(Map, boolean))}. Note if choosing the latter, the user will not
|
||||||
|
* be prompted to confirm.
|
||||||
|
*
|
||||||
|
* @param params the parameters of the model's launcher
|
||||||
|
* @return the chosen arguments
|
||||||
|
*/
|
||||||
|
protected Map<String, ?> getLauncherArgs(Map<String, ParameterDescription<?>> params,
|
||||||
|
boolean prompt) {
|
||||||
|
return prompt
|
||||||
|
? promptLauncherArgs(params)
|
||||||
|
: loadLastLauncherArgs(params, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the model factory, as last configured by the user, for this launcher
|
||||||
|
*
|
||||||
|
* @return the factory
|
||||||
|
*/
|
||||||
|
protected DebuggerModelFactory getModelFactory() {
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: This could be more surgical, and perhaps ought to be part of
|
||||||
|
* {@link DebugModelConventions}.
|
||||||
|
*/
|
||||||
|
static class ValueExpecter extends CompletableFuture<Object> implements DebuggerModelListener {
|
||||||
|
private final DebuggerObjectModel model;
|
||||||
|
private final List<String> path;
|
||||||
|
|
||||||
|
public ValueExpecter(DebuggerObjectModel model, List<String> path) {
|
||||||
|
this.model = model;
|
||||||
|
this.path = path;
|
||||||
|
model.addModelListener(this);
|
||||||
|
retryFetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void retryFetch() {
|
||||||
|
model.fetchModelValue(path).thenAccept(v -> {
|
||||||
|
if (v != null) {
|
||||||
|
model.removeModelListener(this);
|
||||||
|
complete(v);
|
||||||
|
}
|
||||||
|
}).exceptionally(ex -> {
|
||||||
|
model.removeModelListener(this);
|
||||||
|
completeExceptionally(ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rootAdded(TargetObject root) {
|
||||||
|
retryFetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attributesChanged(TargetObject object, Collection<String> removed,
|
||||||
|
Map<String, ?> added) {
|
||||||
|
retryFetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void elementsChanged(TargetObject object, Collection<String> removed,
|
||||||
|
Map<String, ? extends TargetObject> added) {
|
||||||
|
retryFetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
|
||||||
|
monitor.initialize(2);
|
||||||
|
monitor.setMessage("Connecting");
|
||||||
|
return getModelFactory().build().thenApplyAsync(m -> {
|
||||||
|
DebuggerModelService service = tool.getService(DebuggerModelService.class);
|
||||||
|
service.addModel(m);
|
||||||
|
return m;
|
||||||
|
}).thenComposeAsync(m -> {
|
||||||
|
List<String> launcherPath = getLauncherPath();
|
||||||
|
TargetObjectSchema schema = m.getRootSchema().getSuccessorSchema(launcherPath);
|
||||||
|
if (!schema.getInterfaces().contains(TargetLauncher.class)) {
|
||||||
|
throw new AssertionError("LaunchOffer / model implementation error: " +
|
||||||
|
"The given launcher path is not a TargetLauncher, according to its schema");
|
||||||
|
}
|
||||||
|
return new ValueExpecter(m, launcherPath);
|
||||||
|
}, SwingExecutorService.INSTANCE).thenCompose(l -> {
|
||||||
|
monitor.incrementProgress(1);
|
||||||
|
monitor.setMessage("Launching");
|
||||||
|
TargetLauncher launcher = (TargetLauncher) l;
|
||||||
|
return launcher.launch(getLauncherArgs(launcher.getParameters(), prompt));
|
||||||
|
}).thenRun(() -> {
|
||||||
|
monitor.incrementProgress(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.app.plugin.core.debug.service.model.launch;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An offer to launch a program with a given mechanism
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Typically each offer is configured with the program it's going to launch, and knows how to work a
|
||||||
|
* specific connector and platform to obtain a target executing the program's image. The mechanisms
|
||||||
|
* may vary wildly from platform to platform.
|
||||||
|
*/
|
||||||
|
public interface DebuggerProgramLaunchOffer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the program using the offered mechanism
|
||||||
|
*
|
||||||
|
* @param monitor a monitor for progress and cancellation
|
||||||
|
* @param prompt if the user should be prompted to confirm launch parameters
|
||||||
|
* @return a future which completes when the program is launched
|
||||||
|
*/
|
||||||
|
CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A name so that this offer can be recognized later
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The name is saved to configuration files, so that user preferences and priorities can be
|
||||||
|
* memorized. The opinion will generate each offer fresh each time, so it's important that the
|
||||||
|
* "same offer" have the same configuration name. Note that the name <em>cannot</em> depend on
|
||||||
|
* the program name, but can depend on the model factory and program language and/or compiler
|
||||||
|
* spec. This name cannot contain semicolons ({@ code ;}).
|
||||||
|
*
|
||||||
|
* @return the configuration name
|
||||||
|
*/
|
||||||
|
String getConfigName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the icon displayed in the UI for this offer
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Don't override this except for good reason. If you do override, please return a variant that
|
||||||
|
* still resembles this icon, e.g., just overlay on this one.
|
||||||
|
*
|
||||||
|
* @return the icon
|
||||||
|
*/
|
||||||
|
default Icon getIcon() {
|
||||||
|
return DebuggerResources.ICON_DEBUGGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text display on the parent menu for this offer
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Unless there's good reason, this should always be "Debug [executablePath]".
|
||||||
|
*
|
||||||
|
* @return the title
|
||||||
|
*/
|
||||||
|
String getMenuParentTitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text displayed on the menu for this offer
|
||||||
|
*
|
||||||
|
* @return the title
|
||||||
|
*/
|
||||||
|
String getMenuTitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text displayed on buttons for this offer
|
||||||
|
*
|
||||||
|
* @return the title
|
||||||
|
*/
|
||||||
|
default String getButtonTitle() {
|
||||||
|
return getMenuParentTitle() + " " + getMenuTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default priority (position in the menu) of the offer
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that greater priorities will be listed first, with the greatest being the default "quick
|
||||||
|
* launch" offer.
|
||||||
|
*
|
||||||
|
* @return the priority
|
||||||
|
*/
|
||||||
|
default int defaultPriority() {
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.app.plugin.core.debug.service.model.launch;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import ghidra.app.services.DebuggerModelService;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.util.classfinder.ExtensionPoint;
|
||||||
|
|
||||||
|
public interface DebuggerProgramLaunchOpinion extends ExtensionPoint {
|
||||||
|
Collection<DebuggerProgramLaunchOffer> getOffers(Program program, PluginTool tool,
|
||||||
|
DebuggerModelService service);
|
||||||
|
}
|
|
@ -19,20 +19,25 @@ import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
|
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
|
||||||
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
|
||||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||||
import ghidra.dbg.DebuggerModelFactory;
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.framework.plugintool.PluginEvent;
|
import ghidra.framework.plugintool.PluginEvent;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.util.datastruct.CollectionChangeListener;
|
import ghidra.util.datastruct.CollectionChangeListener;
|
||||||
|
|
||||||
@ServiceInfo(defaultProvider = DebuggerModelServiceProxyPlugin.class, description = "Service for managing debug sessions and connections")
|
@ServiceInfo(
|
||||||
|
defaultProvider = DebuggerModelServiceProxyPlugin.class,
|
||||||
|
description = "Service for managing debug sessions and connections")
|
||||||
public interface DebuggerModelService {
|
public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Get the set of model factories found on the classpath
|
* Get the set of model factories found on the classpath
|
||||||
|
@ -58,6 +63,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Get the set of active recorders
|
* Get the set of active recorders
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* A recorder is active as long as its target (usually a process) is valid. It becomes inactive
|
* A recorder is active as long as its target (usually a process) is valid. It becomes inactive
|
||||||
* when the target becomes invalid, or when the user stops the recording.
|
* when the target becomes invalid, or when the user stops the recording.
|
||||||
*
|
*
|
||||||
|
@ -68,6 +74,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Register a model with this service
|
* Register a model with this service
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* In general, the tool will only display models registered here
|
* In general, the tool will only display models registered here
|
||||||
*
|
*
|
||||||
* @param model the model to register
|
* @param model the model to register
|
||||||
|
@ -84,26 +91,18 @@ public interface DebuggerModelService {
|
||||||
*/
|
*/
|
||||||
boolean removeModel(DebuggerObjectModel model);
|
boolean removeModel(DebuggerObjectModel model);
|
||||||
|
|
||||||
/**
|
|
||||||
* Start and connect to a suitable debugger on the local system
|
|
||||||
*
|
|
||||||
* In most circumstances, this will start a local GADP agent compatible with the local operating
|
|
||||||
* system. It will then connect to it via localhost, and register the resulting model with this
|
|
||||||
* service.
|
|
||||||
*
|
|
||||||
* @return a future which completes upon successful session creation.
|
|
||||||
*/
|
|
||||||
CompletableFuture<? extends DebuggerObjectModel> startLocalSession();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new trace on the given target
|
* Start a new trace on the given target
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Following conventions, the target must be a container, usually a process. Ideally, the model
|
* Following conventions, the target must be a container, usually a process. Ideally, the model
|
||||||
* will present the process as having memory, modules, and threads; and the model will present
|
* will present the process as having memory, modules, and threads; and the model will present
|
||||||
* each thread as having registers, or a stack with frame 0 presenting the registers.
|
* each thread as having registers, or a stack with frame 0 presenting the registers.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Any given container can be traced by at most one recorder.
|
* Any given container can be traced by at most one recorder.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: If mappers remain bound to a prospective target, then remove target from the parameters
|
* TODO: If mappers remain bound to a prospective target, then remove target from the parameters
|
||||||
* here.
|
* here.
|
||||||
*
|
*
|
||||||
|
@ -119,6 +118,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Query mapping opinions and record the given target using the "best" offer
|
* Query mapping opinions and record the given target using the "best" offer
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* If exactly one offer is given, this simply uses it. If multiple are given, this automatically
|
* If exactly one offer is given, this simply uses it. If multiple are given, this automatically
|
||||||
* chooses the "best" one without prompting the user. If none are given, this fails.
|
* chooses the "best" one without prompting the user. If none are given, this fails.
|
||||||
*
|
*
|
||||||
|
@ -131,10 +131,12 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Query mapping opinions, prompt the user, and record the given target
|
* Query mapping opinions, prompt the user, and record the given target
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Even if exactly one offer is given, the user is prompted to provide information about the new
|
* Even if exactly one offer is given, the user is prompted to provide information about the new
|
||||||
* recording, and to give the user an opportunity to cancel. If none are given, the prompt says
|
* recording, and to give the user an opportunity to cancel. If none are given, the prompt says
|
||||||
* as much. If the user cancels, the returned future completes with {@code null}.
|
* as much. If the user cancels, the returned future completes with {@code null}.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: Should the prompt allow the user to force an opinion which gave no offers?
|
* TODO: Should the prompt allow the user to force an opinion which gave no offers?
|
||||||
*
|
*
|
||||||
* @see DebuggerMappingOpinion#queryOpinions(TargetObject)
|
* @see DebuggerMappingOpinion#queryOpinions(TargetObject)
|
||||||
|
@ -146,6 +148,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Start and open a new trace on the given target
|
* Start and open a new trace on the given target
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Starts a new trace, and opens it in the tool
|
* Starts a new trace, and opens it in the tool
|
||||||
*
|
*
|
||||||
* @see #recordTarget(TargetObject)
|
* @see #recordTarget(TargetObject)
|
||||||
|
@ -180,8 +183,10 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Get the object (usually a process) associated with the given destination trace
|
* Get the object (usually a process) associated with the given destination trace
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* A recorder uses conventions to discover the "process" in the model, given a target object.
|
* A recorder uses conventions to discover the "process" in the model, given a target object.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: Conventions for targets other than processes are not yet specified.
|
* TODO: Conventions for targets other than processes are not yet specified.
|
||||||
*
|
*
|
||||||
* @param trace the destination trace
|
* @param trace the destination trace
|
||||||
|
@ -200,11 +205,13 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Get the object associated with the given destination trace thread
|
* Get the object associated with the given destination trace thread
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* A recorder uses conventions to discover "threads" for a given target object, usually a
|
* A recorder uses conventions to discover "threads" for a given target object, usually a
|
||||||
* process. Those threads are then assigned to corresponding destination trace threads. Assuming
|
* process. Those threads are then assigned to corresponding destination trace threads. Assuming
|
||||||
* the given trace thread is the destination of an active recorder, this method finds the
|
* the given trace thread is the destination of an active recorder, this method finds the
|
||||||
* corresponding model "thread."
|
* corresponding model "thread."
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: Conventions for targets other than processes (containing threads) are not yet
|
* TODO: Conventions for targets other than processes (containing threads) are not yet
|
||||||
* specified.
|
* specified.
|
||||||
*
|
*
|
||||||
|
@ -216,6 +223,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Get the destination trace thread, if applicable, for a given source thread
|
* Get the destination trace thread, if applicable, for a given source thread
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Consider {@link #getTraceThread(TargetObject, TargetExecutionStateful)} if the caller already
|
* Consider {@link #getTraceThread(TargetObject, TargetExecutionStateful)} if the caller already
|
||||||
* has a handle to the thread's container.
|
* has a handle to the thread's container.
|
||||||
*
|
*
|
||||||
|
@ -227,6 +235,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Get the destination trace thread, if applicable, for a given source thread
|
* Get the destination trace thread, if applicable, for a given source thread
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* This method is slightly faster than {@link #getTraceThread(TargetExecutionStateful)}, since
|
* This method is slightly faster than {@link #getTraceThread(TargetExecutionStateful)}, since
|
||||||
* it doesn't have to search for the applicable recorder. However, if the wrong container is
|
* it doesn't have to search for the applicable recorder. However, if the wrong container is
|
||||||
* given, this method will fail to find the given thread.
|
* given, this method will fail to find the given thread.
|
||||||
|
@ -254,6 +263,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Get the last focused object related to the given target
|
* Get the last focused object related to the given target
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Assuming the target object is being actively traced, find the last focused object among those
|
* Assuming the target object is being actively traced, find the last focused object among those
|
||||||
* being traced by the same recorder. Essentially, given that the target likely belongs to a
|
* being traced by the same recorder. Essentially, given that the target likely belongs to a
|
||||||
* process, find the object within that process that last had focus. This is primarily used when
|
* process, find the object within that process that last had focus. This is primarily used when
|
||||||
|
@ -268,6 +278,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Listen for changes in available model factories
|
* Listen for changes in available model factories
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* The caller must keep a strong reference to the listener, or it will be automatically removed.
|
* The caller must keep a strong reference to the listener, or it will be automatically removed.
|
||||||
*
|
*
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
|
@ -284,8 +295,10 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Listen for changes in registered models
|
* Listen for changes in registered models
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* The caller must beep a strong reference to the listener, or it will be automatically removed.
|
* The caller must beep a strong reference to the listener, or it will be automatically removed.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: Probably replace this with a {@link PluginEvent}
|
* TODO: Probably replace this with a {@link PluginEvent}
|
||||||
*
|
*
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
|
@ -295,6 +308,7 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Remove a listener for changes in registered models
|
* Remove a listener for changes in registered models
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: Probably replace this with a {@link PluginEvent}
|
* TODO: Probably replace this with a {@link PluginEvent}
|
||||||
*
|
*
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
|
@ -304,8 +318,10 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Listen for changes in active trace recorders
|
* Listen for changes in active trace recorders
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* The caller must beep a strong reference to the listener, or it will be automatically removed.
|
* The caller must beep a strong reference to the listener, or it will be automatically removed.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: Probably replace this with a {@link PluginEvent}
|
* TODO: Probably replace this with a {@link PluginEvent}
|
||||||
*
|
*
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
|
@ -315,9 +331,18 @@ public interface DebuggerModelService {
|
||||||
/**
|
/**
|
||||||
* Remove a listener for changes in active trace recorders
|
* Remove a listener for changes in active trace recorders
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* TODO: Probably replace this with a {@link PluginEvent}
|
* TODO: Probably replace this with a {@link PluginEvent}
|
||||||
*
|
*
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
*/
|
*/
|
||||||
void removeTraceRecordersChangedListener(CollectionChangeListener<TraceRecorder> listener);
|
void removeTraceRecordersChangedListener(CollectionChangeListener<TraceRecorder> listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all offers for launching the given program
|
||||||
|
*
|
||||||
|
* @param program the program to launch
|
||||||
|
* @return the offers
|
||||||
|
*/
|
||||||
|
Stream<DebuggerProgramLaunchOffer> getProgramLaunchOffers(Program program);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,20 +19,21 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.core.debug.event.ModelObjectFocusedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.ModelObjectFocusedPluginEvent;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.TestDebuggerProgramLaunchOpinion.TestDebuggerProgramLaunchOffer;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||||
import ghidra.app.services.TraceRecorder;
|
import ghidra.app.services.TraceRecorder;
|
||||||
import ghidra.async.AsyncPairingQueue;
|
import ghidra.async.AsyncPairingQueue;
|
||||||
import ghidra.dbg.DebuggerModelFactory;
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
import ghidra.dbg.DebuggerObjectModel;
|
||||||
import ghidra.dbg.model.TestDebuggerObjectModel;
|
import ghidra.dbg.model.TestDebuggerModelFactory;
|
||||||
import ghidra.dbg.model.TestLocalDebuggerModelFactory;
|
|
||||||
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
import ghidra.dbg.testutil.DebuggerModelTestUtils;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
@ -145,6 +146,17 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetProgramLaunchOffers() throws Exception {
|
||||||
|
createAndOpenProgramWithExePath("/my/fun/path");
|
||||||
|
TestDebuggerModelFactory factory = new TestDebuggerModelFactory();
|
||||||
|
modelServiceInternal.setModelFactories(List.of(factory));
|
||||||
|
List<DebuggerProgramLaunchOffer> offers =
|
||||||
|
modelService.getProgramLaunchOffers(program).collect(Collectors.toList());
|
||||||
|
DebuggerProgramLaunchOffer offer = Unique.assertOne(offers);
|
||||||
|
assertEquals(TestDebuggerProgramLaunchOffer.class, offer.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetModels() throws Exception {
|
public void testGetModels() throws Exception {
|
||||||
assertEquals(Set.of(), modelService.getModels());
|
assertEquals(Set.of(), modelService.getModels());
|
||||||
|
@ -235,21 +247,6 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStartLocalSession() throws Exception {
|
|
||||||
TestLocalDebuggerModelFactory factory = new TestLocalDebuggerModelFactory();
|
|
||||||
modelServiceInternal.setModelFactories(List.of(factory));
|
|
||||||
|
|
||||||
CompletableFuture<? extends DebuggerObjectModel> futureSession =
|
|
||||||
modelService.startLocalSession();
|
|
||||||
TestDebuggerObjectModel model = new TestDebuggerObjectModel();
|
|
||||||
assertEquals(Set.of(), modelService.getModels());
|
|
||||||
factory.pollBuild().complete(model);
|
|
||||||
futureSession.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
assertEquals(Set.of(model), modelService.getModels());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecordThenCloseStopsRecording() throws Throwable {
|
public void testRecordThenCloseStopsRecording() throws Throwable {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* ###
|
||||||
|
* 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 ghidra.app.plugin.core.debug.service.model;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import generic.Unique;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
||||||
|
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
|
||||||
|
import ghidra.app.services.DebuggerModelService;
|
||||||
|
import ghidra.async.AsyncUtils;
|
||||||
|
import ghidra.dbg.DebuggerModelFactory;
|
||||||
|
import ghidra.dbg.model.TestDebuggerModelFactory;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class TestDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOpinion {
|
||||||
|
|
||||||
|
static class TestDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> launchProgram(TaskMonitor monitor, boolean prompt) {
|
||||||
|
return AsyncUtils.NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigName() {
|
||||||
|
return "TEST";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuParentTitle() {
|
||||||
|
return "Debug it";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuTitle() {
|
||||||
|
return "in Fake Debugger";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<DebuggerProgramLaunchOffer> getOffers(Program program, PluginTool tool,
|
||||||
|
DebuggerModelService service) {
|
||||||
|
DebuggerModelFactory factory = Unique.assertOne(service.getModelFactories());
|
||||||
|
assertEquals(TestDebuggerModelFactory.class, factory.getClass());
|
||||||
|
|
||||||
|
return List.of(new TestDebuggerProgramLaunchOffer());
|
||||||
|
}
|
||||||
|
}
|
|
@ -342,22 +342,6 @@ public interface DebuggerObjectModel {
|
||||||
* are refreshed; and {@code A}'s, {@code B[1]}'s, and {@code C[2]}'s attribute caches are
|
* are refreshed; and {@code A}'s, {@code B[1]}'s, and {@code C[2]}'s attribute caches are
|
||||||
* refreshed.
|
* refreshed.
|
||||||
*
|
*
|
||||||
* @implNote The returned value cannot be a {@link TargetObjectRef} unless the value represents
|
|
||||||
* a link. In other words, if the path refers to an object, the model must return the
|
|
||||||
* object, not a ref. When the value is a link, the implementation may optionally
|
|
||||||
* resolve the object, but should only do so if it doesn't incur a significant cost.
|
|
||||||
* Furthermore, such links cannot be resolved -- though they can be substituted for
|
|
||||||
* the target object at the linked path. In other words, the path of the returned ref
|
|
||||||
* (or object) must represent the link's target. Suppose {@code A[1]} is a link to
|
|
||||||
* {@code B[1]}, which is in turn a link to {@code C[1]} -- honestly, linked links
|
|
||||||
* ought to be a rare occurrence -- then fetching {@code A[1]} must return a ref to
|
|
||||||
* {@code B[1]}. It must not return {@code C[1]} nor a ref to it. The reason deals
|
|
||||||
* with caching and updates. If a request for {@code A[1]} were to return
|
|
||||||
* {@code C[1]}, a client may cache that result. Suppose that client then observes a
|
|
||||||
* change causing {@code B[1]} to link to {@code C[2]}. This implies that {@code A[1]}
|
|
||||||
* now resolves to {@code C[2]}; however, the client has not received enough
|
|
||||||
* information to update or invalidate its cache.
|
|
||||||
*
|
|
||||||
* @param path the path
|
* @param path the path
|
||||||
* @param refresh true to refresh caches
|
* @param refresh true to refresh caches
|
||||||
* @return the found value, or {@code null} if it does not exist
|
* @return the found value, or {@code null} if it does not exist
|
||||||
|
|
|
@ -1,44 +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 ghidra.dbg;
|
|
||||||
|
|
||||||
import ghidra.util.classfinder.ExtensionPointProperties;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A factory for a local debugger model
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* These factories are searched when attempting to create a new default debug model targeting the
|
|
||||||
* local environment.
|
|
||||||
*/
|
|
||||||
public interface LocalDebuggerModelFactory extends DebuggerModelFactory {
|
|
||||||
/**
|
|
||||||
* Get the priority of this factory
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* In the event multiple compatible factories are discovered, the one with the highest priority
|
|
||||||
* is selected, breaking ties arbitrarily.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* The default implementation returns the priority given by {@link ExtensionPointProperties}. If
|
|
||||||
* the priority must be determined dynamically, then override this implementation.
|
|
||||||
*
|
|
||||||
* @return the priority, where lower values indicate higher priority.
|
|
||||||
*/
|
|
||||||
default int getPriority() {
|
|
||||||
return ExtensionPointProperties.Util.getPriority(getClass());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +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 ghidra.dbg.model;
|
|
||||||
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import ghidra.dbg.DebuggerObjectModel;
|
|
||||||
import ghidra.dbg.LocalDebuggerModelFactory;
|
|
||||||
import ghidra.dbg.util.ConfigurableFactory.FactoryDescription;
|
|
||||||
|
|
||||||
@FactoryDescription(brief = "Mocked Local Client", htmlDetails = TestDebuggerModelFactory.FAKE_DETAILS)
|
|
||||||
public class TestLocalDebuggerModelFactory implements LocalDebuggerModelFactory {
|
|
||||||
protected final Deque<CompletableFuture<DebuggerObjectModel>> buildQueue =
|
|
||||||
new LinkedList<>();
|
|
||||||
|
|
||||||
public TestLocalDebuggerModelFactory() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<? extends DebuggerObjectModel> build() {
|
|
||||||
CompletableFuture<DebuggerObjectModel> future = new CompletableFuture<>();
|
|
||||||
buildQueue.offer(future);
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<DebuggerObjectModel> pollBuild() {
|
|
||||||
return buildQueue.poll();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,9 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.util.database;
|
package ghidra.util.database;
|
||||||
|
|
||||||
|
import javax.help.UnsupportedOperationException;
|
||||||
|
|
||||||
import ghidra.framework.model.AbortedTransactionListener;
|
import ghidra.framework.model.AbortedTransactionListener;
|
||||||
import ghidra.framework.model.UndoableDomainObject;
|
import ghidra.framework.model.UndoableDomainObject;
|
||||||
import ghidra.program.model.data.DataTypeManager;
|
import ghidra.program.model.data.DataTypeManager;
|
||||||
|
import ghidra.program.model.listing.ProgramUserData;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public interface UndoableTransaction extends AutoCloseable {
|
public interface UndoableTransaction extends AutoCloseable {
|
||||||
|
@ -39,6 +42,11 @@ public interface UndoableTransaction extends AutoCloseable {
|
||||||
return new DataTypeManagerUndoableTransaction(dataTypeManager, tid, commitByDefault);
|
return new DataTypeManagerUndoableTransaction(dataTypeManager, tid, commitByDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UndoableTransaction start(ProgramUserData userData) {
|
||||||
|
int tid = userData.startTransaction();
|
||||||
|
return new ProgramUserDataUndoableTransaction(userData, tid);
|
||||||
|
}
|
||||||
|
|
||||||
abstract class AbstractUndoableTransaction implements UndoableTransaction {
|
abstract class AbstractUndoableTransaction implements UndoableTransaction {
|
||||||
protected final int transactionID;
|
protected final int transactionID;
|
||||||
|
|
||||||
|
@ -109,6 +117,25 @@ public interface UndoableTransaction extends AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ProgramUserDataUndoableTransaction extends AbstractUndoableTransaction {
|
||||||
|
private final ProgramUserData userData;
|
||||||
|
|
||||||
|
private ProgramUserDataUndoableTransaction(ProgramUserData userData, int tid) {
|
||||||
|
super(tid, true);
|
||||||
|
this.userData = userData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void endTransaction(boolean commit) {
|
||||||
|
userData.endTransaction(transactionID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void commit();
|
void commit();
|
||||||
|
|
||||||
void abort();
|
void abort();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue