Merge remote-tracking branch 'origin/GP-2163_Dan_dbgChoosePlatformMenu--SQUASHED'

This commit is contained in:
Ryan Kurtz
2022-08-12 17:05:26 -04:00
38 changed files with 1555 additions and 134 deletions
@@ -97,6 +97,8 @@ src/main/help/help/topics/DebuggerObjectsPlugin/images/stepover.png||GHIDRA||||E
src/main/help/help/topics/DebuggerObjectsPlugin/images/stop.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerPcodeStepperPlugin/images/DebuggerPcodeStepperPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionMapProposalDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionsPlugin.png||GHIDRA||||END|
@@ -169,6 +169,10 @@
sortgroup="p1"
target="help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html" />
<tocdef id="DebuggerPlatformPlugin" text="Platform Selection"
sortgroup="p2"
target="help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html" />
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
sortgroup="q"
target="help/topics/DebuggerBots/DebuggerBots.html" />
@@ -0,0 +1,69 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Platform Selection</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Platform Selection</H1>
<P>The Debugger allows the user to work with multiple platforms, i.e., architectures,
compilers, etc., within the context of a single debug target. This plugin allows the user to
switch among platforms and/or introduce new platforms via overrides.</P>
<P>Note we are in a transitional period between two trace database conventions. This plugin is
a necessary component when working with the new convention. In the legacy convention, the
system must ascertain the target's language and compiler at the time it begins recording. If it
fails or makes an incorrect decision, the UI remains mostly empty, or worse, the system
interrogates and instructs the target using the wrong architecture. This mode still works in
most circumstances and is the default and recommended convention. Additional platforms can
still be introduced, but there is perhaps little use for doing so. One case might be to, e.g.,
disassemble JVM bytecodes when debugging a Java process at the native level.</P>
<P>In the new convention, the system always records the target, using the "DATA" language,
i.e., no language. The object tree is recorded, and objects with memory are assigned overlay
spaces where their contents are recorded. The only way to disassemble instructions is to add a
guest platform. This plugin will do that automatically, in more or less the same fashion as the
language and compiler are ascertained in legacy mode. If this fails, the recording still
proceeds, and most of the UI is still useful. The user can add (perhaps by trial and error) the
correct platform later. In the meantime, memory, registers, threads, etc., are all still
displayed.</P>
<H2>Actions</H2>
<P>This plugin adds actions into the Debugger menu under "Choose Platform."</P>
<H3><A name="choose_platform"></A>Choose Platform</H3>
<P>This action adds or changes to a specific platform in the current trace. One of these
actions is presented for each recommended or previously chosen platform, if any. The
recommendations are given by an opinion service, so new options may be added by extension
modules. It is possible there are no recommendations for the current trace. The current
platform is designated by a check mark or other selection indicator.</P>
<H3><A name="choose_more_platforms"></A><A name="prev_diff"></A>More...</H3>
<P>This action is enabled whenever there is a current trace. It presents a dialog with the
recommended platforms for the trace.</P>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/DebuggerSelectPlatformOfferDialog.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>The "Show Only Recommended Offers" check can be disabled to display override offers as well.
Selecting an offer and confirming the dialog will add or change to the selected platform in the
trace. Furthermore, the choice will be added to the "Choose Platform" menu until the trace is
closed.</P>
</BODY>
</HTML>
@@ -31,6 +31,10 @@ import ghidra.trace.database.DBTraceContentHandler;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.stack.TraceObjectStackFrame;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
@@ -40,13 +44,9 @@ import ghidra.util.Msg;
import ghidra.util.NotOwnerException;
public class DebuggerCoordinates {
public static final DebuggerCoordinates NOWHERE =
new DebuggerCoordinates(null, null, null, null, TraceSchedule.ZERO, 0) {
@Override
public void writeDataState(PluginTool tool, SaveState saveState, String key) {
// Write nothing
}
};
new DebuggerCoordinates(null, null, null, null, TraceSchedule.ZERO, 0, null);
private static final String KEY_TRACE_PROJ_LOC = "TraceProjLoc";
private static final String KEY_TRACE_PROJ_NAME = "TraceProjName";
@@ -55,35 +55,38 @@ public class DebuggerCoordinates {
private static final String KEY_THREAD_KEY = "ThreadKey";
private static final String KEY_TIME = "Time";
private static final String KEY_FRAME = "Frame";
private static final String KEY_OBJ_PATH = "ObjectPath";
public static DebuggerCoordinates all(Trace trace, TraceRecorder recorder, TraceThread thread,
TraceProgramView view, TraceSchedule time, Integer frame) {
TraceProgramView view, TraceSchedule time, Integer frame, TraceObject object) {
if (trace == NOWHERE.trace && recorder == NOWHERE.recorder && thread == NOWHERE.thread &&
view == NOWHERE.view && time == NOWHERE.time && frame == NOWHERE.frame) {
view == NOWHERE.view && time == NOWHERE.time && frame == NOWHERE.frame &&
object == NOWHERE.object) {
return NOWHERE;
}
return new DebuggerCoordinates(trace, recorder, thread, view, time, frame);
return new DebuggerCoordinates(trace, recorder, thread, view, time, frame, object);
}
public static DebuggerCoordinates trace(Trace trace) {
if (trace == null) {
return NOWHERE;
}
return all(trace, null, null, null, null, null);
return all(trace, null, null, null, null, null, null);
}
public static DebuggerCoordinates recorder(TraceRecorder recorder) {
return all(recorder == null ? null : recorder.getTrace(), recorder,
null, null, recorder == null ? null : TraceSchedule.snap(recorder.getSnap()), null);
null, null, recorder == null ? null : TraceSchedule.snap(recorder.getSnap()), null,
null);
}
public static DebuggerCoordinates thread(TraceThread thread) {
return all(thread == null ? null : thread.getTrace(), null, thread,
null, null, null);
return all(thread == null ? null : thread.getTrace(), null, thread, null, null, null, null);
}
public static DebuggerCoordinates rawView(TraceProgramView view) {
return all(view.getTrace(), null, null, view, TraceSchedule.snap(view.getSnap()), null);
return all(view.getTrace(), null, null, view, TraceSchedule.snap(view.getSnap()), null,
null);
}
public static DebuggerCoordinates view(TraceProgramView view) {
@@ -107,7 +110,7 @@ public class DebuggerCoordinates {
}
public static DebuggerCoordinates snap(long snap) {
return all(null, null, null, null, TraceSchedule.snap(snap), null);
return all(null, null, null, null, TraceSchedule.snap(snap), null, null);
}
public static DebuggerCoordinates time(String time) {
@@ -115,16 +118,31 @@ public class DebuggerCoordinates {
}
public static DebuggerCoordinates time(TraceSchedule time) {
return all(null, null, null, null, time, null);
return all(null, null, null, null, time, null, null);
}
public static DebuggerCoordinates frame(int frame) {
return all(null, null, null, null, null, frame);
return all(null, null, null, null, null, frame, null);
}
public static DebuggerCoordinates object(TraceObject object) {
if (object == null) {
return NOWHERE;
}
TraceObjectThread thread = object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
.findFirst()
.orElse(null);
TraceObjectStackFrame frame =
object.queryCanonicalAncestorsInterface(TraceObjectStackFrame.class)
.findFirst()
.orElse(null);
return all(object.getTrace(), null, thread, null, null,
frame == null ? null : frame.getLevel(), object);
}
public static DebuggerCoordinates threadSnap(TraceThread thread, long snap) {
return all(thread == null ? null : thread.getTrace(), null, thread, null,
TraceSchedule.snap(snap), null);
TraceSchedule.snap(snap), null, null);
}
public static boolean equalsIgnoreRecorderAndView(DebuggerCoordinates a,
@@ -150,6 +168,7 @@ public class DebuggerCoordinates {
private final TraceProgramView view;
private final TraceSchedule time;
private final Integer frame;
private final TraceObject object;
private final int hash;
@@ -157,22 +176,23 @@ public class DebuggerCoordinates {
private DefaultTraceTimeViewport viewport;
protected DebuggerCoordinates(Trace trace, TraceRecorder recorder, TraceThread thread,
TraceProgramView view, TraceSchedule time, Integer frame) {
TraceProgramView view, TraceSchedule time, Integer frame, TraceObject object) {
this.trace = trace;
this.recorder = recorder;
this.thread = thread;
this.view = view;
this.time = time;
this.frame = frame;
this.object = object;
this.hash = Objects.hash(trace, recorder, thread, view, time, frame);
this.hash = Objects.hash(trace, recorder, thread, view, time, frame, object);
}
@Override
public String toString() {
return String.format(
"Coords(trace=%s,recorder=%s,thread=%s,view=%s,time=%s,frame=%d)",
trace, recorder, thread, view, time, frame);
"Coords(trace=%s,recorder=%s,thread=%s,view=%s,time=%s,frame=%d,object=%d)",
trace, recorder, thread, view, time, frame, object);
}
@Override
@@ -199,6 +219,10 @@ public class DebuggerCoordinates {
if (!Objects.equals(this.frame, that.frame)) {
return false;
}
if (!Objects.equals(this.object, that.object)) {
return false;
}
return true;
}
@@ -216,7 +240,7 @@ public class DebuggerCoordinates {
}
public DebuggerCoordinates withRecorder(TraceRecorder newRecorder) {
return all(trace, newRecorder, thread, view, time, frame);
return all(trace, newRecorder, thread, view, time, frame, object);
}
public TraceThread getThread() {
@@ -235,7 +259,7 @@ public class DebuggerCoordinates {
}
public DebuggerCoordinates withThread(TraceThread newThread) {
return all(trace, recorder, newThread, view, time, frame);
return all(trace, recorder, newThread, view, time, frame, object);
}
public TraceProgramView getView() {
@@ -254,15 +278,19 @@ public class DebuggerCoordinates {
*/
public DebuggerCoordinates withSnap(Long newSnap) {
return all(trace, recorder, thread, view,
newSnap == null ? time : TraceSchedule.snap(newSnap), frame);
newSnap == null ? time : TraceSchedule.snap(newSnap), frame, object);
}
public DebuggerCoordinates withTime(TraceSchedule newTime) {
return all(trace, recorder, thread, view, newTime, frame);
return all(trace, recorder, thread, view, newTime, frame, object);
}
public DebuggerCoordinates withView(TraceProgramView newView) {
return all(trace, recorder, thread, newView, time, frame);
return all(trace, recorder, thread, newView, time, frame, object);
}
public DebuggerCoordinates withObject(TraceObject newObject) {
return all(trace, recorder, thread, view, time, frame, newObject);
}
public TraceSchedule getTime() {
@@ -273,6 +301,10 @@ public class DebuggerCoordinates {
return frame;
}
public TraceObject getObject() {
return object;
}
public synchronized long getViewSnap() {
if (viewSnap != null) {
return viewSnap;
@@ -304,6 +336,9 @@ public class DebuggerCoordinates {
}
public void writeDataState(PluginTool tool, SaveState saveState, String key) {
if (this == NOWHERE) {
return;
}
SaveState coordState = new SaveState();
// for NOWHERE, key should be completely omitted
if (trace != null) {
@@ -410,9 +445,21 @@ public class DebuggerCoordinates {
if (coordState.hasValue(KEY_FRAME)) {
frame = coordState.getInt(KEY_FRAME, 0);
}
TraceObject object = null;
if (trace != null && coordState.hasValue(KEY_OBJ_PATH)) {
String pathString = coordState.getString(KEY_OBJ_PATH, "");
try {
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathString);
object = trace.getObjectManager().getObjectByCanonicalPath(path);
}
catch (Exception e) {
Msg.error(DebuggerCoordinates.class, "Could not restore object: " + pathString, e);
object = trace.getObjectManager().getRootObject();
}
}
DebuggerCoordinates coords =
DebuggerCoordinates.all(trace, null, thread, null, time, frame);
DebuggerCoordinates.all(trace, null, thread, null, time, frame, object);
if (!resolve) {
return coords;
}
@@ -0,0 +1,42 @@
/* ###
* 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.event;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.framework.plugintool.PluginEvent;
import ghidra.trace.model.Trace;
public class DebuggerPlatformPluginEvent extends PluginEvent {
static final String NAME = "Platform";
private final Trace trace;
private final DebuggerPlatformMapper mapper;
public DebuggerPlatformPluginEvent(String sourceName, Trace trace,
DebuggerPlatformMapper mapper) {
super(sourceName, NAME);
this.trace = trace;
this.mapper = mapper;
}
public Trace getTrace() {
return trace;
}
public DebuggerPlatformMapper getMapper() {
return mapper;
}
}
@@ -758,7 +758,7 @@ public interface DebuggerResources {
String HELP_ANCHOR = "disconnect_all";
public static ActionBuilder builder(Plugin owner, Plugin helpOwner) {
return new ActionBuilder(owner.getName(), NAME)
return new ActionBuilder(NAME, owner.getName())
.description(DESCRIPTION)
.menuIcon(ICON)
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
@@ -773,7 +773,7 @@ public interface DebuggerResources {
public static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(ownerName, NAME)
return new ToggleActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
@@ -788,7 +788,7 @@ public interface DebuggerResources {
public static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(ownerName, NAME)
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.keyBinding("CTRL I")
@@ -805,7 +805,7 @@ public interface DebuggerResources {
public static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(ownerName, NAME)
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuPath(DebuggerPluginPackage.NAME, NAME)
.menuGroup(GROUP)
@@ -2377,4 +2377,12 @@ public interface DebuggerResources {
}
}
String NAME_CHOOSE_PLATFORM = "Choose Platform";
String DESCRIPTION_CHOOSE_PLATFORM = "Choose a platform to use with the current trace";
String NAME_CHOOSE_MORE_PLATFORMS = "Choose More Platforms";
String TITLE_CHOOSE_MORE_PLATFORMS = "More...";
String DESCRIPTION_CHOOSE_MORE_PLATFORMS =
"Choose from more platforms to use with the current trace";
}
@@ -0,0 +1,337 @@
/* ###
* 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.gui.platform;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.event.ChangeListener;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.AutoService.Wiring;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.util.HelpLocation;
import ghidra.util.classfinder.ClassSearcher;
@PluginInfo(
shortDescription = "Debugger platform selection GUI",
description = "GUI to add, edit, and remove trace platforms",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
DebuggerPlatformPluginEvent.class,
},
servicesRequired = {
DebuggerPlatformService.class,
})
public class DebuggerPlatformPlugin extends Plugin {
protected interface ChoosePlatformAction {
String NAME = DebuggerResources.NAME_CHOOSE_PLATFORM;
String DESCRIPTION = DebuggerResources.DESCRIPTION_CHOOSE_PLATFORM;
String GROUP = DebuggerResources.GROUP_MAPPING;
String HELP_ANCHOR = "choose_platform";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
protected interface ChooseMorePlatformsActon {
String NAME = DebuggerResources.NAME_CHOOSE_MORE_PLATFORMS;
String TITLE = DebuggerResources.TITLE_CHOOSE_MORE_PLATFORMS;
String DESCRIPTION = DebuggerResources.DESCRIPTION_CHOOSE_MORE_PLATFORMS;
String GROUP = "zzzz";
String HELP_ANCHOR = "choose_more_platforms";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuPath(DebuggerPluginPackage.NAME, ChoosePlatformAction.NAME, TITLE)
.menuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false;
}
if (!Objects.equals(a.getTime(), b.getTime())) {
return false;
}
if (!Objects.equals(a.getObject(), b.getObject())) {
return false;
}
return true;
}
protected class PlatformActionSet {
private final Trace trace;
private DebuggerCoordinates current;
private final Map<DebuggerPlatformOffer, ToggleDockingAction> actions =
new LinkedHashMap<>();
public PlatformActionSet(Trace trace) {
this.trace = trace;
this.current = traceManager.getCurrentFor(trace);
}
protected Set<DebuggerPlatformOffer> computePlatformOffers(boolean includeOverrides) {
return new LinkedHashSet<>(
DebuggerPlatformOpinion.queryOpinions(trace, current.getObject(), 0,
includeOverrides));
}
protected ToggleDockingAction createActionChoosePlatform(DebuggerPlatformOffer offer) {
ToggleDockingAction action = ChoosePlatformAction.builder(DebuggerPlatformPlugin.this)
.menuPath(DebuggerPluginPackage.NAME, ChoosePlatformAction.NAME,
offer.getDescription())
.onAction(ctx -> activatePlatform(offer))
.build();
String[] path = action.getMenuBarData().getMenuPath();
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), ChoosePlatformAction.GROUP);
return action;
}
protected void activatePlatform(DebuggerPlatformOffer offer) {
platformService.setCurrentMapperFor(trace, offer.take(tool, trace), current.getSnap());
}
protected void cleanOffers() {
actions.keySet().retainAll(computePlatformOffers(true));
}
protected void addPreferredOffers() {
for (DebuggerPlatformOffer offer : computePlatformOffers(false)) {
addOfferAction(offer);
}
}
protected void updatePlatformOffers() {
cleanOffers();
addPreferredOffers();
}
protected ToggleDockingAction addOfferAction(DebuggerPlatformOffer offer) {
return actions.computeIfAbsent(offer, this::createActionChoosePlatform);
}
protected void addChosenOffer(DebuggerPlatformOffer offer) {
ToggleDockingAction action = addOfferAction(offer);
// NB. PluginEvent will cause selections to update
if (currentTrace == trace) {
tool.addAction(action);
}
}
protected void installActions() {
for (ToggleDockingAction action : actions.values()) {
tool.addAction(action);
}
}
protected void uninstallActions() {
for (ToggleDockingAction action : actions.values()) {
tool.removeAction(action);
}
}
protected void coordinatesActivated(DebuggerCoordinates coordinates) {
if (sameCoordinates(current, coordinates)) {
current = coordinates;
return;
}
current = coordinates;
updatePlatformOffers();
}
protected void mapperActivated(DebuggerPlatformMapper mapper) {
for (Entry<DebuggerPlatformOffer, ToggleDockingAction> ent : actions.entrySet()) {
DebuggerPlatformOffer offer = ent.getKey();
ToggleDockingAction action = ent.getValue();
action.setSelected(mapper != null && offer.isCreatorOf(mapper));
}
}
}
@AutoServiceConsumed
DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
DebuggerPlatformService platformService;
@SuppressWarnings("unused")
private final Wiring autoServiceWiring;
private Trace currentTrace;
private final ChangeListener classChangeListener = evt -> this.classesChanged();
protected final DebuggerSelectPlatformOfferDialog offerDialog =
new DebuggerSelectPlatformOfferDialog();
final Map<Trace, PlatformActionSet> actionsChoosePlatform = new WeakHashMap<>();
DockingAction actionMore;
public DebuggerPlatformPlugin(PluginTool tool) {
super(tool);
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
ClassSearcher.addChangeListener(classChangeListener);
createActions();
}
protected void installActions() {
if (currentTrace == null) {
return;
}
PlatformActionSet actions =
actionsChoosePlatform.computeIfAbsent(currentTrace, PlatformActionSet::new);
actions.updatePlatformOffers();
actions.mapperActivated(platformService.getCurrentMapperFor(currentTrace));
actions.installActions();
}
protected void uninstallActions() {
PlatformActionSet actions = actionsChoosePlatform.get(currentTrace);
if (actions != null) {
actions.uninstallActions();
}
}
protected void createActions() {
installActions();
actionMore = ChooseMorePlatformsActon.builder(this)
.enabledWhen(ctx -> currentTrace != null)
.onAction(this::activatedChooseMore)
.buildAndInstall(tool);
String[] path = actionMore.getMenuBarData().getMenuPath();
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), ChoosePlatformAction.GROUP);
tool.contextChanged(null);
}
private void activatedChooseMore(ActionContext ctx) {
if (platformService == null) {
// Still initializing or finalizing
return;
}
// Sort of a backwards way to retrieve the current coordinates....
PlatformActionSet actions = actionsChoosePlatform.get(currentTrace);
if (actions == null) {
return;
}
DebuggerCoordinates current = actions.current;
Trace trace = current.getTrace();
TraceObject object = current.getObject();
long snap = current.getSnap();
DebuggerPlatformOffer offer = chooseOffer(trace, object, snap);
// Dialog allows Swing to do other things, so re-check platformService
if (offer != null && platformService != null) {
actions.addChosenOffer(offer);
platformService.setCurrentMapperFor(trace, offer.take(tool, trace), snap);
// NOTE: DebuggerPlatformPluginEvent will cause selection change
}
}
private void classesChanged() {
uninstallActions();
for (PlatformActionSet actions : actionsChoosePlatform.values()) {
actions.updatePlatformOffers();
}
installActions();
}
protected void coordinatesActivated(DebuggerCoordinates coordinates) {
uninstallActions();
this.currentTrace = coordinates.getTrace();
installActions();
tool.contextChanged(null);
}
protected void traceClosed(Trace trace) {
if (trace == currentTrace) {
coordinatesActivated(DebuggerCoordinates.NOWHERE);
}
actionsChoosePlatform.remove(trace);
}
protected void mapperActivated(Trace trace, DebuggerPlatformMapper mapper) {
if (trace != currentTrace) {
return;
}
PlatformActionSet actions = actionsChoosePlatform.get(trace);
if (actions != null) {
actions.mapperActivated(mapper);
}
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent evt) {
coordinatesActivated(evt.getActiveCoordinates());
}
if (event instanceof TraceClosedPluginEvent evt) {
traceClosed(evt.getTrace());
}
if (event instanceof DebuggerPlatformPluginEvent evt) {
mapperActivated(evt.getTrace(), evt.getMapper());
}
}
/**
* Display a dialog for the user to manually select an offer for the given object
*
* @param object the object for which an offer is desired
* @param snap the snap, usually the current snap
* @return the offer, or null if the dialog was cancelled
*/
protected DebuggerPlatformOffer chooseOffer(Trace trace, TraceObject object, long snap) {
List<DebuggerPlatformOffer> offers =
DebuggerPlatformOpinion.queryOpinions(trace, object, snap, true);
offerDialog.setOffers(offers);
tool.showDialog(offerDialog);
if (offerDialog.isCancelled()) {
return null;
}
return offerDialog.getSelectedOffer();
}
}
@@ -0,0 +1,340 @@
/* ###
* 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.gui.platform;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.*;
import java.util.function.Function;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
protected enum OfferTableColumns
implements EnumeratedTableColumn<OfferTableColumns, DebuggerPlatformOffer> {
CONFIDENCE("Confidence", Integer.class, DebuggerPlatformOffer::getConfidence, SortDirection.DESCENDING),
PROCESSOR("Processor", String.class, OfferTableColumns::getProcessor),
VARIANT("Variant", String.class, OfferTableColumns::getVariant),
SIZE("Size", Integer.class, OfferTableColumns::getSize),
ENDIAN("Endian", Endian.class, OfferTableColumns::getEndian),
COMPILER("Compiler", CompilerSpecID.class, DebuggerPlatformOffer::getCompilerSpecID);
private static final LanguageService LANG_SERV =
DefaultLanguageService.getLanguageService();
private static String getProcessor(DebuggerPlatformOffer offer) {
if (offer.getLanguageID() == null) {
return "Unspecified";
}
try {
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getProcessor().toString();
}
catch (LanguageNotFoundException e) {
return "Not Found";
}
}
private static String getVariant(DebuggerPlatformOffer offer) {
if (offer.getLanguageID() == null) {
return null;
}
try {
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getVariant();
}
catch (LanguageNotFoundException e) {
return "Not Found";
}
}
private static int getSize(DebuggerPlatformOffer offer) {
try {
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getSize();
}
catch (LanguageNotFoundException e) {
return 0;
}
}
private static Endian getEndian(DebuggerPlatformOffer offer) {
try {
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getEndian();
}
catch (LanguageNotFoundException e) {
return null;
}
}
private final String header;
private final Class<?> cls;
private final Function<DebuggerPlatformOffer, ?> getter;
private final SortDirection sortDir;
<T> OfferTableColumns(String header, Class<T> cls,
Function<DebuggerPlatformOffer, T> getter, SortDirection sortDir) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.sortDir = sortDir;
}
<T> OfferTableColumns(String header, Class<T> cls,
Function<DebuggerPlatformOffer, T> getter) {
this(header, cls, getter, SortDirection.ASCENDING);
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(DebuggerPlatformOffer row) {
return getter.apply(row);
}
@Override
public String getHeader() {
return header;
}
@Override
public SortDirection defaultSortDirection() {
return sortDir;
}
}
public static class OfferTableModel
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerPlatformOffer> {
public OfferTableModel() {
super("Offers", OfferTableColumns.class);
}
@Override
public List<OfferTableColumns> defaultSortOrder() {
return List.of(OfferTableColumns.CONFIDENCE, OfferTableColumns.PROCESSOR,
OfferTableColumns.VARIANT, OfferTableColumns.COMPILER);
}
}
public static class OfferPanel extends JPanel {
private final OfferTableModel offerTableModel = new OfferTableModel();
private final GhidraTable offerTable = new GhidraTable(offerTableModel);
private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel =
new GhidraTableFilterPanel<>(offerTable, offerTableModel);
private final JLabel descLabel = new JLabel();
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
private final JScrollPane scrollPane = new JScrollPane(offerTable) {
@Override
public Dimension getPreferredSize() {
Dimension pref = super.getPreferredSize();
if (pref.width != 0) {
pref.height = 150;
}
return pref;
}
};
private final TableFilter<DebuggerPlatformOffer> filterRecommended = new TableFilter<>() {
@Override
public boolean acceptsRow(DebuggerPlatformOffer offer) {
return !offer.isOverride();
}
@Override
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
return false;
}
};
private LanguageID preferredLangID;
private CompilerSpecID preferredCsID;
{
JPanel descPanel = new JPanel(new BorderLayout());
descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
descPanel.add(descLabel, BorderLayout.CENTER);
JPanel nested1 = new JPanel(new BorderLayout());
nested1.add(scrollPane, BorderLayout.CENTER);
nested1.add(offerTableFilterPanel, BorderLayout.SOUTH);
JPanel nested2 = new JPanel(new BorderLayout());
nested2.add(nested1, BorderLayout.CENTER);
nested2.add(descPanel, BorderLayout.SOUTH);
setLayout(new BorderLayout());
add(nested2, BorderLayout.CENTER);
add(overrideCheckBox, BorderLayout.SOUTH);
setFilterRecommended(true);
offerTable.getSelectionModel().addListSelectionListener(e -> {
DebuggerPlatformOffer offer = getSelectedOffer();
descLabel.setText(offer == null ? "" : offer.getDescription());
});
overrideCheckBox.addActionListener(evt -> {
setFilterRecommended(overrideCheckBox.isSelected());
});
}
public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
this.preferredLangID = langID;
this.preferredCsID = csID;
}
public void setOffers(Collection<DebuggerPlatformOffer> offers) {
offerTableModel.clear();
offerTableModel.addAll(offers);
selectPreferred();
}
private void selectPreferred() {
// As sorted and filtered, pick the first matching offer
// NB. It should never be one or the other. Always both or none.
RowObjectFilterModel<DebuggerPlatformOffer> model =
offerTableFilterPanel.getTableFilterModel();
int count = model.getRowCount();
if (preferredLangID != null && preferredCsID != null) {
for (int i = 0; i < count; i++) {
DebuggerPlatformOffer offer = model.getRowObject(i);
if (offer.getLanguageID().equals(preferredLangID) &&
offer.getCompilerSpecID().equals(preferredCsID)) {
offerTable.getSelectionModel().setSelectionInterval(i, i);
return;
}
}
}
// Fall back to first offer; disregard preference
if (model.getRowCount() > 0) {
offerTable.getSelectionModel().setSelectionInterval(0, 0);
}
}
public void setFilterRecommended(boolean recommendedOnly) {
boolean hasSelection = offerTableFilterPanel.getSelectedItem() != null;
overrideCheckBox.setSelected(recommendedOnly);
offerTableFilterPanel.setSecondaryFilter(recommendedOnly ? filterRecommended : null);
if (!hasSelection) {
selectPreferred();
}
}
public void setSelectedOffer(DebuggerPlatformOffer offer) {
offerTableFilterPanel.setSelectedItem(offer);
}
public DebuggerPlatformOffer getSelectedOffer() {
return offerTableFilterPanel.getSelectedItem();
}
// For tests
public List<DebuggerPlatformOffer> getDisplayedOffers() {
return List.copyOf(offerTableFilterPanel.getTableFilterModel().getModelData());
}
}
private final OfferPanel offerPanel = new OfferPanel();
private boolean isCancelled = false;
protected DebuggerSelectPlatformOfferDialog() {
super(DebuggerResources.NAME_CHOOSE_PLATFORM, Set.of(Opt.MODAL, Opt.INCLUDE_BUTTONS));
populateComponents();
}
protected void populateComponents() {
offerPanel.setBorder(BorderFactory.createTitledBorder(" Select Platform "));
addWorkPanel(offerPanel);
addOKButton();
addCancelButton();
setDefaultButton(okButton);
setOkEnabled(false);
// TODO: Separate this a bit
offerPanel.offerTable.getSelectionModel().addListSelectionListener(e -> {
setOkEnabled(getSelectedOffer() != null);
});
}
/**
* Set the preferred language and compiler spec IDs, typically from the current program.
*
* <p>
* This must be called before {@link #setOffers(Collection)}.
*
* @param langID the preferred language
* @param csID the preferred compiler spec (ABI)
*/
public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
offerPanel.setPreferredIDs(langID, csID);
}
public void setOffers(Collection<DebuggerPlatformOffer> offers) {
offerPanel.setOffers(offers);
}
public boolean isCancelled() {
return isCancelled;
}
public void setSelectedOffer(DebuggerPlatformOffer offer) {
offerPanel.setSelectedOffer(offer);
}
public DebuggerPlatformOffer getSelectedOffer() {
return offerPanel.getSelectedOffer();
}
// For tests
protected List<DebuggerPlatformOffer> getDisplayedOffers() {
return offerPanel.getDisplayedOffers();
}
protected void setFilterRecommended(boolean recommendedOnly) {
offerPanel.setFilterRecommended(recommendedOnly);
}
@Override
protected void cancelCallback() {
isCancelled = true;
super.cancelCallback();
}
@Override
protected void okCallback() {
if (getSelectedOffer() != null) {
isCancelled = false;
close();
}
// Do nothing. Should be disabled anyway
}
}
@@ -64,8 +64,6 @@ public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatform
return Set.of();
}
protected abstract CompilerSpec getCompilerSpec(TraceObject object);
@Override
public DisassemblyResult disassemble(TraceThread thread, TraceObject object,
Address start, AddressSetView restricted, long snap, TaskMonitor monitor) {
@@ -15,15 +15,21 @@
*/
package ghidra.app.plugin.core.debug.mapping;
import java.util.Objects;
import ghidra.program.model.lang.CompilerSpec;
public abstract class AbstractDebuggerPlatformOffer implements DebuggerPlatformOffer {
private final String description;
protected final CompilerSpec cSpec;
private final int hash;
public AbstractDebuggerPlatformOffer(String description, CompilerSpec cSpec) {
this.description = description;
this.cSpec = cSpec;
this.hash = Objects.hash(description, cSpec);
}
@Override
@@ -35,4 +41,27 @@ public abstract class AbstractDebuggerPlatformOffer implements DebuggerPlatformO
public CompilerSpec getCompilerSpec() {
return cSpec;
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
AbstractDebuggerPlatformOffer that = (AbstractDebuggerPlatformOffer) obj;
if (!Objects.equals(this.description, that.description)) {
return false;
}
if (!Objects.equals(this.cSpec, that.cSpec)) {
return false;
}
return true;
}
}
@@ -24,19 +24,20 @@ import ghidra.trace.model.target.TraceObject;
public abstract class AbstractDebuggerPlatformOpinion implements DebuggerPlatformOpinion {
protected abstract Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap,
TraceObject env, String debugger, String arch, String os, Endian endian);
TraceObject env, String debugger, String arch, String os, Endian endian,
boolean includeOverrides);
@Override
public Set<DebuggerPlatformOffer> getOffers(Trace trace, TraceObject object, long snap,
boolean includeOverrides) {
TraceObject env = DebuggerPlatformOpinion.getEnvironment(object, snap);
if (env == null) {
return getOffers(object, snap, env, null, null, null, null);
return getOffers(object, snap, env, null, null, null, null, includeOverrides);
}
String debugger = DebuggerPlatformOpinion.getDebugggerFromEnv(env, snap);
String arch = DebuggerPlatformOpinion.getArchitectureFromEnv(env, snap);
String os = DebuggerPlatformOpinion.getOperatingSystemFromEnv(env, snap);
Endian endian = DebuggerPlatformOpinion.getEndianFromEnv(env, snap);
return getOffers(object, snap, env, debugger, arch, os, endian);
return getOffers(object, snap, env, debugger, arch, os, endian, includeOverrides);
}
}
@@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.mapping;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.task.TaskMonitor;
@@ -25,6 +27,26 @@ import ghidra.util.task.TaskMonitor;
* An object for interpreting a trace according to a chosen platform
*/
public interface DebuggerPlatformMapper {
/**
* Get the compiler for a given object
*
* @param object the object
* @return the compiler spec
*/
CompilerSpec getCompilerSpec(TraceObject object);
/**
* Get the language for a given object
*
* @param object the object
* @return the language
*/
default Language getLangauge(TraceObject object) {
CompilerSpec cSpec = getCompilerSpec(object);
return cSpec == null ? null : cSpec.getLanguage();
}
/**
* Prepare the given trace for interpretation under this mapper
*
@@ -62,13 +62,23 @@ public interface DebuggerPlatformOffer {
/**
* Get the language to which this offer can map
*
* @return the langauge
* @return the language
*/
default Language getLanguage() {
CompilerSpec cSpec = getCompilerSpec();
return cSpec == null ? null : cSpec.getLanguage();
}
/**
* Get the language ID to which this offer can map
*
* @return the language ID
*/
default LanguageID getLanguageID() {
Language language = getLanguage();
return language == null ? null : language.getLanguageID();
}
/**
* Get the compiler to which this offer can map
*
@@ -76,6 +86,24 @@ public interface DebuggerPlatformOffer {
*/
CompilerSpec getCompilerSpec();
/**
* Get the compiler spec ID to which this offer can map
*
* @return the language ID
*/
default CompilerSpecID getCompilerSpecID() {
CompilerSpec cSpec = getCompilerSpec();
return cSpec == null ? null : cSpec.getCompilerSpecID();
}
/**
* Load a compiler spec from the language service given the language and cspec IDs
*
* @param langID the langauge ID
* @param cSpecID the compiler spec ID
* @return the compiler spec
* @throws AssertionError if either the language or the compiler spec is not found
*/
default CompilerSpec getCompilerSpec(LanguageID langID, CompilerSpecID cSpecID) {
try {
LanguageService langServ = DefaultLanguageService.getLanguageService();
@@ -96,4 +124,12 @@ public interface DebuggerPlatformOffer {
* @return the mapper
*/
DebuggerPlatformMapper take(PluginTool tool, Trace trace);
/**
* Check if this or an equivalent offer was the creator of the given mapper
*
* @param mapper the mapper
* @return true if this offer could be the mapper's creator
*/
boolean isCreatorOf(DebuggerPlatformMapper mapper);
}
@@ -20,8 +20,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TraceGuestPlatform;
import ghidra.trace.model.guest.TracePlatformManager;
import ghidra.trace.model.guest.*;
import ghidra.trace.model.target.TraceObject;
import ghidra.util.MathUtilities;
import ghidra.util.database.UndoableTransaction;
@@ -49,7 +48,7 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe
}
@Override
protected CompilerSpec getCompilerSpec(TraceObject object) {
public CompilerSpec getCompilerSpec(TraceObject object) {
return cSpec;
}
@@ -59,11 +58,11 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe
cSpec.getLanguage().getLanguageDescription() + "/" + cSpec.getCompilerSpecDescription(),
true)) {
TracePlatformManager platformManager = trace.getPlatformManager();
TraceGuestPlatform platform = platformManager.getOrAddGuestPlatform(cSpec);
if (platform == null) {
return; // It's the host compiler spec
TracePlatform platform = platformManager.getOrAddPlatform(cSpec);
if (platform.isHost()) {
return;
}
addMappedRanges(platform);
addMappedRanges((TraceGuestPlatform) platform);
}
}
@@ -40,7 +40,7 @@ public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion {
}
@Override
protected CompilerSpec getCompilerSpec(TraceObject object) {
public CompilerSpec getCompilerSpec(TraceObject object) {
return trace.getBaseCompilerSpec();
}
@@ -86,6 +86,11 @@ public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion {
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new LegacyDebuggerPlatformMapper(tool, trace);
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == LegacyDebuggerPlatformMapper.class;
}
};
}
@@ -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.platform;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
/**
* An "opinion" which offers every known compiler, but with only default mapping logic.
*/
public class OverrideDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion {
private static final LanguageCompilerSpecQuery ALL_SPECS =
new LanguageCompilerSpecQuery(null, null, null, null, null);
private static final Map<Endian, Set<DebuggerPlatformOffer>> CACHE = new HashMap<>();
protected static class OverridePlatformOffer implements DebuggerPlatformOffer {
private final String description;
private final LanguageID languageID;
private final CompilerSpecID cSpecID;
private final int confidence;
public OverridePlatformOffer(String description, LanguageID languageID,
CompilerSpecID cSpecID, int confidence) {
this.description = description;
this.languageID = languageID;
this.cSpecID = cSpecID;
this.confidence = confidence;
}
@Override
public String getDescription() {
return description;
}
@Override
public LanguageID getLanguageID() {
return languageID;
}
@Override
public CompilerSpecID getCompilerSpecID() {
return cSpecID;
}
@Override
public int getConfidence() {
return confidence;
}
@Override
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new OverrideDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == OverrideDebuggerPlatformMapper.class;
}
@Override
public CompilerSpec getCompilerSpec() {
return getCompilerSpec(languageID, cSpecID);
}
}
protected static class OverrideDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
public OverrideDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
super(tool, trace, cSpec);
}
}
protected DebuggerPlatformOffer computeOfferForEndianAndLCSP(Endian endian,
LanguageCompilerSpecPair lcsp) {
try {
LanguageDescription ldesc = lcsp.getLanguageDescription();
return new OverridePlatformOffer("Override to " + lcsp,
ldesc.getLanguageID(),
lcsp.getCompilerSpecDescription().getCompilerSpecID(),
ldesc.getEndian() == endian ? -10 : -20);
}
catch (LanguageNotFoundException | CompilerSpecNotFoundException e) {
// It couldn't have been generated unless it existed
throw new AssertionError(e);
}
}
protected Set<DebuggerPlatformOffer> computeOffersForEndian(Endian endian) {
LanguageService langServ = DefaultLanguageService.getLanguageService();
return langServ.getLanguageCompilerSpecPairs(ALL_SPECS)
.stream()
.map(lcsp -> computeOfferForEndianAndLCSP(endian, lcsp))
.collect(Collectors.toSet());
}
@Override
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (!includeOverrides) {
return Set.of();
}
synchronized (CACHE) {
return CACHE.computeIfAbsent(endian, this::computeOffersForEndian);
}
}
}
@@ -65,12 +65,17 @@ public class DbgengDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpini
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new DbgengX64DebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == DbgengX64DebuggerPlatformMapper.class;
}
};
}
@Override
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
String debugger, String arch, String os, Endian endian) {
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null || !debugger.toLowerCase().contains("dbg")) {
return Set.of();
}
@@ -79,11 +79,16 @@ public class FridaDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinio
// TODO: May need these per offer
return new FridaDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == FridaDebuggerPlatformMapper.class;
}
}
@Override
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
String debugger, String arch, String os, Endian endian) {
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null ||
os == null | !debugger.toLowerCase().contains("frida")) {
return Set.of();
@@ -66,6 +66,11 @@ public class GdbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new GdbDebuggerPlatformMapper(tool, trace, cSpec);
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == GdbDebuggerPlatformMapper.class;
}
}
protected static class GdbDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
@@ -78,12 +83,12 @@ public class GdbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
protected Set<GdbDebuggerPlatformOffer> offersForLanguageAndCSpec(String arch, Endian endian,
LanguageCompilerSpecPair lcsp)
throws CompilerSpecNotFoundException, LanguageNotFoundException {
return Set.of(GdbDebuggerPlatformOffer.fromArchLCSP("Default GDB for " + arch, lcsp));
return Set.of(GdbDebuggerPlatformOffer.fromArchLCSP(arch, lcsp));
}
@Override
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
String debugger, String arch, String os, Endian endian) {
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || !"gdb".equals(debugger.toLowerCase())) {
return Set.of();
}
@@ -29,7 +29,6 @@ public class JdiDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
protected static final CompilerSpecID COMP_ID_DEFAULT = new CompilerSpecID("default");
protected static class JdiDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
// TODO: Delete this class?
public JdiDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
super(tool, trace, cSpec);
}
@@ -68,11 +67,16 @@ public class JdiDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new JdiDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == JdiDebuggerPlatformMapper.class;
}
}
@Override
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
String debugger, String arch, String os, Endian endian) {
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null || !debugger.contains("Java Debug Interface")) {
return Set.of();
}
@@ -31,7 +31,7 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
protected static final CompilerSpecID COMP_ID_GCC = new CompilerSpecID("gcc");
protected static final CompilerSpecID COMP_ID_VS = new CompilerSpecID("windows");
protected static class LldbDebuggerPlatformMapper
protected static final class LldbDebuggerPlatformMapper
extends DefaultDebuggerPlatformMapper {
public LldbDebuggerPlatformMapper(PluginTool tool, Trace trace,
CompilerSpec cSpec) {
@@ -79,11 +79,16 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
// TODO: May need these per offer
return new LldbDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == LldbDebuggerPlatformMapper.class;
}
}
@Override
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
String debugger, String arch, String os, Endian endian) {
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
if (debugger == null || arch == null ||
os == null | !debugger.toLowerCase().contains("lldb")) {
return Set.of();
@@ -15,21 +15,18 @@
*/
package ghidra.app.plugin.core.debug.service.platform;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import docking.action.builder.ActionBuilder;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.DebuggerPlatformPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ChoosePlatformAction;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.lifecycle.Unfinished;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
@@ -55,8 +52,6 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
ActionBuilder actionChoosePlatform;
private final Map<Trace, DebuggerPlatformMapper> mappersByTrace = new HashMap<>();
public DebuggerPlatformServicePlugin(PluginTool tool) {
@@ -65,13 +60,10 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
}
@Override
protected void init() {
super.init();
createActions();
}
protected void createActions() {
actionChoosePlatform = ChoosePlatformAction.builder(this); // TODO:
public DebuggerPlatformMapper getCurrentMapperFor(Trace trace) {
synchronized (mappersByTrace) {
return mappersByTrace.get(trace);
}
}
@Override
@@ -93,7 +85,7 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
mappersByTrace.put(trace, mapper);
}
mapper.addToTrace(snap);
// TODO: Fire a listener
firePluginEvent(new DebuggerPlatformPluginEvent(getName(), trace, mapper));
return mapper;
}
@@ -109,13 +101,10 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
return null;
}
@Override
public DebuggerPlatformMapper chooseMapper(Trace trace, TraceObject object, long snap) {
return Unfinished.TODO();
}
@Override
public void setCurrentMapperFor(Trace trace, DebuggerPlatformMapper mapper, long snap) {
Objects.requireNonNull(trace);
Objects.requireNonNull(mapper);
if (!traceManager.getOpenTraces().contains(trace)) {
throw new IllegalArgumentException("Trace is not opened in this tool");
}
@@ -123,7 +112,7 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
mappersByTrace.put(trace, mapper);
}
mapper.addToTrace(snap);
// TODO: Fire a listener
firePluginEvent(new DebuggerPlatformPluginEvent(getName(), trace, mapper));
}
@Override
@@ -51,6 +51,9 @@ import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceVariableSnapProgramView;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
@@ -404,6 +407,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return focus == null ? null : recorder.getTraceThreadForSuccessor(focus);
}
protected TraceObject objectFromTargetFocus(TraceRecorder recorder, TargetObject focus) {
return focus == null ? null
: recorder.getTrace()
.getObjectManager()
.getObjectByCanonicalPath(TraceObjectKeyPath.of(focus.getPath()));
}
protected TraceStackFrame frameFromTargetFocus(TraceRecorder recorder, TargetObject focus) {
return focus == null ? null : recorder.getTraceStackFrameForSuccessor(focus);
}
@@ -439,6 +449,32 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
// NOTE, if still null without focus support,
// we will take the eldest live thread at the resolved snap
}
TraceObject object = coordinates.getObject();
if (object == null) {
if (supportsFocus(recorder)) {
object = objectFromTargetFocus(recorder, focus);
}
if (object /*still*/ == null) { // either no focus support, or focus is not recorded
object = lastForTrace == null ? null : lastForTrace.getObject();
if (object != null) {
TraceObjectThread objThread =
object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
.findFirst()
.orElse(null);
if (objThread != thread) {
object = null; // Abandon remembered object
}
}
}
if (object /*still*/ == null && thread instanceof TraceObjectThread objThread) {
// TODO: Seek the frame out?
object = objThread.getObject();
}
if (object /*still*/ == null) {
object = trace.getObjectManager().getRootObject();
// Could still be null, but that means no objects in the trace at all
}
}
/**
* Only select a default thread if the trace is not live. If it is live, and the model
* supports focus, then we should expect the debugger to control thread/frame focus.
@@ -500,7 +536,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
frame = 0;
}
return DebuggerCoordinates.all(trace, recorder, thread, view, Objects.requireNonNull(time),
Objects.requireNonNull(frame));
Objects.requireNonNull(frame), object);
}
protected DebuggerCoordinates doSetCurrent(DebuggerCoordinates newCurrent) {
@@ -557,11 +593,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
}
TraceThread thread = threadFromTargetFocus(recorder, obj);
TraceObject object = objectFromTargetFocus(recorder, obj);
long snap = recorder.getSnap();
TraceStackFrame traceFrame = frameFromTargetFocus(recorder, obj);
Integer frame = traceFrame == null ? null : traceFrame.getLevel();
activateNoFocus(DebuggerCoordinates.all(trace, recorder, thread, null,
TraceSchedule.snap(snap), frame));
TraceSchedule.snap(snap), frame, object));
return true;
}
@@ -667,6 +704,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.getFrame();
}
@Override
public TraceObject getCurrentObject() {
return current.getObject();
}
public Long findSnapshot(DebuggerCoordinates coordinates) {
if (coordinates.getTime().isSnapOnly()) {
return coordinates.getSnap();
@@ -985,6 +1027,16 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return null;
}
TraceRecorder recorder = resolved.getRecorder();
if (!Objects.equals(prev.getObject(), resolved.getObject())) {
TraceObject obj = resolved.getObject();
if (obj != null) {
TargetObject object =
recorder.getTarget().getSuccessor(obj.getCanonicalPath().getKeyList());
if (object != null) {
return object;
}
}
}
if (!Objects.equals(prev.getFrame(), resolved.getFrame())) {
TargetStackFrame frame =
recorder.getTargetStackFrame(resolved.getThread(), resolved.getFrame());
@@ -1066,6 +1118,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
activate(DebuggerCoordinates.frame(frameLevel));
}
@Override
public void activateObject(TraceObject object) {
activate(DebuggerCoordinates.object(object));
}
@Override
public void setAutoActivatePresent(boolean enabled) {
autoActivatePresent.set(enabled, null);
@@ -16,6 +16,7 @@
package ghidra.app.services;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
@@ -23,6 +24,15 @@ import ghidra.trace.model.target.TraceObject;
* A service to manage the current mapper for active traces
*/
public interface DebuggerPlatformService {
/**
* Get the current mapper for the given trace
*
* @param trace the trace
* @return the mapper, or null
*/
DebuggerPlatformMapper getCurrentMapperFor(Trace trace);
/**
* Get a mapper applicable to the given object
*
@@ -37,8 +47,7 @@ public interface DebuggerPlatformService {
* @param snap the snap, usually the current snap
* @return the mapper, or null if no offer was provided
*/
DebuggerPlatformMapper getMapper(Trace trace, TraceObject object,
long snap);
DebuggerPlatformMapper getMapper(Trace trace, TraceObject object, long snap);
/**
* Get a new mapper for the given object, ignoring the trace's current mapper
@@ -54,15 +63,6 @@ public interface DebuggerPlatformService {
*/
DebuggerPlatformMapper getNewMapper(Trace trace, TraceObject object, long snap);
/**
* Display a dialog for the user to manually select a mapper for the given object
*
* @param object the object for which a mapper is desired
* @param snap the snap, usually the current snap
* @return the mapper, or null if the dialog was cancelled
*/
DebuggerPlatformMapper chooseMapper(Trace trace, TraceObject object, long snap);
/**
* Set the current mapper for the trace and initialize the trace for the mapper
*
@@ -26,6 +26,7 @@ import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.TriConsumer;
@@ -141,6 +142,13 @@ public interface DebuggerTraceManagerService {
*/
int getCurrentFrame();
/**
* Get the active object
*
* @return the active object, or null
*/
TraceObject getCurrentObject();
/**
* Open a trace
*
@@ -282,6 +290,13 @@ public interface DebuggerTraceManagerService {
*/
void activateFrame(int frameLevel);
/**
* Activate the given object
*
* @param object the desired object
*/
void activateObject(TraceObject object);
/**
* Control whether the trace manager automatically activates the "present snapshot"
*
@@ -0,0 +1,63 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.platform;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.target.DBTraceObjectManagerTest;
import ghidra.util.database.UndoableTransaction;
import help.screenshot.GhidraScreenShotGenerator;
public class DebuggerPlatformPluginScreenShots extends GhidraScreenShotGenerator {
DebuggerTraceManagerService traceManager;
DebuggerPlatformPlugin platformPlugin;
@Before
public void setUpMine() throws Throwable {
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
platformPlugin = addPlugin(tool, DebuggerPlatformPlugin.class);
}
@Test
public void testCaptureDebuggerSelectPlatformOfferDialog() throws Throwable {
SchemaContext ctx = XmlSchemaContext.deserialize(DBTraceObjectManagerTest.XML_CTX);
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("echo", "DATA:BE:64:default")) {
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager()
.createRootObject(ctx.getSchema(new SchemaName("Session")));
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
performAction(platformPlugin.actionMore, false);
DebuggerSelectPlatformOfferDialog dialog =
waitForDialogComponent(DebuggerSelectPlatformOfferDialog.class);
dialog.setFilterRecommended(false);
waitForSwing();
captureDialog(dialog);
}
}
}
@@ -0,0 +1,102 @@
/* ###
* 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.gui.platform;
import static org.junit.Assert.assertEquals;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import docking.action.ToggleDockingActionIf;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.program.model.lang.LanguageID;
import ghidra.trace.database.ToyDBTraceBuilder;
public class DebuggerPlatformPluginTest extends AbstractGhidraHeadedDebuggerGUITest {
DebuggerPlatformPlugin platformPlugin;
DebuggerPlatformService platformService;
protected List<ToggleDockingActionIf> getPlatformActions() {
return tool.getAllActions()
.stream()
.filter(a -> a.getOwner().equals(platformPlugin.getName()))
.filter(a -> a instanceof ToggleDockingActionIf)
.filter(a -> a != platformPlugin.actionMore)
.map(a -> (ToggleDockingActionIf) a)
.collect(Collectors.toList());
}
@Before
public void setUpPlatformTest() throws Throwable {
platformPlugin = addPlugin(tool, DebuggerPlatformPlugin.class);
platformService = tool.getService(DebuggerPlatformService.class);
}
protected void chooseLanguageIDViaMore(LanguageID langID) {
performAction(platformPlugin.actionMore, false);
DebuggerSelectPlatformOfferDialog dialog =
waitForDialogComponent(DebuggerSelectPlatformOfferDialog.class);
dialog.setFilterRecommended(false);
waitForSwing();
List<DebuggerPlatformOffer> offers = runSwing(() -> dialog.getDisplayedOffers());
DebuggerPlatformOffer toyOffer = offers.stream()
.filter(o -> Objects.equals(langID, o.getLanguageID()))
.findFirst()
.orElseThrow();
runSwing(() -> dialog.setSelectedOffer(toyOffer));
runSwing(() -> dialog.okCallback());
waitForSwing();
}
@Test
public void testActionMore() throws Throwable {
createAndOpenTrace("DATA:BE:64:default");
traceManager.activateTrace(tb.trace);
chooseLanguageIDViaMore(new LanguageID("Toy:BE:64:default"));
DebuggerPlatformMapper mapper = platformService.getCurrentMapperFor(tb.trace);
assertEquals(new LanguageID("Toy:BE:64:default"), mapper.getLangauge(null).getLanguageID());
}
@Test
public void testRemembersChosenOffer() throws Throwable {
createAndOpenTrace("DATA:BE:64:default");
try (ToyDBTraceBuilder tb2 =
new ToyDBTraceBuilder("second-" + name.getMethodName(), "DATA:BE:64:default")) {
traceManager.openTrace(tb2.trace);
traceManager.activateTrace(tb2.trace);
chooseLanguageIDViaMore(new LanguageID("Toy:BE:64:default"));
assertEquals(2, getPlatformActions().size());
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(1, getPlatformActions().size());
traceManager.activateTrace(tb2.trace);
waitForSwing();
assertEquals(2, getPlatformActions().size());
}
}
}
@@ -24,6 +24,12 @@ import ghidra.trace.model.target.TraceObject;
public class TestDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion {
protected static class TestDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
public TestDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
super(tool, trace, cSpec);
}
}
enum Offers implements DebuggerPlatformOffer {
ARM_V8_LE("Test armv8le", "ARM:LE:32:v8", "default"),
X86_64("Test x86-64", "x86:LE:64:default", "gcc");
@@ -58,13 +64,19 @@ public class TestDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
@Override
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
return new DefaultDebuggerPlatformMapper(tool, trace, getCompilerSpec());
return new TestDebuggerPlatformMapper(tool, trace, getCompilerSpec());
}
@Override
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
return mapper.getClass() == TestDebuggerPlatformMapper.class;
}
}
@Override
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap,
TraceObject env, String debugger, String arch, String os, Endian endian) {
TraceObject env, String debugger, String arch, String os, Endian endian,
boolean includeOverrides) {
if (!"test".equals(debugger)) {
return Set.of();
}
@@ -145,7 +145,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor =
TracePcodeUtils.executorForCoordinates(
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
null));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
@@ -182,7 +183,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor =
TracePcodeUtils.executorForCoordinates(
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
null));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
@@ -17,8 +17,7 @@ package ghidra.app.plugin.core.debug.service.tracemgr;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.Set;
import java.util.*;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -30,9 +29,17 @@ import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetStack;
import ghidra.dbg.model.TestTargetStackFrameHasRegisterBank;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.framework.model.DomainFile;
import ghidra.trace.database.target.DBTraceObjectManager;
import ghidra.trace.database.target.DBTraceObjectManagerTest;
import ghidra.trace.model.Trace;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
@@ -194,6 +201,48 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
assertEquals(0, traceManager.getCurrentFrame());
}
@Test
public void testGetCurrentObject() throws Exception {
assertEquals(null, traceManager.getCurrentObject());
createTrace();
waitForDomainObject(tb.trace);
assertEquals(null, traceManager.getCurrentObject());
traceManager.openTrace(tb.trace);
waitForSwing();
assertEquals(null, traceManager.getCurrentObject());
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(null, traceManager.getCurrentObject());
SchemaContext ctx = XmlSchemaContext.deserialize(DBTraceObjectManagerTest.XML_CTX);
TraceObject objThread0;
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceObjectManager objectManager = tb.trace.getObjectManager();
objectManager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild();
objThread0 =
objectManager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"));
}
TraceThread thread =
Objects.requireNonNull(objThread0.queryInterface(TraceObjectThread.class));
traceManager.activateObject(objThread0);
waitForSwing();
assertEquals(objThread0, traceManager.getCurrentObject());
assertEquals(thread, traceManager.getCurrentThread());
traceManager.activateTrace(null);
waitForSwing();
assertEquals(null, traceManager.getCurrentObject());
}
@Test
public void testOpenTrace() throws Exception {
createTrace();
@@ -33,7 +33,9 @@ import ghidra.program.util.DefaultLanguageService;
import ghidra.trace.database.DBTraceUtils.CompilerSpecIDDBFieldCodec;
import ghidra.trace.database.DBTraceUtils.LanguageIDDBFieldCodec;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TracePlatformChangeType;
import ghidra.trace.model.guest.TraceGuestPlatform;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@@ -184,6 +186,8 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
hostAddressSet.delete(hostRange);
guestAddressSet.delete(guestRange);
}
manager.trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.MAPPING_DELETED,
null, this, range, null));
}
@Override
@@ -211,6 +215,7 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
@Override
public DBTraceGuestPlatformMappedRange addMappedRange(Address hostStart, Address guestStart,
long length) throws AddressOverflowException {
DBTraceGuestPlatformMappedRange mappedRange;
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
Address hostEnd = hostStart.addWrap(length - 1);
if (hostAddressSet.intersects(hostStart, hostEnd)) {
@@ -222,14 +227,16 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
if (guestAddressSet.intersects(guestStart, guestEnd)) {
throw new IllegalArgumentException("Range overlaps existing guest mapped range(s)");
}
DBTraceGuestPlatformMappedRange mappedRange = manager.rangeMappingStore.create();
mappedRange = manager.rangeMappingStore.create();
mappedRange.set(hostStart, this, guestStart, length);
rangesByHostAddress.put(hostStart, mappedRange);
rangesByGuestAddress.put(guestStart, mappedRange);
hostAddressSet.add(mappedRange.getHostRange());
guestAddressSet.add(mappedRange.getGuestRange());
return mappedRange;
}
manager.trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.MAPPING_ADDED,
null, this, null, mappedRange));
return mappedRange;
}
@Override
@@ -161,6 +161,6 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
@Override
public void delete(TaskMonitor monitor) throws CancelledException {
manager.platformStore.getObjectAt(guestPlatformKey).deleteMappedRange(this, monitor);
platform.deleteMappedRange(this, monitor);
}
}
@@ -29,7 +29,9 @@ import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager;
import ghidra.trace.database.guest.DBTraceGuestPlatform.DBTraceGuestLanguage;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TracePlatformChangeType;
import ghidra.trace.model.guest.*;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.exception.CancelledException;
@@ -261,6 +263,7 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
platformsByCompiler.remove(platform.getCompilerSpec());
platformStore.delete(platform);
}
trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.DELETED, null, platform));
}
@Override
@@ -281,9 +284,12 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
throw new IllegalArgumentException(
"Base compiler spec cannot be a guest compiler spec");
}
DBTraceGuestPlatform platform;
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return doAddGuestPlatform(compilerSpec);
platform = doAddGuestPlatform(compilerSpec);
}
trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.ADDED, null, platform));
return platform;
}
@Override
@@ -297,18 +303,21 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
}
@Override
public DBTraceGuestPlatform getOrAddGuestPlatform(CompilerSpec compilerSpec) {
public InternalTracePlatform getOrAddPlatform(CompilerSpec compilerSpec) {
if (compilerSpec.getCompilerSpecID()
.equals(trace.getBaseCompilerSpec().getCompilerSpecID())) {
throw new IllegalArgumentException("Base language cannot be a guest language");
return hostPlatform;
}
DBTraceGuestPlatform platform;
try (LockHold hold = LockHold.lock(lock.writeLock())) {
DBTraceGuestPlatform exists = platformsByCompiler.get(compilerSpec);
if (exists != null) {
return exists;
}
return doAddGuestPlatform(compilerSpec);
platform = doAddGuestPlatform(compilerSpec);
}
trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.ADDED, null, platform));
return platform;
}
@Override
@@ -32,7 +32,7 @@ import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointManager;
import ghidra.trace.model.context.TraceRegisterContextManager;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.guest.TracePlatformManager;
import ghidra.trace.model.guest.*;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.*;
@@ -379,6 +379,16 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final TraceSnapshotChangeType<Void> DELETED = new TraceSnapshotChangeType<>();
}
public static final class TracePlatformChangeType<U>
extends DefaultTraceChangeType<TraceGuestPlatform, U> {
public static final TracePlatformChangeType<Void> ADDED = new TracePlatformChangeType<>();
public static final TracePlatformChangeType<Void> DELETED = new TracePlatformChangeType<>();
public static final TracePlatformChangeType<TraceGuestPlatformMappedRange> MAPPING_ADDED =
new TracePlatformChangeType<>();
public static final TracePlatformChangeType<TraceGuestPlatformMappedRange> MAPPING_DELETED =
new TracePlatformChangeType<>();
}
public interface TraceProgramViewListener {
void viewCreated(TraceProgramView view);
}
@@ -33,6 +33,13 @@ public interface TracePlatformManager {
*/
TracePlatform getHostPlatform();
/**
* Get all guest platforms
*
* @return the collection of platforms
*/
Collection<TraceGuestPlatform> getGuestPlatforms();
/**
* Add a guest platform
*
@@ -53,14 +60,7 @@ public interface TracePlatformManager {
* Get or add a platform for the given compiler spec
*
* @param compilerSpec the compiler spec
* @return the new or existing platform, or null if compiler spec is the base compiler spec
* @return the new or existing platform
*/
TraceGuestPlatform getOrAddGuestPlatform(CompilerSpec compilerSpec);
/**
* Get all guest platforms
*
* @return the collection of platforms
*/
Collection<TraceGuestPlatform> getGuestPlatforms();
TracePlatform getOrAddPlatform(CompilerSpec compilerSpec);
}
@@ -40,6 +40,30 @@ import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.util.database.*;
public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationTest {
public static final String XML_CTX = """
<context>
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
<attribute name='curTarget' schema='Target' />
<attribute name='Targets' schema='TargetContainer' />
</schema>
<schema name='TargetContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Target' />
</schema>
<schema name='Target' elementResync='NEVER' attributeResync='NEVER'>
<interface name='Process' />
<attribute name='self' schema='Target' />
<attribute name='Threads' schema='ThreadContainer' />
</schema>
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
attributeResync='NEVER'>
<element schema='Thread' />
</schema>
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
<interface name='Thread' />
</schema>
</context>
""";
protected ToyDBTraceBuilder b;
protected DBTraceObjectManager manager;
@@ -54,29 +78,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
b = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default");
manager = b.trace.getObjectManager();
ctx = XmlSchemaContext.deserialize("" + //
"<context>" + //
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='curTarget' schema='Target' />" + //
" <attribute name='Targets' schema='TargetContainer' />" + //
" </schema>" + //
" <schema name='TargetContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Target' />" + //
" </schema>" + //
" <schema name='Target' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='Process' />" + //
" <attribute name='self' schema='Target' />" + //
" <attribute name='Threads' schema='ThreadContainer' />" + //
" </schema>" + //
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='NEVER'>" + //
" <element schema='Thread' />" + //
" </schema>" + //
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='Thread' />" + //
" </schema>" + //
"</context>");
ctx = XmlSchemaContext.deserialize(XML_CTX);
}
protected void populateModel(int targetCount) {
@@ -45,6 +45,10 @@ import utility.function.Callback;
public class DialogComponentProvider
implements ActionContextProvider, StatusListener, TaskListener {
protected enum Opt {
MODAL, INCLUDE_STATUS, INCLUDE_BUTTONS, CAN_RUN_TASKS;
}
private static final Color WARNING_COLOR = new Color(0xff9900);
private final static int DEFAULT_DELAY = 750;
@@ -99,6 +103,17 @@ public class DialogComponentProvider
private Dimension defaultSize;
/**
* Constructor for GhidraDialogComponent using option set instead of booleans
* @param title the dialog title
* @param options the options. See {@link Option} and
* {{@link #DialogComponentProvider(String, boolean, boolean, boolean, boolean)}
*/
protected DialogComponentProvider(String title, Set<Opt> options) {
this(title, options.contains(Opt.MODAL), options.contains(Opt.INCLUDE_STATUS),
options.contains(Opt.INCLUDE_BUTTONS), options.contains(Opt.CAN_RUN_TASKS));
}
/**
* Constructor for a GhidraDialogComponent that will be modal and will include a status line and
* a button panel. Its title will be the same as its name.