GP-739,741,742,666,681,823: combine listener interfaces, remove attribute-specific callbacks, update-mode schema, recorder refactor, model testing, and double-launch fix

This commit is contained in:
Dan 2021-04-01 10:15:17 -04:00
parent e83a893493
commit 015858b5d3
533 changed files with 29293 additions and 8011 deletions

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.gui.breakpoint;
import static ghidra.lifecycle.Unfinished.*;
import static ghidra.lifecycle.Unfinished.TODO;
import java.awt.BorderLayout;
import java.awt.event.*;
@ -51,6 +51,7 @@ import ghidra.trace.model.Trace.TraceBreakpointChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.util.*;
import ghidra.util.database.ObjectKey;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
@ -121,10 +122,10 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
protected static class LogicalBreakpointTableModel extends RowWrappedEnumeratedColumnTableModel< //
LogicalBreakpointTableColumns, LogicalBreakpointRow, LogicalBreakpoint> {
LogicalBreakpointTableColumns, LogicalBreakpoint, LogicalBreakpointRow, LogicalBreakpoint> {
public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) {
super("Breakpoints", LogicalBreakpointTableColumns.class,
super("Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
lb -> new LogicalBreakpointRow(provider, lb));
}
@ -198,10 +199,11 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
protected static class BreakpointLocationTableModel
extends RowWrappedEnumeratedColumnTableModel< //
BreakpointLocationTableColumns, BreakpointLocationRow, TraceBreakpoint> {
BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> {
public BreakpointLocationTableModel() {
super("Locations", BreakpointLocationTableColumns.class, BreakpointLocationRow::new);
super("Locations", BreakpointLocationTableColumns.class, TraceBreakpoint::getObjectKey,
BreakpointLocationRow::new);
}
@Override
@ -965,7 +967,9 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
public void setSelectedLocations(Set<TraceBreakpoint> sel) {
DebuggerResources.setSelectedRows(sel, locationTableModel.getMap(), locationTable,
DebuggerResources.setSelectedRows(
sel.stream().map(b -> b.getObjectKey()).collect(Collectors.toSet()),
locationTableModel.getMap(), locationTable,
locationTableModel, locationFilterPanel);
}
}

View file

@ -16,6 +16,7 @@
package ghidra.app.plugin.core.debug.gui.interpreters;
import java.io.*;
import java.lang.invoke.MethodHandles;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@ -30,10 +31,9 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.PinInterpreterAction;
import ghidra.app.plugin.core.interpreter.InterpreterComponentProvider;
import ghidra.app.plugin.core.interpreter.InterpreterConsole;
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.target.TargetConsole.TargetConsoleListener;
import ghidra.dbg.target.TargetInterpreter;
import ghidra.dbg.target.TargetInterpreter.TargetInterpreterListener;
import ghidra.dbg.target.TargetObject;
import ghidra.util.Msg;
import ghidra.util.Swing;
@ -45,7 +45,11 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
* We inherit console text output from interpreter listener, even though we may be listening to
* a plain console.
*/
protected class ForInterpreterListener implements TargetInterpreterListener {
protected class ForInterpreterListener extends AnnotatedDebuggerAttributeListener {
public ForInterpreterListener() {
super(MethodHandles.lookup());
}
@Override
public void consoleOutput(TargetObject console, Channel channel, byte[] out) {
OutputStream os;
@ -77,15 +81,15 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
}
}
@Override
@AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME)
public void displayChanged(TargetObject object, String display) {
// TODO: Add setSubTitle(String) to InterpreterConsole
InterpreterComponentProvider provider = (InterpreterComponentProvider) guiConsole;
Swing.runLater(() -> provider.setSubTitle(display));
}
@Override
public void promptChanged(TargetInterpreter i, String prompt) {
@AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME)
public void promptChanged(TargetObject interpreter, String prompt) {
Swing.runLater(() -> guiConsole.setPrompt(prompt));
}
@ -109,7 +113,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
protected final T targetConsole;
protected final AtomicBoolean running = new AtomicBoolean(false);
protected final TargetConsoleListener listener = new ForInterpreterListener();
protected final ForInterpreterListener listener = new ForInterpreterListener();
protected Thread thread;
protected InterpreterConsole guiConsole;
protected BufferedReader inReader;
@ -193,7 +197,7 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
try {
while (running.get()) {
String line = inReader.readLine();
if (!running.get()) {
if (line == null || !running.get()) {
return;
}
sendLine(line).exceptionally(e -> {

View file

@ -27,7 +27,8 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.services.*;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetInterpreter;
import ghidra.dbg.target.TargetObject;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
@ -238,7 +239,7 @@ public class DebuggerObjectsPlugin extends AbstractDebuggerPlugin
System.err.println("modelModified " + model);
}
public void setFocus(TargetFocusScope object, TargetObject focused) {
public void setFocus(TargetObject object, TargetObject focused) {
for (DebuggerObjectsProvider p : providers) {
p.setFocus(object, focused);
}

View file

@ -52,8 +52,8 @@ import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
import ghidra.dbg.target.TargetObject.TargetObjectListener;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.util.DebuggerCallbackReorderer;
import ghidra.dbg.util.PathUtils;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState;
@ -70,10 +70,8 @@ import ghidra.util.*;
import ghidra.util.table.GhidraTable;
import resources.ResourceManager;
public class DebuggerObjectsProvider extends ComponentProviderAdapter implements //AllTargetObjectListenerAdapter,
TargetObjectListener, //
DebuggerModelListener, //
ObjectContainerListener {
public class DebuggerObjectsProvider extends ComponentProviderAdapter
implements ObjectContainerListener {
public static final String PATH_JOIN_CHAR = ".";
//private static final String AUTOUPDATE_ATTRIBUTE_NAME = "autoupdate";
@ -170,7 +168,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
description = "The foreground color for links to items in the objects tree", //
help = @HelpInfo(anchor = "colors") //
)
Color linkForegroundColor = Color.GREEN;
Color linkForegroundColor = Color.GREEN.darker();
@AutoOptionDefined( //
name = "Default Extended Step", //
@ -202,6 +200,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
};
private boolean asTree = true;
private MyObjectListener listener = new MyObjectListener();
public DebuggerMethodInvocationDialog launchDialog;
public DebuggerAttachDialog attachDialog;
@ -301,7 +300,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
public void setModel(DebuggerObjectModel model) {
currentModel = model;
currentModel.addModelListener(this, true);
currentModel.addModelListener(getListener(), true);
refresh();
}
@ -463,8 +462,10 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
List<ObjectContainer> containers = new ArrayList<>();
for (String path : targetMap.keySet()) {
if (path.endsWith(key)) {
ObjectContainer container = targetMap.get(path);
containers.add(container);
synchronized (targetMap) {
ObjectContainer container = targetMap.get(path);
containers.add(container);
}
}
}
for (ObjectContainer container : containers) {
@ -659,8 +660,10 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
if (targetObject != null && !container.isLink()) {
String key = targetObject.getJoinedPath(PATH_JOIN_CHAR);
container.subscribe();
targetMap.put(key, container);
refSet.add(targetObject);
synchronized (targetMap) {
targetMap.put(key, container);
refSet.add(targetObject);
}
if (targetObject instanceof TargetInterpreter) {
TargetInterpreter interpreter = (TargetInterpreter) targetObject;
getPlugin().showConsole(interpreter);
@ -675,8 +678,10 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
public void deleteFromMap(ObjectContainer container) {
TargetObject targetObject = container.getTargetObject();
if (targetObject != null) {
targetMap.remove(targetObject.getJoinedPath(PATH_JOIN_CHAR));
refSet.remove(targetObject);
synchronized (targetMap) {
targetMap.remove(targetObject.getJoinedPath(PATH_JOIN_CHAR));
refSet.remove(targetObject);
}
}
}
@ -741,7 +746,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
public void closeComponent() {
TargetObject targetObject = getRoot().getTargetObject();
if (targetObject != null) {
targetObject.removeListener(this);
targetObject.removeListener(getListener());
}
super.closeComponent();
}
@ -1153,9 +1158,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
.helpLocation(AbstractSetBreakpointAction.help(plugin))
//.withContext(ObjectActionContext.class)
.enabledWhen(ctx ->
isInstance(ctx, TargetBreakpointContainer.class) && isStopped(ctx))
isInstance(ctx, TargetBreakpointSpecContainer.class) && isStopped(ctx))
.popupWhen(ctx ->
isInstance(ctx, TargetBreakpointContainer.class) && isStopped(ctx))
isInstance(ctx, TargetBreakpointSpecContainer.class) && isStopped(ctx))
.onAction(ctx -> performSetBreakpoint(ctx))
.enabled(false)
.buildAndInstallLocal(this);
@ -1348,23 +1353,21 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
}).finish();
}
public CompletableFuture<Void> startRecording(TargetProcess targetObject, boolean prompt) {
CompletableFuture<TraceRecorder> future;
public void startRecording(TargetProcess targetObject, boolean prompt) {
TraceRecorder rec;
if (prompt) {
future = modelService.recordTargetPromptOffers(targetObject);
rec = modelService.recordTargetPromptOffers(targetObject);
}
else {
future = modelService.recordTargetBestOffer(targetObject);
rec = modelService.recordTargetBestOffer(targetObject);
}
return future.thenAccept(rec -> {
if (rec == null) {
return; // Cancelled
}
//this.recorder = rec;
Trace trace = rec.getTrace();
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
});
if (rec == null) {
return; // Cancelled
}
//this.recorder = rec;
Trace trace = rec.getTrace();
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
}
public void stopRecording(TargetObject targetObject) {
@ -1409,22 +1412,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
DebugModelConventions.findSuitable(TargetProcess.class, obj).thenAccept(process -> {
TargetProcess valid = DebugModelConventions.liveProcessOrNull(process);
if (valid != null) {
startRecording(valid, true).exceptionally(ex -> {
Msg.showError(this, null, "Record",
"Could not record and/or open target: " + valid, ex);
return null;
});
startRecording(valid, true);
}
}).exceptionally(DebuggerResources.showError(getComponent(), "Couldn't record"));
}
else {
TargetProcess valid = DebugModelConventions.liveProcessOrNull(obj);
if (valid != null) {
startRecording(valid, true).exceptionally(ex -> {
Msg.showError(this, null, "Record",
"Could not record and/or open target: " + valid, ex);
return null;
});
startRecording(valid, true);
}
}
}
@ -1520,7 +1515,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
public void performSetBreakpoint(ActionContext context) {
TargetObject obj = getObjectFromContext(context);
if (!isLocalOnly()) {
DebugModelConventions.findSuitable(TargetBreakpointContainer.class, obj)
DebugModelConventions.findSuitable(TargetBreakpointSpecContainer.class, obj)
.thenAccept(suitable -> {
breakpointDialog.setContainer(suitable);
tool.showDialog(breakpointDialog);
@ -1529,7 +1524,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
DebuggerResources.showError(getComponent(), "Couldn't set breakpoint"));
}
else {
TargetBreakpointContainer container = (TargetBreakpointContainer) obj;
TargetBreakpointSpecContainer container = (TargetBreakpointSpecContainer) obj;
breakpointDialog.setContainer(container);
tool.showDialog(breakpointDialog);
}
@ -1584,114 +1579,145 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
return false;
}
@Override
public void accessibilityChanged(TargetAccessConditioned object, boolean accessible) {
//this.access = accessibility.equals(TargetAccessibility.ACCESSIBLE);
plugin.getTool().contextChanged(this);
}
class MyObjectListener extends AnnotatedDebuggerAttributeListener {
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
@Override
public void consoleOutput(TargetObject console, Channel channel, String out) {
//getPlugin().showConsole((TargetInterpreter) console);
System.err.println("consoleOutput: " + out);
}
public MyObjectListener() {
super(MethodHandles.lookup());
}
@Override
public void displayChanged(TargetObject object, String display) {
//System.err.println("displayChanged: " + display);
if (ObjectContainer.visibleByDefault(object.getName())) {
pane.signalDataChanged(getContainerByPath(object.getPath()));
@AttributeCallback(TargetAccessConditioned.ACCESSIBLE_ATTRIBUTE_NAME)
public void accessibilityChanged(TargetObject object, boolean accessible) {
//this.access = accessibility.equals(TargetAccessibility.ACCESSIBLE);
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
}
@Override
public void consoleOutput(TargetObject console, Channel channel, String out) {
//getPlugin().showConsole((TargetInterpreter) console);
System.err.println("consoleOutput: " + out);
}
@AttributeCallback(TargetObject.DISPLAY_ATTRIBUTE_NAME)
public void displayChanged(TargetObject object, String display) {
//System.err.println("displayChanged: " + display);
if (ObjectContainer.visibleByDefault(object.getName())) {
pane.signalDataChanged(getContainerByPath(object.getPath()));
}
}
@AttributeCallback(TargetObject.MODIFIED_ATTRIBUTE_NAME)
public void modifiedChanged(TargetObject object, boolean modified) {
//System.err.println("modifiedChanged: " + display);
if (ObjectContainer.visibleByDefault(object.getName())) {
pane.signalDataChanged(getContainerByPath(object.getPath()));
}
}
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
public void executionStateChanged(TargetObject object, TargetExecutionState state) {
//this.state = state;
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
}
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) {
plugin.setFocus(object, focused);
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
}
@Override
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
//System.err.println("memoryUpdated");
}
@Override
public void memoryReadError(TargetObject memory, AddressRange range,
DebuggerMemoryAccessException e) {
System.err.println("memoryReadError");
}
@AttributeCallback(TargetInterpreter.PROMPT_ATTRIBUTE_NAME)
public void promptChanged(TargetObject interpreter, String prompt) {
//System.err.println("promptChanged: " + prompt);
}
@Override
public void registersUpdated(TargetObject bank, Map<String, byte[]> updates) {
Map<String, ? extends TargetObject> cachedElements = bank.getCachedElements();
for (String key : cachedElements.keySet()) {
TargetObject ref = cachedElements.get(key);
displayChanged(ref, "registersUpdated");
}
Map<String, ?> cachedAttributes = bank.getCachedAttributes();
for (String key : cachedAttributes.keySet()) {
Object obj = cachedAttributes.get(key);
if (obj instanceof TargetObject) {
displayChanged((TargetObject) obj, "registersUpdated");
}
}
}
@Override
public void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
//System.err.println("local EC: " + parent);
ObjectContainer container =
parent == null ? null : getContainerByPath(parent.getPath());
if (container != null) {
container.augmentElements(removed, added);
boolean visibleChange = false;
for (String key : removed) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
for (String key : added.keySet()) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
if (visibleChange) {
container.propagateProvider(DebuggerObjectsProvider.this);
update(container);
}
}
}
@Override
public void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
super.attributesChanged(parent, removed, added);
//System.err.println("local AC: " + parent + ":" + removed + ":" + added);
ObjectContainer container =
parent == null ? null : getContainerByPath(parent.getPath());
if (container != null) {
container.augmentAttributes(removed, added);
boolean visibleChange = false;
for (String key : removed) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
for (String key : added.keySet()) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
if (visibleChange) {
container.propagateProvider(DebuggerObjectsProvider.this);
update(container);
}
}
if (parent != null && isAutorecord() &&
parent.getCachedAttribute(TargetExecutionStateful.STATE_ATTRIBUTE_NAME) != null) {
TargetProcess proc = DebugModelConventions.liveProcessOrNull(parent);
if (proc != null) {
startRecording(proc, false);
}
}
}
}
@Override
public void executionStateChanged(TargetExecutionStateful object, TargetExecutionState state) {
//this.state = state;
plugin.getTool().contextChanged(this);
}
@Override
public void focusChanged(TargetFocusScope object, TargetObject focused) {
plugin.setFocus(object, focused);
plugin.getTool().contextChanged(this);
}
public void setFocus(TargetFocusScope object, TargetObject focused) {
public void setFocus(TargetObject object, TargetObject focused) {
if (focused.getModel() != currentModel) {
return;
}
pane.setFocus(object, focused);
}
@Override
public void memoryUpdated(TargetMemory memory, Address address, byte[] data) {
//System.err.println("memoryUpdated");
}
@Override
public void memoryReadError(TargetMemory memory, AddressRange range,
DebuggerMemoryAccessException e) {
System.err.println("memoryReadError");
}
@Override
public void promptChanged(TargetInterpreter interpreter, String prompt) {
System.err.println("promptChanged: " + prompt);
}
@Override
public void registersUpdated(TargetRegisterBank bank, Map<String, byte[]> updates) {
Map<String, ? extends TargetObject> cachedElements = bank.getCachedElements();
for (String key : cachedElements.keySet()) {
TargetObject ref = cachedElements.get(key);
if (ref instanceof TargetObject) {
displayChanged(ref, "registersUpdated");
}
}
}
@Override
public void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
//System.err.println("local EC: " + parent);
ObjectContainer container = parent == null ? null : getContainerByPath(parent.getPath());
if (container != null) {
container.augmentElements(removed, added);
boolean visibleChange = false;
for (String key : removed) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
for (String key : added.keySet()) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
if (visibleChange) {
container.propagateProvider(this);
update(container);
}
}
}
@Override
public void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
//System.err.println("local AC: " + parent + ":" + removed + ":" + added);
ObjectContainer container = parent == null ? null : getContainerByPath(parent.getPath());
if (container != null) {
container.augmentAttributes(removed, added);
boolean visibleChange = false;
for (String key : removed) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
for (String key : added.keySet()) {
visibleChange |= ObjectContainer.visibleByDefault(key);
}
if (visibleChange) {
container.propagateProvider(this);
update(container);
}
}
}
public DebuggerTraceManagerService getTraceManager() {
return traceManager;
}
@ -1797,4 +1823,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
return listingService;
}
public DebuggerModelListener getListener() {
return listener.reorderer;
}
}

View file

@ -20,11 +20,8 @@ import java.util.concurrent.CompletableFuture;
import org.jdom.Element;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetProcess;
import ghidra.dbg.util.PathUtils;
import ghidra.util.Msg;
import ghidra.util.xml.XmlUtilities;
public class ObjectContainer implements Comparable<ObjectContainer> {
@ -157,18 +154,16 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
});
}
/*
protected void checkAutoRecord() {
if (targetObject != null && provider.isAutorecord()) {
TargetProcess proc = DebugModelConventions.liveProcessOrNull(targetObject);
if (proc != null) {
provider.startRecording(proc, false).exceptionally(ex -> {
Msg.error("Could not record and/or open target: " + targetObject, ex);
return null;
});
provider.startRecording(proc, false);
}
// Note that the recorder seeds its own listener with its target
}
}
*/
public void augmentElements(Collection<String> elementsRemoved,
Map<String, ? extends TargetObject> elementsAdded) {
@ -340,7 +335,7 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
c.propagateProvider(provider);
}
provider.fireObjectUpdated(this);
checkAutoRecord();
//checkAutoRecord();
}
// This should only be called once when the connection is activated

View file

@ -26,7 +26,7 @@ import javax.swing.*;
import docking.DialogComponentProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSetBreakpointAction;
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
import ghidra.dbg.target.TargetBreakpointContainer;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.util.MessageType;
import ghidra.util.Msg;
@ -34,7 +34,7 @@ import ghidra.util.layout.PairLayout;
public class DebuggerBreakpointDialog extends DialogComponentProvider {
protected DebuggerObjectsProvider provider;
protected TargetBreakpointContainer container;
protected TargetBreakpointSpecContainer container;
protected JTextField expressionField;
@ -78,7 +78,7 @@ public class DebuggerBreakpointDialog extends DialogComponentProvider {
setStatusText("Adding");
Set<TargetBreakpointKind> kinds = new HashSet<>();
kinds.add(TargetBreakpointKind.SOFTWARE);
kinds.add(TargetBreakpointKind.SW_EXECUTE);
container.placeBreakpoint(expression, kinds).exceptionally(e -> {
Msg.showError(this, getComponent(), "Could not set breakpoint", e);
setStatusText("Could not set breakpoint: " + e.getMessage(), MessageType.ERROR);
@ -87,7 +87,7 @@ public class DebuggerBreakpointDialog extends DialogComponentProvider {
close();
}
public void setContainer(TargetBreakpointContainer container) {
public void setContainer(TargetBreakpointSpecContainer container) {
this.container = container;
}

View file

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerModelListener;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetObject;
@ -172,6 +173,11 @@ public class DummyTargetObject implements TargetObject {
return elements;
}
@Override
public Map<String, ? extends TargetObject> getCallbackElements() {
return elements;
}
@Override
public CompletableFuture<? extends Map<String, ?>> fetchAttributes() {
if (!key.equals(TargetObject.DISPLAY_ATTRIBUTE_NAME)) {
@ -191,7 +197,6 @@ public class DummyTargetObject implements TargetObject {
if (type != null) {
addAttribute(TargetObject.TYPE_ATTRIBUTE_NAME, type);
}
addAttribute(TargetObject.UPDATE_MODE_ATTRIBUTE_NAME, TargetUpdateMode.UNSOLICITED);
}
// Why not completedFuture(attributes)?
return CompletableFuture.supplyAsync(() -> attributes);
@ -203,11 +208,16 @@ public class DummyTargetObject implements TargetObject {
}
@Override
public void addListener(TargetObjectListener l) {
public Map<String, ?> getCallbackAttributes() {
return attributes;
}
@Override
public void removeListener(TargetObjectListener l) {
public void addListener(DebuggerModelListener l) {
}
@Override
public void removeListener(DebuggerModelListener l) {
}
public String getJoinedPath() {

View file

@ -84,14 +84,16 @@ public class ObjectNode extends GTreeSlowLoadingNode { //extends GTreeNode
if (cf != null) {
// NB: We're allowed to do this because we're guaranteed to be
// in our own thread by the GTreeSlowLoadingNode
ObjectContainer oc = cf.get(5, TimeUnit.SECONDS);
ObjectContainer oc = cf.get(60, TimeUnit.SECONDS);
return tree.update(oc);
}
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
// Ignore
catch (InterruptedException | ExecutionException e) {
Msg.warn(this, e);
//e.printStackTrace();
}
catch (TimeoutException e) {
Msg.showWarn(this, container.getProvider().getComponent(), "Timeout Exception",
"Request for children timed - out - try refreshing the node");
}
}
List<GTreeNode> list = new ArrayList<>();

View file

@ -20,7 +20,6 @@ import java.util.List;
import javax.swing.JComponent;
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
import ghidra.dbg.target.TargetFocusScope;
import ghidra.dbg.target.TargetObject;
public interface ObjectPane {
@ -45,7 +44,7 @@ public interface ObjectPane {
public String getName();
public void setFocus(TargetFocusScope object, TargetObject focused);
public void setFocus(TargetObject object, TargetObject focused);
public void setRoot(ObjectContainer root, TargetObject targetObject);

View file

@ -29,7 +29,6 @@ import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.services.*;
import ghidra.dbg.target.TargetFocusScope;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
@ -269,7 +268,7 @@ public class ObjectTable<R> implements ObjectPane {
}
@Override
public void setFocus(TargetFocusScope object, TargetObject focused) {
public void setFocus(TargetObject object, TargetObject focused) {
Swing.runIfSwingOrRunLater(() -> {
setSelectedObject(focused);
});

View file

@ -40,7 +40,8 @@ import ghidra.app.services.*;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetAccessConditioned;
import ghidra.dbg.target.TargetObject;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.util.*;
@ -352,7 +353,7 @@ public class ObjectTree implements ObjectPane {
}
@Override
public void setFocus(TargetFocusScope object, TargetObject focused) {
public void setFocus(TargetObject object, TargetObject focused) {
Swing.runIfSwingOrRunLater(() -> {
List<String> path = focused.getPath();
tree.setSelectedNodeByNamePath(addRootNameToPath(path));
@ -395,7 +396,7 @@ public class ObjectTree implements ObjectPane {
DebuggerObjectsProvider provider = getProvider();
ObjectContainer oc = node.getContainer();
provider.deleteFromMap(oc);
oc.getTargetObject().removeListener(provider);
oc.getTargetObject().removeListener(provider.getListener());
nodeMap.remove(path(node.getContainer()));
}

View file

@ -41,8 +41,7 @@ import ghidra.util.Msg;
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = {
TraceActivatedPluginEvent.class, //
eventsConsumed = { TraceActivatedPluginEvent.class, //
TraceClosedPluginEvent.class, //
}, //
servicesRequired = { //

View file

@ -29,6 +29,8 @@ import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.apache.commons.lang3.exception.ExceptionUtils;
import com.google.common.collect.Range;
import docking.*;
@ -44,6 +46,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.services.*;
import ghidra.async.AsyncLazyValue;
import ghidra.async.AsyncUtils;
import ghidra.base.widgets.table.DataTypeTableCellEditor;
import ghidra.dbg.error.DebuggerModelAccessException;
@ -285,17 +288,11 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
class RegAccessListener implements TraceRecorderListener {
@Override
public void registerBankMapped(TraceRecorder recorder) {
if (readTheseCoords) {
return;
}
Swing.runIfSwingOrRunLater(() -> loadValues());
}
@Override
public void registerAccessibilityChanged(TraceRecorder recorder) {
if (readTheseCoords) {
return;
}
Swing.runIfSwingOrRunLater(() -> loadValues());
}
}
@ -363,7 +360,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE;
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private boolean readTheseCoords = false; /* "read" past tense */
private AsyncLazyValue<Void> readTheseCoords =
new AsyncLazyValue<>(this::readRegistersIfLiveAndAccessible); /* "read" past tense */
private Trace currentTrace; // Copy for transition
private TraceRecorder currentRecorder; // Copy of transition
@ -707,7 +705,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
previous = current;
current = coordinates;
readTheseCoords = false;
readTheseCoords = new AsyncLazyValue<>(this::readRegistersIfLiveAndAccessible);
doSetTrace(current.getTrace());
doSetRecorder(current.getRecorder());
updateSubTitle();
@ -1098,7 +1096,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return AsyncUtils.NIL;
}
regsTableModel.fireTableDataChanged();
return readRegistersIfLiveAndAccessible();
//return AsyncUtils.NIL;
// In case we need to read a non-zero frame
return readTheseCoords.request();
}
private Set<Register> baseRegisters(Set<Register> regs) {
@ -1113,10 +1113,13 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (recorder.getSnap() != current.getSnap()) {
return AsyncUtils.NIL;
}
if (current.getFrame() == 0) {
// Should have been pushed by model. non-zero frames are poll-only
return AsyncUtils.NIL;
}
TraceThread traceThread = current.getThread();
TargetThread targetThread = recorder.getTargetThread(traceThread);
if (targetThread == null ||
!recorder.isRegisterBankAccessible(traceThread, current.getFrame())) {
if (targetThread == null) {
return AsyncUtils.NIL;
}
Set<Register> toRead = new HashSet<>(baseRegisters(getSelectionFor(traceThread)));
@ -1126,16 +1129,14 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return AsyncUtils.NIL;
}
toRead.retainAll(regMapper.getRegistersOnTarget());
TargetRegisterBank bank =
recorder.getTargetRegisterBank(traceThread, current.getFrame());
if (!bank.isValid()) {
TargetRegisterBank bank = recorder.getTargetRegisterBank(traceThread, current.getFrame());
if (bank == null || !bank.isValid()) {
Msg.error(this, "Current frame's bank does not exist");
return AsyncUtils.NIL;
}
CompletableFuture<Void> future =
recorder.captureThreadRegisters(traceThread, current.getFrame(), toRead);
return future.thenAccept(__ -> {
readTheseCoords = true;
}).exceptionally(ex -> {
return future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) {
String msg =
@ -1147,7 +1148,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
Msg.showError(this, getComponent(), "Read Target Registers",
"Could not read target registers for selected thread", ex);
}
return null;
return ExceptionUtils.rethrow(ex);
});
}

View file

@ -221,6 +221,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
}
private void connect(ActionEvent evt) {
connectButton.setEnabled(false);
for (Map.Entry<Property<?>, PropertyEditor> ent : propertyEditors.entrySet()) {
Property<?> prop = ent.getKey();
@SuppressWarnings("unchecked")
@ -232,11 +233,13 @@ public class DebuggerConnectDialog extends DialogComponentProvider
modelService.addModel(model);
setStatusText("");
close();
connectButton.setEnabled(true);
return CompletableFuture.runAsync(() -> modelService.activateModel(model),
SwingExecutorService.INSTANCE);
}).exceptionally(e -> {
Msg.showError(this, getComponent(), "Could not connect", e);
setStatusText("Could not connect: " + e.getMessage(), MessageType.ERROR);
connectButton.setEnabled(true);
return null;
});
}

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.gui.target;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.showError;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
@ -290,7 +290,7 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter {
// TODO: Ensure when tree is populated, correct model is selected
}
// Note, setSelectedNode does not take EventOrigin
tree.setSelectionPaths(new TreePath[] { node.getTreePath() }, EventOrigin.USER_GENERATED);
tree.setSelectionPaths(new TreePath[] { node.getTreePath() }, EventOrigin.API_GENERATED);
}
protected void clearServiceCaches(DebuggerModelService service) {

View file

@ -29,8 +29,7 @@ import ghidra.framework.plugintool.util.PluginStatus;
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = {
TraceOpenedPluginEvent.class, //
eventsConsumed = { TraceOpenedPluginEvent.class, //
TraceClosedPluginEvent.class, //
TraceActivatedPluginEvent.class, //
}, //

View file

@ -16,10 +16,9 @@
package ghidra.app.plugin.core.debug.mapping;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint;
@ -35,22 +34,16 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
* @param target the target to be recorded, usually a process
* @return a future which completes with the set of offers
*/
public static CompletableFuture<List<DebuggerMappingOffer>> queryOpinions(
TargetObject target) {
public static List<DebuggerMappingOffer> queryOpinions(TargetObject target) {
List<DebuggerMappingOffer> result = new ArrayList<>();
AsyncFence fence = new AsyncFence();
for (DebuggerMappingOpinion opinion : ClassSearcher
.getInstances(DebuggerMappingOpinion.class)) {
fence.include(opinion.getOffers(target).thenAccept(offers -> {
synchronized (result) {
result.addAll(offers);
}
}));
synchronized (result) {
result.addAll(opinion.getOffers(target));
}
}
return fence.ready().thenApply(__ -> {
result.sort(HIGHEST_CONFIDENCE_FIRST);
return result;
});
result.sort(HIGHEST_CONFIDENCE_FIRST);
return result;
}
/**
@ -59,5 +52,18 @@ public interface DebuggerMappingOpinion extends ExtensionPoint {
* @param target the target, usually a process
* @return a future which completes with true if it knows, false if not
*/
CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target);
public default Set<DebuggerMappingOffer> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return Set.of();
}
TargetProcess process = (TargetProcess) target;
DebuggerObjectModel model = process.getModel();
List<String> pathToEnv =
model.getRootSchema().searchForSuitable(TargetEnvironment.class, process.getPath());
TargetEnvironment env = (TargetEnvironment) model.getModelObject(pathToEnv);
return offersForEnv(env, process);
}
Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process);
}

View file

@ -20,14 +20,13 @@ import java.util.*;
import ghidra.app.plugin.core.debug.register.RegisterTypeInfo;
import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.target.TargetRegisterContainer;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.lang.*;
public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
protected final Language language;
protected final CompilerSpec cspec;
protected final TargetRegisterContainer targetRegContainer;
//protected final TargetRegisterContainer targetRegContainer;
protected final boolean caseSensitive;
protected final Map<String, Register> languageRegs = new LinkedHashMap<>();
@ -41,7 +40,7 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
TargetRegisterContainer targetRegContainer, boolean caseSensitive) {
this.language = cSpec.getLanguage();
this.cspec = cSpec;
this.targetRegContainer = targetRegContainer;
//this.targetRegContainer = targetRegContainer;
this.caseSensitive = caseSensitive;
this.instrCtrTypeInfo = new RegisterTypeInfo(PointerDataType.dataType,
@ -65,6 +64,16 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
}
}
protected synchronized Register considerRegister(String index) {
String name = normalizeName(index);
Register lReg = filtLanguageRegs.get(name);
if (lReg == null) {
return null;
}
languageRegs.put(name, lReg);
return lReg;
}
protected synchronized Register considerRegister(TargetRegister tReg) {
String name = normalizeName(tReg.getIndex());
Register lReg = filtLanguageRegs.get(name);
@ -132,17 +141,17 @@ public class DefaultDebuggerRegisterMapper implements DebuggerRegisterMapper {
@Override
public synchronized void targetRegisterAdded(TargetRegister register) {
if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
return;
}
//if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
// return;
//}
considerRegister(register);
}
@Override
public synchronized void targetRegisterRemoved(TargetRegister register) {
if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
return;
}
//if (!PathUtils.isAncestor(targetRegContainer.getPath(), register.getPath())) {
// return;
//}
removeRegister(register);
}
}

View file

@ -44,6 +44,19 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
return true;
}
@Override
protected synchronized Register considerRegister(String index) {
Register lReg = super.considerRegister(index);
if (lReg == null) {
return null;
}
//synchronized (present) {
present.computeIfAbsent(lReg.getBaseRegister(), r -> new TreeSet<>(LENGTH_COMPARATOR))
.add(lReg);
//}
return lReg;
}
@Override
protected synchronized Register considerRegister(TargetRegister tReg) {
Register lReg = super.considerRegister(tReg);
@ -120,7 +133,10 @@ public class LargestSubDebuggerRegisterMapper extends DefaultDebuggerRegisterMap
}
Register lReg = languageRegs.get(normalizeName(tRegName));
if (lReg == null) {
return null;
lReg = considerRegister(tRegName);
if (lReg == null) {
return null;
}
}
Register lbReg = lReg.getBaseRegister();
TreeSet<Register> subs = present.get(lbReg);

View file

@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.platform;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.program.model.lang.*;
@ -57,8 +55,7 @@ public class DbgengX64DebuggerMappingOpinion implements DebuggerMappingOpinion {
}
@Override
protected DebuggerRegisterMapper createRegisterMapper(
TargetRegisterContainer registers) {
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
return new DefaultDebuggerRegisterMapper(cSpec, registers, false);
}
}
@ -85,19 +82,7 @@ public class DbgengX64DebuggerMappingOpinion implements DebuggerMappingOpinion {
}
}
@Override
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return CompletableFuture.completedFuture(Set.of());
}
TargetProcess process = (TargetProcess) target;
CompletableFuture<? extends TargetEnvironment> futureEnv =
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
return futureEnv.thenApply(env -> offersForEnv(env, process));
}
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
TargetProcess process) {
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
if (env == null || !env.getDebugger().toLowerCase().contains("dbg")) {
return Set.of();
}

View file

@ -16,22 +16,19 @@
package ghidra.app.plugin.core.debug.platform;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
public class GdbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
protected static final LanguageID LANG_ID_ARM_LE_V8 = new LanguageID("ARM:LE:32:v8");
protected static final LanguageID LANG_ID_ARM_BE_V8 = new LanguageID("ARM:BE:32:v8");
protected static final LanguageID LANG_ID_AARCH64_LE_V8A =
new LanguageID("AARCH64:LE:64:v8A");
protected static final LanguageID LANG_ID_AARCH64_BE_V8A =
new LanguageID("AARCH64:BE:64:v8A");
protected static final LanguageID LANG_ID_AARCH64_LE_V8A = new LanguageID("AARCH64:LE:64:v8A");
protected static final LanguageID LANG_ID_AARCH64_BE_V8A = new LanguageID("AARCH64:BE:64:v8A");
protected static final CompilerSpecID COMP_ID_DEFAULT = new CompilerSpecID("default");
protected static class GdbArmLELinuxOffer extends AbstractGdbDebuggerMappingOffer {
@ -48,19 +45,7 @@ public class GdbArmDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
}
@Override
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return CompletableFuture.completedFuture(Set.of());
}
TargetProcess process = (TargetProcess) target;
CompletableFuture<? extends TargetEnvironment> futureEnv =
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
return futureEnv.thenApply(env -> offersForEnv(env, process));
}
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
TargetProcess process) {
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of();
}

View file

@ -16,12 +16,11 @@
package ghidra.app.plugin.core.debug.platform;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
@ -77,16 +76,6 @@ public class GdbMipsDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
@Override
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return CompletableFuture.completedFuture(Set.of());
}
TargetProcess process = (TargetProcess) target;
CompletableFuture<? extends TargetEnvironment> futureEnv =
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
return futureEnv.thenApply(env -> offersForEnv(env, process));
}
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of();

View file

@ -16,12 +16,11 @@
package ghidra.app.plugin.core.debug.platform;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer;
import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOpinion;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.TargetProcess;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.LanguageID;
@ -63,16 +62,6 @@ public class GdbPowerPCDebuggerMappingOpinion implements DebuggerMappingOpinion
}
@Override
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return CompletableFuture.completedFuture(Set.of());
}
TargetProcess process = (TargetProcess) target;
CompletableFuture<? extends TargetEnvironment> futureEnv =
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
return futureEnv.thenApply(env -> offersForEnv(env, process));
}
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of();

View file

@ -16,10 +16,8 @@
package ghidra.app.plugin.core.debug.platform;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.program.model.lang.*;
import ghidra.util.Msg;
@ -114,19 +112,7 @@ public class GdbX86DebuggerMappingOpinion implements DebuggerMappingOpinion {
}
}
@Override
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return CompletableFuture.completedFuture(Set.of());
}
TargetProcess process = (TargetProcess) target;
CompletableFuture<? extends TargetEnvironment> futureEnv =
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
return futureEnv.thenApply(env -> offersForEnv(env, process));
}
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
TargetProcess process) {
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
if (!env.getDebugger().toLowerCase().contains("gdb")) {
return Set.of();
}

View file

@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.platform;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.program.model.lang.*;
@ -44,8 +42,7 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
@Override
protected DebuggerRegisterMapper createRegisterMapper(
TargetRegisterContainer registers) {
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
return new DefaultDebuggerRegisterMapper(cSpec, registers, false);
}
}
@ -66,23 +63,11 @@ public class JdiDalvikDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
}
@Override
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return CompletableFuture.completedFuture(Set.of());
}
TargetProcess process = (TargetProcess) target;
CompletableFuture<? extends TargetEnvironment> futureEnv =
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
return futureEnv.thenApply(env -> offersForEnv(env, process));
}
protected static boolean containsRecognizedJvmName(String name) {
return DALVIK_VM_NAMES.stream().anyMatch(name::contains);
}
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
TargetProcess process) {
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of();
}

View file

@ -17,10 +17,8 @@ package ghidra.app.plugin.core.debug.platform;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.program.model.lang.*;
@ -44,8 +42,7 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
@Override
protected DebuggerRegisterMapper createRegisterMapper(
TargetRegisterContainer registers) {
protected DebuggerRegisterMapper createRegisterMapper(TargetRegisterContainer registers) {
return new DefaultDebuggerRegisterMapper(cSpec, registers, false);
}
}
@ -66,23 +63,11 @@ public class JdiJavaDebuggerMappingOpinion implements DebuggerMappingOpinion {
}
}
@Override
public CompletableFuture<Set<DebuggerMappingOffer>> getOffers(TargetObject target) {
if (!(target instanceof TargetProcess)) {
return CompletableFuture.completedFuture(Set.of());
}
TargetProcess process = (TargetProcess) target;
CompletableFuture<? extends TargetEnvironment> futureEnv =
DebugModelConventions.findSuitable(TargetEnvironment.class, target);
return futureEnv.thenApply(env -> offersForEnv(env, process));
}
protected static boolean containsRecognizedJvmName(String name) {
return JVM_NAMES.stream().anyMatch(name::contains);
}
protected Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env,
TargetProcess process) {
public Set<DebuggerMappingOffer> offersForEnv(TargetEnvironment env, TargetProcess process) {
if (!env.getDebugger().contains("Java Debug Interface")) {
return Set.of();
}

View file

@ -369,7 +369,7 @@ interface LogicalBreakpointInternal extends LogicalBreakpoint {
Set<TargetBreakpointKind> tKinds =
TraceRecorder.traceToTargetBreakpointKinds(kinds);
for (TargetBreakpointContainer cont : recorder
for (TargetBreakpointSpecContainer cont : recorder
.collectBreakpointContainers(null)) {
LinkedHashSet<TargetBreakpointKind> supKinds = new LinkedHashSet<>(tKinds);
supKinds.retainAll(cont.getSupportedBreakpointKinds());

View file

@ -18,17 +18,17 @@ package ghidra.app.plugin.core.debug.service.breakpoint;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetBreakpointContainer;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.program.model.address.*;
public class PlaceBreakpointActionItem implements BreakpointActionItem {
private final TargetBreakpointContainer container;
private final TargetBreakpointSpecContainer container;
private final Address address;
private final long length;
private final Set<TargetBreakpointKind> kinds;
public PlaceBreakpointActionItem(TargetBreakpointContainer container, Address address,
public PlaceBreakpointActionItem(TargetBreakpointSpecContainer container, Address address,
long length, Collection<TargetBreakpointKind> kinds) {
this.container = Objects.requireNonNull(container);
this.address = Objects.requireNonNull(address);

View file

@ -17,7 +17,6 @@ package ghidra.app.plugin.core.debug.service.model;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
@ -133,5 +132,5 @@ public interface DebuggerModelServiceInternal extends DebuggerModelService {
* @param target the target to record
* @return a future which completes with the resulting recorder, unless cancelled
*/
CompletableFuture<TraceRecorder> doRecordTargetPromptOffers(PluginTool t, TargetObject target);
TraceRecorder doRecordTargetPromptOffers(PluginTool t, TargetObject target);
}

View file

@ -15,9 +15,10 @@
*/
package ghidra.app.plugin.core.debug.service.model;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.showError;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.CharBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@ -33,15 +34,12 @@ import docking.ActionContext;
import docking.action.DockingAction;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.services.*;
import ghidra.async.AsyncFence;
import ghidra.dbg.*;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetFocusScope.TargetFocusScopeListener;
import ghidra.dbg.target.TargetObject.TargetObjectListener;
import ghidra.dbg.util.PathUtils;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.FrontEndOnly;
@ -59,17 +57,8 @@ import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.CollectionChangeListener;
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 = {
DebuggerModelService.class,
})
@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, })
public class DebuggerModelServicePlugin extends Plugin
implements DebuggerModelServiceInternal, FrontEndOnly {
@ -83,7 +72,7 @@ public class DebuggerModelServicePlugin extends Plugin
protected TargetObject root;
protected TargetFocusScope focusScope;
protected TargetObjectListener forRemoval = new TargetObjectListener() {
protected DebuggerModelListener forRemoval = new DebuggerModelListener() {
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
synchronized (listenersByModel) {
@ -99,19 +88,20 @@ public class DebuggerModelServicePlugin extends Plugin
}
};
protected TargetFocusScopeListener forFocus = new TargetFocusScopeListener() {
@Override
public void focusChanged(TargetFocusScope object, TargetObject focused) {
fireFocusEvent(focused);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
protected DebuggerModelListener forFocus =
new AnnotatedDebuggerAttributeListener(MethodHandles.lookup()) {
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) {
fireFocusEvent(focused);
List<DebuggerModelServiceProxyPlugin> copy;
synchronized (proxies) {
copy = List.copyOf(proxies);
}
for (DebuggerModelServiceProxyPlugin proxy : copy) {
proxy.fireFocusEvent(focused);
}
}
for (DebuggerModelServiceProxyPlugin proxy : copy) {
proxy.fireFocusEvent(focused);
}
}
};
};
protected ListenersForRemovalAndFocus(DebuggerObjectModel model) {
this.model = model;
@ -344,6 +334,10 @@ public class DebuggerModelServicePlugin extends Plugin
}
recorder = doBeginRecording(target, mapper);
recorder.addListener(listenerOnRecorders);
recorder.init().exceptionally(e -> {
Msg.showError(this, null, "Record Trace", "Error initializing recorder", e);
return null;
});
recordersByTarget.put(target, recorder);
}
recorderListeners.fire.elementAdded(recorder);
@ -355,74 +349,64 @@ public class DebuggerModelServicePlugin extends Plugin
}
@Override
public CompletableFuture<TraceRecorder> recordTargetBestOffer(TargetObject target) {
public TraceRecorder recordTargetBestOffer(TargetObject target) {
synchronized (recordersByTarget) {
TraceRecorder recorder = recordersByTarget.get(target);
if (recorder != null) {
Msg.warn(this, "Target is already being recorded: " + target);
return CompletableFuture.completedFuture(recorder);
return recorder;
}
}
return DebuggerMappingOpinion.queryOpinions(target).thenApply(offers -> {
DebuggerTargetTraceMapper mapper = DebuggerMappingOffer.first(offers);
if (mapper == null) {
throw new NoSuchElementException("No mapper for target: " + target);
}
try {
return recordTarget(target, mapper);
}
catch (IOException e) {
throw new AssertionError("Could not record target: " + target, e);
}
}).exceptionally(ex -> {
Msg.error(this, "Could not query trace-recording opinions", ex);
return null;
});
DebuggerTargetTraceMapper mapper =
DebuggerMappingOffer.first(DebuggerMappingOpinion.queryOpinions(target));
if (mapper == null) {
throw new NoSuchElementException("No mapper for target: " + target);
}
try {
return recordTarget(target, mapper);
}
catch (IOException e) {
throw new AssertionError("Could not record target: " + target, e);
}
}
@Override
@Internal
public CompletableFuture<TraceRecorder> doRecordTargetPromptOffers(PluginTool t,
TargetObject target) {
public TraceRecorder doRecordTargetPromptOffers(PluginTool t, TargetObject target) {
synchronized (recordersByTarget) {
TraceRecorder recorder = recordersByTarget.get(target);
if (recorder != null) {
Msg.warn(this, "Target is already being recorded: " + target);
return CompletableFuture.completedFuture(recorder);
return recorder;
}
}
return DebuggerMappingOpinion.queryOpinions(target).thenApply(offers -> {
DebuggerMappingOffer selected;
if (offers.size() == 1) {
selected = offers.get(0);
List<DebuggerMappingOffer> offers = DebuggerMappingOpinion.queryOpinions(target);
DebuggerMappingOffer selected;
if (offers.size() == 1) {
selected = offers.get(0);
}
else {
offerDialog.setOffers(offers);
t.showDialog(offerDialog);
// TODO: Is cancelled?
if (offerDialog.isCancelled()) {
return null;
}
else {
offerDialog.setOffers(offers);
t.showDialog(offerDialog);
// TODO: Is cancelled?
if (offerDialog.isCancelled()) {
return null;
}
selected = offerDialog.getSelectedOffer();
}
assert selected != null;
DebuggerTargetTraceMapper mapper = selected.take();
try {
return recordTarget(target, mapper);
}
catch (IOException e) {
throw new AssertionError("Could not record target: " + target, e);
// TODO: For certain errors, It may not be appropriate to close the dialog.
}
}).exceptionally(ex -> {
Msg.showError(this, null, DebuggerResources.AbstractRecordAction.NAME,
"Could not query trace-recording opinions", ex);
return null;
});
selected = offerDialog.getSelectedOffer();
}
assert selected != null;
DebuggerTargetTraceMapper mapper = selected.take();
try {
return recordTarget(target, mapper);
}
catch (IOException e) {
throw new AssertionError("Could not record target: " + target, e);
// TODO: For certain errors, It may not be appropriate to close the dialog.
}
}
@Override
public CompletableFuture<TraceRecorder> recordTargetPromptOffers(TargetObject target) {
public TraceRecorder recordTargetPromptOffers(TargetObject target) {
return doRecordTargetPromptOffers(tool, target);
}
@ -546,10 +530,6 @@ public class DebuggerModelServicePlugin extends Plugin
//DefaultTraceRecorder recorder = new DefaultTraceRecorder(this, trace, target, mapper);
TraceRecorder recorder = mapper.startRecording(this, trace);
trace.release(this); // The recorder now owns it (on behalf of the service)
recorder.init().exceptionally(e -> {
Msg.showError(this, null, "Record Trace", "Error initializing recorder", e);
return null;
});
return recorder;
}

View file

@ -48,21 +48,20 @@ import ghidra.util.datastruct.ListenerSet;
import ghidra.util.task.TaskMonitor;
@PluginInfo( //
shortDescription = "Debugger models manager service (proxy to front-end)", //
description = "Manage debug sessions, connections, and trace recording", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = {
ProgramActivatedPluginEvent.class, //
ProgramClosedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerTraceManagerService.class, //
}, //
servicesProvided = { //
DebuggerModelService.class, //
} //
shortDescription = "Debugger models manager service (proxy to front-end)", //
description = "Manage debug sessions, connections, and trace recording", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
eventsConsumed = { ProgramActivatedPluginEvent.class, //
ProgramClosedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerTraceManagerService.class, //
}, //
servicesProvided = { //
DebuggerModelService.class, //
} //
)
public class DebuggerModelServiceProxyPlugin extends Plugin
implements DebuggerModelServiceInternal {
@ -362,18 +361,17 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
}
@Override
public CompletableFuture<TraceRecorder> recordTargetBestOffer(TargetObject target) {
public TraceRecorder recordTargetBestOffer(TargetObject target) {
return delegate.recordTargetBestOffer(target);
}
@Override
public CompletableFuture<TraceRecorder> doRecordTargetPromptOffers(PluginTool t,
TargetObject target) {
public TraceRecorder doRecordTargetPromptOffers(PluginTool t, TargetObject target) {
return delegate.doRecordTargetPromptOffers(t, target);
}
@Override
public CompletableFuture<TraceRecorder> recordTargetPromptOffers(TargetObject target) {
public TraceRecorder recordTargetPromptOffers(TargetObject target) {
return doRecordTargetPromptOffers(tool, target);
}

View file

@ -0,0 +1,204 @@
/* ###
* 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 java.util.Set;
import java.util.concurrent.Executors;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedBreakpointRecorder;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder {
public static AddressRange range(Address min, Integer length) {
if (length == null) {
length = 1;
}
try {
return new AddressRangeImpl(min, length);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
protected static String nameBreakpoint(TargetBreakpointLocation bpt) {
if (bpt instanceof TargetBreakpointSpec) {
return bpt.getIndex();
}
return bpt.getSpecification().getIndex() + "." + bpt.getIndex();
}
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceBreakpointManager breakpointManager;
final PermanentTransactionExecutor tx;
protected TargetBreakpointSpecContainer breakpointContainer;
public DefaultBreakpointRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
this.breakpointManager = trace.getBreakpointManager();
this.tx = new PermanentTransactionExecutor(trace,
"BreakpointRecorder:" + recorder.target.getJoinedPath("."),
Executors::newCachedThreadPool, 100);
}
@Override
public void offerBreakpointContainer(TargetBreakpointSpecContainer bc) {
if (breakpointContainer != null) {
Msg.warn(this, "Already have a breakpoint container for this process");
}
breakpointContainer = bc;
}
@Override
public void offerBreakpointLocation(TargetObject containerParent,
TargetBreakpointLocation bpt) {
synchronized (this) {
if (recorder.getMemoryMapper() == null) {
return;
}
}
RecorderBreakpointLocationResolver resolver =
new RecorderBreakpointLocationResolver(recorder, bpt);
resolver.updateBreakpoint(containerParent, bpt);
}
protected void doRecordBreakpoint(long snap, TargetBreakpointLocation loc,
Set<TraceThread> traceThreads) {
synchronized (this) {
if (recorder.getMemoryMapper() == null) {
throw new IllegalStateException(
"No memory mapper! Have not recorded a region, yet.");
}
}
String path = PathUtils.toString(loc.getPath());
String name = nameBreakpoint(loc);
Address traceAddr = recorder.getMemoryMapper().targetToTrace(loc.getAddress());
AddressRange traceRange = range(traceAddr, loc.getLength());
try {
TargetBreakpointSpec spec = loc.getSpecification();
boolean enabled = spec.isEnabled();
Set<TraceBreakpointKind> traceKinds =
TraceRecorder.targetToTraceBreakpointKinds(spec.getKinds());
TraceBreakpoint traceBpt =
breakpointManager.placeBreakpoint(path, snap,
traceRange, traceThreads, traceKinds, enabled, spec.getExpression());
traceBpt.setName(name);
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not record placed breakpoint: " + e);
}
}
@Override
public void recordBreakpoint(TargetBreakpointLocation loc,
Set<TraceThread> traceThreads) {
String path = loc.getJoinedPath(".");
long snap = recorder.getSnap();
tx.execute("Breakpoint " + path + " placed", () -> {
doRecordBreakpoint(snap, loc, traceThreads);
});
}
protected void doRemoveBreakpointLocation(long snap, TargetBreakpointLocation loc) {
String path = loc.getJoinedPath(".");
for (TraceBreakpoint traceBpt : breakpointManager.getBreakpointsByPath(path)) {
try {
if (traceBpt.getPlacedSnap() > snap) {
Msg.error(this,
"Tracked, now removed breakpoint was placed in the future? " + path);
}
else if (traceBpt.getPlacedSnap() == snap) {
// TODO: I forget if this is allowed for DBTrace iteration
traceBpt.delete();
}
else {
traceBpt.setClearedSnap(snap - 1);
}
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not record breakpoint removal: " + e);
}
}
}
@Override
public void removeBreakpointLocation(TargetBreakpointLocation loc) {
String path = loc.getJoinedPath(".");
long snap = recorder.getSnap();
tx.execute("Breakpoint " + path + " deleted", () -> {
doRemoveBreakpointLocation(snap, loc);
});
}
protected void doBreakpointLengthChanged(long snap, int length, Address traceAddr,
String path) {
for (TraceBreakpoint traceBpt : breakpointManager.getBreakpointsByPath(path)) {
if (traceBpt.getLength() == length) {
continue; // Nothing to change
}
// TODO: Verify all other attributes match?
// TODO: Should this be allowed to happen?
try {
if (traceBpt.getPlacedSnap() == snap) {
traceBpt.delete();
}
else {
traceBpt.setClearedSnap(snap - 1);
}
breakpointManager.placeBreakpoint(path, snap, range(traceAddr, length),
traceBpt.getThreads(), traceBpt.getKinds(), traceBpt.isEnabled(),
traceBpt.getComment());
}
catch (DuplicateNameException e) {
// Split, and length matters not
Msg.error(this, "Could not record breakpoint length change: " + e);
}
}
}
@Override
public void breakpointLengthChanged(int length, Address traceAddr, String path)
throws AssertionError {
long snap = recorder.getSnap();
tx.execute("Breakpoint length changed", () -> {
doBreakpointLengthChanged(snap, length, traceAddr, path);
});
}
@Override
public TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt) {
String path = PathUtils.toString(bpt.getPath());
return breakpointManager.getPlacedBreakpointByPath(recorder.getSnap(), path);
}
@Override
public TargetBreakpointSpecContainer getBreakpointContainer() {
return breakpointContainer;
}
}

View file

@ -0,0 +1,88 @@
/* ###
* 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 java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.TargetDataTypeConverter;
import ghidra.program.model.data.*;
import ghidra.trace.model.Trace;
import ghidra.util.task.TaskMonitor;
public class DefaultDataTypeRecorder {
//private DefaultTraceRecorder recorder;
private Trace trace;
private final TargetDataTypeConverter typeConverter;
public DefaultDataTypeRecorder(DefaultTraceRecorder recorder) {
//this.recorder = recorder;
this.trace = recorder.getTrace();
this.typeConverter = new TargetDataTypeConverter(trace.getDataTypeManager());
}
public CompletableFuture<Void> captureDataTypes(TargetDataTypeNamespace namespace,
TaskMonitor monitor) {
String path = PathUtils.toString(namespace.getPath());
monitor.setMessage("Capturing data types for " + path);
return namespace.getTypes().thenCompose(types -> {
monitor.initialize(types.size());
AsyncFence fence = new AsyncFence();
List<DataType> converted = new ArrayList<>();
for (TargetNamedDataType type : types) {
if (monitor.isCancelled()) {
fence.ready().cancel(false);
return AsyncUtils.nil();
}
monitor.incrementProgress(1);
fence.include(typeConverter.convertTargetDataType(type).thenAccept(converted::add));
}
return fence.ready().thenApply(__ -> converted);
}).thenAccept(converted -> {
if (converted == null) {
return;
}
try (RecorderPermanentTransaction tid =
RecorderPermanentTransaction.start(trace, "Capture data types for " + path)) {
// NOTE: createCategory is actually getOrCreate
Category category =
trace.getDataTypeManager().createCategory(new CategoryPath("/" + path));
for (DataType dataType : converted) {
category.addDataType(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
});
}
public CompletableFuture<Void> captureDataTypes(TargetModule targetModule,
TaskMonitor monitor) {
CompletableFuture<? extends Map<String, ? extends TargetDataTypeNamespace>> future =
targetModule.fetchChildrenSupporting(TargetDataTypeNamespace.class);
// NOTE: I should expect exactly one namespace...
return future.thenCompose(namespaces -> {
AsyncFence fence = new AsyncFence();
for (TargetDataTypeNamespace ns : namespaces.values()) {
fence.include(captureDataTypes(ns, monitor));
}
return fence.ready();
});
}
}

View file

@ -0,0 +1,174 @@
/* ###
* 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 java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedMemoryRecorder;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
// For large memory captures
private static final int BLOCK_SIZE = 4096;
private static final long BLOCK_MASK = -1L << 12;
protected static AddressSetView expandToBlocks(AddressSetView asv) {
AddressSet result = new AddressSet();
// Not terribly efficient, but this is one range most of the time
for (AddressRange range : asv) {
AddressSpace space = range.getAddressSpace();
Address min = space.getAddress(range.getMinAddress().getOffset() & BLOCK_MASK);
Address max = space.getAddress(range.getMaxAddress().getOffset() | ~BLOCK_MASK);
result.add(new AddressRangeImpl(min, max));
}
return result;
}
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceMemoryManager memoryManager;
final PermanentTransactionExecutor tx;
public DefaultMemoryRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
this.memoryManager = trace.getMemoryManager();
this.tx = new PermanentTransactionExecutor(trace,
"MemoryRecorder:" + recorder.target.getJoinedPath("."),
Executors::newSingleThreadExecutor, 100);
}
public CompletableFuture<Void> captureProcessMemory(AddressSetView set, TaskMonitor monitor) {
// TODO: Figure out how to display/select per-thread memory.
// Probably need a thread parameter passed in then?
// NOTE: That thread memory will already be chained to process memory. Good.
int total = 0;
AddressSetView expSet = expandToBlocks(set)
.intersect(trace.getMemoryManager().getRegionsAddressSet(recorder.getSnap()));
for (AddressRange r : expSet) {
total += Long.divideUnsigned(r.getLength() + BLOCK_SIZE - 1, BLOCK_SIZE);
}
monitor.initialize(total);
monitor.setMessage("Capturing memory");
// TODO: Read blocks in parallel? Probably NO. Tends to overload the agent.
return AsyncUtils.each(TypeSpec.VOID, expSet.iterator(), (r, loop) -> {
AddressRangeChunker it = new AddressRangeChunker(r, BLOCK_SIZE);
AsyncUtils.each(TypeSpec.VOID, it.iterator(), (vRng, inner) -> {
// The listener in the recorder will copy to the Trace.
monitor.incrementProgress(1);
AddressRange tRng = recorder.getMemoryMapper().traceToTarget(vRng);
recorder.getProcessMemory()
.readMemory(tRng.getMinAddress(), (int) tRng.getLength())
.thenApply(b -> !monitor.isCancelled())
.handle(inner::repeatWhile);
}).exceptionally(e -> {
Msg.error(this, "Error reading range " + r + ": " + e);
// NOTE: Above may double log, since recorder listens for errors, too
return null; // Continue looping on errors
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
});
}
@Override
public void offerProcessRegion(TargetMemoryRegion region) {
TargetMemory mem = region.getMemory();
recorder.getProcessMemory().addRegion(region, mem);
//recorder.objectManager.addMemory(mem);
String path = PathUtils.toString(region.getPath());
long snap = recorder.getSnap();
tx.execute("Memory region " + path + " added", () -> {
try {
TraceMemoryRegion traceRegion =
memoryManager.getLiveRegionByPath(snap, path);
if (traceRegion != null) {
Msg.warn(this, "Region " + path + " already recorded");
return;
}
traceRegion = memoryManager.addRegion(path, Range.atLeast(snap),
recorder.getMemoryMapper().targetToTrace(region.getRange()),
getTraceFlags(region));
traceRegion.setName(region.getName());
}
catch (TraceOverlappedRegionException e) {
Msg.error(this, "Failed to create region due to overlap: " + e);
}
catch (DuplicateNameException e) {
Msg.error(this, "Failed to create region due to duplicate: " + e);
}
});
}
@Override
public void removeProcessRegion(TargetMemoryRegion region) {
// Already removed from processMemory. That's how we knew to go here.
String path = PathUtils.toString(region.getPath());
long snap = recorder.getSnap();
tx.execute("Memory region " + path + " removed", () -> {
try {
TraceMemoryRegion traceRegion = memoryManager.getLiveRegionByPath(snap, path);
if (traceRegion == null) {
Msg.warn(this, "Could not find region " + path + " in trace to remove");
return;
}
traceRegion.setDestructionSnap(snap - 1);
}
catch (DuplicateNameException | TraceOverlappedRegionException e) {
// Region is shrinking in time
Msg.error(this, "Failed to record region removal: " + e);
}
});
}
@Override
public TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region) {
String path = PathUtils.toString(region.getPath());
return memoryManager.getLiveRegionByPath(recorder.getSnap(), path);
}
public Collection<TraceMemoryFlag> getTraceFlags(TargetMemoryRegion region) {
Collection<TraceMemoryFlag> flags = new HashSet<>();
if (region.isReadable()) {
flags.add(TraceMemoryFlag.READ);
}
if (region.isWritable()) {
flags.add(TraceMemoryFlag.WRITE);
}
if (region.isExecutable()) {
flags.add(TraceMemoryFlag.EXECUTE);
}
// TODO: Volatile? Can any debugger report that?
return flags;
}
}

View file

@ -0,0 +1,153 @@
/* ###
* 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 java.util.concurrent.Executors;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedModuleRecorder;
import ghidra.dbg.target.TargetModule;
import ghidra.dbg.target.TargetSection;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
public class DefaultModuleRecorder implements ManagedModuleRecorder {
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceModuleManager moduleManager;
final PermanentTransactionExecutor tx;
public DefaultModuleRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
this.moduleManager = trace.getModuleManager();
this.tx = new PermanentTransactionExecutor(trace,
"ModuleRecorder:" + recorder.target.getJoinedPath("."),
f -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), f), 500);
}
protected TraceModule doRecordProcessModule(long snap, TargetModule module) {
String path = module.getJoinedPath(".");
if (recorder.getMemoryMapper() == null) {
Msg.error(this, "Got module before memory mapper: " + path);
return null;
}
// Short-circuit the DuplicateNameException for efficiency?
TraceModule exists = moduleManager.getLoadedModuleByPath(snap, path);
if (exists != null) {
return exists;
}
try {
AddressRange targetRange = module.getRange();
if (targetRange == null) {
Msg.error(this, "Range not found for " + module);
return null;
}
AddressRange traceRange = recorder.getMemoryMapper().targetToTrace(targetRange);
return moduleManager.addLoadedModule(path, module.getModuleName(), traceRange, snap);
}
catch (DuplicateNameException e) {
// This resolves the race condition, since DB access is synchronized
return moduleManager.getLoadedModuleByPath(snap, path);
}
}
@Override
public void offerProcessModule(TargetModule module) {
long snap = recorder.getSnap();
String path = module.getJoinedPath(".");
tx.execute("Module " + path + " loaded", () -> {
doRecordProcessModule(snap, module);
});
}
protected TraceSection doRecordProcessModuleSection(long snap, TargetSection section) {
String path = section.getJoinedPath(".");
if (recorder.getMemoryMapper() == null) {
Msg.error(this, "Got module section before memory mapper: " + path);
return null;
}
TraceModule traceModule = doRecordProcessModule(snap, section.getModule());
if (traceModule == null) {
return null; // Failure should already be logged
}
try {
AddressRange targetRange = section.getRange();
AddressRange traceRange = recorder.getMemoryMapper().targetToTrace(targetRange);
return traceModule.addSection(path, section.getIndex(), traceRange);
}
catch (DuplicateNameException e) {
Msg.warn(this, path + " already recorded");
return moduleManager.getLoadedSectionByPath(snap, path);
}
}
@Override
public void offerProcessModuleSection(TargetSection section) {
long snap = recorder.getSnap();
String path = section.getJoinedPath(".");
tx.execute("Section " + path + " added", () -> {
doRecordProcessModuleSection(snap, section);
});
}
protected void doRemoveProcessModule(long snap, TargetModule module) {
String path = PathUtils.toString(module.getPath());
//TraceThread eventThread = recorder.getSnapshot().getEventThread();
TraceModule traceModule = moduleManager.getLoadedModuleByPath(snap, path);
if (traceModule == null) {
Msg.warn(this, "unloaded " + path + " is not in the trace");
return;
}
try {
if (traceModule.getLoadedSnap() == snap) {
Msg.warn(this, "Observed module unload in the same snap as its load");
//recorder.createSnapshot("WARN: Module removed", eventThread, tid);
}
traceModule.setUnloadedSnap(snap);
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not record process module removed: " + e);
}
}
@Override
public void removeProcessModule(TargetModule module) {
long snap = recorder.getSnap();
String path = PathUtils.toString(module.getPath());
tx.execute("Module " + path + " unloaded", () -> {
doRemoveProcessModule(snap, module);
});
}
@Override
public TraceModule getTraceModule(TargetModule module) {
String path = PathUtils.toString(module.getPath());
return moduleManager.getLoadedModuleByPath(recorder.getSnap(), path);
}
@Override
public TraceSection getTraceSection(TargetSection section) {
String path = PathUtils.toString(section.getPath());
return moduleManager.getLoadedSectionByPath(recorder.getSnap(), path);
}
}

View file

@ -0,0 +1,93 @@
/* ###
* 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 java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace;
import ghidra.util.TriConsumer;
public class DefaultProcessRecorder implements ManagedProcessRecorder {
private final AbstractRecorderMemory processMemory;
protected final TriConsumer<Boolean, Boolean, Void> listenerProcMemAccChanged =
this::processMemoryAccessibilityChanged;
private DefaultBreakpointRecorder breakpointRecorder;
private DefaultTraceRecorder recorder;
public DefaultProcessRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.processMemory = new RecorderSimpleMemory();
//this.processMemory = new RecorderComposedMemory(this.getProcessMemory());
//getProcessMemory().getMemAccListeners().add(listenerProcMemAccChanged);
this.breakpointRecorder = new DefaultBreakpointRecorder(recorder);
}
protected void processMemoryAccessibilityChanged(boolean old,
boolean acc, Void __) {
recorder.getListeners().fire.processMemoryAccessibilityChanged(recorder);
}
public CompletableFuture<byte[]> readProcessMemory(Address start, int length) {
Address tStart = recorder.getMemoryMapper().traceToTarget(start);
return getProcessMemory().readMemory(tStart, length);
}
public CompletableFuture<Void> writeProcessMemory(Address start, byte[] data) {
Address tStart = recorder.getMemoryMapper().traceToTarget(start);
return getProcessMemory().writeMemory(tStart, data);
}
public AddressSetView getAccessibleProcessMemory() {
// TODO: Efficiently distinguish which memory is process vs. thread
///TODO Is this correct?
return getProcessMemory().getAccessibleMemory(mem -> true, recorder.getMemoryMapper());
}
@Override
public AbstractRecorderMemory getProcessMemory() {
return processMemory;
}
@Override
public ManagedBreakpointRecorder getBreakpointRecorder() {
return breakpointRecorder;
}
@Override
public Trace getTrace() {
return recorder.trace;
}
@Override
public long getSnap() {
return recorder.getSnap();
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return recorder.getMemoryMapper();
}
}

View file

@ -0,0 +1,162 @@
/* ###
* 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 java.util.*;
import java.util.concurrent.Executors;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedStackRecorder;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
import ghidra.trace.model.stack.*;
import ghidra.trace.model.thread.TraceThread;
public class DefaultStackRecorder implements ManagedStackRecorder {
protected static int getFrameLevel(TargetStackFrame frame) {
// TODO: A fair assumption? frames are elements with numeric base-10 indices
return Integer.decode(frame.getIndex());
}
private NavigableMap<Integer, TargetStackFrame> stack =
Collections.synchronizedNavigableMap(new TreeMap<>());
private final TraceThread thread;
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceStackManager stackManager;
final PermanentTransactionExecutor tx;
public DefaultStackRecorder(TraceThread thread, DefaultTraceRecorder recorder) {
this.thread = thread;
this.recorder = recorder;
this.trace = recorder.getTrace();
this.stackManager = trace.getStackManager();
this.tx = new PermanentTransactionExecutor(trace,
"ModuleRecorder:" + recorder.target.getJoinedPath("."),
Executors::newSingleThreadExecutor, 100);
}
@Override
public void offerStackFrame(TargetStackFrame frame) {
recordFrame(frame);
}
@Override
public void recordStack() {
long snap = recorder.getSnap();
tx.execute("Stack changed", () -> {
TraceStack traceStack = stackManager.getStack(thread, snap, true);
traceStack.setDepth(stackDepth(), false);
for (Map.Entry<Integer, TargetStackFrame> ent : stack.entrySet()) {
Address tracePc = recorder.getMemoryMapper()
.targetToTrace(ent.getValue().getProgramCounter());
doRecordFrame(traceStack, ent.getKey(), tracePc);
}
});
}
public void popStack() {
long snap = recorder.getSnap();
tx.execute("Stack popped", () -> {
TraceStack traceStack = stackManager.getStack(thread, snap, true);
traceStack.setDepth(stackDepth(), false);
});
}
public void doRecordFrame(TraceStack traceStack, int frameLevel, Address pc) {
TraceStackFrame traceFrame = traceStack.getFrame(frameLevel, true);
traceFrame.setProgramCounter(pc);
}
public void recordFrame(TargetStackFrame frame) {
tx.execute("Stack frame added", () -> {
stack.put(getFrameLevel(frame), frame);
DebuggerMemoryMapper memoryMapper = recorder.getMemoryMapper();
if (memoryMapper == null) {
return;
}
Address pc = frame.getProgramCounter();
Address tracePc = pc == null ? null : memoryMapper.targetToTrace(pc);
TraceStack traceStack = stackManager.getStack(thread, recorder.getSnap(), true);
doRecordFrame(traceStack, getFrameLevel(frame), tracePc);
});
}
protected int stackDepth() {
return stack.isEmpty() ? 0 : stack.lastKey() + 1;
}
@Override
public int getSuccessorFrameLevel(TargetObject successor) {
NavigableSet<Integer> observedPathLengths = new TreeSet<>();
for (TargetStackFrame frame : stack.values()) {
observedPathLengths.add(frame.getPath().size());
}
List<String> path = successor.getPath();
for (int l : observedPathLengths.descendingSet()) {
if (l > path.size()) {
continue;
}
List<String> sub = path.subList(0, l);
if (!PathUtils.isIndex(sub)) {
continue;
}
int index = Integer.decode(PathUtils.getIndex(sub));
TargetStackFrame frame = stack.get(index);
if (frame == null || !Objects.equals(sub, frame.getPath())) {
continue;
}
return index;
}
return 0;
}
protected boolean checkStackFrameRemoved(TargetObject invalid) {
if (stack.values().remove(invalid)) {
popStack();
return true;
}
return false;
}
public Address pcFromStack() {
TargetStackFrame frame = stack.get(0);
if (frame == null) {
return null;
}
return frame.getProgramCounter();
}
@Override
public TraceStackFrame getTraceStackFrame(TraceThread thread, int level) {
TraceStack latest = stackManager.getLatestStack(thread, recorder.getSnap());
if (latest == null) {
return null;
}
return latest.getFrame(level, false);
}
@Override
public TargetStackFrame getTargetStackFrame(int frameLevel) {
return stack.get(frameLevel);
}
}

View file

@ -0,0 +1,137 @@
/* ###
* 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 java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.symbol.SourceType;
import ghidra.trace.model.Trace;
import ghidra.trace.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class DefaultSymbolRecorder {
private DefaultTraceRecorder recorder;
private Trace trace;
public DefaultSymbolRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
}
public CompletableFuture<Void> captureSymbols(TargetSymbolNamespace namespace,
TaskMonitor monitor) {
String path = PathUtils.toString(namespace.getPath());
monitor.setMessage("Capturing symbols for " + path);
return namespace.getSymbols().thenAccept(symbols -> {
try (RecorderPermanentTransaction tid = RecorderPermanentTransaction.start(trace,
"Capture types and symbols for " + path)) {
TraceNamespaceSymbol ns = createNamespaceIfAbsent(path);
monitor.setMessage("Capturing symbols for " + path);
monitor.initialize(symbols.size());
TraceEquateManager equateManager = trace.getEquateManager();
for (TargetSymbol sym : symbols) {
if (monitor.isCancelled()) {
return;
}
monitor.incrementProgress(1);
String symName = sym.getIndex();
if (sym.isConstant()) {
// TODO: Equate namespaces?
TraceEquate equate = equateManager.getByName(symName);
long symVal = sym.getValue().getOffset();
if (equate != null && equate.getValue() == symVal) {
continue;
}
try {
equateManager.create(symName, symVal);
}
catch (DuplicateNameException | IllegalArgumentException e) {
Msg.error(this, "Could not create equate: " + symName, e);
}
continue;
}
Address addr = recorder.getMemoryMapper().targetToTrace(sym.getValue());
try {
trace.getSymbolManager()
.labels()
.create(recorder.getSnap(), null, addr, symName, ns,
SourceType.IMPORTED);
}
catch (InvalidInputException e) {
Msg.error(this, "Could not add module symbol " + sym + ": " + e);
}
/**
* TODO: Lay down data type, if present
*
* TODO: Interpret "address" type correctly. A symbol with this type is itself
* the pointer. In other words, it is not specifying the type to lay down in
* memory.
*/
}
}
});
}
public CompletableFuture<Void> captureSymbols(TargetModule targetModule,
TaskMonitor monitor) {
CompletableFuture<? extends Map<String, ? extends TargetSymbolNamespace>> future =
targetModule.fetchChildrenSupporting(TargetSymbolNamespace.class);
// NOTE: I should expect exactly one namespace...
return future.thenCompose(namespaces -> {
AsyncFence fence = new AsyncFence();
for (TargetSymbolNamespace ns : namespaces.values()) {
fence.include(captureSymbols(ns, monitor));
}
return fence.ready();
});
}
private TraceNamespaceSymbol createNamespaceIfAbsent(String path) {
TraceSymbolManager symbolManager = trace.getSymbolManager();
try {
return symbolManager.namespaces()
.add(path, symbolManager.getGlobalNamespace(), SourceType.IMPORTED);
}
catch (DuplicateNameException e) {
Msg.info(this, "Namespace for module " + path +
" already exists or another exists with a conflicting name. Using the existing one: " +
e);
TraceNamespaceSymbol ns = symbolManager.namespaces().getGlobalNamed(path);
if (ns != null) {
return ns;
}
Msg.error(this, "Existing namespace for " + path +
" is not a plain namespace. Using global namespace.");
return symbolManager.getGlobalNamespace();
}
catch (InvalidInputException | IllegalArgumentException e) {
Msg.error(this,
"Could not create namespace for new module: " + path + ". Using global namespace.",
e);
return symbolManager.getGlobalNamespace();
}
}
}

View file

@ -0,0 +1,547 @@
/* ###
* 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 java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.data.Pointer;
import ghidra.program.model.lang.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.TimedMsg;
import ghidra.util.exception.DuplicateNameException;
public class DefaultThreadRecorder implements ManagedThreadRecorder {
//private static final boolean LOG_STACK_TRACE = false;
private final TargetThread targetThread;
private final TraceThread traceThread;
protected final AbstractRecorderMemory threadMemory;
//private AbstractRecorderRegisterSet threadRegisters;
protected TargetBreakpointSpecContainer threadBreakpointContainer;
protected Map<Integer, TargetRegisterBank> regs = new HashMap<>();
protected Collection<TargetRegister> extraRegs;
protected TargetExecutionState state = TargetExecutionState.ALIVE;
private final DefaultTraceRecorder recorder;
private final Trace trace;
private final TraceObjectManager objectManager;
private final TraceMemoryManager memoryManager;
private DebuggerRegisterMapper regMapper;
private final AbstractDebuggerTargetTraceMapper mapper;
private final DefaultStackRecorder stackRecorder;
private final DefaultBreakpointRecorder breakpointRecorder;
final PermanentTransactionExecutor tx;
protected static int getFrameLevel(TargetStackFrame frame) {
// TODO: A fair assumption? frames are elements with numeric base-10 indices
return Integer.decode(frame.getIndex());
}
public DefaultThreadRecorder(DefaultTraceRecorder recorder,
AbstractDebuggerTargetTraceMapper mapper, TargetThread targetThread,
TraceThread traceThread) {
this.recorder = recorder;
this.mapper = mapper;
this.trace = recorder.getTrace();
this.objectManager = recorder.objectManager;
this.targetThread = targetThread;
this.traceThread = traceThread;
this.memoryManager = trace.getMemoryManager();
this.tx = new PermanentTransactionExecutor(trace,
"ThreadRecorder:" + recorder.target.getJoinedPath("."),
f -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), f), 100);
//this.threadMemory = new RecorderComposedMemory(recorder.getProcessMemory());
this.threadMemory = recorder.getProcessMemory();
//this.threadRegisters = recorder.getThreadRegisters();
if (targetThread instanceof TargetExecutionStateful) {
TargetExecutionStateful stateful = (TargetExecutionStateful) targetThread;
state = stateful.getExecutionState();
}
this.stackRecorder = new DefaultStackRecorder(traceThread, recorder);
this.breakpointRecorder = new DefaultBreakpointRecorder(recorder);
}
protected synchronized CompletableFuture<Void> initRegMapper(
TargetRegisterContainer registers) {
/**
* TODO: At the moment, this assumes the recorded thread has one register container, or at
* least that all register banks in the thread use the same register container
* (descriptors). If this becomes a problem, then we'll need to keep a separate register
* mapper per register container. This would likely also require some notion of multiple
* languages in the mapper (seems an unlikely design choice). NOTE: In cases where a single
* process may (at least appear to) execute multiple languages, the model should strive to
* present the registers of the physical machine, as they are most likely uniform across the
* process, not those being emulated in the moment. In cases where an abstract machine is
* involved, it is probably more fitting to present separate containers (likely provided by
* separate models) than to present both the physical and abstract machine in the same
* target.
*
* <p>
* TODO: Should I formalize that only one register container is present in a recorded
* thread? This seems counter to the model's flexibility. Traces allow polyglot disassembly,
* but not polyglot register spaces.
*/
return objectManager.getRegMappers().get(registers).thenAccept(rm -> {
synchronized (this) {
regMapper = rm;
Language language = trace.getBaseLanguage();
extraRegs = new LinkedHashSet<>();
for (String rn : mapper.getExtraRegNames()) {
Register traceReg = language.getRegister(rn);
if (traceReg == null) {
Msg.error(this,
"Mapper's extra register '" + rn + "' is not in the language!");
continue;
}
TargetRegister targetReg = regMapper.traceToTarget(traceReg);
if (targetReg == null) {
Msg.error(this,
"Mapper's extra register '" + traceReg + "' is not mappable!");
continue;
}
extraRegs.add(targetReg);
}
}
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize register mapper", ex);
return null;
});
}
@Override
public CompletableFuture<Void> doFetchAndInitRegMapper(TargetRegisterBank bank) {
TargetRegisterContainer descs = bank.getDescriptions();
if (descs == null) {
Msg.error(this, "Cannot create mapper, yet: Descriptions is null.");
return AsyncUtils.NIL;
}
return initRegMapper(descs).thenAccept(__ -> {
recorder.getListeners().fire.registerBankMapped(recorder);
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize register mapper", ex);
return null;
});
}
public CompletableFuture<Void> captureThreadRegisters(TraceThread thread, int frameLevel,
Set<Register> registers) {
if (regMapper == null) {
throw new IllegalStateException("Have not found register descriptions for " + thread);
}
if (!regMapper.getRegistersOnTarget().containsAll(registers)) {
throw new IllegalArgumentException(
"All given registers must be recognized by the target");
}
if (registers.isEmpty()) {
return AsyncUtils.NIL;
}
List<TargetRegister> tRegs =
registers.stream().map(regMapper::traceToTarget).collect(Collectors.toList());
TargetRegisterBank bank = getTargetRegisterBank(thread, frameLevel);
if (bank == null) {
throw new IllegalArgumentException(
"Given thread and frame level does not have a live register bank");
}
// NOTE: Cache update, if applicable, will cause recorder to write values to trace
System.err.println("captureThreadRegisters " + thread + ":" + bank);
return bank.readRegisters(tRegs).thenApply(__ -> null);
}
public TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel) {
return regs.get(frameLevel);
}
@Override
public void regMapperAmended(DebuggerRegisterMapper rm, TargetRegister reg, boolean removed) {
String name = reg.getIndex();
synchronized (this) {
if (regMapper != rm) {
return;
}
if (mapper.getExtraRegNames().contains(name)) {
if (removed) {
extraRegs.remove(reg);
}
else {
extraRegs.add(reg);
}
}
}
}
@Override
public void offerRegisters(TargetRegisterBank bank) {
if (regMapper == null) {
doFetchAndInitRegMapper(bank);
}
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
TargetRegisterBank old = regs.put(frameLevel, bank);
if (null != old) {
Msg.warn(this, "Unexpected register bank replacement");
}
}
@Override
public void removeRegisters(TargetRegisterBank bank) {
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
TargetRegisterBank old = regs.remove(frameLevel);
if (bank != old) {
Msg.warn(this, "Unexpected register bank upon removal");
}
}
@Override
public void offerThreadRegion(TargetMemoryRegion region) {
TargetMemory mem = region.getMemory();
threadMemory.addRegion(region, mem);
}
@Override
public void stateChanged(final TargetExecutionState newState) {
state = newState;
}
public void threadDestroyed() {
String path = getTargetThread().getJoinedPath(".");
long snap = recorder.getSnap();
tx.execute("Thread " + path + " destroyed", () -> {
// TODO: Should it be key - 1
// Perhaps, since the thread should not exist
// But it could imply earlier destruction than actually observed
try {
getTraceThread().setDestructionSnap(snap);
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be shrinking
}
});
}
@Override
public void recordRegisterValues(TargetRegisterBank bank, Map<String, byte[]> updates) {
synchronized (recorder) {
if (regMapper == null) {
doFetchAndInitRegMapper(bank);
}
}
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
long snap = recorder.getSnap();
String path = bank.getJoinedPath(".");
TimedMsg.info(this, "Reg values changed: " + updates.keySet());
tx.execute("Registers " + path + " changed", () -> {
TraceCodeManager codeManager = trace.getCodeManager();
TraceCodeRegisterSpace codeRegisterSpace =
codeManager.getCodeRegisterSpace(traceThread, false);
TraceDefinedDataRegisterView definedData =
codeRegisterSpace == null ? null : codeRegisterSpace.definedData();
TraceMemoryRegisterSpace regSpace =
memoryManager.getMemoryRegisterSpace(traceThread, frameLevel, true);
for (Entry<String, byte[]> ent : updates.entrySet()) {
RegisterValue rv = regMapper.targetToTrace(ent.getKey(), ent.getValue());
if (rv == null) {
continue; // mapper does not know this register....
}
regSpace.setValue(snap, rv);
Register register = rv.getRegister();
if (definedData != null) {
TraceData td = definedData.getForRegister(snap, register);
if (td != null && td.getDataType() instanceof Pointer) {
Address addr = registerValueToTargetAddress(rv, ent.getValue());
readAlignedConditionally(ent.getKey(), addr); // NB: Reports errors
}
}
}
});
}
@Override
public void recordRegisterValue(TargetRegister targetRegister, byte[] value) {
TargetRegisterBank bank = (TargetRegisterBank) targetRegister.getParent();
synchronized (recorder) {
if (regMapper == null) {
doFetchAndInitRegMapper(bank);
}
}
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
long snap = recorder.getSnap();
String path = targetRegister.getJoinedPath(".");
//TimedMsg.info(this, "Register value changed: " + targetRegister);
tx.execute("Register " + path + " changed", () -> {
TraceCodeManager codeManager = trace.getCodeManager();
TraceCodeRegisterSpace codeRegisterSpace =
codeManager.getCodeRegisterSpace(traceThread, false);
TraceDefinedDataRegisterView definedData =
codeRegisterSpace == null ? null : codeRegisterSpace.definedData();
TraceMemoryRegisterSpace regSpace =
memoryManager.getMemoryRegisterSpace(traceThread, frameLevel, true);
String key = targetRegister.getName();
if (PathUtils.isIndex(key)) {
key = key.substring(1, key.length() - 1);
}
RegisterValue rv = regMapper.targetToTrace(key, value);
if (rv == null) {
return; // mapper does not know this register....
}
regSpace.setValue(snap, rv);
Register register = rv.getRegister();
if (definedData != null) {
TraceData td = definedData.getForRegister(snap, register);
if (td != null && td.getDataType() instanceof Pointer) {
Address addr = registerValueToTargetAddress(rv, value);
readAlignedConditionally(key, addr); // NB: Reports errors
}
}
});
}
public CompletableFuture<Void> writeThreadRegisters(int frameLevel,
Map<Register, RegisterValue> values) {
if (!regMapper.getRegistersOnTarget().containsAll(values.keySet())) {
throw new IllegalArgumentException(
"All given registers must be recognized by the target");
}
if (values.isEmpty()) {
return AsyncUtils.NIL;
}
Map<String, byte[]> tVals = values.entrySet().stream().map(ent -> {
if (ent.getKey() != ent.getValue().getRegister()) {
throw new IllegalArgumentException("register name mismatch in value");
}
return regMapper.traceToTarget(ent.getValue());
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
TargetRegisterBank bank = getTargetRegisterBank(traceThread, frameLevel);
if (bank == null) {
throw new IllegalArgumentException(
"Given thread and frame level does not have a live register bank");
}
// NOTE: Model + recorder will cause applicable trace updates
return bank.writeRegistersNamed(tVals).thenApply(__ -> null);
}
Address registerValueToTargetAddress(RegisterValue rv, byte[] value) {
Address traceAddress =
trace.getBaseLanguage().getDefaultSpace().getAddress(rv.getUnsignedValue().longValue());
return objectManager.getMemoryMapper().traceToTarget(traceAddress);
}
protected CompletableFuture<?> readAlignedConditionally(String name, Address targetAddress) {
if (targetAddress == null) {
return AsyncUtils.NIL;
}
Address traceAddress = objectManager.getMemoryMapper().targetToTrace(targetAddress);
if (traceAddress == null) {
return AsyncUtils.NIL;
}
if (!checkReadCondition(traceAddress)) {
return AsyncUtils.NIL;
}
AddressRange targetRange = threadMemory.alignAndLimitToFloor(targetAddress, 1);
if (targetRange == null) {
return AsyncUtils.NIL;
}
TimedMsg.info(this,
" Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")");
// NOTE: Recorder takes data via memoryUpdated callback
// TODO: In that callback, sort out process memory from thread memory?
return threadMemory.readMemory(targetRange.getMinAddress(), (int) targetRange.getLength())
.exceptionally(ex -> {
Msg.error(this, "Could not read memory at " + name, ex);
return null;
});
}
protected boolean checkReadCondition(Address traceAddress) {
/**
* TODO: This heuristic doesn't really belong here, but I have to implement it here so that
* it doesn't "override" the listing's implementation. Once watches are implemented, we
* should be able to drop this garbage.
*/
TraceMemoryRegion region =
memoryManager.getRegionContaining(recorder.getSnap(), traceAddress);
if (region == null) {
return false;
}
if (region.isWrite()) {
return true;
}
Entry<TraceAddressSnapRange, TraceMemoryState> ent =
memoryManager.getMostRecentStateEntry(recorder.getSnap(), traceAddress);
if (ent == null) {
return true;
}
if (ent.getValue() == TraceMemoryState.KNOWN) {
return false;
}
return true;
}
@Override
public TargetThread getTargetThread() {
return targetThread;
}
@Override
public TraceThread getTraceThread() {
return traceThread;
}
@Override
public long getSnap() {
return recorder.getSnap();
}
@Override
public Trace getTrace() {
return recorder.getTrace();
}
@Override
public DebuggerMemoryMapper getMemoryMapper() {
return recorder.objectManager.getMemoryMapper();
}
@Override
public ManagedStackRecorder getStackRecorder() {
return stackRecorder;
}
@Override
public ManagedBreakpointRecorder getBreakpointRecorder() {
return breakpointRecorder;
}
/**
* Inform the recorder the given object is no longer valid
*
* @param invalid the invalidated object
* @return true if this recorder should be invalidated, too
*/
// UNUSED?
@Override
public synchronized boolean objectRemoved(TargetObject invalid) {
if (checkThreadRemoved(invalid)) {
return true;
}
if (stackRecorder.checkStackFrameRemoved(invalid)) {
return false;
}
if (threadMemory.removeRegion(invalid)) {
return false;
}
Msg.trace(this, "Ignored removed object: " + invalid);
return false;
}
protected boolean checkThreadRemoved(TargetObject invalid) {
if (getTargetThread() == invalid) {
threadDestroyed();
return true;
}
return false;
}
public DebuggerRegisterMapper getRegisterMapper() {
return regMapper;
}
/*
public CompletableFuture<Void> updateRegsMem(TargetMemoryRegion limit) {
TargetRegisterBank bank;
TargetRegister pc;
TargetRegister sp;
Set<TargetRegister> toRead = new LinkedHashSet<>();
synchronized (recorder) {
if (regMapper == null) {
return AsyncUtils.NIL;
}
bank = regs.get(0);
pc = pcReg;
sp = spReg;
toRead.addAll(extraRegs);
toRead.add(sp);
toRead.add(pc);
}
if (bank == null || pc == null || sp == null) {
return AsyncUtils.NIL;
}
System.err.println("URM:" + getTargetThread());
TimedMsg.info(this, "Reading " + toRead + " of " + getTargetThread());
return bank.readRegisters(toRead).thenCompose(vals -> {
synchronized (recorder) {
if (memoryManager == null) {
return AsyncUtils.NIL;
}
}
if (threadMemory == null) {
return AsyncUtils.NIL;
}
AsyncFence fence = new AsyncFence();
Address pcTargetAddr = stackRecorder.pcFromStack();
if (pcTargetAddr == null) {
pcTargetAddr = registerValueToTargetAddress(pcReg, vals.get(pcReg.getIndex()));
}
fence.include(readAlignedConditionally("PC", pcTargetAddr, limit));
Address spTargetAddr = registerValueToTargetAddress(spReg, vals.get(spReg.getIndex()));
fence.include(readAlignedConditionally("SP", spTargetAddr, limit));
return fence.ready();
}).exceptionally(ex -> {
if (LOG_STACK_TRACE) {
Msg.error(this, "Could not read registers", ex);
}
else {
Msg.error(this, "Could not read registers");
}
return null;
});
}
*/
}

View file

@ -0,0 +1,65 @@
/* ###
* 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 ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
public class DefaultTimeRecorder {
private DefaultTraceRecorder recorder;
private Trace trace;
private TraceSnapshot snapshot = null;
public DefaultTimeRecorder(DefaultTraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
}
public TraceSnapshot getSnapshot() {
return snapshot;
}
public long getSnap() {
return snapshot.getKey();
}
protected synchronized void doAdvanceSnap(String description, TraceThread eventThread) {
snapshot = trace.getTimeManager().createSnapshot(description);
snapshot.setEventThread(eventThread);
}
public TraceSnapshot forceSnapshot() {
createSnapshot("User-forced snapshot", null, null);
return snapshot;
}
public void createSnapshot(String description, TraceThread eventThread,
RecorderPermanentTransaction tid) {
if (tid != null) {
doAdvanceSnap(description, eventThread);
recorder.getListeners().fire.snapAdvanced(recorder, getSnap());
return;
}
// NB. The also serves as the snap counter, so it must be on the service thread
try (RecorderPermanentTransaction tid2 =
RecorderPermanentTransaction.start(trace, description)) {
doAdvanceSnap(description, eventThread);
}
recorder.getListeners().fire.snapAdvanced(recorder, getSnap());
}
}

View file

@ -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 ghidra.app.plugin.core.debug.service.model;
import java.util.concurrent.*;
import java.util.function.Function;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import ghidra.app.plugin.core.debug.utils.DefaultTransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer;
import ghidra.app.plugin.core.debug.utils.TransactionCoalescer.CoalescedTx;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.Msg;
public class PermanentTransactionExecutor {
private final TransactionCoalescer txc;
private final Executor executor;
public PermanentTransactionExecutor(UndoableDomainObject obj, String name,
Function<ThreadFactory, Executor> executorFactory, int delayMs) {
txc = new DefaultTransactionCoalescer<>(obj, RecorderPermanentTransaction::start, delayMs);
this.executor = executorFactory.apply(
new BasicThreadFactory.Builder().namingPattern(name + "-thread-%d").build());
}
public void execute(String description, Runnable runnable) {
CompletableFuture.runAsync(() -> {
try (CoalescedTx tx = txc.start(description)) {
runnable.run();
}
}, executor).exceptionally(e -> {
Msg.error(this, "Trouble recording " + description, e);
return null;
});
}
}

View file

@ -0,0 +1,107 @@
/* ###
* 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 java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
import ghidra.async.AsyncFence;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
public class RecorderBreakpointLocationResolver {
// TODO: I'm not sure this class really offers anything anymore
private DefaultTraceRecorder recorder;
private final TargetBreakpointLocation bpt;
private final TargetBreakpointSpec spec;
private boolean affectsProcess = false;
private final Set<TraceThread> threadsAffected = new LinkedHashSet<>();
public RecorderBreakpointLocationResolver(DefaultTraceRecorder recorder,
TargetBreakpointLocation bpt) {
this.recorder = recorder;
this.bpt = bpt;
this.spec = bpt.getSpecification();
}
// TODO: This is a stopgap, since Location.getAffects is removed
// Do we really need to worry about per-thread breakpoints?
static Collection<TargetObject> getAffects(TargetBreakpointLocation bpt) {
TargetObject findProc = bpt;
while (!(findProc instanceof TargetProcess)) {
findProc = findProc.getParent();
}
return List.of(findProc);
}
private CompletableFuture<Void> resolve(TargetObject obj) {
AsyncFence fence = new AsyncFence();
if (obj.equals(recorder.getTarget())) {
affectsProcess = true;
}
else {
fence.include(resolveThread(obj));
}
return fence.ready();
}
// TODO: If affects is empty/null, also try to default to the containing process
private CompletableFuture<Void> resolveThread(TargetObject ref) {
return DebugModelConventions.findThread(ref).thenAccept(thread -> {
if (thread == null) {
Msg.error(this,
"Could not find process or thread from breakpoint-affected object: " + ref);
return;
}
if (!ref.equals(thread)) {
Msg.warn(this, "Effective breakpoint should apply to process or threads. Got " +
ref + ". Resolved to " + thread);
return;
}
if (!PathUtils.isAncestor(recorder.getTarget().getPath(), thread.getPath())) {
/**
* Perfectly normal if the breakpoint container is outside the process container.
* Don't record such in this trace, though.
*/
return;
}
ManagedThreadRecorder rec = recorder.getThreadRecorder(thread); //listenerForRecord.getOrCreateThreadRecorder(thread);
synchronized (threadsAffected) {
threadsAffected.add(rec.getTraceThread());
}
}).exceptionally(ex -> {
Msg.error(this, "Error resolving thread from breakpoint-affected object: " + ref);
return null;
});
}
public void updateBreakpoint(TargetObject containerParent, TargetBreakpointLocation loc) {
resolve(containerParent).thenAccept(__ -> {
if (affectsProcess || !threadsAffected.isEmpty()) {
recorder.breakpointRecorder.recordBreakpoint(loc, threadsAffected);
}
}).exceptionally(ex -> {
Msg.error(this, "Could record target breakpoint: " + loc, ex);
return null;
});
}
}

View file

@ -0,0 +1,236 @@
/* ###
* 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 java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.AbstractRecorderMemory;
import ghidra.async.AsyncLazyMap;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
import ghidra.dbg.target.*;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
import ghidra.util.TriConsumer;
import ghidra.util.datastruct.ListenerSet;
public class RecorderComposedMemory implements AbstractRecorderMemory {
private static final int BLOCK_SIZE = 4096;
private static final long BLOCK_MASK = -1L << 12;
protected final RecorderComposedMemory chain;
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
protected final Map<TargetMemoryRegion, TargetMemory> byRegion = new HashMap<>();
protected final AsyncLazyMap<TargetMemory, AllRequiredAccess> accessibilityByMemory =
new AsyncLazyMap<>(new HashMap<>(), this::fetchMemAccessibility) {
public AllRequiredAccess remove(TargetMemory key) {
AllRequiredAccess acc = super.remove(key);
if (acc != null) {
acc.removeChangeListener(getMemAccListeners().fire);
}
return acc;
}
};
protected CompletableFuture<AllRequiredAccess> fetchMemAccessibility(TargetMemory mem) {
return DebugModelConventions.trackAccessibility(mem).thenApply(acc -> {
acc.addChangeListener(getMemAccListeners().fire);
return acc;
});
}
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
@Override
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper) {
synchronized (accessibilityByMemory) {
// TODO: Might accomplish by using listeners and tracking the accessible set
AddressSet accessible = new AddressSet();
for (Entry<TargetMemoryRegion, TargetMemory> ent : byRegion.entrySet()) {
TargetMemory mem = ent.getValue();
if (!pred.test(mem)) {
continue;
}
AllRequiredAccess acc = accessibilityByMemory.getCompletedMap().get(mem);
if (acc == null || !acc.getAllAccessibility()) {
continue;
}
accessible.add(memMapper.targetToTrace(ent.getKey().getRange()));
}
return accessible;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private final ListenerSet<TriConsumer<Boolean, Boolean, Void>> memAccListeners =
new ListenerSet(TriConsumer.class);
public RecorderComposedMemory(AbstractRecorderMemory memory) {
this.chain = (RecorderComposedMemory) memory;
}
protected TargetMemory getMemory(Address address, int length) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
throw new IllegalArgumentException(
"address " + address + " is not in any known region");
}
Address max;
try {
max = address.addNoWrap(length - 1);
}
catch (AddressOverflowException e) {
throw new IllegalArgumentException("read extends beyond the address space");
}
if (!floor.getValue().getRange().contains(max)) {
throw new IllegalArgumentException("read extends beyond a single region");
}
return byRegion.get(floor.getValue());
}
@Override
public void addRegion(TargetMemoryRegion region, TargetMemory memory) {
synchronized (accessibilityByMemory) {
TargetMemory old = byRegion.put(region, memory);
assert old == null;
byMin.put(region.getRange().getMinAddress(), region);
accessibilityByMemory.get(memory).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
Msg.error(this, "Could not track memory accessibility: " + e.getMessage());
return null;
});
}
}
@Override
public boolean removeRegion(TargetObject invalid) {
if (!(invalid instanceof TargetMemoryRegion)) {
return false;
}
synchronized (accessibilityByMemory) {
TargetMemoryRegion invRegion = (TargetMemoryRegion) invalid;
TargetMemory old = byRegion.remove(invRegion);
assert old != null;
byMin.remove(invRegion.getRange().getMinAddress());
if (!old.isValid() || !byRegion.containsValue(old)) {
accessibilityByMemory.remove(old);
}
return true;
}
}
/*
protected AllRequiredAccess findChainedMemoryAccess(TargetMemoryRegion region) {
synchronized (accessibilityByMemory) {
TargetMemory mem = byRegion.get(region);
if (mem != null) {
return accessibilityByMemory.getCompletedMap().get(mem);
}
return chain == null ? null : chain.findChainedMemoryAccess(region);
}
}
*/
public Entry<Address, TargetMemoryRegion> findChainedFloor(Address address) {
synchronized (accessibilityByMemory) {
Entry<Address, TargetMemoryRegion> myFloor = byMin.floorEntry(address);
Entry<Address, TargetMemoryRegion> byChain =
chain == null ? null : chain.findChainedFloor(address);
if (byChain == null) {
return myFloor;
}
if (myFloor == null) {
return byChain;
}
int c = myFloor.getKey().compareTo(byChain.getKey());
if (c < 0) {
return byChain;
}
return myFloor;
}
}
protected AddressRange align(Address address, int length) {
AddressSpace space = address.getAddressSpace();
long offset = address.getOffset();
Address start = space.getAddress(offset & BLOCK_MASK);
Address end = space.getAddress(((offset + length - 1) & BLOCK_MASK) + BLOCK_SIZE - 1);
return new AddressRangeImpl(start, end);
}
protected AddressRange alignWithLimit(Address address, int length,
TargetMemoryRegion limit) {
return align(address, length).intersect(limit.getRange());
}
@Override
public AddressRange alignAndLimitToFloor(Address address, int length) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
return null;
}
return alignWithLimit(address, length, floor.getValue());
}
public AddressRange alignWithOptionalLimit(Address address, int length,
TargetMemoryRegion limit) {
if (limit == null) {
return alignAndLimitToFloor(address, length);
}
return alignWithLimit(address, length, limit);
}
@Override
public CompletableFuture<byte[]> readMemory(Address address, int length) {
synchronized (accessibilityByMemory) {
TargetMemory mem = getMemory(address, length);
if (mem != null) {
return mem.readMemory(address, length);
}
return CompletableFuture.completedFuture(new byte[0]);
}
}
@Override
public CompletableFuture<Void> writeMemory(Address address, byte[] data) {
synchronized (accessibilityByMemory) {
TargetMemory mem = getMemory(address, data.length);
if (mem != null) {
return mem.writeMemory(address, data);
}
throw new IllegalArgumentException("read starts outside any address space");
}
}
public ListenerSet<TriConsumer<Boolean, Boolean, Void>> getMemAccListeners() {
return memAccListeners;
}
}

View file

@ -0,0 +1,108 @@
/* ###
* 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 java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncLazyMap;
import ghidra.async.AsyncLazyMap.KeyedFuture;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.DebugModelConventions.AllRequiredAccess;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.util.Msg;
import ghidra.util.TriConsumer;
public class RecorderComposedRegisterSet {
private TraceRecorder recorder;
protected final TriConsumer<Boolean, Boolean, Void> listenerRegAccChanged =
this::registerAccessibilityChanged;
protected void registerAccessibilityChanged(boolean old, boolean acc,
Void __) {
recorder.getListeners().fire.registerAccessibilityChanged(recorder);
}
protected final AsyncLazyMap<TargetRegisterBank, AllRequiredAccess> accessibilityByRegBank =
new AsyncLazyMap<>(new HashMap<>(), this::fetchRegAccessibility) {
public AllRequiredAccess remove(TargetRegisterBank key) {
AllRequiredAccess acc = super.remove(key);
if (acc != null) {
acc.removeChangeListener(listenerRegAccChanged);
}
return acc;
}
};
protected CompletableFuture<AllRequiredAccess> fetchRegAccessibility(
TargetRegisterBank bank) {
return DebugModelConventions.trackAccessibility(bank).thenApply(acc -> {
acc.addChangeListener(listenerRegAccChanged);
return acc;
});
}
public RecorderComposedRegisterSet(TraceRecorder recorder) {
this.recorder = recorder;
}
public void updateRegisters(TargetRegisterBank newRegs, TargetRegisterBank oldRegs) {
synchronized (accessibilityByRegBank) {
if (oldRegs != null) {
accessibilityByRegBank.remove(oldRegs);
}
accessibilityByRegBank.get(newRegs).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
Msg.error(this, "Could not track register accessibility: " + e.getMessage());
return null;
});
}
}
public boolean checkRegistersRemoved(Map<Integer, TargetRegisterBank> regs,
TargetObject invalid) {
synchronized (accessibilityByRegBank) {
if (regs.values().remove(invalid)) {
accessibilityByRegBank.remove((TargetRegisterBank) invalid);
return true;
}
return false;
}
}
public boolean isRegisterBankAccessible(TargetRegisterBank bank) {
if (bank == null) {
return false;
}
synchronized (accessibilityByRegBank) {
KeyedFuture<?, AllRequiredAccess> future = accessibilityByRegBank.get(bank);
if (future == null) {
return false;
}
AllRequiredAccess acc = future.getNow(null);
if (acc == null) {
return false;
}
return acc.get();
}
}
}

View file

@ -0,0 +1,41 @@
/* ###
* 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 ghidra.framework.model.UndoableDomainObject;
import ghidra.util.database.UndoableTransaction;
public class RecorderPermanentTransaction implements AutoCloseable {
static RecorderPermanentTransaction start(UndoableDomainObject obj, String description) {
UndoableTransaction tid = UndoableTransaction.start(obj, description, true);
return new RecorderPermanentTransaction(obj, tid);
}
private final UndoableDomainObject obj;
private final UndoableTransaction tid;
public RecorderPermanentTransaction(UndoableDomainObject obj, UndoableTransaction tid) {
this.obj = obj;
this.tid = tid;
}
@Override
public void close() {
tid.close();
obj.clearUndo();
}
}

View file

@ -0,0 +1,126 @@
/* ###
* 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 java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.service.model.interfaces.AbstractRecorderMemory;
import ghidra.dbg.target.*;
import ghidra.program.model.address.*;
public class RecorderSimpleMemory implements AbstractRecorderMemory {
private static final int BLOCK_SIZE = 4096;
private static final long BLOCK_MASK = -1L << 12;
protected final NavigableMap<Address, TargetMemoryRegion> byMin = new TreeMap<>();
protected TargetMemory memory;
public RecorderSimpleMemory() {
}
@Override
public void addRegion(TargetMemoryRegion region, TargetMemory memory) {
synchronized (this) {
if (this.memory == null) {
this.memory = memory;
}
byMin.put(region.getRange().getMinAddress(), region);
}
}
@Override
public boolean removeRegion(TargetObject invalid) {
if (!(invalid instanceof TargetMemoryRegion)) {
return false;
}
synchronized (this) {
TargetMemoryRegion invRegion = (TargetMemoryRegion) invalid;
byMin.remove(invRegion.getRange().getMinAddress());
return true;
}
}
@Override
public CompletableFuture<byte[]> readMemory(Address address, int length) {
synchronized (this) {
if (memory != null) {
return memory.readMemory(address, length);
}
return CompletableFuture.completedFuture(new byte[0]);
}
}
@Override
public CompletableFuture<Void> writeMemory(Address address, byte[] data) {
synchronized (this) {
if (memory != null) {
return memory.writeMemory(address, data);
}
throw new IllegalArgumentException("read starts outside any address space");
}
}
/**
* Get accessible memory, as viewed in the trace
*
* @param pred an additional predicate applied via "AND" with accessibility
* @param memMapper target-to-trace mapping utility
* @return the computed set
*/
@Override
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper) {
synchronized (this) {
// TODO: Might accomplish by using listeners and tracking the accessible set
AddressSet accessible = new AddressSet();
if (memMapper != null) {
for (Entry<Address, TargetMemoryRegion> ent : byMin.entrySet()) {
accessible.add(memMapper.targetToTrace(ent.getValue().getRange()));
}
}
return accessible;
}
}
@Override
public AddressRange alignAndLimitToFloor(Address address, int length) {
Entry<Address, TargetMemoryRegion> floor = findChainedFloor(address);
if (floor == null) {
return null;
}
return align(address, length).intersect(floor.getValue().getRange());
}
protected Entry<Address, TargetMemoryRegion> findChainedFloor(Address address) {
synchronized (this) {
return byMin.floorEntry(address);
}
}
protected AddressRange align(Address address, int length) {
AddressSpace space = address.getAddressSpace();
long offset = address.getOffset();
Address start = space.getAddress(offset & BLOCK_MASK);
Address end = space.getAddress(((offset + length - 1) & BLOCK_MASK) + BLOCK_SIZE - 1);
return new AddressRangeImpl(start, end);
}
}

View file

@ -0,0 +1,34 @@
/* ###
* 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 ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetRegisterBank;
public class RecorderSimpleRegisterSet {
private TraceRecorder recorder;
private TargetRegisterBank bank;
public RecorderSimpleRegisterSet(TraceRecorder recorder) {
this.recorder = recorder;
}
public void updateRegisters(TargetRegisterBank newRegs, TargetRegisterBank oldRegs) {
this.bank = newRegs;
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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 java.util.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetThread;
import ghidra.trace.model.thread.TraceThread;
public class RecorderThreadMap {
protected final NavigableSet<Integer> observedThreadPathLengths = new TreeSet<>();
protected final Map<TargetThread, ManagedThreadRecorder> byTargetThread = new HashMap<>();
protected final Map<TraceThread, ManagedThreadRecorder> byTraceThread = new HashMap<>();
public void put(ManagedThreadRecorder rec) {
observedThreadPathLengths.add(rec.getTargetThread().getPath().size());
byTargetThread.put(rec.getTargetThread(), rec);
byTraceThread.put(rec.getTraceThread(), rec);
}
/*
public ManagedThreadRecorder getForSuccessor(TargetObject successor) {
while (successor != null) {
ManagedThreadRecorder rec = byTargetThread.get(successor);
if (rec != null) {
return rec;
}
successor = successor.getParent();
}
return null;
}
*/
public ManagedThreadRecorder get(TargetThread thread) {
return byTargetThread.get(thread);
}
public ManagedThreadRecorder get(TargetObject maybeThread) {
return byTargetThread.get(maybeThread);
}
public ManagedThreadRecorder get(TraceThread thread) {
return byTraceThread.get(thread);
}
public void remove(ManagedThreadRecorder rec) {
ManagedThreadRecorder rByTarget = byTargetThread.remove(rec.getTargetThread());
ManagedThreadRecorder rByTrace = byTraceThread.remove(rec.getTraceThread());
assert rec == rByTarget;
assert rec == rByTrace;
}
public Collection<ManagedThreadRecorder> recorders() {
return byTargetThread.values();
}
}

View file

@ -0,0 +1,269 @@
/* ###
* 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 java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.util.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedStackRecorder;
import ghidra.app.plugin.core.debug.service.model.interfaces.ManagedThreadRecorder;
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetEventScope.TargetEventType;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.util.DebuggerCallbackReorderer;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.modules.TraceModule;
import ghidra.util.Msg;
import ghidra.util.TimedMsg;
import ghidra.util.exception.DuplicateNameException;
public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
private final DefaultTraceRecorder recorder;
private final TargetObject target;
private final Trace trace;
private final TraceMemoryManager memoryManager;
private boolean valid = true;
protected TargetObject curFocus;
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
public TraceEventListener(TraceObjectManager collection) {
super(MethodHandles.lookup());
this.recorder = collection.getRecorder();
this.target = recorder.getTarget();
this.trace = recorder.getTrace();
this.memoryManager = trace.getMemoryManager();
}
public void init() {
DebuggerObjectModel model = target.getModel();
model.addModelListener(reorderer, true);
}
private boolean successor(TargetObject ref) {
return PathUtils.isAncestor(target.getPath(), ref.getPath());
}
private boolean anyRef(Collection<Object> parameters) {
for (Object p : parameters) {
if (!(p instanceof TargetObject)) {
continue;
}
return true;
}
return false;
}
private boolean anySuccessor(Collection<Object> parameters) {
for (Object p : parameters) {
if (!(p instanceof TargetObject)) {
continue;
}
TargetObject ref = (TargetObject) p;
if (!successor(ref)) {
continue;
}
return true;
}
return false;
}
private boolean eventApplies(TargetObject eventThread, TargetEventType type,
List<Object> parameters) {
if (type == TargetEventType.RUNNING) {
return false;
/**
* TODO: Perhaps some configuration for this later. It's kind of interesting to record
* the RUNNING event time, but it gets pedantic when these exist between steps.
*/
}
if (eventThread != null) {
return successor(eventThread);
}
if (anyRef(parameters)) {
return anySuccessor(parameters);
}
return true; // Some session-wide event, I suppose
}
@Override
public void event(TargetObject object, TargetThread eventThread, TargetEventType type,
String description, List<Object> parameters) {
if (!valid) {
return;
}
TimedMsg.info(this, "Event: " + type + " thread=" + eventThread + " description=" +
description + " params=" + parameters);
// Just use this to step the snaps. Creation/destruction still handled in add/remove
if (eventThread == null) {
if (!type.equals(TargetEventType.PROCESS_CREATED)) {
Msg.error(this, "Null eventThread for " + type);
}
return;
}
if (!eventApplies(eventThread, type, parameters)) {
return;
}
ManagedThreadRecorder rec = recorder.getThreadRecorder(eventThread);
recorder.createSnapshot(description, rec == null ? null : rec.getTraceThread(), null);
if (type == TargetEventType.MODULE_LOADED) {
long snap = recorder.getSnap();
Object p0 = parameters.get(0);
if (!(p0 instanceof TargetModule)) {
return;
}
TargetModule mod = (TargetModule) p0;
recorder.moduleRecorder.tx.execute("Adjust module load", () -> {
TraceModule traceModule = recorder.getTraceModule(mod);
if (traceModule == null) {
return;
}
try {
traceModule.setLoadedSnap(snap);
}
catch (DuplicateNameException e) {
Msg.error(this, "Could not set module loaded snap", e);
}
});
}
}
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
public void executionStateChanged(TargetObject stateful, TargetExecutionState state) {
if (!valid) {
return;
}
TimedMsg.info(this, "State " + state + " for " + stateful);
TargetObject x = recorder.objectManager.findThreadOrProcess(stateful);
if (x != null) {
if (x == target && state == TargetExecutionState.TERMINATED) {
recorder.stopRecording();
return;
}
ManagedThreadRecorder rec = null;
if (x instanceof TargetThread) {
rec = recorder.getThreadRecorder((TargetThread) x);
}
if (rec != null) {
rec.stateChanged(state);
}
// Else we'll discover it and sync state later
}
}
@Override
public void registersUpdated(TargetObject bank, Map<String, byte[]> updates) {
if (!valid) {
return;
}
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(bank);
if (rec != null) {
rec.recordRegisterValues((TargetRegisterBank) bank, updates);
}
}
@Override
public void memoryUpdated(TargetObject memory, Address address, byte[] data) {
if (!valid) {
return;
}
synchronized (recorder) {
if (recorder.getMemoryMapper() == null) {
Msg.warn(this, "Received memory write before a region has been added");
return;
}
}
Address traceAddr = recorder.getMemoryMapper().targetToTrace(address);
long snap = recorder.getSnap();
TimedMsg.info(this, "Memory updated: " + address + " (" + data.length + ")");
recorder.memoryRecorder.tx.execute("Memory observed", () -> {
memoryManager.putBytes(snap, traceAddr, ByteBuffer.wrap(data));
});
}
@Override
public void memoryReadError(TargetObject memory, AddressRange range,
DebuggerMemoryAccessException e) {
if (!valid) {
return;
}
Msg.error(this, "Error reading range " + range, e);
Address traceMin = recorder.getMemoryMapper().targetToTrace(range.getMinAddress());
long snap = recorder.getSnap();
recorder.memoryRecorder.tx.execute("Memory read error", () -> {
memoryManager.setState(snap, traceMin, TraceMemoryState.ERROR);
// TODO: Bookmark to describe error?
});
}
@AttributeCallback(TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME)
public void breakpointToggled(TargetObject obj, boolean enabled) {
if (!valid) {
return;
}
TargetBreakpointSpec spec = (TargetBreakpointSpec) obj;
long snap = recorder.getSnap();
spec.getLocations().thenAccept(bpts -> {
recorder.breakpointRecorder.tx.execute("Breakpoint toggled", () -> {
for (TargetBreakpointLocation eb : bpts) {
TraceBreakpoint traceBpt = recorder.getTraceBreakpoint(eb);
if (traceBpt == null) {
String path = PathUtils.toString(eb.getPath());
Msg.warn(this, "Cannot find toggled trace breakpoint for " + path);
continue;
}
// Verify attributes match? Eh. If they don't, someone has fiddled with it.
traceBpt.splitWithEnabled(snap, enabled);
}
});
}).exceptionally(ex -> {
Msg.error(this, "Error recording toggled breakpoint spec: " + spec, ex);
return null;
});
}
protected void stackUpdated(TargetStack stack) {
ManagedStackRecorder rec = recorder.getThreadRecorderForSuccessor(stack).getStackRecorder();
rec.recordStack();
}
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject scope, TargetObject focused) {
if (!valid) {
return;
}
if (PathUtils.isAncestor(target.getPath(), focused.getPath())) {
curFocus = focused;
}
}
public RecorderThreadMap getThreadMap() {
return recorder.getThreadMap();
}
}

View file

@ -0,0 +1,263 @@
/* ###
* 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 java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import ghidra.async.AsyncFence;
import ghidra.dbg.*;
import ghidra.dbg.target.*;
import ghidra.dbg.util.DebuggerCallbackReorderer;
import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.util.Msg;
public class TraceObjectListener implements DebuggerModelListener {
private TraceObjectManager objectManager;
private TargetObject target;
protected boolean disposed = false;
protected final NavigableMap<List<String>, TargetObject> initialized =
new TreeMap<>(PathComparator.KEYED);
protected final DebuggerCallbackReorderer reorderer = new DebuggerCallbackReorderer(this);
public TraceObjectListener(TraceObjectManager manager) {
this.objectManager = manager;
this.target = objectManager.getTarget();
}
public void init() {
findInitialObjects(target).thenAccept(adds -> {
for (TargetObject added : adds) {
processInit(added);
}
DebuggerObjectModel model = target.getModel();
model.addModelListener(reorderer, true);
});
}
boolean matchesTarget(TargetObject object) {
TargetObject proc = object;
while (proc != null) {
if (proc == target)
return true;
if (proc.getClass().equals(target.getClass()))
return false;
proc = proc.getParent();
}
return true;
}
protected void processCreate(TargetObject added) {
if (!objectManager.hasObject(added) && matchesTarget(added)) {
objectManager.addObject(added);
objectManager.createObject(added);
}
/*
else {
Msg.info(this, "processCreate dropped " + added);
}
*/
}
protected void processInit(TargetObject added) {
if (objectManager.hasObject(added)) {
if (!initialized.containsKey(added.getPath())) {
initialized.put(added.getPath(), added);
objectManager.initObject(added);
}
}
}
protected void processRemove(TargetObject removed) {
if (objectManager.hasObject(removed)) {
objectManager.removeObject(removed);
objectManager.removeObject(removed.getPath());
}
}
protected void processAttributesChanged(TargetObject changed, Map<String, ?> added) {
if (objectManager.hasObject(changed)) {
objectManager.attributesChanged(changed, added);
}
}
protected void processElementsChanged(TargetObject changed, Map<String, ?> added) {
if (objectManager.hasObject(changed)) {
objectManager.elementsChanged(changed, added);
}
}
@Override
public void created(TargetObject object) {
//System.err.println("CR:" + object);
processCreate(object);
}
@Override
public void invalidated(TargetObject object, TargetObject branch, String reason) {
processRemove(object);
}
@Override
public void attributesChanged(TargetObject parent, Collection<String> removed,
Map<String, ?> added) {
//System.err.println("AC:" + added + ":" + parent);
if (parent.isValid()) {
processInit(parent);
processAttributesChanged(parent, added);
}
}
@Override
public void elementsChanged(TargetObject parent, Collection<String> removed,
Map<String, ? extends TargetObject> added) {
//System.err.println("EC:" + added + ":" + parent);
if (parent.isValid()) {
processElementsChanged(parent, added);
}
}
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
synchronized (objectManager.objects) {
return objectManager.collectBreakpoints(thread);
}
}
protected void onProcessBreakpointContainers(
Consumer<? super TargetBreakpointSpecContainer> action) {
synchronized (objectManager.objects) {
objectManager.onProcessBreakpointContainers(action);
}
}
protected void onThreadBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointSpecContainer> action) {
synchronized (objectManager.objects) {
objectManager.onThreadBreakpointContainers(thread, action);
}
}
/*
public boolean addListener(TargetObject obj) {
if (obj == null) {
return false;
}
obj.addListener(this);
synchronized (objects) {
if (objects.put(obj.getPath(), obj) == obj) {
return false;
}
}
return true;
}
public void dispose() {
synchronized (objects) {
disposed = true;
for (Iterator<TargetObject> it = objects.values().iterator(); it.hasNext();) {
TargetObject obj = it.next();
obj.removeListener(this);
it.remove();
}
}
}
*/
private CompletableFuture<List<TargetObject>> findInitialObjects(TargetObject target) {
List<TargetObject> result = new ArrayList<>();
result.add(target);
AsyncFence fence = new AsyncFence();
CompletableFuture<? extends TargetEventScope> futureEvents =
DebugModelConventions.findSuitable(TargetEventScope.class, target);
fence.include(futureEvents.thenAccept(events -> {
if (events != null) {
result.add(events);
}
}).exceptionally(e -> {
Msg.warn(this, "Could not search for event scope", e);
return null;
}));
CompletableFuture<? extends TargetFocusScope> futureFocus =
DebugModelConventions.findSuitable(TargetFocusScope.class, target);
fence.include(futureFocus.thenAccept(focus -> {
if (focus != null) {
// Don't descend. Scope may be the entire session.
result.add(focus);
}
}).exceptionally(e -> {
Msg.error(this, "Could not search for focus scope", e);
return null;
}));
return fence.ready().thenApply(__ -> {
return result;
});
}
/*
private CompletableFuture<List<TargetObject>> findDependenciesTop(TargetObject added) {
List<TargetObject> result = new ArrayList<>();
result.add(added);
return findDependencies(added, result);
}
private CompletableFuture<List<TargetObject>> findDependencies(TargetObject added,
List<TargetObject> result) {
//System.err.println("findDependencies " + added);
AsyncFence fence = new AsyncFence();
fence.include(added.fetchAttributes(false).thenCompose(attrs -> {
AsyncFence af = new AsyncFence();
for (String key : attrs.keySet()) { //requiredObjKeys) {
Object object = attrs.get(key);
if (!(object instanceof TargetObject)) {
continue;
}
TargetObject ref = (TargetObject) object;
if (PathUtils.isLink(added.getPath(), key, ref.getPath())) {
continue;
}
af.include(ref.fetch().thenCompose(obj -> {
if (!objectManager.isRequired(obj)) {
return CompletableFuture.completedFuture(result);
}
synchronized (result) {
result.add(obj);
}
return findDependencies(obj, result);
}));
}
return af.ready();
}));
fence.include(added.fetchElements(false).thenCompose(elems -> {
AsyncFence ef = new AsyncFence();
for (TargetObject ref : elems.values()) {
ef.include(ref.fetch().thenCompose(obj -> {
synchronized (result) {
result.add(obj);
}
return findDependencies(obj, result);
}));
}
return ef.ready();
}));
return fence.ready().thenApply(__ -> {
return result;
});
}
*/
}

View file

@ -0,0 +1,655 @@
/* ###
* 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 java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.*;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.interfaces.*;
import ghidra.app.services.TraceRecorderListener;
import ghidra.async.AsyncLazyMap;
import ghidra.dbg.target.*;
import ghidra.dbg.util.PathUtils;
import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.program.model.address.Address;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.DuplicateNameException;
public class TraceObjectManager {
private final TargetObject target;
private final TraceEventListener eventListener;
final TraceObjectListener objectListener;
protected final NavigableMap<List<String>, TargetObject> objects =
new TreeMap<>(PathComparator.KEYED);
private DefaultTraceRecorder recorder;
private AbstractDebuggerTargetTraceMapper mapper;
protected DebuggerMemoryMapper memMapper;
protected AsyncLazyMap<TargetRegisterContainer, DebuggerRegisterMapper> regMappers;
//private AbstractRecorderRegisterSet threadRegisters;
private final ListenerSet<TraceRecorderListener> listeners =
new ListenerSet<>(TraceRecorderListener.class);
protected final Set<TargetBreakpointLocation> breakpoints = new HashSet<>();
// NB: We add the objects in top-down order and initialize them bottom-up
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapCreate =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapInit =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMapRemove =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMapElements =
new LinkedHashMap<>();
private LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMapAttributes =
new LinkedHashMap<>();
public TraceObjectManager(TargetObject target, AbstractDebuggerTargetTraceMapper mapper,
DefaultTraceRecorder recorder) {
this.target = target;
this.mapper = mapper;
this.recorder = recorder;
this.regMappers = new AsyncLazyMap<>(new HashMap<>(), ref -> mapper.offerRegisters(ref));
//this.threadRegisters = new RecorderComposedRegisterSet(recorder);
defaultHandlers();
this.eventListener = new TraceEventListener(this);
this.objectListener = new TraceObjectListener(this);
//objectListener.addListenerAndConsiderSuccessors(target);
}
public void init() {
objectListener.init();
eventListener.init();
}
private void defaultHandlers() {
putCreateHandler(TargetThread.class, this::createThread);
putCreateHandler(TargetMemory.class, this::createMemory);
putCreateHandler(TargetRegister.class, this::createRegister);
putInitHandler(TargetStack.class, this::addStack);
putInitHandler(TargetStackFrame.class, this::addStackFrame);
putInitHandler(TargetRegisterBank.class, this::addRegisterBank);
putInitHandler(TargetRegisterContainer.class, this::addRegisterContainer);
//putInitHandler(TargetMemoryRegion.class, this::addMemoryRegion);
putInitHandler(TargetModule.class, this::addModule);
//putInitHandler(TargetSection.class, this::addSection); // This is brutally expensive
putInitHandler(TargetBreakpointSpecContainer.class, this::addBreakpointContainer);
putInitHandler(TargetBreakpointSpec.class, this::addBreakpointSpec);
putInitHandler(TargetBreakpointLocation.class, this::addBreakpointLocation);
putElementsHandler(TargetBreakpointLocationContainer.class,
this::elementsChangedBreakpointLocationContainer);
putElementsHandler(TargetMemory.class, this::elementsChangedMemory);
putElementsHandler(TargetSectionContainer.class, this::elementsChangedSectionContainer);
putElementsHandler(TargetStack.class, this::elementsChangedStack);
putAttributesHandler(TargetBreakpointLocation.class,
this::attributesChangedBreakpointLocation);
putAttributesHandler(TargetRegister.class, this::attributesChangedRegister);
putAttributesHandler(TargetStackFrame.class, this::attributesChangedStackFrame);
putRemHandler(TargetProcess.class, this::removeProcess);
putRemHandler(TargetThread.class, this::removeThread);
putRemHandler(TargetStack.class, this::removeStack);
putRemHandler(TargetStackFrame.class, this::removeStackFrame);
putRemHandler(TargetStack.class, this::removeRegisterBank);
putRemHandler(TargetRegisterContainer.class, this::removeRegisterContainer);
putRemHandler(TargetRegister.class, this::removeRegister);
putRemHandler(TargetMemory.class, this::removeMemory);
putRemHandler(TargetMemoryRegion.class, this::removeMemoryRegion);
putRemHandler(TargetModule.class, this::removeModule);
putRemHandler(TargetSection.class, this::removeSection);
putRemHandler(TargetBreakpointSpecContainer.class, this::removeBreakpointContainer);
putRemHandler(TargetBreakpointSpec.class, this::removeBreakpointSpec);
putRemHandler(TargetBreakpointLocation.class, this::removeBreakpointLocation);
}
private <U extends TargetObject> Function<TargetObject, Void> putHandler(Class<?> key,
Consumer<TargetObject> handler,
LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMap) {
return handlerMap.put(key, (u) -> {
handler.accept(u);
return null;
});
}
private <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putHandler(
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler,
LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) {
return handlerMap.put(key, (u, v) -> {
handler.accept(u, v);
return null;
});
}
public <U extends TargetObject> Function<TargetObject, Void> putCreateHandler(Class<?> key,
Consumer<TargetObject> handler) {
return putHandler(key, handler, handlerMapCreate);
}
public <U extends TargetObject> Function<TargetObject, Void> putInitHandler(Class<?> key,
Consumer<TargetObject> handler) {
return putHandler(key, handler, handlerMapInit);
}
public <U extends TargetObject> Function<TargetObject, Void> putRemHandler(Class<?> key,
Consumer<TargetObject> handler) {
return putHandler(key, handler, handlerMapRemove);
}
public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putAttributesHandler(
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler) {
return putHandler(key, handler, handlerMapAttributes);
}
public <U extends TargetObject> BiFunction<TargetObject, Map<String, ?>, Void> putElementsHandler(
Class<?> key, BiConsumer<TargetObject, Map<String, ?>> handler) {
return putHandler(key, handler, handlerMapElements);
}
private void processObject(TargetObject targetObject,
LinkedHashMap<Class<?>, Function<TargetObject, Void>> handlerMap) {
Set<Class<? extends TargetObject>> interfaces = targetObject.getSchema().getInterfaces();
for (Class<? extends TargetObject> ifc : interfaces) {
Function<TargetObject, ? extends Void> function = handlerMap.get(ifc);
if (function != null) {
function.apply(targetObject);
}
}
}
private void processObject(TargetObject targetObject, Map<String, ?> map,
LinkedHashMap<Class<?>, BiFunction<TargetObject, Map<String, ?>, Void>> handlerMap) {
Set<Class<? extends TargetObject>> interfaces = targetObject.getSchema().getInterfaces();
for (Class<? extends TargetObject> ifc : interfaces) {
BiFunction<TargetObject, Map<String, ?>, ? extends Void> function = handlerMap.get(ifc);
if (function != null) {
function.apply(targetObject, map);
}
}
}
public void createObject(TargetObject toInit) {
processObject(toInit, handlerMapCreate);
}
public void initObject(TargetObject added) {
//System.err.println("initObject " + added);
processObject(added, handlerMapInit);
}
public void removeObject(TargetObject removed) {
processObject(removed, handlerMapRemove);
}
public void attributesChanged(TargetObject changed, Map<String, ?> added) {
processObject(changed, added, handlerMapAttributes);
}
public void elementsChanged(TargetObject changed, Map<String, ?> added) {
processObject(changed, added, handlerMapElements);
}
public boolean isRequired(TargetObject obj) {
if (obj.getName().equals("Debug"))
return true;
if (obj.getName().equals("Stack"))
return true;
Set<Class<? extends TargetObject>> interfaces = obj.getSchema().getInterfaces();
for (Class<? extends TargetObject> ifc : interfaces) {
if (handlerMapInit.keySet().contains(ifc)) {
return true;
}
}
return false;
}
public void addProcess(TargetObject added) {
// Create a new processRecorder
recorder.init();
}
public void removeProcess(TargetObject removed) {
recorder.stopRecording();
}
public void createThread(TargetObject added) {
//System.err.println("createThread " + added + ":" + this);
synchronized (recorder.threadMap) {
ManagedThreadRecorder threadRecorder = recorder.getThreadRecorder((TargetThread) added);
TraceThread traceThread = threadRecorder.getTraceThread();
recorder.createSnapshot(traceThread + " started", traceThread, null);
try (UndoableTransaction tid =
UndoableTransaction.start(recorder.getTrace(), "Adjust thread creation", true)) {
traceThread.setCreationSnap(recorder.getSnap());
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // Should be shrinking
}
}
}
public void removeThread(TargetObject removed) {
synchronized (recorder.threadMap) {
ManagedThreadRecorder threadRecorder =
recorder.getThreadRecorder((TargetThread) removed);
threadRecorder.objectRemoved(removed);
}
}
public void addStack(TargetObject added) {
//addEventListener(added);
}
public void removeStack(TargetObject removed) {
// Nothing for now
}
public void addStackFrame(TargetObject added) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
if (rec == null) {
Msg.error(this, "Frame without thread?: " + added);
}
else {
rec.getStackRecorder().offerStackFrame((TargetStackFrame) added);
}
}
public void removeStackFrame(TargetObject removed) {
synchronized (recorder.threadMap) {
ManagedThreadRecorder threadRecorder = recorder.getThreadRecorderForSuccessor(removed);
threadRecorder.objectRemoved(removed);
}
}
public void addRegisterBank(TargetObject added) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
rec.offerRegisters((TargetRegisterBank) added);
}
public void removeRegisterBank(TargetObject removed) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(removed);
rec.removeRegisters((TargetRegisterBank) removed);
}
public void addRegisterContainer(TargetObject added) {
// These are picked up when a bank is added with these descriptions
}
public void removeRegisterContainer(TargetObject removed) {
regMappers.remove((TargetRegisterContainer) removed);
}
public void createRegister(TargetObject added) {
if (added.getCachedAttribute(TargetRegister.CONTAINER_ATTRIBUTE_NAME) != null) {
TargetRegister register = (TargetRegister) added;
regMappers.get(register.getContainer()).thenAccept(rm -> {
if (rm != null) {
rm.targetRegisterAdded(register);
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, register, false);
}
}
});
}
}
public void removeRegister(TargetObject removed) {
TargetRegister register = (TargetRegister) removed;
TargetRegisterContainer cont = register.getContainer();
DebuggerRegisterMapper rm = regMappers.getCompletedMap().get(cont);
if (rm == null) {
return;
}
rm.targetRegisterRemoved(register);
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, register, true);
}
}
public void createMemory(TargetObject added) {
if (memMapper != null) {
return;
}
mapper.offerMemory((TargetMemory) added).thenAccept(mm -> {
synchronized (this) {
memMapper = mm;
//addEventListener(added);
}
//listenerForRecord.retroOfferMemMapperDependents();
}).exceptionally(ex -> {
Msg.error(this, "Could not intialize memory mapper", ex);
return null;
});
}
public void removeMemory(TargetObject removed) {
// Nothing for now
}
public void addMemoryRegion(TargetObject added) {
/*
TargetMemoryRegion region = (TargetMemoryRegion) added;
findThreadOrProcess(added).thenAccept(obj -> {
if (obj == target) {
recorder.memoryRecorder.offerProcessRegion(region);
return;
}
if (obj instanceof TargetThread) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(added);
rec.offerThreadRegion(region);
}
}).exceptionally(ex -> {
Msg.error(this, "Error recording memory region", ex);
return null;
});
*/
}
public void removeMemoryRegion(TargetObject removed) {
recorder.memoryRecorder.removeProcessRegion((TargetMemoryRegion) removed);
}
public void addModule(TargetObject added) {
recorder.moduleRecorder.offerProcessModule((TargetModule) added);
}
public void removeModule(TargetObject removed) {
recorder.moduleRecorder.removeProcessModule((TargetModule) removed);
}
public void addSection(TargetObject added) {
/*
TargetSection section = (TargetSection) added;
TargetModule module = section.getModule();
recorder.moduleRecorder.offerProcessModuleSection(module, section);
// I hope this should never be a per-thread thing
*/
}
public void removeSection(TargetObject removed) {
// Nothing for now
}
public void addBreakpointContainer(TargetObject added) {
TargetObject obj = findThreadOrProcess(added);
if (obj != null) {
ManagedBreakpointRecorder breakpointRecorder = recorder.breakpointRecorder;
if (obj instanceof TargetThread) {
ManagedBreakpointRecorder rec =
recorder.getThreadRecorderForSuccessor(added).getBreakpointRecorder();
rec.offerBreakpointContainer((TargetBreakpointSpecContainer) added);
return;
}
breakpointRecorder.offerBreakpointContainer((TargetBreakpointSpecContainer) added);
}
else {
Msg.error(this, "Error recording breakpoint container " + added);
}
}
public void removeBreakpointContainer(TargetObject removed) {
// Nothing for now
}
public void addBreakpointSpec(TargetObject added) {
// Nothing for now
}
public void removeBreakpointSpec(TargetObject removed) {
// Nothing for now
}
public void addBreakpointLocation(TargetObject added) {
// Nothing for now
//breakpoints.add((TargetBreakpointLocation) added);
//recorder.breakpointRecorder.offerEffectiveBreakpoint((TargetBreakpointLocation) added);
}
public void removeBreakpointLocation(TargetObject removed) {
breakpoints.remove(removed);
recorder.breakpointRecorder.removeBreakpointLocation((TargetBreakpointLocation) removed);
}
protected TargetObject findThreadOrProcess(TargetObject successor) {
TargetObject object = successor;
while (object != null) {
if (object instanceof TargetProcess)
return object;
if (object instanceof TargetThread)
return object;
object = object.getParent();
}
return object;
}
public AbstractDebuggerTargetTraceMapper getMapper() {
return mapper;
}
public DebuggerMemoryMapper getMemoryMapper() {
return memMapper;
}
public AsyncLazyMap<TargetRegisterContainer, DebuggerRegisterMapper> getRegMappers() {
return regMappers;
}
public Set<TargetBreakpointLocation> getBreakpoints() {
return breakpoints;
}
public void attributesChangedBreakpointLocation(TargetObject bpt, Map<String, ?> added) {
if (added.containsKey(TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME)) {
Address traceAddr = recorder.getMemoryMapper()
.targetToTrace(((TargetBreakpointLocation) bpt).getAddress());
String path = bpt.getJoinedPath(".");
Integer length = (Integer) added.get(TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME);
recorder.breakpointRecorder.breakpointLengthChanged(length, traceAddr, path);
}
}
public void attributesChangedRegister(TargetObject parent, Map<String, ?> added) {
if (added.containsKey(TargetRegister.CONTAINER_ATTRIBUTE_NAME)) {
TargetRegister register = (TargetRegister) parent;
regMappers.get(register.getContainer()).thenAccept(rm -> {
rm.targetRegisterAdded(register);
for (ManagedThreadRecorder rec : recorder.threadMap.byTargetThread.values()) {
rec.regMapperAmended(rm, register, false);
}
});
}
if (added.containsKey(TargetObject.VALUE_ATTRIBUTE_NAME)) {
TargetRegister register = (TargetRegister) parent;
String valstr = (String) added.get(TargetObject.VALUE_ATTRIBUTE_NAME);
byte[] value = new BigInteger(valstr, 16).toByteArray();
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(register);
rec.recordRegisterValue(register, value);
}
}
public void attributesChangedStackFrame(TargetObject frame, Map<String, ?> added) {
if (added.containsKey(TargetStackFrame.PC_ATTRIBUTE_NAME)) {
ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(frame);
if (rec != null) {
rec.getStackRecorder().offerStackFrame((TargetStackFrame) frame);
}
}
}
public void elementsChangedBreakpointLocationContainer(TargetObject locationContainer,
Map<String, ?> added) {
TargetObject x = findThreadOrProcess(locationContainer);
if (x != null) {
for (Entry<String, ?> entry : added.entrySet()) {
TargetBreakpointLocation loc = (TargetBreakpointLocation) entry.getValue();
if (loc.isValid()) {
breakpoints.add(loc);
recorder.breakpointRecorder.offerBreakpointLocation(x, loc);
}
}
}
}
public void elementsChangedMemory(TargetObject memory, Map<String, ?> added) {
// TODO: This should probably only ever be a process
TargetObject threadOrProcess = findThreadOrProcess(memory);
if (threadOrProcess != null) {
for (Object object : added.values()) {
TargetMemoryRegion region = (TargetMemoryRegion) object;
if (!region.isValid()) {
continue;
}
if (threadOrProcess == target) {
recorder.memoryRecorder.offerProcessRegion(region);
}
else if (threadOrProcess instanceof TargetThread) {
ManagedThreadRecorder rec =
recorder.getThreadRecorderForSuccessor(threadOrProcess);
rec.offerThreadRegion(region);
}
}
}
else {
Msg.error(this, "Could not find process/thread for " + memory);
}
}
public void elementsChangedSectionContainer(TargetObject sectionContainer,
Map<String, ?> added) {
for (Object object : added.values()) {
TargetSection section = (TargetSection) object;
if (!section.isValid()) {
continue;
}
recorder.moduleRecorder.offerProcessModuleSection(section);
}
}
public void elementsChangedStack(TargetObject stack, Map<String, ?> added) {
ManagedStackRecorder rec = recorder.getThreadRecorderForSuccessor(stack).getStackRecorder();
rec.recordStack();
}
public TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region) {
synchronized (objects) {
return (TargetMemoryRegion) objects.get(PathUtils.parse(region.getPath()));
}
}
public TargetModule getTargetModule(TraceModule module) {
synchronized (objects) {
return (TargetModule) objects.get(PathUtils.parse(module.getPath()));
}
}
public TargetSection getTargetSection(TraceSection section) {
synchronized (objects) {
return (TargetSection) objects.get(PathUtils.parse(section.getPath()));
}
}
public TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt) {
synchronized (objects) {
return (TargetBreakpointLocation) objects.get(PathUtils.parse(bpt.getPath()));
}
}
public List<TargetBreakpointLocation> collectBreakpoints(TargetThread thread) {
return getBreakpoints().stream().collect(Collectors.toList());
}
public void onBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointSpecContainer> action) {
if (thread == null) {
objectListener.onProcessBreakpointContainers(action);
}
else {
objectListener.onThreadBreakpointContainers(thread, action);
}
}
public void onProcessBreakpointContainers(
Consumer<? super TargetBreakpointSpecContainer> action) {
TargetBreakpointSpecContainer breakpointContainer =
recorder.breakpointRecorder.getBreakpointContainer();
if (breakpointContainer == null) {
for (TargetThread thread : recorder.getThreadsView()) {
objectListener.onThreadBreakpointContainers(thread, action);
}
}
else {
action.accept(breakpointContainer);
}
}
public void onThreadBreakpointContainers(TargetThread thread,
Consumer<? super TargetBreakpointSpecContainer> action) {
TargetBreakpointSpecContainer breakpointContainer =
recorder.getThreadRecorder(thread).getBreakpointRecorder().getBreakpointContainer();
if (breakpointContainer == null) {
return;
}
action.accept(breakpointContainer);
}
// Needed by TraceRecorder
public TraceEventListener getEventListener() {
return eventListener;
}
public ListenerSet<TraceRecorderListener> getListeners() {
return listeners;
}
public TargetObject getTarget() {
return target;
}
public DefaultTraceRecorder getRecorder() {
return recorder;
}
public boolean hasObject(TargetObject object) {
return objects.containsKey(object.getPath());
}
public void addObject(TargetObject added) {
objects.put(added.getPath(), added);
}
public void removeObject(List<String> path) {
objects.remove(path);
}
}

View file

@ -0,0 +1,40 @@
/* ###
* 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.interfaces;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.dbg.target.*;
import ghidra.program.model.address.*;
public interface AbstractRecorderMemory {
public void addRegion(TargetMemoryRegion region, TargetMemory memory);
public boolean removeRegion(TargetObject invalid);
public CompletableFuture<byte[]> readMemory(Address address, int length);
public CompletableFuture<Void> writeMemory(Address address, byte[] data);
public AddressSet getAccessibleMemory(Predicate<TargetMemory> pred,
DebuggerMemoryMapper memMapper);
public AddressRange alignAndLimitToFloor(Address targetAddress, int length);
}

View file

@ -0,0 +1,31 @@
/* ###
* 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.interfaces;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.trace.model.Trace;
public interface AbstractTraceRecorder {
public Trace getTrace();
public long getSnap();
public DebuggerMemoryMapper getMemoryMapper();
public ManagedBreakpointRecorder getBreakpointRecorder();
}

View file

@ -0,0 +1,41 @@
/* ###
* 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.interfaces;
import java.util.Set;
import ghidra.dbg.target.*;
import ghidra.program.model.address.Address;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.thread.TraceThread;
public interface ManagedBreakpointRecorder {
void offerBreakpointContainer(TargetBreakpointSpecContainer added);
void offerBreakpointLocation(TargetObject target, TargetBreakpointLocation added);
void recordBreakpoint(TargetBreakpointLocation loc, Set<TraceThread> traceThreads);
void removeBreakpointLocation(TargetBreakpointLocation removed);
TargetBreakpointSpecContainer getBreakpointContainer();
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
void breakpointLengthChanged(int length, Address traceAddr, String path);
}

View file

@ -0,0 +1,29 @@
/* ###
* 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.interfaces;
import ghidra.dbg.target.TargetMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryRegion;
public interface ManagedMemoryRecorder {
void offerProcessRegion(TargetMemoryRegion region);
void removeProcessRegion(TargetMemoryRegion region);
TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region);
}

View file

@ -0,0 +1,35 @@
/* ###
* 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.interfaces;
import ghidra.dbg.target.TargetModule;
import ghidra.dbg.target.TargetSection;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
public interface ManagedModuleRecorder {
void offerProcessModule(TargetModule module);
void offerProcessModuleSection(TargetSection section);
void removeProcessModule(TargetModule module);
TraceModule getTraceModule(TargetModule module);
TraceSection getTraceSection(TargetSection section);
}

View file

@ -0,0 +1,22 @@
/* ###
* 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.interfaces;
public interface ManagedProcessRecorder extends AbstractTraceRecorder {
public AbstractRecorderMemory getProcessMemory();
}

View file

@ -0,0 +1,35 @@
/* ###
* 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.interfaces;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
public interface ManagedStackRecorder {
void offerStackFrame(TargetStackFrame added);
void recordStack();
int getSuccessorFrameLevel(TargetObject successor);
TraceStackFrame getTraceStackFrame(TraceThread traceThread, int level);
TargetStackFrame getTargetStackFrame(int frameLevel);
}

View file

@ -0,0 +1,52 @@
/* ###
* 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.interfaces;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.trace.model.thread.TraceThread;
public interface ManagedThreadRecorder extends AbstractTraceRecorder {
public TargetThread getTargetThread();
public TraceThread getTraceThread();
public void offerRegisters(TargetRegisterBank added);
public void removeRegisters(TargetRegisterBank removed);
public void offerThreadRegion(TargetMemoryRegion region);
public void recordRegisterValue(TargetRegister targetRegister, byte[] value);
public void recordRegisterValues(TargetRegisterBank bank, Map<String, byte[]> updates);
public boolean objectRemoved(TargetObject removed);
public void stateChanged(TargetExecutionState state);
public void regMapperAmended(DebuggerRegisterMapper rm, TargetRegister reg, boolean b);
public CompletableFuture<Void> doFetchAndInitRegMapper(TargetRegisterBank parent);
public ManagedStackRecorder getStackRecorder();
}

View file

@ -101,15 +101,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
private void threadAdded(TraceThread thread) {
TraceRecorder recorder = current.getRecorder();
if (supportsFocus(recorder)) {
// TODO: Same for stack frame? I can't imagine it's as common as this....
if (thread == recorder.getTraceThreadForSuccessor(recorder.getFocus())) {
activate(DebuggerCoordinates.thread(thread));
}
return;
}
if (current.getTrace() != trace) {
return;
}
if (current.getThread() != null) {
return;
}
if (supportsFocus(current.getRecorder())) {
return;
}
activate(DebuggerCoordinates.thread(thread));
}
@ -198,8 +203,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return t;
}
protected <T> CompletableFuture<T> tryHarder(Supplier<CompletableFuture<T>> action,
int retries, long retryAfterMillis) {
protected <T> CompletableFuture<T> tryHarder(Supplier<CompletableFuture<T>> action, int retries,
long retryAfterMillis) {
Executor exe = CompletableFuture.delayedExecutor(retryAfterMillis, TimeUnit.MILLISECONDS);
// NB. thenCompose(f -> f) also ensures exceptions are handled here, not passed through
CompletableFuture<T> result =
@ -478,7 +483,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
// Note, not likely we can view non-zero frame with emulated ticks
Integer frame = coordinates.getFrame();
if (frame == null) {
if (recorder != null && recorder.isSupportsFocus()) {
if (supportsFocus(recorder)) {
TraceStackFrame traceFrame = frameFromTargetFocus(recorder, focus);
if (traceFrame == null) {
Msg.warn(this,
@ -664,7 +669,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (varView != null) {
varView.setSnap(coordinates.getSnap());
}
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
Swing.runIfSwingOrRunLater(() -> {
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
});
}
@Override

View file

@ -0,0 +1,99 @@
/* ###
* 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.utils;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.Msg;
public class DefaultTransactionCoalescer<T extends UndoableDomainObject, U extends AutoCloseable>
implements TransactionCoalescer {
protected class Coalescer {
private final AsyncDebouncer<Void> debouncer =
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, delayMs);
private final U tid;
private volatile int activeCount = 0;
public Coalescer(String description) {
this.tid = factory.apply(obj, description);
debouncer.addListener(this::settled);
}
private void enter() {
++activeCount;
}
private void exit() {
if (--activeCount == 0) {
debouncer.contact(null);
}
}
private void settled(Void __) {
synchronized (lock) {
if (activeCount == 0) {
try {
tid.close();
}
catch (Exception e) {
Msg.error(this, "Could not close transaction: ", e);
}
tx = null;
}
}
}
}
public class DefaultCoalescedTx implements CoalescedTx {
protected DefaultCoalescedTx(String description) {
synchronized (lock) {
if (tx == null) {
tx = new Coalescer(description);
}
tx.enter();
}
}
@Override
public void close() {
synchronized (lock) {
tx.exit();
}
}
}
protected final Object lock = new Object();
protected final T obj;
protected final TxFactory<? super T, U> factory;
protected final int delayMs;
protected Coalescer tx;
public DefaultTransactionCoalescer(T obj, TxFactory<? super T, U> factory, int delayMs) {
this.obj = obj;
this.factory = factory;
this.delayMs = delayMs;
}
@Override
public DefaultCoalescedTx start(String description) {
return new DefaultCoalescedTx(description);
}
}

View file

@ -15,51 +15,19 @@
*/
package ghidra.app.plugin.core.debug.utils;
import java.util.Deque;
import java.util.LinkedList;
import java.util.function.BiFunction;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.framework.model.UndoableDomainObject;
public class TransactionCoalescer {
protected final Program program;
protected final AsyncDebouncer<Void> debouncer;
protected final Deque<Runnable> coalesced = new LinkedList<>();
public TransactionCoalescer(Program program, int delayWindow) {
this.program = program;
this.debouncer = new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, delayWindow);
this.debouncer.addListener(v -> processCoalesced());
public interface TransactionCoalescer {
public interface TxFactory<T extends UndoableDomainObject, U>
extends BiFunction<T, String, U> {
}
protected void processCoalesced() {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Coalesced", false)) {
while (true) {
Runnable next;
synchronized (coalesced) {
next = coalesced.poll();
}
if (next == null) {
break;
}
next.run();
}
tid.commit();
}
catch (Exception e) {
Msg.error(this, "Cancelled coalesced transaction due to exception", e);
}
// TODO: Is this really a good place for this?
program.clearUndo();
public interface CoalescedTx extends AutoCloseable {
@Override
void close();
}
public synchronized void submit(Runnable runnable) {
coalesced.offer(runnable);
debouncer.contact(null);
}
CoalescedTx start(String description);
}

View file

@ -32,9 +32,7 @@ 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
@ -128,7 +126,7 @@ public interface DebuggerModelService {
* @param target the target to record.
* @return a future which completes with the recorder, or completes exceptionally
*/
CompletableFuture<TraceRecorder> recordTargetBestOffer(TargetObject target);
TraceRecorder recordTargetBestOffer(TargetObject target);
/**
* Query mapping opinions, prompt the user, and record the given target
@ -143,7 +141,7 @@ public interface DebuggerModelService {
* @param target the target to record.
* @return a future which completes with the recorder, or completes exceptionally
*/
CompletableFuture<TraceRecorder> recordTargetPromptOffers(TargetObject target);
TraceRecorder recordTargetPromptOffers(TargetObject target);
/**
* Start and open a new trace on the given target

View file

@ -21,7 +21,7 @@ import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.plugin.core.debug.service.model.DefaultTraceRecorder.ListenerForRecord;
import ghidra.app.plugin.core.debug.service.model.TraceEventListener;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
@ -36,11 +36,11 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.task.TaskMonitor;
/**
@ -75,9 +75,9 @@ public interface TraceRecorder {
return TraceBreakpointKind.READ;
case WRITE:
return TraceBreakpointKind.WRITE;
case EXECUTE:
case HW_EXECUTE:
return TraceBreakpointKind.EXECUTE;
case SOFTWARE:
case SW_EXECUTE:
return TraceBreakpointKind.SOFTWARE;
default:
throw new AssertionError();
@ -110,9 +110,9 @@ public interface TraceRecorder {
case WRITE:
return TargetBreakpointKind.WRITE;
case EXECUTE:
return TargetBreakpointKind.EXECUTE;
return TargetBreakpointKind.HW_EXECUTE;
case SOFTWARE:
return TargetBreakpointKind.SOFTWARE;
return TargetBreakpointKind.SW_EXECUTE;
default:
throw new AssertionError();
}
@ -204,8 +204,6 @@ public interface TraceRecorder {
*/
void removeListener(TraceRecorderListener listener);
boolean isViewAtPresent(TraceProgramView view);
TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt);
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
@ -407,7 +405,7 @@ public interface TraceRecorder {
* @param thread an optional thread, or {@code null} for the process
* @return the list of collected containers, possibly empty
*/
List<TargetBreakpointContainer> collectBreakpointContainers(TargetThread thread);
List<TargetBreakpointSpecContainer> collectBreakpointContainers(TargetThread thread);
/**
* Collect effective breakpoint pertinent to the target or a given thread
@ -424,8 +422,8 @@ public interface TraceRecorder {
/**
* Get the kinds of breakpoints supported by any of the recorded breakpoint containers.
*
* This is the union of all kinds supported among all {@link TargetBreakpointContainer}s found
* applicable to the target by this recorder. Chances are, there is only one container.
* This is the union of all kinds supported among all {@link TargetBreakpointSpecContainer}s
* found applicable to the target by this recorder. Chances are, there is only one container.
*
* @return the set of supported kinds
*/
@ -478,5 +476,7 @@ public interface TraceRecorder {
* @return the listener
*/
@Internal
ListenerForRecord getListenerForRecord();
TraceEventListener getListenerForRecord();
ListenerSet<TraceRecorderListener> getListeners();
}

View file

@ -34,9 +34,9 @@ import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerService
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.dbg.model.TestDebuggerModelBuilder;
import ghidra.dbg.target.TargetBreakpointContainer;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.util.DebuggerModelTestUtils;
import ghidra.dbg.testutil.DebuggerModelTestUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
@ -131,16 +131,16 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false);
}
TargetBreakpointContainer bc1 =
TargetBreakpointSpecContainer bc1 =
waitFor(() -> Unique.assertAtMostOne(recorder1.collectBreakpointContainers(null)),
"No container");
waitOn(bc1.placeBreakpoint(mb.addr(0x00401234), Set.of(TargetBreakpointKind.SOFTWARE)));
waitOn(bc1.placeBreakpoint(mb.addr(0x00401234), Set.of(TargetBreakpointKind.SW_EXECUTE)));
waitOn(bc1.placeBreakpoint(mb.rng(0x00604321, 0x00604324),
Set.of(TargetBreakpointKind.WRITE)));
TargetBreakpointContainer bc3 =
TargetBreakpointSpecContainer bc3 =
waitFor(() -> Unique.assertAtMostOne(recorder3.collectBreakpointContainers(null)),
"No container");
waitOn(bc3.placeBreakpoint(mb.addr(0x7fac1234), Set.of(TargetBreakpointKind.SOFTWARE)));
waitOn(bc3.placeBreakpoint(mb.addr(0x7fac1234), Set.of(TargetBreakpointKind.SW_EXECUTE)));
TraceBreakpoint bpt = waitForValue(() -> Unique.assertAtMostOne(
trace3.getBreakpointManager().getBreakpointsAt(0, addr(trace3, 0x7fac1234))));

View file

@ -28,7 +28,7 @@ import ghidra.app.plugin.core.debug.gui.objects.components.*;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
import ghidra.dbg.model.*;
import ghidra.dbg.target.*;
import ghidra.dbg.util.DebuggerModelTestUtils;
import ghidra.dbg.testutil.DebuggerModelTestUtils;
import ghidra.util.Swing;
import help.screenshot.GhidraScreenShotGenerator;

View file

@ -208,7 +208,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
}
}
protected static TargetBreakpointContainer getBreakpointContainer(TraceRecorder r) {
protected static TargetBreakpointSpecContainer getBreakpointContainer(TraceRecorder r) {
return waitFor(() -> Unique.assertAtMostOne(r.collectBreakpointContainers(null)),
"No container");
}

View file

@ -45,7 +45,7 @@ import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.Enablement;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.dbg.target.TargetBreakpointContainer;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.framework.store.LockException;
import ghidra.program.disassemble.Disassembler;
@ -100,8 +100,8 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
protected void addLiveMemoryAndBreakpoint(TraceRecorder recorder)
throws InterruptedException, ExecutionException, TimeoutException {
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
TargetBreakpointContainer cont = getBreakpointContainer(recorder);
cont.placeBreakpoint(mb.addr(0x55550123), Set.of(TargetBreakpointKind.SOFTWARE))
TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
cont.placeBreakpoint(mb.addr(0x55550123), Set.of(TargetBreakpointKind.SW_EXECUTE))
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}

View file

@ -33,9 +33,10 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
import ghidra.app.services.*;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.model.TestTargetProcess;
import ghidra.dbg.target.TargetBreakpointContainer;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.Program;
@ -50,7 +51,8 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebuggerGUITest
implements AsyncTestUtils {
protected static final long TIMEOUT_MILLIS =
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
@ -84,14 +86,13 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
}
protected void addLiveBreakpoint(TraceRecorder recorder, long offset) throws Exception {
TargetBreakpointContainer cont = getBreakpointContainer(recorder);
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SOFTWARE))
TargetBreakpointSpecContainer cont = getBreakpointContainer(recorder);
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
protected void addStaticMemoryAndBreakpoint()
throws LockException, DuplicateNameException, MemoryConflictException,
AddressOverflowException, CancelledException {
protected void addStaticMemoryAndBreakpoint() throws LockException, DuplicateNameException,
MemoryConflictException, AddressOverflowException, CancelledException {
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Add bookmark break", true)) {
program.getMemory()
@ -114,7 +115,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
}
@Test
public void testAddLiveOpenTracePopulatesProvider() throws Exception {
public void testAddLiveOpenTracePopulatesProvider() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
@ -122,6 +123,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
Trace trace = recorder.getTrace();
addLiveMemoryAndBreakpoint(mb.testProcess1, recorder);
waitOn(mb.testModel.flushEvents());
waitForDomainObject(trace);
// NB, optionally open trace. Mapping only works if open...
@ -514,8 +516,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
assertEquals(2, lb2.getTraceBreakpoints().size());
});
List<LogicalBreakpointRow> breakData =
bptModel.getModelData();
List<LogicalBreakpointRow> breakData = bptModel.getModelData();
List<BreakpointLocationRow> filtLocs =
breakpointsProvider.locationFilterPanel.getTableFilterModel().getModelData();
@ -574,8 +575,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
}
public static final Set<String> POPUP_ACTIONS = Set.of(
AbstractEnableSelectedBreakpointsAction.NAME,
AbstractDisableSelectedBreakpointsAction.NAME,
AbstractEnableSelectedBreakpointsAction.NAME, AbstractDisableSelectedBreakpointsAction.NAME,
AbstractClearSelectedBreakpointsAction.NAME);
@Test
@ -590,10 +590,10 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
// NOTE: the row becomes selected by right-click
clickTableCellWithButton(breakpointsProvider.breakpointTable, 0, 0, MouseEvent.BUTTON3);
waitForSwing();
assertMenu(POPUP_ACTIONS, Set.of(
AbstractEnableSelectedBreakpointsAction.NAME,
AbstractDisableSelectedBreakpointsAction.NAME,
AbstractClearSelectedBreakpointsAction.NAME));
assertMenu(POPUP_ACTIONS,
Set.of(AbstractEnableSelectedBreakpointsAction.NAME,
AbstractDisableSelectedBreakpointsAction.NAME,
AbstractClearSelectedBreakpointsAction.NAME));
// NOTE: With no selection, no actions (even table built-in) apply, so no menu
}

View file

@ -29,7 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.interpreter.InterpreterComponentProvider;
import ghidra.dbg.model.TestTargetInterpreter.ExecuteCall;
import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.util.DebuggerModelTestUtils;
import ghidra.dbg.testutil.DebuggerModelTestUtils;
public class DebuggerInterpreterPluginTest extends AbstractGhidraHeadedDebuggerGUITest
implements DebuggerModelTestUtils {

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.gui.register;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.lifecycle.Unfinished.*;
import static org.junit.Assert.*;
import java.math.BigInteger;
@ -30,12 +30,11 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingTrackLocationAction.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncTestUtils;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
@ -47,7 +46,8 @@ import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.DuplicateNameException;
public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerGUITest
implements AsyncTestUtils {
static {
DebuggerModelServiceTest.addTestModelPathPatterns();
}
@ -151,6 +151,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
if (thread == null) {
return false;
}
/*
DebuggerRegisterMapper mapper = recorder.getRegisterMapper(thread);
if (mapper == null) {
return false;
@ -158,6 +159,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
if (!mapper.getRegistersOnTarget().containsAll(baseRegs)) {
return false;
}
*/
return true;
});
return recorder;
@ -320,15 +322,18 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
}
@Test
public void testLiveActivateThenAddValuesPopulatesPanel() throws Exception {
public void testLiveActivateThenAddValuesPopulatesPanel() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
mb.testBank1.writeRegister("pc", new byte[] { 0x00, 0x40, 0x00, 0x00 });
waitOn(mb.testModel.flushEvents());
waitForDomainObject(recorder.getTrace());
RegisterRow rowL = findRegisterRow(pc);
waitForPass(() -> assertTrue(rowL.isKnown()));
assertPCRowValuePopulated();
}
@ -396,7 +401,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
}
@Test
public void testLiveModifySubValueAffectsTarget() throws Exception {
public void testLiveModifySubValueAffectsTarget() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
Trace trace = recorder.getTrace();
traceManager.openTrace(trace);
@ -407,14 +412,12 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Pretend fetch", true)) {
TraceMemoryRegisterSpace regs =
trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
regs.setValue(0, new RegisterValue(r0, BigInteger.ZERO));
}
mb.testBank1.writeRegistersNamed(Map.of("r0", new byte[] { 0 }));
waitOn(mb.testModel.flushEvents());
waitForDomainObject(trace);
RegisterRow rowL = findRegisterRow(r0l);
assertTrue(rowL.isValueEditable());
waitForPass(() -> assertTrue(rowL.isValueEditable()));
setRowText(rowL, "05060708");
waitForSwing();
@ -656,7 +659,8 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
// TODO: It'd be nice if plugin tracked disconnected providers....
DebuggerRegistersProvider cloned =
(DebuggerRegistersProvider) tool.getActiveComponentProvider();
assertEquals("[Registers: Thread1, 0]", cloned.getTitle());
assertEquals("[Registers]", cloned.getTitle());
assertEquals("Thread1", cloned.getSubTitle());
traceManager.activateThread(thread2);
waitForSwing();

View file

@ -34,7 +34,7 @@ import ghidra.dbg.model.TestTargetMemoryRegion;
import ghidra.dbg.model.TestTargetProcess;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.util.DebuggerModelTestUtils;
import ghidra.dbg.testutil.DebuggerModelTestUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.Program;
@ -290,7 +290,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
}
protected void addTargetAccessBreakpoint(TraceRecorder r) throws Exception {
TargetBreakpointContainer cont = getBreakpointContainer(r);
TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
cont.placeBreakpoint(mb.testModel.getAddress("ram", 0x56550123),
Set.of(TargetBreakpointKind.READ, TargetBreakpointKind.WRITE))
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
@ -301,13 +301,13 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
TraceMemoryRegion textRegion =
waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region);
long offset = textRegion.getMinAddress().getOffset() + 0x0123;
TargetBreakpointContainer cont = getBreakpointContainer(r);
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SOFTWARE))
TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
cont.placeBreakpoint(mb.addr(offset), Set.of(TargetBreakpointKind.SW_EXECUTE))
.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
protected void removeTargetSoftwareBreakpoint(TraceRecorder r) throws Exception {
TargetBreakpointContainer cont = getBreakpointContainer(r);
TargetBreakpointSpecContainer cont = getBreakpointContainer(r);
cont.fetchElements().thenAccept(elements -> {
for (TargetObject obj : elements.values()) {
if (!(obj instanceof TargetBreakpointSpec) ||
@ -315,7 +315,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
continue;
}
TargetBreakpointSpec spec = (TargetBreakpointSpec) obj;
if (!spec.getKinds().contains(TargetBreakpointKind.SOFTWARE)) {
if (!spec.getKinds().contains(TargetBreakpointKind.SW_EXECUTE)) {
continue;
}
TargetDeletable del = (TargetDeletable) obj;

View file

@ -33,7 +33,9 @@ import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.model.TestDebuggerObjectModel;
import ghidra.dbg.model.TestLocalDebuggerModelFactory;
import ghidra.dbg.util.*;
import ghidra.dbg.testutil.DebuggerModelTestUtils;
import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathUtils;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.SystemUtilities;
@ -51,8 +53,43 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
protected static final long TIMEOUT_MILLIS =
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
public static final PathMatcher HARDCODED_MATCHER = new PathMatcher() {
{
// Paths for GDB
addPattern(PathUtils.parse("Breakpoints[]."));
addPattern(PathUtils.parse("Inferiors[].Memory[]"));
addPattern(PathUtils.parse("Inferiors[].Modules[].Sections[]"));
addPattern(PathUtils.parse("Inferiors[].Registers[]"));
addPattern(PathUtils.parse("Inferiors[].Threads[]"));
addPattern(PathUtils.parse("Inferiors[].Threads[].Stack[]"));
// Paths for dbgeng
addPattern(PathUtils.parse("Sessions[].Processes[].Memory[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Modules[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Registers[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Stack[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Debug.Breakpoints[]"));
// (Additional) paths for dbgmodel
addPattern(PathUtils.parse("Sessions[].Attributes"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Stack.Frames[]"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].TTD.Position"));
addPattern(PathUtils.parse("Sessions[].Processes[].Threads[].Registers.User."));
// Paths for JDI
addPattern(PathUtils.parse("VirtualMachines[]"));
addPattern(PathUtils.parse("VirtualMachines[].Breakpoints"));
addPattern(PathUtils.parse("VirtualMachines[].Classes[]"));
addPattern(PathUtils.parse("VirtualMachines[].Classes[].Sections[]"));
addPattern(PathUtils.parse("VirtualMachines[].Threads[]"));
addPattern(PathUtils.parse("VirtualMachines[].Threads[].Registers[]"));
addPattern(PathUtils.parse("VirtualMachines[].Threads[].Stack[]"));
}
};
public static void addTestModelPathPatterns() {
PathMatcher m = DefaultTraceRecorder.HARDCODED_MATCHER;
PathMatcher m = HARDCODED_MATCHER;
m.addPattern(PathUtils.parse("Processes[]"));
m.addPattern(PathUtils.parse("Processes[].Breakpoints[]"));
m.addPattern(PathUtils.parse("Processes[].Memory[]"));
@ -84,8 +121,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes
void elementRemoved(E element);
}
static class CollectionChangeDelegateWrapper<E>
implements CollectionChangeListener<E> {
static class CollectionChangeDelegateWrapper<E> implements CollectionChangeListener<E> {
protected final CollectionChangeDelegate<E> delegate;
public CollectionChangeDelegateWrapper(CollectionChangeDelegate<E> delegate) {

View file

@ -22,16 +22,20 @@ import java.util.Map.Entry;
import org.junit.Test;
import com.google.common.collect.Range;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetMemoryRegion;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.dbg.util.PathUtils;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.listing.TraceCodeRegisterSpace;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
@ -106,13 +110,14 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
Language lang = trace.getBaseLanguage();
Register r0 = lang.getRegister("r0");
Register r1 = lang.getRegister("r1");
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
//TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
TraceThread thread = recorder.getTraceThread(mb.testThread1);
TraceMemoryRegisterSpace rs = createRegSpace(thread);
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
waitForCondition(() -> registerMapped(recorder, thread, r0));
//waitForCondition(() -> registerMapped(recorder, thread, r0));
regs.writeRegister("r0", tb.arr(1)).get();
waitForPass(() -> {
@ -145,12 +150,16 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
r -> r.isBaseRegister() && r != pc && r != sp);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type", true)) {
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
code.definedData().create(Range.atLeast(0L), pc, PointerDataType.dataType);
}
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), pc.getAddress()));
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), sp.getAddress()));
mb.testProcess1.regs.addRegister(pc);
waitForCondition(() -> registerMapped(recorder, thread, pc));
//waitForCondition(() -> registerMapped(recorder, thread, pc));
regs.writeRegister("pc", tb.arr(0x55, 0x55, 0x01, 0x23));
waitForPass(() -> {
@ -190,12 +199,16 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
r -> r.isBaseRegister() && r != pc && r != sp);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add SP type", true)) {
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
code.definedData().create(Range.atLeast(0L), sp, PointerDataType.dataType);
}
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), pc.getAddress()));
assertNull(rs.getMostRecentStateEntry(recorder.getSnap(), sp.getAddress()));
mb.testProcess1.regs.addRegister(sp);
waitForCondition(() -> registerMapped(recorder, thread, sp));
//waitForCondition(() -> registerMapped(recorder, thread, sp));
regs.writeRegister("sp", tb.arr(0x22, 0x22, 0x03, 0x21));
waitForPass(() -> {
@ -228,13 +241,17 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
Trace trace = recorder.getTrace();
Language lang = trace.getBaseLanguage();
Register pc = lang.getRegister("pc");
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550123, 0x55550321), "rx");
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
waitForCondition(() -> registerMapped(recorder, thread, pc));
//waitForCondition(() -> registerMapped(recorder, thread, pc));
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type", true)) {
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
code.definedData().create(Range.atLeast(0L), pc, PointerDataType.dataType);
}
regs.writeRegister("pc", tb.arr(0x55, 0x55, 0x02, 0x22));
TraceMemoryManager mm = trace.getMemoryManager();

View file

@ -28,7 +28,7 @@ import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.dbg.util.DebuggerModelTestUtils;
import ghidra.dbg.testutil.DebuggerModelTestUtils;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;