mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-24 22:08:40 +08:00
Merge remote-tracking branch 'origin/GP-2163_Dan_dbgChoosePlatformMenu--SQUASHED'
This commit is contained in:
@@ -97,6 +97,8 @@ src/main/help/help/topics/DebuggerObjectsPlugin/images/stepover.png||GHIDRA||||E
|
||||
src/main/help/help/topics/DebuggerObjectsPlugin/images/stop.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerPcodeStepperPlugin/images/DebuggerPcodeStepperPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerPlatformPlugin/images/DebuggerSelectPlatformOfferDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionMapProposalDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionsPlugin.png||GHIDRA||||END|
|
||||
|
||||
@@ -169,6 +169,10 @@
|
||||
sortgroup="p1"
|
||||
target="help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerPlatformPlugin" text="Platform Selection"
|
||||
sortgroup="p2"
|
||||
target="help/topics/DebuggerPlatformPlugin/DebuggerPlatformPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
||||
sortgroup="q"
|
||||
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Debugger: Platform Selection</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="plugin"></A>Debugger: Platform Selection</H1>
|
||||
|
||||
<P>The Debugger allows the user to work with multiple platforms, i.e., architectures,
|
||||
compilers, etc., within the context of a single debug target. This plugin allows the user to
|
||||
switch among platforms and/or introduce new platforms via overrides.</P>
|
||||
|
||||
<P>Note we are in a transitional period between two trace database conventions. This plugin is
|
||||
a necessary component when working with the new convention. In the legacy convention, the
|
||||
system must ascertain the target's language and compiler at the time it begins recording. If it
|
||||
fails or makes an incorrect decision, the UI remains mostly empty, or worse, the system
|
||||
interrogates and instructs the target using the wrong architecture. This mode still works in
|
||||
most circumstances and is the default and recommended convention. Additional platforms can
|
||||
still be introduced, but there is perhaps little use for doing so. One case might be to, e.g.,
|
||||
disassemble JVM bytecodes when debugging a Java process at the native level.</P>
|
||||
|
||||
<P>In the new convention, the system always records the target, using the "DATA" language,
|
||||
i.e., no language. The object tree is recorded, and objects with memory are assigned overlay
|
||||
spaces where their contents are recorded. The only way to disassemble instructions is to add a
|
||||
guest platform. This plugin will do that automatically, in more or less the same fashion as the
|
||||
language and compiler are ascertained in legacy mode. If this fails, the recording still
|
||||
proceeds, and most of the UI is still useful. The user can add (perhaps by trial and error) the
|
||||
correct platform later. In the meantime, memory, registers, threads, etc., are all still
|
||||
displayed.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>This plugin adds actions into the Debugger menu under "Choose Platform."</P>
|
||||
|
||||
<H3><A name="choose_platform"></A>Choose Platform</H3>
|
||||
|
||||
<P>This action adds or changes to a specific platform in the current trace. One of these
|
||||
actions is presented for each recommended or previously chosen platform, if any. The
|
||||
recommendations are given by an opinion service, so new options may be added by extension
|
||||
modules. It is possible there are no recommendations for the current trace. The current
|
||||
platform is designated by a check mark or other selection indicator.</P>
|
||||
|
||||
<H3><A name="choose_more_platforms"></A><A name="prev_diff"></A>More...</H3>
|
||||
|
||||
<P>This action is enabled whenever there is a current trace. It presents a dialog with the
|
||||
recommended platforms for the trace.</P>
|
||||
|
||||
<TABLE width="100%">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD align="center" width="100%"><IMG alt="" border="1" src=
|
||||
"images/DebuggerSelectPlatformOfferDialog.png"></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
<P>The "Show Only Recommended Offers" check can be disabled to display override offers as well.
|
||||
Selecting an offer and confirming the dialog will add or change to the selected platform in the
|
||||
trace. Furthermore, the choice will be added to the "Choose Platform" menu until the trace is
|
||||
closed.</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
+75
-28
@@ -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;
|
||||
}
|
||||
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
-4
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.platform;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.app.services.DebuggerPlatformService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger platform selection GUI",
|
||||
description = "GUI to add, edit, and remove trace platforms",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class,
|
||||
TraceClosedPluginEvent.class,
|
||||
DebuggerPlatformPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerPlatformService.class,
|
||||
})
|
||||
public class DebuggerPlatformPlugin extends Plugin {
|
||||
|
||||
protected interface ChoosePlatformAction {
|
||||
String NAME = DebuggerResources.NAME_CHOOSE_PLATFORM;
|
||||
String DESCRIPTION = DebuggerResources.DESCRIPTION_CHOOSE_PLATFORM;
|
||||
String GROUP = DebuggerResources.GROUP_MAPPING;
|
||||
String HELP_ANCHOR = "choose_platform";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
protected interface ChooseMorePlatformsActon {
|
||||
String NAME = DebuggerResources.NAME_CHOOSE_MORE_PLATFORMS;
|
||||
String TITLE = DebuggerResources.TITLE_CHOOSE_MORE_PLATFORMS;
|
||||
String DESCRIPTION = DebuggerResources.DESCRIPTION_CHOOSE_MORE_PLATFORMS;
|
||||
String GROUP = "zzzz";
|
||||
String HELP_ANCHOR = "choose_more_platforms";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(DebuggerPluginPackage.NAME, ChoosePlatformAction.NAME, TITLE)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getObject(), b.getObject())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected class PlatformActionSet {
|
||||
private final Trace trace;
|
||||
private DebuggerCoordinates current;
|
||||
private final Map<DebuggerPlatformOffer, ToggleDockingAction> actions =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
public PlatformActionSet(Trace trace) {
|
||||
this.trace = trace;
|
||||
this.current = traceManager.getCurrentFor(trace);
|
||||
}
|
||||
|
||||
protected Set<DebuggerPlatformOffer> computePlatformOffers(boolean includeOverrides) {
|
||||
return new LinkedHashSet<>(
|
||||
DebuggerPlatformOpinion.queryOpinions(trace, current.getObject(), 0,
|
||||
includeOverrides));
|
||||
}
|
||||
|
||||
protected ToggleDockingAction createActionChoosePlatform(DebuggerPlatformOffer offer) {
|
||||
ToggleDockingAction action = ChoosePlatformAction.builder(DebuggerPlatformPlugin.this)
|
||||
.menuPath(DebuggerPluginPackage.NAME, ChoosePlatformAction.NAME,
|
||||
offer.getDescription())
|
||||
.onAction(ctx -> activatePlatform(offer))
|
||||
.build();
|
||||
String[] path = action.getMenuBarData().getMenuPath();
|
||||
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), ChoosePlatformAction.GROUP);
|
||||
return action;
|
||||
}
|
||||
|
||||
protected void activatePlatform(DebuggerPlatformOffer offer) {
|
||||
platformService.setCurrentMapperFor(trace, offer.take(tool, trace), current.getSnap());
|
||||
}
|
||||
|
||||
protected void cleanOffers() {
|
||||
actions.keySet().retainAll(computePlatformOffers(true));
|
||||
}
|
||||
|
||||
protected void addPreferredOffers() {
|
||||
for (DebuggerPlatformOffer offer : computePlatformOffers(false)) {
|
||||
addOfferAction(offer);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updatePlatformOffers() {
|
||||
cleanOffers();
|
||||
addPreferredOffers();
|
||||
}
|
||||
|
||||
protected ToggleDockingAction addOfferAction(DebuggerPlatformOffer offer) {
|
||||
return actions.computeIfAbsent(offer, this::createActionChoosePlatform);
|
||||
}
|
||||
|
||||
protected void addChosenOffer(DebuggerPlatformOffer offer) {
|
||||
ToggleDockingAction action = addOfferAction(offer);
|
||||
// NB. PluginEvent will cause selections to update
|
||||
if (currentTrace == trace) {
|
||||
tool.addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
protected void installActions() {
|
||||
for (ToggleDockingAction action : actions.values()) {
|
||||
tool.addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
protected void uninstallActions() {
|
||||
for (ToggleDockingAction action : actions.values()) {
|
||||
tool.removeAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
protected void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
if (sameCoordinates(current, coordinates)) {
|
||||
current = coordinates;
|
||||
return;
|
||||
}
|
||||
current = coordinates;
|
||||
updatePlatformOffers();
|
||||
}
|
||||
|
||||
protected void mapperActivated(DebuggerPlatformMapper mapper) {
|
||||
for (Entry<DebuggerPlatformOffer, ToggleDockingAction> ent : actions.entrySet()) {
|
||||
DebuggerPlatformOffer offer = ent.getKey();
|
||||
ToggleDockingAction action = ent.getValue();
|
||||
action.setSelected(mapper != null && offer.isCreatorOf(mapper));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
DebuggerTraceManagerService traceManager;
|
||||
@AutoServiceConsumed
|
||||
DebuggerPlatformService platformService;
|
||||
@SuppressWarnings("unused")
|
||||
private final Wiring autoServiceWiring;
|
||||
|
||||
private Trace currentTrace;
|
||||
|
||||
private final ChangeListener classChangeListener = evt -> this.classesChanged();
|
||||
|
||||
protected final DebuggerSelectPlatformOfferDialog offerDialog =
|
||||
new DebuggerSelectPlatformOfferDialog();
|
||||
|
||||
final Map<Trace, PlatformActionSet> actionsChoosePlatform = new WeakHashMap<>();
|
||||
DockingAction actionMore;
|
||||
|
||||
public DebuggerPlatformPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||
|
||||
ClassSearcher.addChangeListener(classChangeListener);
|
||||
|
||||
createActions();
|
||||
}
|
||||
|
||||
protected void installActions() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
PlatformActionSet actions =
|
||||
actionsChoosePlatform.computeIfAbsent(currentTrace, PlatformActionSet::new);
|
||||
actions.updatePlatformOffers();
|
||||
actions.mapperActivated(platformService.getCurrentMapperFor(currentTrace));
|
||||
actions.installActions();
|
||||
}
|
||||
|
||||
protected void uninstallActions() {
|
||||
PlatformActionSet actions = actionsChoosePlatform.get(currentTrace);
|
||||
if (actions != null) {
|
||||
actions.uninstallActions();
|
||||
}
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
installActions();
|
||||
actionMore = ChooseMorePlatformsActon.builder(this)
|
||||
.enabledWhen(ctx -> currentTrace != null)
|
||||
.onAction(this::activatedChooseMore)
|
||||
.buildAndInstall(tool);
|
||||
String[] path = actionMore.getMenuBarData().getMenuPath();
|
||||
tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), ChoosePlatformAction.GROUP);
|
||||
tool.contextChanged(null);
|
||||
}
|
||||
|
||||
private void activatedChooseMore(ActionContext ctx) {
|
||||
if (platformService == null) {
|
||||
// Still initializing or finalizing
|
||||
return;
|
||||
}
|
||||
// Sort of a backwards way to retrieve the current coordinates....
|
||||
PlatformActionSet actions = actionsChoosePlatform.get(currentTrace);
|
||||
if (actions == null) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates current = actions.current;
|
||||
Trace trace = current.getTrace();
|
||||
TraceObject object = current.getObject();
|
||||
long snap = current.getSnap();
|
||||
|
||||
DebuggerPlatformOffer offer = chooseOffer(trace, object, snap);
|
||||
// Dialog allows Swing to do other things, so re-check platformService
|
||||
if (offer != null && platformService != null) {
|
||||
actions.addChosenOffer(offer);
|
||||
platformService.setCurrentMapperFor(trace, offer.take(tool, trace), snap);
|
||||
// NOTE: DebuggerPlatformPluginEvent will cause selection change
|
||||
}
|
||||
}
|
||||
|
||||
private void classesChanged() {
|
||||
uninstallActions();
|
||||
for (PlatformActionSet actions : actionsChoosePlatform.values()) {
|
||||
actions.updatePlatformOffers();
|
||||
}
|
||||
installActions();
|
||||
}
|
||||
|
||||
protected void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
uninstallActions();
|
||||
this.currentTrace = coordinates.getTrace();
|
||||
installActions();
|
||||
tool.contextChanged(null);
|
||||
}
|
||||
|
||||
protected void traceClosed(Trace trace) {
|
||||
if (trace == currentTrace) {
|
||||
coordinatesActivated(DebuggerCoordinates.NOWHERE);
|
||||
}
|
||||
actionsChoosePlatform.remove(trace);
|
||||
}
|
||||
|
||||
protected void mapperActivated(Trace trace, DebuggerPlatformMapper mapper) {
|
||||
if (trace != currentTrace) {
|
||||
return;
|
||||
}
|
||||
PlatformActionSet actions = actionsChoosePlatform.get(trace);
|
||||
if (actions != null) {
|
||||
actions.mapperActivated(mapper);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent evt) {
|
||||
coordinatesActivated(evt.getActiveCoordinates());
|
||||
}
|
||||
if (event instanceof TraceClosedPluginEvent evt) {
|
||||
traceClosed(evt.getTrace());
|
||||
}
|
||||
if (event instanceof DebuggerPlatformPluginEvent evt) {
|
||||
mapperActivated(evt.getTrace(), evt.getMapper());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a dialog for the user to manually select an offer for the given object
|
||||
*
|
||||
* @param object the object for which an offer is desired
|
||||
* @param snap the snap, usually the current snap
|
||||
* @return the offer, or null if the dialog was cancelled
|
||||
*/
|
||||
protected DebuggerPlatformOffer chooseOffer(Trace trace, TraceObject object, long snap) {
|
||||
List<DebuggerPlatformOffer> offers =
|
||||
DebuggerPlatformOpinion.queryOpinions(trace, object, snap, true);
|
||||
offerDialog.setOffers(offers);
|
||||
tool.showDialog(offerDialog);
|
||||
if (offerDialog.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
return offerDialog.getSelectedOffer();
|
||||
}
|
||||
}
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.platform;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.ColumnSortState.SortDirection;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider {
|
||||
|
||||
protected enum OfferTableColumns
|
||||
implements EnumeratedTableColumn<OfferTableColumns, DebuggerPlatformOffer> {
|
||||
CONFIDENCE("Confidence", Integer.class, DebuggerPlatformOffer::getConfidence, SortDirection.DESCENDING),
|
||||
PROCESSOR("Processor", String.class, OfferTableColumns::getProcessor),
|
||||
VARIANT("Variant", String.class, OfferTableColumns::getVariant),
|
||||
SIZE("Size", Integer.class, OfferTableColumns::getSize),
|
||||
ENDIAN("Endian", Endian.class, OfferTableColumns::getEndian),
|
||||
COMPILER("Compiler", CompilerSpecID.class, DebuggerPlatformOffer::getCompilerSpecID);
|
||||
|
||||
private static final LanguageService LANG_SERV =
|
||||
DefaultLanguageService.getLanguageService();
|
||||
|
||||
private static String getProcessor(DebuggerPlatformOffer offer) {
|
||||
if (offer.getLanguageID() == null) {
|
||||
return "Unspecified";
|
||||
}
|
||||
try {
|
||||
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getProcessor().toString();
|
||||
}
|
||||
catch (LanguageNotFoundException e) {
|
||||
return "Not Found";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getVariant(DebuggerPlatformOffer offer) {
|
||||
if (offer.getLanguageID() == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getVariant();
|
||||
}
|
||||
catch (LanguageNotFoundException e) {
|
||||
return "Not Found";
|
||||
}
|
||||
}
|
||||
|
||||
private static int getSize(DebuggerPlatformOffer offer) {
|
||||
try {
|
||||
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getSize();
|
||||
}
|
||||
catch (LanguageNotFoundException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static Endian getEndian(DebuggerPlatformOffer offer) {
|
||||
try {
|
||||
return LANG_SERV.getLanguageDescription(offer.getLanguageID()).getEndian();
|
||||
}
|
||||
catch (LanguageNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final String header;
|
||||
private final Class<?> cls;
|
||||
private final Function<DebuggerPlatformOffer, ?> getter;
|
||||
private final SortDirection sortDir;
|
||||
|
||||
<T> OfferTableColumns(String header, Class<T> cls,
|
||||
Function<DebuggerPlatformOffer, T> getter, SortDirection sortDir) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.sortDir = sortDir;
|
||||
}
|
||||
|
||||
<T> OfferTableColumns(String header, Class<T> cls,
|
||||
Function<DebuggerPlatformOffer, T> getter) {
|
||||
this(header, cls, getter, SortDirection.ASCENDING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(DebuggerPlatformOffer row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortDirection defaultSortDirection() {
|
||||
return sortDir;
|
||||
}
|
||||
}
|
||||
|
||||
public static class OfferTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<OfferTableColumns, DebuggerPlatformOffer> {
|
||||
|
||||
public OfferTableModel() {
|
||||
super("Offers", OfferTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OfferTableColumns> defaultSortOrder() {
|
||||
return List.of(OfferTableColumns.CONFIDENCE, OfferTableColumns.PROCESSOR,
|
||||
OfferTableColumns.VARIANT, OfferTableColumns.COMPILER);
|
||||
}
|
||||
}
|
||||
|
||||
public static class OfferPanel extends JPanel {
|
||||
private final OfferTableModel offerTableModel = new OfferTableModel();
|
||||
private final GhidraTable offerTable = new GhidraTable(offerTableModel);
|
||||
private final GhidraTableFilterPanel<DebuggerPlatformOffer> offerTableFilterPanel =
|
||||
new GhidraTableFilterPanel<>(offerTable, offerTableModel);
|
||||
private final JLabel descLabel = new JLabel();
|
||||
private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers");
|
||||
|
||||
private final JScrollPane scrollPane = new JScrollPane(offerTable) {
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension pref = super.getPreferredSize();
|
||||
if (pref.width != 0) {
|
||||
pref.height = 150;
|
||||
}
|
||||
return pref;
|
||||
}
|
||||
};
|
||||
private final TableFilter<DebuggerPlatformOffer> filterRecommended = new TableFilter<>() {
|
||||
@Override
|
||||
public boolean acceptsRow(DebuggerPlatformOffer offer) {
|
||||
return !offer.isOverride();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private LanguageID preferredLangID;
|
||||
private CompilerSpecID preferredCsID;
|
||||
|
||||
{
|
||||
JPanel descPanel = new JPanel(new BorderLayout());
|
||||
descPanel.setBorder(BorderFactory.createTitledBorder("Description"));
|
||||
descPanel.add(descLabel, BorderLayout.CENTER);
|
||||
|
||||
JPanel nested1 = new JPanel(new BorderLayout());
|
||||
nested1.add(scrollPane, BorderLayout.CENTER);
|
||||
nested1.add(offerTableFilterPanel, BorderLayout.SOUTH);
|
||||
|
||||
JPanel nested2 = new JPanel(new BorderLayout());
|
||||
nested2.add(nested1, BorderLayout.CENTER);
|
||||
nested2.add(descPanel, BorderLayout.SOUTH);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(nested2, BorderLayout.CENTER);
|
||||
add(overrideCheckBox, BorderLayout.SOUTH);
|
||||
|
||||
setFilterRecommended(true);
|
||||
offerTable.getSelectionModel().addListSelectionListener(e -> {
|
||||
DebuggerPlatformOffer offer = getSelectedOffer();
|
||||
descLabel.setText(offer == null ? "" : offer.getDescription());
|
||||
});
|
||||
|
||||
overrideCheckBox.addActionListener(evt -> {
|
||||
setFilterRecommended(overrideCheckBox.isSelected());
|
||||
});
|
||||
}
|
||||
|
||||
public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
|
||||
this.preferredLangID = langID;
|
||||
this.preferredCsID = csID;
|
||||
}
|
||||
|
||||
public void setOffers(Collection<DebuggerPlatformOffer> offers) {
|
||||
offerTableModel.clear();
|
||||
offerTableModel.addAll(offers);
|
||||
|
||||
selectPreferred();
|
||||
}
|
||||
|
||||
private void selectPreferred() {
|
||||
// As sorted and filtered, pick the first matching offer
|
||||
// NB. It should never be one or the other. Always both or none.
|
||||
RowObjectFilterModel<DebuggerPlatformOffer> model =
|
||||
offerTableFilterPanel.getTableFilterModel();
|
||||
int count = model.getRowCount();
|
||||
if (preferredLangID != null && preferredCsID != null) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
DebuggerPlatformOffer offer = model.getRowObject(i);
|
||||
if (offer.getLanguageID().equals(preferredLangID) &&
|
||||
offer.getCompilerSpecID().equals(preferredCsID)) {
|
||||
offerTable.getSelectionModel().setSelectionInterval(i, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to first offer; disregard preference
|
||||
if (model.getRowCount() > 0) {
|
||||
offerTable.getSelectionModel().setSelectionInterval(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFilterRecommended(boolean recommendedOnly) {
|
||||
boolean hasSelection = offerTableFilterPanel.getSelectedItem() != null;
|
||||
overrideCheckBox.setSelected(recommendedOnly);
|
||||
offerTableFilterPanel.setSecondaryFilter(recommendedOnly ? filterRecommended : null);
|
||||
if (!hasSelection) {
|
||||
selectPreferred();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedOffer(DebuggerPlatformOffer offer) {
|
||||
offerTableFilterPanel.setSelectedItem(offer);
|
||||
}
|
||||
|
||||
public DebuggerPlatformOffer getSelectedOffer() {
|
||||
return offerTableFilterPanel.getSelectedItem();
|
||||
}
|
||||
|
||||
// For tests
|
||||
public List<DebuggerPlatformOffer> getDisplayedOffers() {
|
||||
return List.copyOf(offerTableFilterPanel.getTableFilterModel().getModelData());
|
||||
}
|
||||
}
|
||||
|
||||
private final OfferPanel offerPanel = new OfferPanel();
|
||||
|
||||
private boolean isCancelled = false;
|
||||
|
||||
protected DebuggerSelectPlatformOfferDialog() {
|
||||
super(DebuggerResources.NAME_CHOOSE_PLATFORM, Set.of(Opt.MODAL, Opt.INCLUDE_BUTTONS));
|
||||
|
||||
populateComponents();
|
||||
}
|
||||
|
||||
protected void populateComponents() {
|
||||
offerPanel.setBorder(BorderFactory.createTitledBorder(" Select Platform "));
|
||||
addWorkPanel(offerPanel);
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
|
||||
setDefaultButton(okButton);
|
||||
setOkEnabled(false);
|
||||
|
||||
// TODO: Separate this a bit
|
||||
offerPanel.offerTable.getSelectionModel().addListSelectionListener(e -> {
|
||||
setOkEnabled(getSelectedOffer() != null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the preferred language and compiler spec IDs, typically from the current program.
|
||||
*
|
||||
* <p>
|
||||
* This must be called before {@link #setOffers(Collection)}.
|
||||
*
|
||||
* @param langID the preferred language
|
||||
* @param csID the preferred compiler spec (ABI)
|
||||
*/
|
||||
public void setPreferredIDs(LanguageID langID, CompilerSpecID csID) {
|
||||
offerPanel.setPreferredIDs(langID, csID);
|
||||
}
|
||||
|
||||
public void setOffers(Collection<DebuggerPlatformOffer> offers) {
|
||||
offerPanel.setOffers(offers);
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return isCancelled;
|
||||
}
|
||||
|
||||
public void setSelectedOffer(DebuggerPlatformOffer offer) {
|
||||
offerPanel.setSelectedOffer(offer);
|
||||
}
|
||||
|
||||
public DebuggerPlatformOffer getSelectedOffer() {
|
||||
return offerPanel.getSelectedOffer();
|
||||
}
|
||||
|
||||
// For tests
|
||||
protected List<DebuggerPlatformOffer> getDisplayedOffers() {
|
||||
return offerPanel.getDisplayedOffers();
|
||||
}
|
||||
|
||||
protected void setFilterRecommended(boolean recommendedOnly) {
|
||||
offerPanel.setFilterRecommended(recommendedOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
isCancelled = true;
|
||||
super.cancelCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
if (getSelectedOffer() != null) {
|
||||
isCancelled = false;
|
||||
close();
|
||||
}
|
||||
// Do nothing. Should be disabled anyway
|
||||
}
|
||||
}
|
||||
-2
@@ -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) {
|
||||
|
||||
+29
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -24,19 +24,20 @@ import ghidra.trace.model.target.TraceObject;
|
||||
public abstract class AbstractDebuggerPlatformOpinion implements DebuggerPlatformOpinion {
|
||||
|
||||
protected abstract Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap,
|
||||
TraceObject env, String debugger, String arch, String os, Endian endian);
|
||||
TraceObject env, String debugger, String arch, String os, Endian endian,
|
||||
boolean includeOverrides);
|
||||
|
||||
@Override
|
||||
public Set<DebuggerPlatformOffer> getOffers(Trace trace, TraceObject object, long snap,
|
||||
boolean includeOverrides) {
|
||||
TraceObject env = DebuggerPlatformOpinion.getEnvironment(object, snap);
|
||||
if (env == null) {
|
||||
return getOffers(object, snap, env, null, null, null, null);
|
||||
return getOffers(object, snap, env, null, null, null, null, includeOverrides);
|
||||
}
|
||||
String debugger = DebuggerPlatformOpinion.getDebugggerFromEnv(env, snap);
|
||||
String arch = DebuggerPlatformOpinion.getArchitectureFromEnv(env, snap);
|
||||
String os = DebuggerPlatformOpinion.getOperatingSystemFromEnv(env, snap);
|
||||
Endian endian = DebuggerPlatformOpinion.getEndianFromEnv(env, snap);
|
||||
return getOffers(object, snap, env, debugger, arch, os, endian);
|
||||
return getOffers(object, snap, env, debugger, arch, os, endian, includeOverrides);
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -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
|
||||
*
|
||||
|
||||
+37
-1
@@ -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);
|
||||
}
|
||||
|
||||
+6
-7
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.platform;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
|
||||
/**
|
||||
* An "opinion" which offers every known compiler, but with only default mapping logic.
|
||||
*/
|
||||
public class OverrideDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion {
|
||||
private static final LanguageCompilerSpecQuery ALL_SPECS =
|
||||
new LanguageCompilerSpecQuery(null, null, null, null, null);
|
||||
private static final Map<Endian, Set<DebuggerPlatformOffer>> CACHE = new HashMap<>();
|
||||
|
||||
protected static class OverridePlatformOffer implements DebuggerPlatformOffer {
|
||||
|
||||
private final String description;
|
||||
private final LanguageID languageID;
|
||||
private final CompilerSpecID cSpecID;
|
||||
private final int confidence;
|
||||
|
||||
public OverridePlatformOffer(String description, LanguageID languageID,
|
||||
CompilerSpecID cSpecID, int confidence) {
|
||||
this.description = description;
|
||||
this.languageID = languageID;
|
||||
this.cSpecID = cSpecID;
|
||||
this.confidence = confidence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LanguageID getLanguageID() {
|
||||
return languageID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompilerSpecID getCompilerSpecID() {
|
||||
return cSpecID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
|
||||
return new OverrideDebuggerPlatformMapper(tool, trace, getCompilerSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == OverrideDebuggerPlatformMapper.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompilerSpec getCompilerSpec() {
|
||||
return getCompilerSpec(languageID, cSpecID);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class OverrideDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
|
||||
public OverrideDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
|
||||
super(tool, trace, cSpec);
|
||||
}
|
||||
}
|
||||
|
||||
protected DebuggerPlatformOffer computeOfferForEndianAndLCSP(Endian endian,
|
||||
LanguageCompilerSpecPair lcsp) {
|
||||
try {
|
||||
LanguageDescription ldesc = lcsp.getLanguageDescription();
|
||||
return new OverridePlatformOffer("Override to " + lcsp,
|
||||
ldesc.getLanguageID(),
|
||||
lcsp.getCompilerSpecDescription().getCompilerSpecID(),
|
||||
ldesc.getEndian() == endian ? -10 : -20);
|
||||
}
|
||||
catch (LanguageNotFoundException | CompilerSpecNotFoundException e) {
|
||||
// It couldn't have been generated unless it existed
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Set<DebuggerPlatformOffer> computeOffersForEndian(Endian endian) {
|
||||
LanguageService langServ = DefaultLanguageService.getLanguageService();
|
||||
return langServ.getLanguageCompilerSpecPairs(ALL_SPECS)
|
||||
.stream()
|
||||
.map(lcsp -> computeOfferForEndianAndLCSP(endian, lcsp))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||
if (!includeOverrides) {
|
||||
return Set.of();
|
||||
}
|
||||
synchronized (CACHE) {
|
||||
return CACHE.computeIfAbsent(endian, this::computeOffersForEndian);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -65,12 +65,17 @@ public class DbgengDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpini
|
||||
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
|
||||
return new DbgengX64DebuggerPlatformMapper(tool, trace, getCompilerSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == DbgengX64DebuggerPlatformMapper.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||
String debugger, String arch, String os, Endian endian) {
|
||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||
if (debugger == null || arch == null || !debugger.toLowerCase().contains("dbg")) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
+6
-1
@@ -79,11 +79,16 @@ public class FridaDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinio
|
||||
// TODO: May need these per offer
|
||||
return new FridaDebuggerPlatformMapper(tool, trace, getCompilerSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == FridaDebuggerPlatformMapper.class;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||
String debugger, String arch, String os, Endian endian) {
|
||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||
if (debugger == null || arch == null ||
|
||||
os == null | !debugger.toLowerCase().contains("frida")) {
|
||||
return Set.of();
|
||||
|
||||
+7
-2
@@ -66,6 +66,11 @@ public class GdbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
|
||||
return new GdbDebuggerPlatformMapper(tool, trace, cSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == GdbDebuggerPlatformMapper.class;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class GdbDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
|
||||
@@ -78,12 +83,12 @@ public class GdbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||
protected Set<GdbDebuggerPlatformOffer> offersForLanguageAndCSpec(String arch, Endian endian,
|
||||
LanguageCompilerSpecPair lcsp)
|
||||
throws CompilerSpecNotFoundException, LanguageNotFoundException {
|
||||
return Set.of(GdbDebuggerPlatformOffer.fromArchLCSP("Default GDB for " + arch, lcsp));
|
||||
return Set.of(GdbDebuggerPlatformOffer.fromArchLCSP(arch, lcsp));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||
String debugger, String arch, String os, Endian endian) {
|
||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||
if (debugger == null || !"gdb".equals(debugger.toLowerCase())) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
+6
-2
@@ -29,7 +29,6 @@ public class JdiDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||
protected static final CompilerSpecID COMP_ID_DEFAULT = new CompilerSpecID("default");
|
||||
|
||||
protected static class JdiDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
|
||||
// TODO: Delete this class?
|
||||
public JdiDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
|
||||
super(tool, trace, cSpec);
|
||||
}
|
||||
@@ -68,11 +67,16 @@ public class JdiDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
|
||||
return new JdiDebuggerPlatformMapper(tool, trace, getCompilerSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == JdiDebuggerPlatformMapper.class;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||
String debugger, String arch, String os, Endian endian) {
|
||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||
if (debugger == null || arch == null || !debugger.contains("Java Debug Interface")) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
+7
-2
@@ -31,7 +31,7 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||
protected static final CompilerSpecID COMP_ID_GCC = new CompilerSpecID("gcc");
|
||||
protected static final CompilerSpecID COMP_ID_VS = new CompilerSpecID("windows");
|
||||
|
||||
protected static class LldbDebuggerPlatformMapper
|
||||
protected static final class LldbDebuggerPlatformMapper
|
||||
extends DefaultDebuggerPlatformMapper {
|
||||
public LldbDebuggerPlatformMapper(PluginTool tool, Trace trace,
|
||||
CompilerSpec cSpec) {
|
||||
@@ -79,11 +79,16 @@ public class LldbDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||
// TODO: May need these per offer
|
||||
return new LldbDebuggerPlatformMapper(tool, trace, getCompilerSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == LldbDebuggerPlatformMapper.class;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap, TraceObject env,
|
||||
String debugger, String arch, String os, Endian endian) {
|
||||
String debugger, String arch, String os, Endian endian, boolean includeOverrides) {
|
||||
if (debugger == null || arch == null ||
|
||||
os == null | !debugger.toLowerCase().contains("lldb")) {
|
||||
return Set.of();
|
||||
|
||||
+10
-21
@@ -15,21 +15,18 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.service.platform;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.DebuggerPlatformPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ChoosePlatformAction;
|
||||
import ghidra.app.plugin.core.debug.mapping.*;
|
||||
import ghidra.app.services.DebuggerPlatformService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.lifecycle.Unfinished;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
|
||||
@@ -55,8 +52,6 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
ActionBuilder actionChoosePlatform;
|
||||
|
||||
private final Map<Trace, DebuggerPlatformMapper> mappersByTrace = new HashMap<>();
|
||||
|
||||
public DebuggerPlatformServicePlugin(PluginTool tool) {
|
||||
@@ -65,13 +60,10 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
createActions();
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
actionChoosePlatform = ChoosePlatformAction.builder(this); // TODO:
|
||||
public DebuggerPlatformMapper getCurrentMapperFor(Trace trace) {
|
||||
synchronized (mappersByTrace) {
|
||||
return mappersByTrace.get(trace);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,7 +85,7 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
|
||||
mappersByTrace.put(trace, mapper);
|
||||
}
|
||||
mapper.addToTrace(snap);
|
||||
// TODO: Fire a listener
|
||||
firePluginEvent(new DebuggerPlatformPluginEvent(getName(), trace, mapper));
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@@ -109,13 +101,10 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerPlatformMapper chooseMapper(Trace trace, TraceObject object, long snap) {
|
||||
return Unfinished.TODO();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentMapperFor(Trace trace, DebuggerPlatformMapper mapper, long snap) {
|
||||
Objects.requireNonNull(trace);
|
||||
Objects.requireNonNull(mapper);
|
||||
if (!traceManager.getOpenTraces().contains(trace)) {
|
||||
throw new IllegalArgumentException("Trace is not opened in this tool");
|
||||
}
|
||||
@@ -123,7 +112,7 @@ public class DebuggerPlatformServicePlugin extends Plugin implements DebuggerPla
|
||||
mappersByTrace.put(trace, mapper);
|
||||
}
|
||||
mapper.addToTrace(snap);
|
||||
// TODO: Fire a listener
|
||||
firePluginEvent(new DebuggerPlatformPluginEvent(getName(), trace, mapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+59
-2
@@ -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);
|
||||
|
||||
+11
-11
@@ -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
|
||||
*
|
||||
|
||||
+15
@@ -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"
|
||||
*
|
||||
|
||||
+63
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.platform;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.action.ToggleDockingActionIf;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
|
||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
|
||||
import ghidra.app.services.DebuggerPlatformService;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
|
||||
public class DebuggerPlatformPluginTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
DebuggerPlatformPlugin platformPlugin;
|
||||
DebuggerPlatformService platformService;
|
||||
|
||||
protected List<ToggleDockingActionIf> getPlatformActions() {
|
||||
return tool.getAllActions()
|
||||
.stream()
|
||||
.filter(a -> a.getOwner().equals(platformPlugin.getName()))
|
||||
.filter(a -> a instanceof ToggleDockingActionIf)
|
||||
.filter(a -> a != platformPlugin.actionMore)
|
||||
.map(a -> (ToggleDockingActionIf) a)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUpPlatformTest() throws Throwable {
|
||||
platformPlugin = addPlugin(tool, DebuggerPlatformPlugin.class);
|
||||
platformService = tool.getService(DebuggerPlatformService.class);
|
||||
}
|
||||
|
||||
protected void chooseLanguageIDViaMore(LanguageID langID) {
|
||||
performAction(platformPlugin.actionMore, false);
|
||||
DebuggerSelectPlatformOfferDialog dialog =
|
||||
waitForDialogComponent(DebuggerSelectPlatformOfferDialog.class);
|
||||
dialog.setFilterRecommended(false);
|
||||
waitForSwing();
|
||||
|
||||
List<DebuggerPlatformOffer> offers = runSwing(() -> dialog.getDisplayedOffers());
|
||||
DebuggerPlatformOffer toyOffer = offers.stream()
|
||||
.filter(o -> Objects.equals(langID, o.getLanguageID()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
runSwing(() -> dialog.setSelectedOffer(toyOffer));
|
||||
runSwing(() -> dialog.okCallback());
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionMore() throws Throwable {
|
||||
createAndOpenTrace("DATA:BE:64:default");
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
chooseLanguageIDViaMore(new LanguageID("Toy:BE:64:default"));
|
||||
DebuggerPlatformMapper mapper = platformService.getCurrentMapperFor(tb.trace);
|
||||
assertEquals(new LanguageID("Toy:BE:64:default"), mapper.getLangauge(null).getLanguageID());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemembersChosenOffer() throws Throwable {
|
||||
createAndOpenTrace("DATA:BE:64:default");
|
||||
try (ToyDBTraceBuilder tb2 =
|
||||
new ToyDBTraceBuilder("second-" + name.getMethodName(), "DATA:BE:64:default")) {
|
||||
traceManager.openTrace(tb2.trace);
|
||||
traceManager.activateTrace(tb2.trace);
|
||||
|
||||
chooseLanguageIDViaMore(new LanguageID("Toy:BE:64:default"));
|
||||
assertEquals(2, getPlatformActions().size());
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
assertEquals(1, getPlatformActions().size());
|
||||
|
||||
traceManager.activateTrace(tb2.trace);
|
||||
waitForSwing();
|
||||
assertEquals(2, getPlatformActions().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-2
@@ -24,6 +24,12 @@ import ghidra.trace.model.target.TraceObject;
|
||||
|
||||
public class TestDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion {
|
||||
|
||||
protected static class TestDebuggerPlatformMapper extends DefaultDebuggerPlatformMapper {
|
||||
public TestDebuggerPlatformMapper(PluginTool tool, Trace trace, CompilerSpec cSpec) {
|
||||
super(tool, trace, cSpec);
|
||||
}
|
||||
}
|
||||
|
||||
enum Offers implements DebuggerPlatformOffer {
|
||||
ARM_V8_LE("Test armv8le", "ARM:LE:32:v8", "default"),
|
||||
X86_64("Test x86-64", "x86:LE:64:default", "gcc");
|
||||
@@ -58,13 +64,19 @@ public class TestDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion
|
||||
|
||||
@Override
|
||||
public DebuggerPlatformMapper take(PluginTool tool, Trace trace) {
|
||||
return new DefaultDebuggerPlatformMapper(tool, trace, getCompilerSpec());
|
||||
return new TestDebuggerPlatformMapper(tool, trace, getCompilerSpec());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorOf(DebuggerPlatformMapper mapper) {
|
||||
return mapper.getClass() == TestDebuggerPlatformMapper.class;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<DebuggerPlatformOffer> getOffers(TraceObject object, long snap,
|
||||
TraceObject env, String debugger, String arch, String os, Endian endian) {
|
||||
TraceObject env, String debugger, String arch, String os, Endian endian,
|
||||
boolean includeOverrides) {
|
||||
if (!"test".equals(debugger)) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
+4
-2
@@ -145,7 +145,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
||||
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
AsyncPcodeExecutor<byte[]> executor =
|
||||
TracePcodeUtils.executorForCoordinates(
|
||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
|
||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
|
||||
null));
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||
@@ -182,7 +183,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
AsyncPcodeExecutor<byte[]> executor =
|
||||
TracePcodeUtils.executorForCoordinates(
|
||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
|
||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
|
||||
null));
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||
|
||||
+51
-2
@@ -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();
|
||||
|
||||
+9
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -29,7 +29,9 @@ import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.DBTraceManager;
|
||||
import ghidra.trace.database.guest.DBTraceGuestPlatform.DBTraceGuestLanguage;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TracePlatformChangeType;
|
||||
import ghidra.trace.model.guest.*;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@@ -261,6 +263,7 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
|
||||
platformsByCompiler.remove(platform.getCompilerSpec());
|
||||
platformStore.delete(platform);
|
||||
}
|
||||
trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.DELETED, null, platform));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -281,9 +284,12 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
|
||||
throw new IllegalArgumentException(
|
||||
"Base compiler spec cannot be a guest compiler spec");
|
||||
}
|
||||
DBTraceGuestPlatform platform;
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
return doAddGuestPlatform(compilerSpec);
|
||||
platform = doAddGuestPlatform(compilerSpec);
|
||||
}
|
||||
trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.ADDED, null, platform));
|
||||
return platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -297,18 +303,21 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceGuestPlatform getOrAddGuestPlatform(CompilerSpec compilerSpec) {
|
||||
public InternalTracePlatform getOrAddPlatform(CompilerSpec compilerSpec) {
|
||||
if (compilerSpec.getCompilerSpecID()
|
||||
.equals(trace.getBaseCompilerSpec().getCompilerSpecID())) {
|
||||
throw new IllegalArgumentException("Base language cannot be a guest language");
|
||||
return hostPlatform;
|
||||
}
|
||||
DBTraceGuestPlatform platform;
|
||||
try (LockHold hold = LockHold.lock(lock.writeLock())) {
|
||||
DBTraceGuestPlatform exists = platformsByCompiler.get(compilerSpec);
|
||||
if (exists != null) {
|
||||
return exists;
|
||||
}
|
||||
return doAddGuestPlatform(compilerSpec);
|
||||
platform = doAddGuestPlatform(compilerSpec);
|
||||
}
|
||||
trace.setChanged(new TraceChangeRecord<>(TracePlatformChangeType.ADDED, null, platform));
|
||||
return platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,7 +32,7 @@ import ghidra.trace.model.breakpoint.TraceBreakpoint;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointManager;
|
||||
import ghidra.trace.model.context.TraceRegisterContextManager;
|
||||
import ghidra.trace.model.data.TraceBasedDataTypeManager;
|
||||
import ghidra.trace.model.guest.TracePlatformManager;
|
||||
import ghidra.trace.model.guest.*;
|
||||
import ghidra.trace.model.listing.*;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.trace.model.modules.*;
|
||||
@@ -379,6 +379,16 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||
public static final TraceSnapshotChangeType<Void> DELETED = new TraceSnapshotChangeType<>();
|
||||
}
|
||||
|
||||
public static final class TracePlatformChangeType<U>
|
||||
extends DefaultTraceChangeType<TraceGuestPlatform, U> {
|
||||
public static final TracePlatformChangeType<Void> ADDED = new TracePlatformChangeType<>();
|
||||
public static final TracePlatformChangeType<Void> DELETED = new TracePlatformChangeType<>();
|
||||
public static final TracePlatformChangeType<TraceGuestPlatformMappedRange> MAPPING_ADDED =
|
||||
new TracePlatformChangeType<>();
|
||||
public static final TracePlatformChangeType<TraceGuestPlatformMappedRange> MAPPING_DELETED =
|
||||
new TracePlatformChangeType<>();
|
||||
}
|
||||
|
||||
public interface TraceProgramViewListener {
|
||||
void viewCreated(TraceProgramView view);
|
||||
}
|
||||
|
||||
+9
-9
@@ -33,6 +33,13 @@ public interface TracePlatformManager {
|
||||
*/
|
||||
TracePlatform getHostPlatform();
|
||||
|
||||
/**
|
||||
* Get all guest platforms
|
||||
*
|
||||
* @return the collection of platforms
|
||||
*/
|
||||
Collection<TraceGuestPlatform> getGuestPlatforms();
|
||||
|
||||
/**
|
||||
* Add a guest platform
|
||||
*
|
||||
@@ -53,14 +60,7 @@ public interface TracePlatformManager {
|
||||
* Get or add a platform for the given compiler spec
|
||||
*
|
||||
* @param compilerSpec the compiler spec
|
||||
* @return the new or existing platform, or null if compiler spec is the base compiler spec
|
||||
* @return the new or existing platform
|
||||
*/
|
||||
TraceGuestPlatform getOrAddGuestPlatform(CompilerSpec compilerSpec);
|
||||
|
||||
/**
|
||||
* Get all guest platforms
|
||||
*
|
||||
* @return the collection of platforms
|
||||
*/
|
||||
Collection<TraceGuestPlatform> getGuestPlatforms();
|
||||
TracePlatform getOrAddPlatform(CompilerSpec compilerSpec);
|
||||
}
|
||||
|
||||
+25
-23
@@ -40,6 +40,30 @@ import ghidra.trace.model.thread.TraceObjectThread;
|
||||
import ghidra.util.database.*;
|
||||
|
||||
public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
public static final String XML_CTX = """
|
||||
<context>
|
||||
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||
<attribute name='curTarget' schema='Target' />
|
||||
<attribute name='Targets' schema='TargetContainer' />
|
||||
</schema>
|
||||
<schema name='TargetContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<element schema='Target' />
|
||||
</schema>
|
||||
<schema name='Target' elementResync='NEVER' attributeResync='NEVER'>
|
||||
<interface name='Process' />
|
||||
<attribute name='self' schema='Target' />
|
||||
<attribute name='Threads' schema='ThreadContainer' />
|
||||
</schema>
|
||||
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='NEVER'>
|
||||
<element schema='Thread' />
|
||||
</schema>
|
||||
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||
<interface name='Thread' />
|
||||
</schema>
|
||||
</context>
|
||||
""";
|
||||
protected ToyDBTraceBuilder b;
|
||||
protected DBTraceObjectManager manager;
|
||||
|
||||
@@ -54,29 +78,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
|
||||
b = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default");
|
||||
manager = b.trace.getObjectManager();
|
||||
|
||||
ctx = XmlSchemaContext.deserialize("" + //
|
||||
"<context>" + //
|
||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
||||
" <attribute name='curTarget' schema='Target' />" + //
|
||||
" <attribute name='Targets' schema='TargetContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='TargetContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element schema='Target' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='Target' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||
" <interface name='Process' />" + //
|
||||
" <attribute name='self' schema='Target' />" + //
|
||||
" <attribute name='Threads' schema='ThreadContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='NEVER'>" + //
|
||||
" <element schema='Thread' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||
" <interface name='Thread' />" + //
|
||||
" </schema>" + //
|
||||
"</context>");
|
||||
ctx = XmlSchemaContext.deserialize(XML_CTX);
|
||||
}
|
||||
|
||||
protected void populateModel(int targetCount) {
|
||||
|
||||
@@ -45,6 +45,10 @@ import utility.function.Callback;
|
||||
public class DialogComponentProvider
|
||||
implements ActionContextProvider, StatusListener, TaskListener {
|
||||
|
||||
protected enum Opt {
|
||||
MODAL, INCLUDE_STATUS, INCLUDE_BUTTONS, CAN_RUN_TASKS;
|
||||
}
|
||||
|
||||
private static final Color WARNING_COLOR = new Color(0xff9900);
|
||||
|
||||
private final static int DEFAULT_DELAY = 750;
|
||||
@@ -99,6 +103,17 @@ public class DialogComponentProvider
|
||||
|
||||
private Dimension defaultSize;
|
||||
|
||||
/**
|
||||
* Constructor for GhidraDialogComponent using option set instead of booleans
|
||||
* @param title the dialog title
|
||||
* @param options the options. See {@link Option} and
|
||||
* {{@link #DialogComponentProvider(String, boolean, boolean, boolean, boolean)}
|
||||
*/
|
||||
protected DialogComponentProvider(String title, Set<Opt> options) {
|
||||
this(title, options.contains(Opt.MODAL), options.contains(Opt.INCLUDE_STATUS),
|
||||
options.contains(Opt.INCLUDE_BUTTONS), options.contains(Opt.CAN_RUN_TASKS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a GhidraDialogComponent that will be modal and will include a status line and
|
||||
* a button panel. Its title will be the same as its name.
|
||||
|
||||
Reference in New Issue
Block a user