diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 2470f40bad..c71daadd7e 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -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| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index 2e86c6a687..d40d06104b 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -169,6 +169,10 @@ sortgroup="p1" target="help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html new file mode 100644 index 0000000000..23ac62c62a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html @@ -0,0 +1,69 @@ + + + + + + + Debugger: Platform Selection + + + + + +

Debugger: Platform Selection

+ +

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.

+ +

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.

+ +

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.

+ +

Actions

+ +

This plugin adds actions into the Debugger menu under "Choose Platform."

+ +

Choose Platform

+ +

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.

+ +

More...

+ +

This action is enabled whenever there is a current trace. It presents a dialog with the + recommended platforms for the trace.

+ + + + + + + +
+ +

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.

+ + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png new file mode 100644 index 0000000000..48d0fd9b3e Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png differ diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java index 4e9eccadd2..266bc61c0c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java @@ -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; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/DebuggerPlatformPluginEvent.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/DebuggerPlatformPluginEvent.java new file mode 100644 index 0000000000..09ea9d2528 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/event/DebuggerPlatformPluginEvent.java @@ -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; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 1b288160a9..c4d179589c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -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"; + } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java new file mode 100644 index 0000000000..4ab731a435 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java @@ -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 actions = + new LinkedHashMap<>(); + + public PlatformActionSet(Trace trace) { + this.trace = trace; + this.current = traceManager.getCurrentFor(trace); + } + + protected Set 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 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 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 offers = + DebuggerPlatformOpinion.queryOpinions(trace, object, snap, true); + offerDialog.setOffers(offers); + tool.showDialog(offerDialog); + if (offerDialog.isCancelled()) { + return null; + } + return offerDialog.getSelectedOffer(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java new file mode 100644 index 0000000000..e5b2812ae6 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java @@ -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 { + 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 getter; + private final SortDirection sortDir; + + OfferTableColumns(String header, Class cls, + Function getter, SortDirection sortDir) { + this.header = header; + this.cls = cls; + this.getter = getter; + this.sortDir = sortDir; + } + + OfferTableColumns(String header, Class cls, + Function 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 { + + public OfferTableModel() { + super("Offers", OfferTableColumns.class); + } + + @Override + public List 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 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 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 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 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 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. + * + *

+ * 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 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 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 + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java index b1da0a65fe..32fcd97de4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java @@ -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) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java index 0e190f1514..638de62e07 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOffer.java @@ -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; + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java index 6a9234f164..653f68c6bd 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformOpinion.java @@ -24,19 +24,20 @@ import ghidra.trace.model.target.TraceObject; public abstract class AbstractDebuggerPlatformOpinion implements DebuggerPlatformOpinion { protected abstract Set 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 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); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java index 730d66cf46..92f3268c03 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformMapper.java @@ -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 * diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java index 714bb427b7..c836929c1f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DebuggerPlatformOffer.java @@ -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); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java index e2d607933d..c78382eac4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java @@ -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); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java index ce4834f6b4..bd9481fba6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java @@ -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; + } }; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/OverrideDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/OverrideDebuggerPlatformOpinion.java new file mode 100644 index 0000000000..a5eb05c5a3 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/OverrideDebuggerPlatformOpinion.java @@ -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> 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 computeOffersForEndian(Endian endian) { + LanguageService langServ = DefaultLanguageService.getLanguageService(); + return langServ.getLanguageCompilerSpecPairs(ALL_SPECS) + .stream() + .map(lcsp -> computeOfferForEndianAndLCSP(endian, lcsp)) + .collect(Collectors.toSet()); + } + + @Override + protected Set 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); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java index 176f88a033..95b4dc606e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java @@ -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 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(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java index 1ff3247f48..0bc8f55c10 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/frida/FridaDebuggerPlatformOpinion.java @@ -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 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(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java index f9bdbe240b..842dfa338f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/gdb/GdbDebuggerPlatformOpinion.java @@ -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 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 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(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java index a3a1345164..a2fc91c6a6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/jdi/JdiDebuggerPlatformOpinion.java @@ -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 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(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java index 4548ba78fe..626477fd98 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/lldb/LldbDebuggerPlatformOpinion.java @@ -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 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(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java index fb64b1e1cc..18c2da95d9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/platform/DebuggerPlatformServicePlugin.java @@ -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 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 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 4366d1a03f..cf41855161 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -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); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java index 7dab95f9b4..b5527c35f9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java @@ -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 * diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index a6e4f6ffeb..0f94111f81 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -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" * diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginScreenShots.java new file mode 100644 index 0000000000..947566be71 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginScreenShots.java @@ -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); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginTest.java new file mode 100644 index 0000000000..5bf700b8d5 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPluginTest.java @@ -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 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 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()); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java index 64aed3469f..a5956da67e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java @@ -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 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(); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java index 473ce0e94e..7ac389202c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java @@ -145,7 +145,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge TraceThread thread = tb.getOrAddThread("Threads[0]", 0); AsyncPcodeExecutor 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 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"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java index 1e0bca2d3d..abf3ff2d30 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java @@ -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(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java index d5e6325645..3f3eb0b025 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java @@ -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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java index ea5b72fbfe..29d17c8df5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java @@ -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); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java index cf3156f53a..f9a05376be 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java @@ -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 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java index fd3f31b9e9..454e4cdcbe 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java @@ -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 DELETED = new TraceSnapshotChangeType<>(); } + public static final class TracePlatformChangeType + extends DefaultTraceChangeType { + public static final TracePlatformChangeType ADDED = new TracePlatformChangeType<>(); + public static final TracePlatformChangeType DELETED = new TracePlatformChangeType<>(); + public static final TracePlatformChangeType MAPPING_ADDED = + new TracePlatformChangeType<>(); + public static final TracePlatformChangeType MAPPING_DELETED = + new TracePlatformChangeType<>(); + } + public interface TraceProgramViewListener { void viewCreated(TraceProgramView view); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java index 2a471245fd..1582732f3d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatformManager.java @@ -33,6 +33,13 @@ public interface TracePlatformManager { */ TracePlatform getHostPlatform(); + /** + * Get all guest platforms + * + * @return the collection of platforms + */ + Collection 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 getGuestPlatforms(); + TracePlatform getOrAddPlatform(CompilerSpec compilerSpec); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java index 12e11aed86..35e89715b7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java @@ -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 = """ + + + + + + + + + + + + + + + + + + + + + """; 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("" + // - "" + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - ""); + ctx = XmlSchemaContext.deserialize(XML_CTX); } protected void populateModel(int targetCount) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java index 67922c58fc..0f262fff87 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java @@ -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 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.