getOffers(Program program, PluginTool tool,
+ DebuggerModelService service);
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java
index d0d3233653..8015b9c27f 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerModelService.java
@@ -19,20 +19,25 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Set;
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.DebuggerTargetTraceMapper;
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.DebuggerObjectModel;
import ghidra.dbg.target.*;
import ghidra.framework.plugintool.PluginEvent;
import ghidra.framework.plugintool.ServiceInfo;
+import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
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 {
/**
* Get the set of model factories found on the classpath
@@ -58,6 +63,7 @@ public interface DebuggerModelService {
/**
* Get the set of active recorders
*
+ *
* 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.
*
@@ -68,6 +74,7 @@ public interface DebuggerModelService {
/**
* Register a model with this service
*
+ *
* In general, the tool will only display models registered here
*
* @param model the model to register
@@ -84,26 +91,18 @@ public interface DebuggerModelService {
*/
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
*
+ *
* 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
* each thread as having registers, or a stack with frame 0 presenting the registers.
*
+ *
* Any given container can be traced by at most one recorder.
*
+ *
* TODO: If mappers remain bound to a prospective target, then remove target from the parameters
* here.
*
@@ -119,6 +118,7 @@ public interface DebuggerModelService {
/**
* Query mapping opinions and record the given target using the "best" offer
*
+ *
* 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.
*
@@ -131,10 +131,12 @@ public interface DebuggerModelService {
/**
* Query mapping opinions, prompt the user, and record the given target
*
+ *
* 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
* as much. If the user cancels, the returned future completes with {@code null}.
*
+ *
* TODO: Should the prompt allow the user to force an opinion which gave no offers?
*
* @see DebuggerMappingOpinion#queryOpinions(TargetObject)
@@ -146,6 +148,7 @@ public interface DebuggerModelService {
/**
* Start and open a new trace on the given target
*
+ *
* Starts a new trace, and opens it in the tool
*
* @see #recordTarget(TargetObject)
@@ -180,8 +183,10 @@ public interface DebuggerModelService {
/**
* Get the object (usually a process) associated with the given destination trace
*
+ *
* A recorder uses conventions to discover the "process" in the model, given a target object.
*
+ *
* TODO: Conventions for targets other than processes are not yet specified.
*
* @param trace the destination trace
@@ -200,11 +205,13 @@ public interface DebuggerModelService {
/**
* Get the object associated with the given destination trace thread
*
+ *
* 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
* the given trace thread is the destination of an active recorder, this method finds the
* corresponding model "thread."
*
+ *
* TODO: Conventions for targets other than processes (containing threads) are not yet
* specified.
*
@@ -216,6 +223,7 @@ public interface DebuggerModelService {
/**
* Get the destination trace thread, if applicable, for a given source thread
*
+ *
* Consider {@link #getTraceThread(TargetObject, TargetExecutionStateful)} if the caller already
* 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
*
+ *
* 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
* 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
*
+ *
* 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
* 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
*
+ *
* The caller must keep a strong reference to the listener, or it will be automatically removed.
*
* @param listener the listener
@@ -284,8 +295,10 @@ public interface DebuggerModelService {
/**
* Listen for changes in registered models
*
+ *
* The caller must beep a strong reference to the listener, or it will be automatically removed.
*
+ *
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
@@ -295,6 +308,7 @@ public interface DebuggerModelService {
/**
* Remove a listener for changes in registered models
*
+ *
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
@@ -304,8 +318,10 @@ public interface DebuggerModelService {
/**
* Listen for changes in active trace recorders
*
+ *
* The caller must beep a strong reference to the listener, or it will be automatically removed.
*
+ *
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
@@ -315,9 +331,18 @@ public interface DebuggerModelService {
/**
* Remove a listener for changes in active trace recorders
*
+ *
* TODO: Probably replace this with a {@link PluginEvent}
*
* @param listener the listener
*/
void removeTraceRecordersChangedListener(CollectionChangeListener listener);
+
+ /**
+ * Collect all offers for launching the given program
+ *
+ * @param program the program to launch
+ * @return the offers
+ */
+ Stream getProgramLaunchOffers(Program program);
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java
index 1889c3b723..b1ef828057 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java
@@ -19,20 +19,21 @@ import static org.junit.Assert.*;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.core.debug.event.ModelObjectFocusedPluginEvent;
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.async.AsyncPairingQueue;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
-import ghidra.dbg.model.TestDebuggerObjectModel;
-import ghidra.dbg.model.TestLocalDebuggerModelFactory;
+import ghidra.dbg.model.TestDebuggerModelFactory;
import ghidra.dbg.testutil.DebuggerModelTestUtils;
import ghidra.trace.model.Trace;
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 offers =
+ modelService.getProgramLaunchOffers(program).collect(Collectors.toList());
+ DebuggerProgramLaunchOffer offer = Unique.assertOne(offers);
+ assertEquals(TestDebuggerProgramLaunchOffer.class, offer.getClass());
+ }
+
@Test
public void testGetModels() throws Exception {
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
public void testRecordThenCloseStopsRecording() throws Throwable {
createTestModel();
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/TestDebuggerProgramLaunchOpinion.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/TestDebuggerProgramLaunchOpinion.java
new file mode 100644
index 0000000000..27b6baab6a
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/TestDebuggerProgramLaunchOpinion.java
@@ -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 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 getOffers(Program program, PluginTool tool,
+ DebuggerModelService service) {
+ DebuggerModelFactory factory = Unique.assertOne(service.getModelFactories());
+ assertEquals(TestDebuggerModelFactory.class, factory.getClass());
+
+ return List.of(new TestDebuggerProgramLaunchOffer());
+ }
+}
diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java
index b8806d47b5..2f4a665df2 100644
--- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java
+++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerObjectModel.java
@@ -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
* 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 refresh true to refresh caches
* @return the found value, or {@code null} if it does not exist
diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/LocalDebuggerModelFactory.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/LocalDebuggerModelFactory.java
deleted file mode 100644
index a3aa178858..0000000000
--- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/LocalDebuggerModelFactory.java
+++ /dev/null
@@ -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
- *
- *
- * 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
- *
- *
- * In the event multiple compatible factories are discovered, the one with the highest priority
- * is selected, breaking ties arbitrarily.
- *
- *
- * 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());
- }
-}
diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestLocalDebuggerModelFactory.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestLocalDebuggerModelFactory.java
deleted file mode 100644
index 1beea17a2f..0000000000
--- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestLocalDebuggerModelFactory.java
+++ /dev/null
@@ -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> buildQueue =
- new LinkedList<>();
-
- public TestLocalDebuggerModelFactory() {
- }
-
- @Override
- public CompletableFuture extends DebuggerObjectModel> build() {
- CompletableFuture future = new CompletableFuture<>();
- buildQueue.offer(future);
- return future;
- }
-
- public CompletableFuture pollBuild() {
- return buildQueue.poll();
- }
-}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/UndoableTransaction.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/UndoableTransaction.java
index d3d29a2305..9ba290dfb7 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/UndoableTransaction.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/UndoableTransaction.java
@@ -15,9 +15,12 @@
*/
package ghidra.util.database;
+import javax.help.UnsupportedOperationException;
+
import ghidra.framework.model.AbortedTransactionListener;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.program.model.data.DataTypeManager;
+import ghidra.program.model.listing.ProgramUserData;
import ghidra.util.Msg;
public interface UndoableTransaction extends AutoCloseable {
@@ -39,6 +42,11 @@ public interface UndoableTransaction extends AutoCloseable {
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 {
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 abort();