Merge remote-tracking branch 'origin/GP-870_d-millar_TimeOverview_RB250303--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-03-25 06:39:36 -04:00
commit 8f30c4da14
12 changed files with 1593 additions and 0 deletions

View file

@ -56,6 +56,7 @@ src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugi
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerTimeOverviewPlugin/DebuggerTimeOverviewPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END|

View file

@ -20,6 +20,35 @@ color.debugger.plugin.memview.box.type.read.memory = color.palette.darkgray
color.debugger.plugin.memview.box.type.write.memory = color.palette.blue color.debugger.plugin.memview.box.type.write.memory = color.palette.blue
color.debugger.plugin.memview.box.type.breakpoint = color.palette.red color.debugger.plugin.memview.box.type.breakpoint = color.palette.red
color.debugger.plugin.timeoverview.box = color.palette.blue
color.debugger.plugin.timeoverview.box.type.instructions = color.palette.darkred
color.debugger.plugin.timeoverview.box.type.process = color.palette.lightcornflowerblue
color.debugger.plugin.timeoverview.box.type.thread.added = color.palette.lightskyblue
color.debugger.plugin.timeoverview.box.type.thread.removed = color.palette.lightskyblue
color.debugger.plugin.timeoverview.box.type.thread.changed = color.palette.lightskyblue
color.debugger.plugin.timeoverview.box.type.module.added = color.palette.lime
color.debugger.plugin.timeoverview.box.type.module.removed = color.palette.lime
color.debugger.plugin.timeoverview.box.type.module.changed = color.palette.lime
color.debugger.plugin.timeoverview.box.type.region.added = color.palette.yellow
color.debugger.plugin.timeoverview.box.type.region.removed = color.palette.yellow
color.debugger.plugin.timeoverview.box.type.region.changed = color.palette.yellow
color.debugger.plugin.timeoverview.box.type.image = color.palette.magenta
color.debugger.plugin.timeoverview.box.type.virtual.alloc = color.palette.lightgray
color.debugger.plugin.timeoverview.box.type.heap.create = color.palette.blue
color.debugger.plugin.timeoverview.box.type.heap.alloc = color.palette.darkgreen
color.debugger.plugin.timeoverview.box.type.pool = color.palette.indigo
color.debugger.plugin.timeoverview.box.type.stack = color.palette.cyan
color.debugger.plugin.timeoverview.box.type.perfinfo = color.palette.lightgray
color.debugger.plugin.timeoverview.box.type.read.memory = color.palette.darkgray
color.debugger.plugin.timeoverview.box.type.write.memory = color.palette.blue
color.debugger.plugin.timeoverview.box.type.breakpoint.added = color.palette.red
color.debugger.plugin.timeoverview.box.type.breakpoint.removed = color.palette.red
color.debugger.plugin.timeoverview.box.type.breakpoint.changed = color.palette.red
color.debugger.plugin.timeoverview.box.type.snap.added = color.palette.lightgray
color.debugger.plugin.timeoverview.box.type.snap.removed = color.palette.lightgray
color.debugger.plugin.timeoverview.box.type.snap.changed = color.palette.lightgray
color.debugger.plugin.timeoverview.box.type.undefined = color.palette.black
color.bg.debugger.plugin.objects.default = color.bg color.bg.debugger.plugin.objects.default = color.bg
color.fg.debugger.plugin.objects.default = color.fg color.fg.debugger.plugin.objects.default = color.fg
color.fg.debugger.plugin.objects.invisible = color.palette.lightgray color.fg.debugger.plugin.objects.invisible = color.palette.lightgray

View file

@ -106,6 +106,10 @@
sortgroup="u" sortgroup="u"
target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" /> target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" />
<tocdef id="DebuggerTimeOverviewPlugin" text="Time Overview Sidebar"
sortgroup="m"
target="help/topics/DebuggerTimeOverviewPlugin/DebuggerTimeOverviewPlugin.html" />
<tocdef id="DebuggerPcodeStepperPlugin" text="P-code Stepper" <tocdef id="DebuggerPcodeStepperPlugin" text="P-code Stepper"
sortgroup="v" sortgroup="v"
target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" /> target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" />

View file

@ -0,0 +1,44 @@
<!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: Time Overview</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Time Overview</H1>
<P>Patterned on the Program Overview, this plugin provides a pair of sidebars for the
Dynamic Listing that indicate the history of the current trace, similar to those in the
<A href="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time</A>
and <A href="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html">Memview</A> plugins.
The Trace Overview bar gives a compressed view of various events
(thread creation/destruction, module loads/unloads, et cetera). Events are added in time
snap order <em>without</em> spaces between consecutive events, as in the Memview display. The Trace
Selection sidebar allows the user to click &amp; drag over a section of either sidebar and zooms
in on that time span. In the zoomed version, events are in snap order <em>with</em> intervening space
to indicate the actual time delays (although these are dictated, in part, by the numbering
schema for events).
</P>
<H2>Navigation</H2>
<H3><A name="zoom"></A><IMG alt="" src="icon.widget.imagepanel.zoom.in">Zoom</H3>
<P>Clicking and dragging over a region of the Trace Overview causes that
span to be displayed uncompressed in the Trace Selection. The same action can also
be applied to the Trace Selection itself, resulting in a more detailed zoom.</P>
<H3><A name="move"></A><IMG alt="" src="icon.widget.imagepanel.zoom.in">Move</H3>
<P>Shift click &amp; drag on the Trace Selection allows the user to move forward and
backward in the view without rescaling.
</P>
</BODY>
</HTML>

View file

@ -0,0 +1,251 @@
/* ###
* 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.timeoverview;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.TreeSet;
import javax.swing.*;
import docking.action.DockingActionIf;
import ghidra.app.nav.Navigatable;
import ghidra.app.util.viewer.listingpanel.OverviewProvider;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Lifespan;
import ghidra.util.task.SwingUpdateManager;
/**
* Overview bar component. Uses color to indicate various snap-based properties for a program.
* Uses an {@link TimeOverviewColorService} to get the appropriate color for a snaps.
*/
public class TimeOverviewColorComponent extends JPanel implements OverviewProvider {
private static final Color DEFAULT_COLOR = Color.GRAY;
protected TimeOverviewColorService service;
private Color[] colorsByPixel = new Color[0];
private final SwingUpdateManager refreshUpdater =
new SwingUpdateManager(100, 15000, () -> doRefresh());
private PluginTool tool;
private List<DockingActionIf> actions;
private TimeOverviewColorPlugin plugin;
/**
* Constructor
*
* @param tool the PluginTool
* @param overviewColorService the {@link TimeOverviewColorService} that provides colors for
* various snaps.
*/
public TimeOverviewColorComponent(PluginTool tool,
TimeOverviewColorService overviewColorService) {
this.tool = tool;
this.service = overviewColorService;
overviewColorService.setOverviewComponent(this);
addMouseListener(new MouseAdapter() {
private int pressedY;
private boolean enableDrag = false;
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
Long snap = service.getSnap(e.getY());
gotoSnap(snap);
}
}
@Override
public void mousePressed(MouseEvent e) {
enableDrag = true;
pressedY = e.getY();
}
@Override
public void mouseReleased(MouseEvent e) {
if (enableDrag) {
Long start = service.getSnap(pressedY);
Long stop = service.getSnap(e.getY());
if (start == null || stop == null) {
return;
}
Lifespan span;
if ((e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0) {
Lifespan prev = getLifespan();
if (prev != null) {
int shift = stop.intValue() - start.intValue();
span = Lifespan.span(prev.lmin() - shift, prev.lmax() - shift);
}
else {
span = prev;
}
}
else {
if (start > stop) {
Long tmp = stop;
stop = start;
start = tmp;
}
span = Lifespan.span(start, stop);
}
plugin.setLifespan(span);
enableDrag = false;
}
}
});
ToolTipManager.sharedInstance().registerComponent(this);
actions = service.getActions();
}
/**
* Installs actions for this component
*/
public void installActions() {
if (actions == null) {
return;
}
for (DockingActionIf action : actions) {
tool.addAction(action);
}
}
/**
* Removes previous installed actions for this component.
*/
public void uninstallActions() {
if (actions == null) {
return;
}
for (DockingActionIf action : actions) {
tool.removeAction(action);
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(16, 1);
}
protected void gotoSnap(Long snap) {
plugin.gotoSnap(snap);
}
@Override
public String getToolTipText(MouseEvent e) {
Long snap = service.getSnap(e.getY());
return service.getToolTipText(snap);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int pixelCount = getOverviewPixelCount();
g.setColor(getBackground());
g.fillRect(0, 0, width - 1, getHeight() - 1);
if (service.getTrace() == null) {
return;
}
for (int i = 0; i < pixelCount; i++) {
Color color = getColor(i);
g.setColor(color);
g.fillRect(1, i, width - 3, 1);
}
if (colorsByPixel.length != pixelCount) {
colorsByPixel = new Color[pixelCount];
refreshUpdater.updateLater();
}
}
private Color getColor(int index) {
if (colorsByPixel != null && index < colorsByPixel.length) {
return colorsByPixel[index];
}
return DEFAULT_COLOR;
}
public int getOverviewPixelCount() {
return Math.max(getHeight(), 0);
}
private void doRefresh() {
for (int i = 0; i < colorsByPixel.length; i++) {
if (colorsByPixel[i] == null) {
Long snap = service.getSnap(i);
colorsByPixel[i] = service.getColor(snap);
}
}
repaint();
}
@Override
public JComponent getComponent() {
return this;
}
public void setLifeSet(TreeSet<Long> set) {
service.setIndices(set);
colorsByPixel = new Color[getOverviewPixelCount()];
refreshUpdater.updateLater();
}
/**
* Causes this component to completely compute the colors used to paint the overview bar.
*/
public void refreshAll() {
colorsByPixel = new Color[getOverviewPixelCount()];
refreshUpdater.updateLater();
}
/**
* Returns the PluginTool
*
* @return the PluginTool
*/
public PluginTool getTool() {
return tool;
}
public void setPlugin(TimeOverviewColorPlugin plugin) {
this.plugin = plugin;
service.setPlugin(plugin);
}
public Lifespan getLifespan() {
return service.getBounds();
}
public void setLifespan(Lifespan bounds) {
service.setBounds(bounds);
}
@Override
public void setProgram(Program program, AddressIndexMap map) {
// Ignored
}
@Override
public void setNavigatable(Navigatable navigatable) {
// Ignored
}
}

View file

@ -0,0 +1,374 @@
/* ###
* 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.timeoverview;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import docking.ActionContext;
import docking.action.*;
import docking.menu.MultiActionDockingAction;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.timeoverview.timetype.TimeType;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.bookmark.TraceBookmark;
import ghidra.trace.model.bookmark.TraceBookmarkManager;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import resources.ResourceManager;
/**
* Plugin to manage {@link TimeOverviewColorService}s. It creates actions for each service and
* installs and removes {@link TimeOverviewColorComponent} as indicated by the action.
*/
@PluginInfo(
status = PluginStatus.UNSTABLE,
packageName = DebuggerPluginPackage.NAME,
category = PluginCategoryNames.DEBUGGER,
shortDescription = "Time Overview Color Manager",
description = "Provides various color mappings for the trace snap space.",
eventsConsumed = {
TraceOpenedPluginEvent.class, //
TraceClosedPluginEvent.class, //
TraceActivatedPluginEvent.class, //
}, //
servicesRequired = { //
DebuggerTraceManagerService.class, //
} //
)
public class TimeOverviewColorPlugin extends AbstractDebuggerPlugin {
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerListingService listingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
public static final String HELP_TOPIC = "OverviewPlugin";
private static final String ACTIVE_SERVICES = "ActiveServices";
private List<TimeOverviewColorService> allServices;
private Map<TimeOverviewColorService, TimeOverviewColorComponent> activeServices =
new LinkedHashMap<>(); // maintain the left to right order of the active overview bars.
private Map<TimeOverviewColorService, OverviewToggleAction> actionMap = new HashMap<>();
private MultiActionDockingAction multiAction;
private Trace currentTrace;
private final TimeOverviewEventListener eventListener = new TimeOverviewEventListener(this);
private Map<Trace, TreeSet<Long>> sets = new WeakHashMap<>();
private Map<Long, Set<Pair<TimeType, String>>> types = new HashMap<>();
private long LMAX = Lifespan.ALL.lmax();
public TimeOverviewColorPlugin(PluginTool tool) {
super(tool);
autoServiceWiring = AutoService.wireServicesConsumed(tool, this);
}
@Override
protected void init() {
super.init();
allServices = ClassSearcher.getInstances(TimeOverviewColorService.class);
createActions();
for (TimeOverviewColorService service : allServices) {
service.initialize(tool);
}
}
@Override
public void readConfigState(SaveState saveState) {
String[] activeServiceNames = saveState.getStrings(ACTIVE_SERVICES, new String[0]);
for (String serviceName : activeServiceNames) {
TimeOverviewColorService service = getService(serviceName);
if (service == null) {
Msg.warn(this, "Can't restore TimeOverviewColorService: " + serviceName);
continue;
}
OverviewToggleAction action = actionMap.get(service);
action.setSelected(true);
// do this later so that they show up to the left of the standard marker service overview.
SwingUtilities.invokeLater(() -> installOverview(service));
}
}
private TimeOverviewColorService getService(String serviceName) {
for (TimeOverviewColorService service : allServices) {
if (service.getName().equals(serviceName)) {
return service;
}
}
return null;
}
@Override
protected void cleanup() {
List<TimeOverviewColorService> services = new ArrayList<>(activeServices.keySet());
for (TimeOverviewColorService service : services) {
uninstallOverview(service);
}
listingService.removeLocalAction(multiAction);
}
@Override
public void writeConfigState(SaveState saveState) {
saveState.putStrings(ACTIVE_SERVICES, getActiveServiceNames());
}
private String[] getActiveServiceNames() {
List<String> names =
activeServices.keySet().stream().map(s -> s.getName()).collect(Collectors.toList());
return names.toArray(new String[names.size()]);
}
private void createActions() {
for (TimeOverviewColorService overviewColorService : allServices) {
actionMap.put(overviewColorService,
new OverviewToggleAction(getName(), overviewColorService));
}
multiAction = new MultiActionDockingAction("TimeOverview", getName());
//multiAction.setPerformActionOnButtonClick(false);
multiAction.setActions(new ArrayList<DockingActionIf>(actionMap.values()));
multiAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/x-office-document-template.png")));
listingService.addLocalAction(multiAction);
multiAction.setDescription("Toggles trace overview margin displays.");
multiAction.setHelpLocation(
new HelpLocation(TimeOverviewColorPlugin.HELP_TOPIC,
TimeOverviewColorPlugin.HELP_TOPIC));
}
private class OverviewToggleAction extends ToggleDockingAction {
private TimeOverviewColorService service;
public OverviewToggleAction(String owner, TimeOverviewColorService service) {
super(service.getName(), owner);
this.service = service;
setMenuBarData(new MenuData(new String[] { "Show " + service.getName() }));
setHelpLocation(service.getHelpLocation());
}
@Override
public void actionPerformed(ActionContext context) {
if (isSelected()) {
installOverview(service);
}
else {
uninstallOverview(service);
}
}
}
/**
* Installs the given {@link TimeOverviewColorService} into the Listing margin bars. This is
* public only for testing and screenshot purposes.
*
* @param overviewColorService the service to display colors in the Listing's margin bars.
*/
public void installOverview(TimeOverviewColorService overviewColorService) {
overviewColorService.setTrace(currentTrace);
TimeOverviewColorComponent overview =
new TimeOverviewColorComponent(tool, overviewColorService);
activeServices.put(overviewColorService, overview);
listingService.addOverviewProvider(overview);
overview.installActions();
overview.setPlugin(this);
}
private void uninstallOverview(TimeOverviewColorService overviewColorService) {
TimeOverviewColorComponent overviewComponent = activeServices.get(overviewColorService);
overviewComponent.uninstallActions();
listingService.removeOverviewProvider(overviewComponent);
activeServices.remove(overviewColorService);
overviewColorService.setTrace(null);
}
protected void traceActivated(Trace trace) {
if (trace != null && trace != currentTrace) {
if (currentTrace != null) {
currentTrace.removeListener(eventListener);
}
currentTrace = trace;
currentTrace.addListener(eventListener);
for (TimeOverviewColorService service : activeServices.keySet()) {
service.setTrace(trace);
}
updateMap();
}
}
protected void traceDeactivated(Trace trace) {
if (trace == currentTrace) {
currentTrace.removeListener(eventListener);
currentTrace = null;
for (TimeOverviewColorService service : activeServices.keySet()) {
service.setTrace(null);
}
}
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent ev) {
DebuggerCoordinates coordinates = ev.getActiveCoordinates();
traceActivated(coordinates.getTrace());
eventListener.coordinatesActivated(coordinates);
}
else if (event instanceof TraceClosedPluginEvent ev) {
Trace trace = ev.getTrace();
if (trace == currentTrace) {
sets.remove(trace);
traceDeactivated(trace);
}
}
}
void updateMap() {
TreeSet<Long> set = new TreeSet<>();
Trace trace = traceManager.getCurrentTrace();
TraceThreadManager threadManager = trace.getThreadManager();
for (TraceThread thread : threadManager.getAllThreads()) {
if (thread instanceof TraceObjectThread objThread) {
addObject(set, objThread.getObject());
}
}
TraceModuleManager moduleManager = trace.getModuleManager();
for (TraceModule module : moduleManager.getAllModules()) {
if (module instanceof TraceObjectModule objModule) {
addObject(set, objModule.getObject());
}
}
TraceMemoryManager memoryManager = trace.getMemoryManager();
for (TraceMemoryRegion region : memoryManager.getAllRegions()) {
if (region instanceof TraceObjectMemoryRegion objRegion) {
addObject(set, objRegion.getObject());
}
}
TraceBreakpointManager breakpointManager = trace.getBreakpointManager();
for (TraceBreakpoint bpt : breakpointManager.getAllBreakpoints()) {
if (bpt instanceof TraceObjectBreakpointLocation objBreakpoint) {
addObject(set, objBreakpoint.getObject());
}
}
TraceBookmarkManager bookmarkManager = trace.getBookmarkManager();
for (TraceBookmark mark : bookmarkManager.getAllBookmarks()) {
Lifespan span = mark.getLifespan();
set.add(span.min());
if (span.lmax() == LMAX) {
set.add(span.max());
}
}
for (TimeOverviewColorComponent provider : activeServices.values()) {
provider.setLifeSet(set);
}
}
private void addObject(TreeSet<Long> set, TraceObject obj) {
for (Lifespan span : obj.getLife().spans()) {
set.add(span.min());
if (span.lmax() == LMAX) {
set.add(span.max());
}
}
}
void updateMap(long offset, TimeType type, String desc, boolean override) {
TreeSet<Long> set = getLifeSet();
if (!override && set.contains(offset)) {
return;
}
if (offset == LMAX) {
return;
}
set.add(offset);
setLifespanType(offset, type, desc);
for (TimeOverviewColorComponent provider : activeServices.values()) {
provider.setLifeSet(set);
}
}
TreeSet<Long> getLifeSet() {
TreeSet<Long> set = sets.get(currentTrace);
if (set == null) {
set = new TreeSet<>();
sets.put(currentTrace, set);
}
return set;
}
/**
* Determines the {@link TimeType} for the given offset
*
* @param offset the offset for which to get an LifespanType.
* @return the {@link TimeType} for the given offset.
*/
public Set<Pair<TimeType, String>> getTypes(Long offset) {
Set<Pair<TimeType, String>> set = types.get(offset);
if (set == null) {
set = new HashSet<>();
}
return set;
}
void setLifespanType(Long offset, TimeType type, String desc) {
Set<Pair<TimeType, String>> set = getTypes(offset);
set.add(new ImmutablePair<TimeType, String>(type, desc));
types.put(offset, set);
}
public void gotoSnap(Long offset) {
if (offset == null) {
offset = 0L;
}
traceManager.activateSnap(offset);
}
public void setLifespan(Lifespan span) {
for (TimeOverviewColorComponent provider : activeServices.values()) {
provider.setLifespan(span);
}
}
}

View file

@ -0,0 +1,139 @@
/* ###
* 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.timeoverview;
import java.awt.Color;
import java.util.*;
import docking.action.DockingActionIf;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.util.HelpLocation;
import ghidra.util.classfinder.ExtensionPoint;
/**
* Interface for services that know how to associate colors with any snap in a program. Instances
* of these services are discovered and presented as options on the Listing's right margin area.
*/
public interface TimeOverviewColorService extends ExtensionPoint {
/**
* Returns the name of this color service.
*
* @return the name of this color service.
*/
public String getName();
/**
* Returns the color that this service associates with the given snap.
*
* @param snap the snap to convert to a color.
* @return the color that this service associates with the given snap.
*/
public Color getColor(Long snap);
/**
* Sets the trace that this service will provide snap colors for.
*
* @param trace the program that this service will provide snap colors for.
*/
public void setTrace(Trace trace);
/**
* Sets the component that will be displaying the colors for this
* service.
*
* @param component the {@link TimeOverviewColorComponent} that will be displaying the colors
* for this service.
*/
public void setOverviewComponent(TimeOverviewColorComponent component);
/**
* Returns the tool tip that the {@link TimeOverviewColorComponent} should display when the
* mouse is hovering on the pixel that maps to the given snap.
*
* @param snap the snap for which to get a tooltip.
* @return the tooltip text for the given snap.
*/
public String getToolTipText(Long snap);
/**
* Returns a list of popup actions to be shown when the user right-clicks on the
* {@link TimeOverviewColorComponent} associated with this service.
*
* @return the list of popup actions.
*/
public List<DockingActionIf> getActions();
/**
* Returns the {@link HelpLocation} for this service
*
* @return the {@link HelpLocation} for this service
*/
public HelpLocation getHelpLocation();
/**
* Initialize the service which typically is used to read options for the service.
*
* @param tool the {@link PluginTool} using this service.
*/
public void initialize(PluginTool tool);
/**
* Returns the current trace used by the service.
*
* @return the current trace used by the service.
*/
public Trace getTrace();
/**
* Set the plugin
*
* @param plugin overview plugin
*/
public void setPlugin(TimeOverviewColorPlugin plugin);
/**
* Get the snap for a given pixel's time coordinate
*
* @param pixel location in the display
* @return snap
*/
public Long getSnap(int pixel);
/**
* Set the indices for mapping pixels->indices->snaps (and vice-versa)
*
* @param set tree-set of snaps
*/
public void setIndices(TreeSet<Long> set);
/**
* Get the display bounds
*
* @return bounds time-range to display
*/
public Lifespan getBounds();
/**
* Set the display bounds
*
* @param bounds time-range to display
*/
public void setBounds(Lifespan bounds);
}

View file

@ -0,0 +1,329 @@
/* ###
* 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.timeoverview;
import java.util.Objects;
import ghidra.app.plugin.core.debug.gui.timeoverview.timetype.TimeType;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.trace.model.*;
import ghidra.trace.model.bookmark.TraceBookmark;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.thread.*;
import ghidra.trace.util.TraceEvents;
import ghidra.util.Swing;
public class TimeOverviewEventListener extends TraceDomainObjectListener {
private TimeOverviewColorPlugin p;
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
public TimeOverviewEventListener(TimeOverviewColorPlugin plugin) {
this.p = plugin;
listenForUntyped(DomainObjectEvent.RESTORED, this::objectRestored);
listenFor(TraceEvents.THREAD_ADDED, this::threadAdded);
listenFor(TraceEvents.THREAD_CHANGED, this::threadChanged);
listenFor(TraceEvents.THREAD_LIFESPAN_CHANGED, this::threadChanged);
listenFor(TraceEvents.THREAD_DELETED, this::threadDeleted);
listenFor(TraceEvents.MODULE_ADDED, this::moduleAdded);
listenFor(TraceEvents.MODULE_CHANGED, this::moduleChanged);
listenFor(TraceEvents.MODULE_LIFESPAN_CHANGED, this::moduleChanged);
listenFor(TraceEvents.MODULE_DELETED, this::moduleDeleted);
listenFor(TraceEvents.REGION_ADDED, this::regionAdded);
listenFor(TraceEvents.REGION_CHANGED, this::regionChanged);
listenFor(TraceEvents.REGION_LIFESPAN_CHANGED, this::regionChanged);
listenFor(TraceEvents.REGION_DELETED, this::regionDeleted);
listenFor(TraceEvents.BREAKPOINT_ADDED, this::bptAdded);
listenFor(TraceEvents.BREAKPOINT_CHANGED, this::bptChanged);
listenFor(TraceEvents.BREAKPOINT_LIFESPAN_CHANGED, this::bptChanged);
listenFor(TraceEvents.BREAKPOINT_DELETED, this::bptDeleted);
listenFor(TraceEvents.BOOKMARK_ADDED, this::bookmarkAdded);
listenFor(TraceEvents.BOOKMARK_CHANGED, this::bookmarkChanged);
listenFor(TraceEvents.BOOKMARK_LIFESPAN_CHANGED, this::bookmarkChanged);
listenFor(TraceEvents.BOOKMARK_DELETED, this::bookmarkDeleted);
}
public void coordinatesActivated(DebuggerCoordinates coordinates) {
//DebuggerCoordinates adjusted = adjustCoordinates(coordinates);
setCoordinates(coordinates);
Trace trace = coordinates.getTrace();
if (trace != null) {
Swing.runLater(() -> processTrace(trace));
}
}
protected void addListener() {
Trace trace = current.getTrace();
if (trace != null) {
trace.addListener(this);
}
}
protected void removeListener() {
Trace trace = current.getTrace();
if (trace != null) {
trace.removeListener(this);
}
}
public void setCoordinates(DebuggerCoordinates coordinates) {
boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace());
if (doListeners) {
removeListener();
}
current = coordinates;
if (doListeners) {
addListener();
}
}
private void processTrace(Trace trace) {
//updateList.clear();
TraceThreadManager threadManager = trace.getThreadManager();
for (TraceThread thread : threadManager.getAllThreads()) {
threadChanged(thread);
}
TraceModuleManager moduleManager = trace.getModuleManager();
for (TraceModule module : moduleManager.getAllModules()) {
moduleChanged(module);
}
TraceMemoryManager memoryManager = trace.getMemoryManager();
for (TraceMemoryRegion region : memoryManager.getAllRegions()) {
regionChanged(region);
}
TraceBreakpointManager breakpointManager = trace.getBreakpointManager();
for (TraceBreakpoint bpt : breakpointManager.getAllBreakpoints()) {
bptChanged(bpt);
}
}
private void threadAdded(TraceThread thread) {
if (!(thread instanceof TraceObjectThread objThread)) {
return;
}
TraceObject obj = objThread.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMinSnap();
p.updateMap(snap, TimeType.BPT_ADDED, thread.getName(snap), true);
});
}
private void threadChanged(TraceThread thread) {
if (!(thread instanceof TraceObjectThread objThread)) {
return;
}
TraceObject obj = objThread.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectThread.KEY_TID, true)
.forEach(v -> {
long snapMin = v.getMinSnap();
long snapMax = v.getMaxSnap();
if (snapMin == snapMax) {
p.updateMap(snapMin, TimeType.THREAD_CHANGED, thread.getName(snapMin), true);
}
else {
p.updateMap(snapMin, TimeType.THREAD_ADDED, thread.getName(snapMin), true);
p.updateMap(snapMax, TimeType.THREAD_REMOVED, thread.getName(snapMax), true);
}
});
}
private void threadDeleted(TraceThread thread) {
if (!(thread instanceof TraceObjectThread objThread)) {
return;
}
TraceObject obj = objThread.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMaxSnap();
p.updateMap(snap, TimeType.THREAD_REMOVED, thread.getName(snap), true);
});
}
private void moduleAdded(TraceModule module) {
if (!(module instanceof TraceObjectModule objMod)) {
return;
}
TraceObject obj = objMod.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMinSnap();
p.updateMap(snap, TimeType.MODULE_ADDED, module.getName(snap), true);
});
}
private void moduleChanged(TraceModule module) {
if (!(module instanceof TraceObjectModule objMod)) {
return;
}
TraceObject obj = objMod.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snapMin = v.getMinSnap();
long snapMax = v.getMaxSnap();
if (snapMin == snapMax) {
p.updateMap(snapMin, TimeType.MODULE_CHANGED, module.getName(snapMin), true);
}
else {
p.updateMap(snapMin, TimeType.MODULE_ADDED, module.getName(snapMin), true);
p.updateMap(snapMax, TimeType.MODULE_REMOVED, module.getName(snapMax), true);
}
});
}
private void moduleDeleted(TraceModule module) {
if (!(module instanceof TraceObjectModule objMod)) {
return;
}
TraceObject obj = objMod.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMaxSnap();
p.updateMap(snap, TimeType.MODULE_REMOVED, module.getName(snap), true);
});
}
private void regionAdded(TraceMemoryRegion region) {
if (!(region instanceof TraceObjectMemoryRegion objReg)) {
return;
}
TraceObject obj = objReg.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMinSnap();
p.updateMap(snap, TimeType.REGION_ADDED, region.getName(snap), true);
});
}
private void regionChanged(TraceMemoryRegion region) {
if (!(region instanceof TraceObjectMemoryRegion objReg)) {
return;
}
TraceObject obj = objReg.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snapMin = v.getMinSnap();
long snapMax = v.getMaxSnap();
if (snapMin == snapMax) {
p.updateMap(snapMin, TimeType.REGION_CHANGED, region.getName(snapMin), true);
}
else {
p.updateMap(snapMin, TimeType.REGION_ADDED, region.getName(snapMin), true);
p.updateMap(snapMax, TimeType.REGION_REMOVED, region.getName(snapMax), true);
}
});
}
private void regionDeleted(TraceMemoryRegion region) {
if (!(region instanceof TraceObjectMemoryRegion objReg)) {
return;
}
TraceObject obj = objReg.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMaxSnap();
p.updateMap(snap, TimeType.REGION_REMOVED, region.getName(snap), true);
});
}
private void bptAdded(TraceBreakpoint bpt) {
if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) {
return;
}
TraceObject obj = objBpt.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMinSnap();
p.updateMap(snap, TimeType.BPT_ADDED, bpt.getName(snap), true);
});
}
private void bptChanged(TraceBreakpoint bpt) {
if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) {
return;
}
TraceObject obj = objBpt.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snapMin = v.getMinSnap();
long snapMax = v.getMaxSnap();
if (snapMin == snapMax) {
p.updateMap(snapMin, TimeType.BPT_CHANGED, bpt.getName(snapMin), true);
}
else {
p.updateMap(snapMin, TimeType.BPT_ADDED, bpt.getName(snapMin), true);
p.updateMap(snapMax, TimeType.BPT_REMOVED, bpt.getName(snapMax), true);
}
});
}
private void bptDeleted(TraceBreakpoint bpt) {
if (!(bpt instanceof TraceObjectBreakpointLocation objBpt)) {
return;
}
TraceObject obj = objBpt.getObject();
obj.getOrderedValues(Lifespan.ALL, TraceObjectBreakpointLocation.KEY_RANGE, true)
.forEach(v -> {
long snap = v.getMaxSnap();
p.updateMap(snap, TimeType.BPT_REMOVED, bpt.getName(snap), true);
});
}
private void bookmarkAdded(TraceBookmark bookmark) {
long snap = bookmark.getLifespan().lmin();
p.updateMap(snap, TimeType.BOOKMARK_ADDED, bookmark.getComment(), true);
}
private void bookmarkChanged(TraceBookmark bookmark) {
long snapMin = bookmark.getLifespan().lmin();
p.updateMap(snapMin, TimeType.BOOKMARK_CHANGED, bookmark.getComment(), false);
}
private void bookmarkDeleted(TraceBookmark bookmark) {
long snap = bookmark.getLifespan().lmax();
p.updateMap(snap, TimeType.BOOKMARK_REMOVED, bookmark.getComment(), true);
}
private void objectRestored(DomainObjectChangeRecord domainobjectchangerecord1) {
p.updateMap();
}
}

View file

@ -0,0 +1,60 @@
/* ###
* 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.timeoverview.timetype;
import java.util.*;
import ghidra.trace.model.Lifespan;
public class TimeSelectionOverviewColorService
extends TimeTypeOverviewColorService {
@Override
public String getName() {
return "Trace Selection";
}
@Override
public void setIndices(TreeSet<Long> set) {
snapToIndex = new HashMap<>();
indexToSnap = new HashMap<>();
if (bounds != null) {
set.add(bounds.min());
int splits = overviewComponent.getOverviewPixelCount();
float span = (float)(bounds.lmax() - bounds.lmin())/splits;
for (int i = 0; i < splits; i++) {
long snap = (long)(bounds.lmin() + i*span);
snapToIndex.put(snap, i);
indexToSnap.put(i, snap);
}
}
}
@Override
public Lifespan getBounds() {
return bounds;
}
@Override
public void setBounds(Lifespan bounds) {
this.bounds = bounds;
TreeSet<Long> minset = new TreeSet<>();
minset.add(bounds.min());
overviewComponent.setLifeSet(minset);
}
}

View file

@ -0,0 +1,71 @@
/* ###
* 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.timeoverview.timetype;
import java.awt.Color;
import generic.theme.GColor;
/**
* An enum for the different types that are represented by unique colors by the
* {@link TimeTypeOverviewColorService}
*/
public enum TimeType {
THREAD_ADDED("+T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.added")),
THREAD_REMOVED("-T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.removed")),
THREAD_CHANGED("*T", new GColor("color.debugger.plugin.timeoverview.box.type.thread.changed")),
MODULE_ADDED("+M", new GColor("color.debugger.plugin.timeoverview.box.type.module.added")),
MODULE_REMOVED("-M", new GColor("color.debugger.plugin.timeoverview.box.type.module.removed")),
MODULE_CHANGED("*M", new GColor("color.debugger.plugin.timeoverview.box.type.module.changed")),
REGION_ADDED("+R", new GColor("color.debugger.plugin.timeoverview.box.type.region.added")),
REGION_REMOVED("-R", new GColor("color.debugger.plugin.timeoverview.box.type.region.removed")),
REGION_CHANGED("*R", new GColor("color.debugger.plugin.timeoverview.box.type.region.changed")),
BPT_ADDED("+B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.added")),
BPT_REMOVED("-B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.removed")),
BPT_CHANGED("*B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.changed")),
BPT_HIT(">B", new GColor("color.debugger.plugin.timeoverview.box.type.breakpoint.hit")),
BOOKMARK_ADDED("+MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.added")),
BOOKMARK_REMOVED("-MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.removed")),
BOOKMARK_CHANGED("*MK", new GColor("color.debugger.plugin.timeoverview.box.type.bookmark.changed")),
UNDEFINED("", new GColor("color.debugger.plugin.timeoverview.box.type.undefined"));
final private String description;
final private Color color;
TimeType(String description, Color color) {
this.description = description;
this.color = color;
}
/**
* Returns a description of this enum value.
*
* @return a description of this enum value.
*/
public String getDescription() {
return description;
}
/**
* Returns a color of this enum value.
*
* @return a color of this enum value.
*/
public Color getDefaultColor() {
return color;
}
}

View file

@ -0,0 +1,209 @@
/* ###
* 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.timeoverview.timetype;
import java.awt.Color;
import java.math.BigInteger;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import docking.DialogComponentProvider;
import docking.action.DockingActionIf;
import docking.action.builder.ActionBuilder;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.debug.gui.timeoverview.*;
import ghidra.app.plugin.core.overview.OverviewColorLegendDialog;
import ghidra.app.plugin.core.overview.OverviewColorPlugin;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.util.*;
public class TimeTypeOverviewColorService implements TimeOverviewColorService {
private static final String OPTIONS_NAME = "Time Overview";
private static final Color DEFAULT_UNDEFINED_COLOR = Color.LIGHT_GRAY;
private static final Color DEFAULT_UNINITIALIZED_COLOR = Color.GRAY;
Map<TimeType, Color> colorMap = new HashMap<>();
Color undefinedColor = DEFAULT_UNDEFINED_COLOR;
Color uninitializedColor = DEFAULT_UNINITIALIZED_COLOR;
private Trace trace;
protected TimeOverviewColorComponent overviewComponent;
private PluginTool tool;
private DialogComponentProvider legendDialog;
private TimeTypeOverviewLegendPanel legendPanel;
private TimeOverviewColorPlugin plugin;
protected Map<Integer,Long> indexToSnap = new HashMap<>();
protected Map<Long,Integer> snapToIndex = new HashMap<>();
protected Lifespan bounds;
@Override
public String getName() {
return "Trace Overview";
}
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation("DebuggerTimeOverviewPlugin", "plugin");
}
@Override
public Color getColor(Long snap) {
Set<Pair<TimeType, String>> types = plugin.getTypes(snap);
Color c = Colors.BACKGROUND;
for (Pair<TimeType, String> pair : types) {
c = ColorUtils.addColors(c, pair.getLeft().getDefaultColor());
}
return c;
}
@Override
public String getToolTipText(Long snap) {
// TODO: Right now, there's an inconsistency in how time is rendered
// the Time Table uses decimal; the Model Tree, Memview, and Overview
// use hex
if (snap == null) {
return "";
}
Set<Pair<TimeType, String>> types = plugin.getTypes(snap);
StringBuffer buffer = new StringBuffer();
buffer.append("<b>");
buffer.append(HTMLUtilities.escapeHTML(getName()));
buffer.append(" (");
buffer.append(Long.toHexString(snap));
buffer.append(")");
buffer.append("</b>\n");
for (Pair<TimeType, String> pair : types) {
TimeType tt = pair.getLeft();
String key = pair.getRight();
buffer.append(tt.getDescription() + " : " + key + "\n");
}
return HTMLUtilities.toWrappedHTML(buffer.toString(), 0);
}
@Override
public List<DockingActionIf> getActions() {
List<DockingActionIf> actions = new ArrayList<>();
actions.add(new ActionBuilder("Show Legend", getName())
.popupMenuPath("Show Legend")
.description("Show types and associated colors")
.helpLocation(getHelpLocation())
.enabledWhen(c -> c.getContextObject() == overviewComponent)
.onAction(c -> tool.showDialog(getLegendDialog()))
.build());
return actions;
}
@Override
public void setTrace(Trace trace) {
this.trace = trace;
}
@Override
public void initialize(PluginTool pluginTool) {
this.tool = pluginTool;
}
@Override
public void setOverviewComponent(TimeOverviewColorComponent component) {
this.overviewComponent = component;
}
/**
* Returns the color associated with the given {@link TimeType}
*
* @param timeType the span type for which to get a color.
* @return the color associated with the given {@link TimeType}
*/
public Color getColor(TimeType timeType) {
Color color = colorMap.get(timeType);
if (color == null) {
colorMap.put(timeType, timeType.getDefaultColor());
}
return color;
}
/**
* Sets the color to be associated with a given {@link TimeType}
*
* @param type the LifespanType for which to assign the color.
* @param newColor the new color for the given {@link TimeType}
*/
public void setColor(TimeType type, Color newColor) {
ToolOptions options = tool.getOptions(OPTIONS_NAME);
options.setColor(type.getDescription(), newColor);
}
private DialogComponentProvider getLegendDialog() {
if (legendDialog == null) {
legendPanel = new TimeTypeOverviewLegendPanel(this);
legendDialog =
new OverviewColorLegendDialog("Overview Legend", legendPanel, getHelpLocation());
}
return legendDialog;
}
@Override
public Trace getTrace() {
return trace;
}
@Override
public void setPlugin(TimeOverviewColorPlugin plugin) {
this.plugin = plugin;
}
@Override
public Long getSnap(int pixelIndex) {
BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount());
BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex);
BigInteger span = BigInteger.valueOf(indexToSnap.size());
BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight);
return indexToSnap.get(offset.intValue());
}
@Override
public void setIndices(TreeSet<Long> set) {
snapToIndex = new HashMap<>();
indexToSnap = new HashMap<>();
int index = 0;
Iterator<Long> iterator = set.iterator();
while (iterator.hasNext()) {
Long snap = iterator.next();
snapToIndex.put(snap, index);
indexToSnap.put(index, snap);
index++;
}
}
@Override
public Lifespan getBounds() {
return bounds;
}
@Override
public void setBounds(Lifespan bounds) {
this.bounds = bounds;
}
}

View file

@ -0,0 +1,82 @@
/* ###
* 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.timeoverview.timetype;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
import docking.widgets.label.GLabel;
import ghidra.util.layout.PairLayout;
/**
* A component for displaying the color legend for the {@link TimeTypeOverviewColorService}
*/
public class TimeTypeOverviewLegendPanel extends JPanel {
private static Dimension COLOR_SIZE = new Dimension(15, 15);
private TimeTypeOverviewColorService colorService;
public TimeTypeOverviewLegendPanel(TimeTypeOverviewColorService colorService) {
this.colorService = colorService;
setLayout(new PairLayout(4, 10));
setBorder(BorderFactory.createEmptyBorder(4, 20, 4, 30));
buildLegend();
}
/**
* Kick to repaint when the colors have changed.
*/
public void updateColors() {
repaint();
}
private void buildLegend() {
removeAll();
TimeType[] values = TimeType.values();
for (TimeType timeType : values) {
JPanel panel = new ColorPanel(timeType);
add(panel);
add(new GLabel(timeType.getDescription()));
}
}
private class ColorPanel extends JPanel {
private TimeType type;
ColorPanel(TimeType type) {
this.type = type;
setPreferredSize(COLOR_SIZE);
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
Color newColor =
JColorChooser.showDialog(ColorPanel.this, "Select Color", getBackground());
colorService.setColor(type, newColor);
}
});
}
@Override
protected void paintComponent(Graphics g) {
setBackground(colorService.getColor(type));
super.paintComponent(g);
}
}
}