mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 13:16:48 +08:00
Merge remote-tracking branch 'origin/GP-5895_ghidra_red_BreakpointsInTimeMargin--SQUASHED'
This commit is contained in:
@@ -8,3 +8,4 @@ DebuggerRegisterColumnFactory
|
||||
DisassemblyInject
|
||||
EmulatorFactory
|
||||
LocationTrackingSpecFactory
|
||||
TimeOverviewColorService
|
||||
|
||||
@@ -49,6 +49,14 @@ color.debugger.plugin.timeoverview.box.type.snap.removed = color.palette.lightgr
|
||||
color.debugger.plugin.timeoverview.box.type.snap.changed = color.palette.lightgray
|
||||
color.debugger.plugin.timeoverview.box.type.undefined = color.palette.black
|
||||
|
||||
color.debugger.plugin.breakpoint.timeline.grid = color.fg
|
||||
color.debugger.plugin.breakpoint.timeline.selection = color.palette.lime
|
||||
color.debugger.plugin.breakpoint.timeline.hover = color.palette.lime
|
||||
color.debugger.plugin.breakpoint.timeline.current = color.palette.lime
|
||||
color.debugger.plugin.breakpoint.timeline.type.read.memory = color.palette.lightcornflowerblue
|
||||
color.debugger.plugin.breakpoint.timeline.type.write.memory = color.palette.lemonchiffon
|
||||
color.debugger.plugin.breakpoint.timeline.type.instructions = color.palette.red
|
||||
|
||||
color.bg.debugger.plugin.objects.default = color.bg
|
||||
color.fg.debugger.plugin.objects.default = color.fg
|
||||
color.fg.debugger.plugin.objects.invisible = color.palette.lightgray
|
||||
@@ -238,6 +246,13 @@ icon.debugger.select.registers = select-registers.png
|
||||
icon.debugger.enable.edits = editbytes.gif
|
||||
icon.debugger.disassemble = editbytes.gif // TODO this icon was missing 'disassemble.png'
|
||||
|
||||
|
||||
icon.debugger.breakpoint.timeline.outline = tick.png
|
||||
icon.debugger.breakpoint.timeline.no_outline = edit-delete.png
|
||||
icon.debugger.breakpoint.timeline.grid = color_swatch.png
|
||||
icon.debugger.breakpoint.timeline.single_column = StackFrame_Red.png
|
||||
icon.debugger.breakpoint.timeline.zoom_in = zoom_in.png
|
||||
icon.debugger.breakpoint.timeline.zoom_out = zoom_out.png
|
||||
icon.debugger.breakpoint.timeline.zoom_out_max = zoom.png
|
||||
icon.debugger.breakpoint.timeline.close_all_zoom_windows = delete.png
|
||||
|
||||
[Dark Defaults]
|
||||
|
||||
+473
@@ -0,0 +1,473 @@
|
||||
/* ###
|
||||
* 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.breakpoint.timeline;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.plugin.core.debug.gui.breakpoint.timeline.BreakpointTimelineProvider.BreakpointHitEvent;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
|
||||
import ghidra.util.ColorUtils;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
class BreakpointTimelinePanel extends JPanel {
|
||||
private static class CachedIndex {
|
||||
private final long startSnap;
|
||||
private final long stopSnap;
|
||||
private final List<BreakpointHitEvent> breakpointEvents;
|
||||
Rectangle rect;
|
||||
long index;
|
||||
|
||||
CachedIndex(long startSnap, long stopSnap, Rectangle rect, long index) {
|
||||
this.startSnap = startSnap;
|
||||
this.stopSnap = stopSnap;
|
||||
this.rect = rect;
|
||||
this.index = index;
|
||||
breakpointEvents = new ArrayList<>();
|
||||
}
|
||||
|
||||
void addEvent(BreakpointHitEvent event) {
|
||||
breakpointEvents.add(event);
|
||||
}
|
||||
|
||||
Color getColor() {
|
||||
Color retColor = null;
|
||||
final List<TraceBreakpointKind> uniqueBreakTypes =
|
||||
breakpointEvents.stream().map(BreakpointHitEvent::breakType).distinct().toList();
|
||||
final double blendRatio = 1.0d / uniqueBreakTypes.size();
|
||||
|
||||
for (final TraceBreakpointKind bt : uniqueBreakTypes) {
|
||||
if (retColor == null) {
|
||||
retColor = BreakpointTimelinePanel.BREAKTYPE_TO_COLOR.get(bt);
|
||||
continue;
|
||||
}
|
||||
retColor = ColorUtils.blend(BreakpointTimelinePanel.BREAKTYPE_TO_COLOR.get(bt),
|
||||
retColor, blendRatio);
|
||||
}
|
||||
|
||||
return (retColor == null) ? BreakpointTimelinePanel.BG_COLOR : retColor;
|
||||
}
|
||||
|
||||
long getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
BreakpointHitEvent getMainEvent() {
|
||||
if (breakpointEvents.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return breakpointEvents.getFirst();
|
||||
}
|
||||
|
||||
long getMainSnap() {
|
||||
final BreakpointHitEvent mainEvent = getMainEvent();
|
||||
return (mainEvent != null) ? mainEvent.snap() : startSnap;
|
||||
}
|
||||
|
||||
Rectangle getRect() {
|
||||
return rect;
|
||||
}
|
||||
|
||||
long getStartSnap() {
|
||||
return startSnap;
|
||||
}
|
||||
|
||||
long getStopSnap() {
|
||||
return stopSnap;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean singleColumn = false;
|
||||
private static boolean showGridOutline = true;
|
||||
|
||||
private static GColor BG_COLOR = Colors.BACKGROUND;
|
||||
private static GColor GRID_COLOR = Colors.FOREGROUND_DISABLED;
|
||||
private static GColor SELECTION_COLOR =
|
||||
new GColor("color.debugger.plugin.breakpoint.timeline.selection");
|
||||
private static GColor HOVER_COLOR =
|
||||
new GColor("color.debugger.plugin.breakpoint.timeline.hover");
|
||||
private static GColor CURRENT_SNAP_COLOR =
|
||||
new GColor("color.debugger.plugin.breakpoint.timeline.current");
|
||||
private static GColor INSTRUCTION_HIT_COLOR =
|
||||
new GColor("color.debugger.plugin.breakpoint.timeline.type.instructions");
|
||||
private static GColor MEMORY_READ_COLOR =
|
||||
new GColor("color.debugger.plugin.breakpoint.timeline.type.read.memory");
|
||||
private static GColor MEMORY_WRITE_COLOR =
|
||||
new GColor("color.debugger.plugin.breakpoint.timeline.type.write.memory");
|
||||
private static Map<TraceBreakpointKind, GColor> BREAKTYPE_TO_COLOR = Map.ofEntries(
|
||||
Map.entry(TraceBreakpointKind.HW_EXECUTE, BreakpointTimelinePanel.INSTRUCTION_HIT_COLOR),
|
||||
Map.entry(TraceBreakpointKind.SW_EXECUTE, BreakpointTimelinePanel.INSTRUCTION_HIT_COLOR),
|
||||
Map.entry(TraceBreakpointKind.READ, BreakpointTimelinePanel.MEMORY_READ_COLOR),
|
||||
Map.entry(TraceBreakpointKind.WRITE, BreakpointTimelinePanel.MEMORY_WRITE_COLOR));
|
||||
|
||||
private long defaultCellSize = 10;
|
||||
|
||||
private List<BreakpointHitEvent> events;
|
||||
private final BreakpointTimelineProvider provider;
|
||||
|
||||
private long cellWidth = defaultCellSize;
|
||||
private long cellHeight = defaultCellSize;
|
||||
private long visibleStart;
|
||||
|
||||
private long visibleEnd;
|
||||
private Point dragStart;
|
||||
private Point dragEnd;
|
||||
private Point mousePos;
|
||||
|
||||
private long gridWidth;
|
||||
private long gridHeight;
|
||||
|
||||
private CachedIndex lastHighlightedIndex;
|
||||
private CachedIndex startDragIndex;
|
||||
private CachedIndex endDragIndex;
|
||||
|
||||
private final Map<Long, CachedIndex> cells = new HashMap<>();
|
||||
|
||||
BreakpointTimelinePanel(BreakpointTimelineProvider provider) {
|
||||
events = null;
|
||||
this.provider = provider;
|
||||
setup();
|
||||
}
|
||||
|
||||
private void calculateGridAndBuildCache() {
|
||||
Swing.runIfSwingOrRunLater(this::doCalculateGridAndBuildCache);
|
||||
}
|
||||
|
||||
private void click(Point p) {
|
||||
final Optional<BreakpointTimelinePanel.CachedIndex> first =
|
||||
cells.values().stream().filter(s -> s.getRect().contains(p)).findFirst();
|
||||
if (first.isPresent()) {
|
||||
getTraceManagerService().activateSnap(first.get().getMainSnap());
|
||||
}
|
||||
}
|
||||
|
||||
void decreaseDefaultCellSize() {
|
||||
defaultCellSize = Math.max(cellHeight - 1, 1);
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
|
||||
private void doCalculateGridAndBuildCache() {
|
||||
if ((visibleStart == 0) && (visibleEnd == 0)) {
|
||||
cells.clear();
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
|
||||
if ((width <= 0) && (height <= 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BreakpointTimelinePanel.singleColumn) {
|
||||
final long numCells = visibleEnd - visibleStart;
|
||||
|
||||
double xSideLength;
|
||||
double ySideLength;
|
||||
|
||||
final double xPixelsPerCell = Math.ceil(Math.sqrt((numCells * width) / height));
|
||||
if ((Math.floor((xPixelsPerCell * height) / width) * xPixelsPerCell) < numCells) {
|
||||
xSideLength = (height / Math.ceil((height * xPixelsPerCell) / width));
|
||||
}
|
||||
else {
|
||||
xSideLength = width / xPixelsPerCell;
|
||||
}
|
||||
|
||||
final double yPixelsPerCell = Math.ceil(Math.sqrt((numCells * height) / width));
|
||||
if ((Math.floor((yPixelsPerCell * width) / height) * yPixelsPerCell) < numCells) {
|
||||
ySideLength = (width / Math.ceil((width * yPixelsPerCell) / height));
|
||||
}
|
||||
else {
|
||||
ySideLength = height / yPixelsPerCell;
|
||||
}
|
||||
|
||||
final long potentialSideLength = (long) Math.max(xSideLength, ySideLength);
|
||||
|
||||
cellWidth = Math.max(defaultCellSize, potentialSideLength);
|
||||
cellHeight = cellWidth;
|
||||
|
||||
gridWidth = Math.max(width / cellWidth, 1);
|
||||
}
|
||||
else {
|
||||
final long numCells = visibleEnd - visibleStart;
|
||||
final long cellHeightOption = height / numCells;
|
||||
cellHeight = Math.max(defaultCellSize, cellHeightOption);
|
||||
cellWidth = width;
|
||||
gridWidth = 1;
|
||||
}
|
||||
gridHeight = Math.max(height / cellHeight, 1);
|
||||
|
||||
cells.clear();
|
||||
|
||||
final long totalCells = gridWidth * gridHeight;
|
||||
final long range = visibleEnd - visibleStart;
|
||||
final long span = Math.max(((range + totalCells) - 1) / totalCells, 1);
|
||||
long index = 0;
|
||||
|
||||
for (long i = 0; i < range; i += span) {
|
||||
final long row = index / gridWidth;
|
||||
final long col = index % gridWidth;
|
||||
final long x = col * cellWidth;
|
||||
final long y = row * cellHeight;
|
||||
final long startSnap = i + visibleStart;
|
||||
final long stopSnap = i + visibleStart + Math.min(span, range - i);
|
||||
cells.put(index, new CachedIndex(startSnap, stopSnap,
|
||||
new Rectangle((int) x, (int) y, (int) cellWidth, (int) cellHeight), index));
|
||||
index++;
|
||||
}
|
||||
|
||||
setPreferredSize(
|
||||
new Dimension((int) (gridWidth * cellWidth), (int) (gridHeight * cellHeight)));
|
||||
|
||||
for (final BreakpointHitEvent event : events) {
|
||||
if ((event.snap() >= visibleStart) && (event.snap() < visibleEnd)) {
|
||||
final long cellIndex = (event.snap() - visibleStart) / span;
|
||||
final CachedIndex curSpan = cells.get(cellIndex);
|
||||
curSpan.addEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent event) {
|
||||
final Optional<BreakpointTimelinePanel.CachedIndex> first =
|
||||
cells.values().stream().filter(s -> s.getRect().contains(event.getPoint())).findFirst();
|
||||
if (first.isPresent()) {
|
||||
final CachedIndex index = first.get();
|
||||
final BreakpointHitEvent mainEvent = index.getMainEvent();
|
||||
if (mainEvent != null) {
|
||||
return """
|
||||
Snapshots %d - %d
|
||||
Jumps to snapshot %d
|
||||
Event type %s
|
||||
From %s
|
||||
""".formatted(index.getStartSnap(), index.getStopSnap() - 1,
|
||||
index.getMainSnap(), mainEvent.breakType(), mainEvent.breakpointName());
|
||||
}
|
||||
return """
|
||||
Snapshots %d - %d
|
||||
Jumps to snapshot %d
|
||||
""".formatted(index.getStartSnap(), index.getStopSnap() - 1,
|
||||
index.getMainSnap());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private DebuggerTraceManagerService getTraceManagerService() {
|
||||
return provider.getTool().getService(DebuggerTraceManagerService.class);
|
||||
}
|
||||
|
||||
void increaseDefaultCellSize() {
|
||||
defaultCellSize = cellHeight + 1;
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
if ((visibleStart == 0) && (visibleEnd == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((gridWidth == 0) || (gridHeight == 0)) {
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
|
||||
final Graphics2D g2d = (Graphics2D) g;
|
||||
|
||||
for (final Long cellIndex : cells.keySet()) {
|
||||
final CachedIndex curSpan = cells.get(cellIndex);
|
||||
if ((startDragIndex != null) && (endDragIndex != null) &&
|
||||
(cellIndex >= startDragIndex.getIndex()) &&
|
||||
(cellIndex <= endDragIndex.getIndex())) {
|
||||
g2d.setColor(BreakpointTimelinePanel.SELECTION_COLOR);
|
||||
}
|
||||
else if ((getTraceManagerService().getCurrentSnap() >= curSpan.getStartSnap()) &&
|
||||
(getTraceManagerService().getCurrentSnap() < curSpan.getStopSnap())) {
|
||||
g2d.setColor(BreakpointTimelinePanel.CURRENT_SNAP_COLOR);
|
||||
}
|
||||
else if ((mousePos != null) && curSpan.getRect().contains(mousePos)) {
|
||||
g2d.setColor(BreakpointTimelinePanel.HOVER_COLOR);
|
||||
}
|
||||
else {
|
||||
g2d.setColor(curSpan.getColor());
|
||||
}
|
||||
g2d.fillRect(curSpan.getRect().x, curSpan.getRect().y, curSpan.getRect().width,
|
||||
curSpan.getRect().height);
|
||||
if (BreakpointTimelinePanel.showGridOutline) {
|
||||
g2d.setColor(BreakpointTimelinePanel.GRID_COLOR);
|
||||
g2d.drawRect(curSpan.getRect().x, curSpan.getRect().y, curSpan.getRect().width,
|
||||
curSpan.getRect().height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
|
||||
void setEventsAndVisibleRange(List<BreakpointHitEvent> events, long start, long stop) {
|
||||
this.events = events;
|
||||
visibleStart = start;
|
||||
visibleEnd = stop;
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
|
||||
void setMinimumDefaultCellSize() {
|
||||
defaultCellSize = 1;
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
setToolTipText("");
|
||||
setBackground(BreakpointTimelinePanel.BG_COLOR);
|
||||
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
});
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
mousePos = null;
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
dragStart = e.getPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (dragEnd != null) {
|
||||
zoom();
|
||||
}
|
||||
else {
|
||||
click(dragStart);
|
||||
}
|
||||
dragStart = null;
|
||||
dragEnd = null;
|
||||
startDragIndex = null;
|
||||
endDragIndex = null;
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
|
||||
addMouseWheelListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
final int rotation = e.getWheelRotation();
|
||||
if (rotation > 0) {
|
||||
getTraceManagerService()
|
||||
.activateSnap(getTraceManagerService().getCurrentSnap() + 1);
|
||||
}
|
||||
else {
|
||||
getTraceManagerService()
|
||||
.activateSnap(getTraceManagerService().getCurrentSnap() - 1);
|
||||
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
|
||||
addMouseMotionListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
dragEnd = e.getPoint();
|
||||
final Optional<BreakpointTimelinePanel.CachedIndex> start = cells.values()
|
||||
.stream()
|
||||
.filter(s -> s.getRect().contains(dragStart))
|
||||
.findFirst();
|
||||
if (start.isPresent()) {
|
||||
final Optional<BreakpointTimelinePanel.CachedIndex> end = cells.values()
|
||||
.stream()
|
||||
.filter(s -> s.getRect().contains(dragEnd))
|
||||
.findFirst();
|
||||
if (end.isPresent()) {
|
||||
if (start.get().getIndex() < end.get().getIndex()) {
|
||||
startDragIndex = start.get();
|
||||
endDragIndex = end.get();
|
||||
}
|
||||
else {
|
||||
startDragIndex = end.get();
|
||||
endDragIndex = start.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
mousePos = e.getPoint();
|
||||
final Optional<BreakpointTimelinePanel.CachedIndex> indexSpan =
|
||||
cells.values().stream().filter(s -> s.getRect().contains(mousePos)).findFirst();
|
||||
if (indexSpan.isPresent() && (lastHighlightedIndex != indexSpan.get())) {
|
||||
lastHighlightedIndex = indexSpan.get();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void toggleGridOrColumn() {
|
||||
BreakpointTimelinePanel.singleColumn = !BreakpointTimelinePanel.singleColumn;
|
||||
calculateGridAndBuildCache();
|
||||
}
|
||||
|
||||
void toggleGridOutline() {
|
||||
BreakpointTimelinePanel.showGridOutline = !BreakpointTimelinePanel.showGridOutline;
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void zoom() {
|
||||
final Optional<BreakpointTimelinePanel.CachedIndex> start =
|
||||
cells.values().stream().filter(s -> s.getRect().contains(dragStart)).findFirst();
|
||||
if (start.isPresent()) {
|
||||
long startSnap = start.get().getStartSnap();
|
||||
|
||||
final Optional<BreakpointTimelinePanel.CachedIndex> stop =
|
||||
cells.values().stream().filter(s -> s.getRect().contains(dragEnd)).findFirst();
|
||||
if (stop.isPresent()) {
|
||||
long endSnap = stop.get().getStopSnap();
|
||||
|
||||
if (startSnap > endSnap) {
|
||||
final long temp = startSnap;
|
||||
startSnap = endSnap;
|
||||
endSnap = temp;
|
||||
}
|
||||
|
||||
final String zoomName = "Timeline Zoom: %d - %d".formatted(startSnap, endSnap - 1);
|
||||
provider.createZoomProvider(zoomName, startSnap, endSnap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/* ###
|
||||
* 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.breakpoint.timeline;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
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.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger breakpoint hit timeline",
|
||||
description = "Timeline of all snapshots showing breakpoint hits",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.UNSTABLE,
|
||||
servicesRequired = { DebuggerTraceManagerService.class, },
|
||||
eventsConsumed = { TraceClosedPluginEvent.class, TraceActivatedPluginEvent.class, }
|
||||
)
|
||||
public class BreakpointTimelinePlugin extends AbstractDebuggerPlugin {
|
||||
BreakpointTimelineProvider provider;
|
||||
private Trace currentTrace;
|
||||
|
||||
private final Map<Trace, List<BreakpointTimelineProvider>> traceSpecificZoomProviders =
|
||||
new HashMap<>();
|
||||
|
||||
public BreakpointTimelinePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
void createZoomProvider(String title, long start, long stop) {
|
||||
traceSpecificZoomProviders.computeIfAbsent(currentTrace, k -> new ArrayList<>())
|
||||
.add(new BreakpointTimelineProvider(provider, title, start, stop));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
for (final var providers : traceSpecificZoomProviders.values()) {
|
||||
for (final var provider : providers) {
|
||||
tool.removeComponentProvider(provider);
|
||||
}
|
||||
}
|
||||
tool.removeComponentProvider(provider);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void hideZoomProviders(Trace t) {
|
||||
final List<BreakpointTimelineProvider> zoomProviders = traceSpecificZoomProviders.get(t);
|
||||
if (zoomProviders != null) {
|
||||
for (final BreakpointTimelineProvider p : zoomProviders) {
|
||||
tool.showComponentProvider(p, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
provider = new BreakpointTimelineProvider(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
switch (event) {
|
||||
case final TraceClosedPluginEvent evt -> {
|
||||
final Trace t = evt.getTrace();
|
||||
|
||||
if (currentTrace == t) {
|
||||
currentTrace = null;
|
||||
provider.setTrace(null);
|
||||
}
|
||||
removeZoomProviders(t);
|
||||
}
|
||||
case final TraceActivatedPluginEvent evt -> {
|
||||
final Trace t = evt.getActiveCoordinates().getTrace();
|
||||
if (t == null) {
|
||||
provider.setTrace(null);
|
||||
}
|
||||
else if (currentTrace != t) {
|
||||
hideZoomProviders(currentTrace);
|
||||
currentTrace = t;
|
||||
provider.setTrace(currentTrace);
|
||||
showZoomProviders(currentTrace);
|
||||
}
|
||||
else {
|
||||
refreshAllProviders(null);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void refreshAllProviders(BreakpointTimelineProvider currentProvider) {
|
||||
final List<BreakpointTimelineProvider> zoomProviders =
|
||||
traceSpecificZoomProviders.get(currentTrace);
|
||||
if (zoomProviders != null) {
|
||||
for (final BreakpointTimelineProvider p : zoomProviders) {
|
||||
if (p != currentProvider) {
|
||||
p.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (provider != currentProvider) {
|
||||
provider.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void removeZoomProviders(Trace t) {
|
||||
final List<BreakpointTimelineProvider> zoomProviders = traceSpecificZoomProviders.remove(t);
|
||||
if (zoomProviders != null) {
|
||||
for (final BreakpointTimelineProvider p : zoomProviders) {
|
||||
tool.removeComponentProvider(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showZoomProviders(Trace t) {
|
||||
final List<BreakpointTimelineProvider> zoomProviders = traceSpecificZoomProviders.get(t);
|
||||
if (zoomProviders != null) {
|
||||
for (final BreakpointTimelineProvider p : zoomProviders) {
|
||||
tool.showComponentProvider(p, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
/* ###
|
||||
* 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.breakpoint.timeline;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToolBarData;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.symbol.TraceReference;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.util.TraceEvents;
|
||||
|
||||
class BreakpointTimelineProvider extends ComponentProvider {
|
||||
record BreakpointHitEvent(long snap, TraceBreakpointKind breakType, String breakpointName) {}
|
||||
|
||||
private class BreakpointTimeOverviewEventListener extends TraceDomainObjectListener {
|
||||
|
||||
public BreakpointTimeOverviewEventListener() {
|
||||
listenFor(TraceEvents.BREAKPOINT_CHANGED, this::breakpointChanged);
|
||||
listenFor(TraceEvents.BREAKPOINT_DELETED, this::breakpointDeleted);
|
||||
}
|
||||
|
||||
void breakpointChanged(TraceBreakpointLocation tb) {
|
||||
refreshBreakpointHits();
|
||||
breakpointTimelinePlugin.refreshAllProviders(null);
|
||||
}
|
||||
|
||||
void breakpointDeleted(TraceBreakpointLocation tb) {
|
||||
refreshBreakpointHits();
|
||||
breakpointTimelinePlugin.refreshAllProviders(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class CloseAllZoomWindowsAction extends DockingAction {
|
||||
private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.close_all_zoom_windows");
|
||||
|
||||
CloseAllZoomWindowsAction(ComponentProvider provider) {
|
||||
super("Close all zoom windows", provider.getOwner());
|
||||
setEnabled(true);
|
||||
setToolBarData(new ToolBarData(ICON, "1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
breakpointTimelinePlugin.removeZoomProviders(currentTrace);
|
||||
}
|
||||
}
|
||||
|
||||
private class SmallestCellSizeAction extends DockingAction {
|
||||
private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.zoom_out_max");
|
||||
|
||||
SmallestCellSizeAction(ComponentProvider provider) {
|
||||
super("Set default cell size to the smallest", provider.getOwner());
|
||||
setEnabled(true);
|
||||
setToolBarData(new ToolBarData(ICON, "zoom"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
breakpointTimelinePanel.setMinimumDefaultCellSize();
|
||||
}
|
||||
}
|
||||
|
||||
private class ToggleGridAction extends DockingAction {
|
||||
private final GIcon OUTLINE_ICON = new GIcon("icon.debugger.breakpoint.timeline.outline");
|
||||
private final GIcon NO_OUTLINE_ICON = new GIcon("icon.debugger.breakpoint.timeline.no_outline");
|
||||
private boolean grid = true;
|
||||
|
||||
ToggleGridAction(ComponentProvider provider) {
|
||||
super("Toggle Grid Outline", provider.getOwner());
|
||||
setEnabled(true);
|
||||
setToolBarData(new ToolBarData(OUTLINE_ICON, "2"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
grid = !grid;
|
||||
getToolBarData().setIcon(grid ? OUTLINE_ICON : NO_OUTLINE_ICON);
|
||||
breakpointTimelinePanel.toggleGridOutline();
|
||||
breakpointTimelinePlugin.refreshAllProviders(BreakpointTimelineProvider.this);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class ToggleGridOrColumnAction extends DockingAction {
|
||||
private final GIcon GRID_ICON = new GIcon("icon.debugger.breakpoint.timeline.grid");
|
||||
private final GIcon SINGLE_COLUMN_ICON =
|
||||
new GIcon("icon.debugger.breakpoint.timeline.single_column");
|
||||
private boolean grid = true;
|
||||
|
||||
ToggleGridOrColumnAction(ComponentProvider provider) {
|
||||
super("Toggle between grid and single column", provider.getOwner());
|
||||
setEnabled(true);
|
||||
setToolBarData(new ToolBarData(GRID_ICON, "2"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
grid = !grid;
|
||||
getToolBarData().setIcon(grid ? GRID_ICON : SINGLE_COLUMN_ICON);
|
||||
breakpointTimelinePanel.toggleGridOrColumn();
|
||||
breakpointTimelinePlugin.refreshAllProviders(BreakpointTimelineProvider.this);
|
||||
}
|
||||
}
|
||||
|
||||
private class ZoomInAction extends DockingAction {
|
||||
private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.zoom_in");
|
||||
|
||||
ZoomInAction(ComponentProvider provider) {
|
||||
super("Increase cell size", provider.getOwner());
|
||||
setEnabled(true);
|
||||
setToolBarData(new ToolBarData(ICON, "zoom"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
breakpointTimelinePanel.increaseDefaultCellSize();
|
||||
}
|
||||
}
|
||||
|
||||
private class ZoomOutAction extends DockingAction {
|
||||
private final GIcon ICON = new GIcon("icon.debugger.breakpoint.timeline.zoom_out");
|
||||
|
||||
ZoomOutAction(ComponentProvider provider) {
|
||||
super("Decrease cell size", provider.getOwner());
|
||||
setEnabled(true);
|
||||
setToolBarData(new ToolBarData(ICON, "zoom"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
breakpointTimelinePanel.decreaseDefaultCellSize();
|
||||
}
|
||||
}
|
||||
|
||||
private Trace currentTrace;
|
||||
private final BreakpointTimelinePanel breakpointTimelinePanel;
|
||||
|
||||
private final JPanel wrapperPanel;
|
||||
private final BreakpointTimelinePlugin breakpointTimelinePlugin;
|
||||
private final BreakpointTimeOverviewEventListener listener =
|
||||
new BreakpointTimeOverviewEventListener();
|
||||
private List<BreakpointHitEvent> breakpointHits;
|
||||
|
||||
BreakpointTimelineProvider(BreakpointTimelinePlugin breakpointTimelinePlugin) {
|
||||
this(breakpointTimelinePlugin, false);
|
||||
}
|
||||
|
||||
BreakpointTimelineProvider(BreakpointTimelinePlugin breakpointTimelinePlugin,
|
||||
boolean makeTransient) {
|
||||
super(breakpointTimelinePlugin.getTool(), "Breakpoint Timeline",
|
||||
breakpointTimelinePlugin.getName());
|
||||
this.breakpointTimelinePlugin = breakpointTimelinePlugin;
|
||||
wrapperPanel = new JPanel(new BorderLayout());
|
||||
breakpointTimelinePanel = new BreakpointTimelinePanel(this);
|
||||
breakpointTimelinePanel.setFocusable(true);
|
||||
wrapperPanel.add(breakpointTimelinePanel, BorderLayout.CENTER);
|
||||
breakpointHits = new ArrayList<>();
|
||||
|
||||
if (makeTransient) {
|
||||
setTransient();
|
||||
}
|
||||
|
||||
dockingTool.addComponentProvider(this, true);
|
||||
createActions();
|
||||
|
||||
}
|
||||
|
||||
BreakpointTimelineProvider(BreakpointTimelineProvider provider, String title, long start,
|
||||
long end) {
|
||||
this(provider.breakpointTimelinePlugin, true);
|
||||
currentTrace = provider.currentTrace;
|
||||
setTitle(title);
|
||||
breakpointHits = provider.breakpointHits;
|
||||
breakpointTimelinePanel.setEventsAndVisibleRange(breakpointHits, start, end);
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
dockingTool.addLocalAction(this, new ToggleGridOrColumnAction(this));
|
||||
dockingTool.addLocalAction(this, new ToggleGridAction(this));
|
||||
dockingTool.addLocalAction(this, new ZoomInAction(this));
|
||||
dockingTool.addLocalAction(this, new ZoomOutAction(this));
|
||||
dockingTool.addLocalAction(this, new CloseAllZoomWindowsAction(this));
|
||||
dockingTool.addLocalAction(this, new SmallestCellSizeAction(this));
|
||||
}
|
||||
|
||||
void createZoomProvider(String title, long start, long stop) {
|
||||
breakpointTimelinePlugin.createZoomProvider(title, start, stop);
|
||||
}
|
||||
|
||||
private void findAndAddAllBreakpointHitsAtLocation(TraceBreakpointLocation breakpointLocation,
|
||||
AddressRange range, String kind) {
|
||||
for (final TraceBreakpointKind breakpointKind : TraceBreakpointKindSet.decode(kind, false)) {
|
||||
switch (breakpointKind) {
|
||||
case HW_EXECUTE, SW_EXECUTE -> findAndAddExecuteBreakpointHits(breakpointLocation,
|
||||
range);
|
||||
case READ, WRITE -> findAndAddMemoryBreakpointHits(breakpointLocation, range,
|
||||
breakpointKind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findAndAddExecuteBreakpointHits(TraceBreakpointLocation breakpointLocation,
|
||||
AddressRange range) {
|
||||
final Collection<? extends TraceObjectValue> intersecting = currentTrace.getObjectManager()
|
||||
.getValuesIntersecting(Lifespan.ALL, range, TraceStackFrame.KEY_PC);
|
||||
for (final TraceObjectValue tov : intersecting) {
|
||||
breakpointHits.add(new BreakpointHitEvent(tov.getMinSnap(),
|
||||
TraceBreakpointKind.SW_EXECUTE, breakpointLocation.getName(tov.getMinSnap())));
|
||||
}
|
||||
}
|
||||
|
||||
private void findAndAddMemoryBreakpointHits(TraceBreakpointLocation breakpointLocation,
|
||||
AddressRange range, TraceBreakpointKind kind) {
|
||||
for (final TraceReference reference : currentTrace.getReferenceManager()
|
||||
.getReferencesToRange(Lifespan.ALL, range)) {
|
||||
|
||||
if ((reference.getReferenceType() == RefType.READ) &&
|
||||
(kind == TraceBreakpointKind.READ)) {
|
||||
breakpointHits.add(
|
||||
new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.READ,
|
||||
breakpointLocation.getName(reference.getStartSnap())));
|
||||
}
|
||||
|
||||
if ((reference.getReferenceType() == RefType.WRITE) &&
|
||||
(kind == TraceBreakpointKind.WRITE)) {
|
||||
breakpointHits.add(
|
||||
new BreakpointHitEvent(reference.getStartSnap(), TraceBreakpointKind.WRITE,
|
||||
breakpointLocation.getName(reference.getStartSnap())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return wrapperPanel;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
breakpointTimelinePanel.refresh();
|
||||
}
|
||||
|
||||
private void refreshBreakpointHits() {
|
||||
breakpointHits.clear();
|
||||
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// LATER: Check if breakpoint is enabled after GP-6441 is done
|
||||
for (final TraceBreakpointLocation breakpointLocation : currentTrace.getBreakpointManager()
|
||||
.getAllBreakpointLocations()) {
|
||||
|
||||
for (final TraceObjectValue traceVal : breakpointLocation.getObject()
|
||||
.getValues(Lifespan.ALL, TraceBreakpointLocation.KEY_RANGE)) {
|
||||
if (!(traceVal.getValue() instanceof final AddressRange range)) {
|
||||
continue;
|
||||
}
|
||||
for (final TraceObjectValue specValue : breakpointLocation.getSpecification()
|
||||
.getObject()
|
||||
.getValues(Lifespan.ALL, TraceBreakpointSpec.KEY_KINDS)) {
|
||||
|
||||
if (!(specValue.getValue() instanceof final String kind)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
findAndAddAllBreakpointHitsAtLocation(breakpointLocation, range, kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setTrace(Trace trace) {
|
||||
if (currentTrace != null) {
|
||||
currentTrace.removeListener(listener);
|
||||
}
|
||||
currentTrace = trace;
|
||||
refreshBreakpointHits();
|
||||
|
||||
if (currentTrace != null) {
|
||||
breakpointTimelinePanel.setEventsAndVisibleRange(breakpointHits, 0,
|
||||
currentTrace.getTimeManager().getMaxSnap());
|
||||
currentTrace.addListener(listener);
|
||||
}
|
||||
else {
|
||||
breakpointTimelinePanel.setEventsAndVisibleRange(null, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/* ###
|
||||
* 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.breakpoint;
|
||||
|
||||
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;
|
||||
|
||||
public class BreakTypeOverviewLegendPanel extends JPanel {
|
||||
private static Dimension COLOR_SIZE = new Dimension(15, 15);
|
||||
private BreakpointTimeOverviewColorService colorService;
|
||||
|
||||
public BreakTypeOverviewLegendPanel(BreakpointTimeOverviewColorService 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();
|
||||
CellType[] values = CellType.values();
|
||||
for (CellType breakType : values) {
|
||||
JPanel panel = new ColorPanel(breakType);
|
||||
add(panel);
|
||||
add(new GLabel(breakType.getDescription()));
|
||||
}
|
||||
}
|
||||
|
||||
private class ColorPanel extends JPanel {
|
||||
private CellType type;
|
||||
|
||||
ColorPanel(CellType 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
/* ###
|
||||
* 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.breakpoint;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import generic.ULongSpan;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.plugin.core.debug.gui.timeoverview.*;
|
||||
import ghidra.app.plugin.core.overview.OverviewColorLegendDialog;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.breakpoint.*;
|
||||
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.symbol.TraceReference;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.util.TraceEvents;
|
||||
import ghidra.util.ColorUtils;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
record BreakpointEvent(long snap, CellType breakType) {}
|
||||
|
||||
public class BreakpointTimeOverviewColorService implements TimeOverviewColorService {
|
||||
private class BreakpointTimeOverviewEventListener extends TraceDomainObjectListener {
|
||||
|
||||
public BreakpointTimeOverviewEventListener() {
|
||||
listenFor(TraceEvents.BREAKPOINT_ADDED, this::breakpointAdded);
|
||||
listenFor(TraceEvents.BREAKPOINT_CHANGED, this::breakpointChanged);
|
||||
listenFor(TraceEvents.BREAKPOINT_DELETED, this::breakpointDeleted);
|
||||
listenFor(TraceEvents.BREAKPOINT_LIFESPAN_CHANGED, this::breakpointLifespanChanged);
|
||||
}
|
||||
|
||||
void breakpointAdded(TraceBreakpointLocation tb) {
|
||||
// Empty because all new breakpoints fire a breakpoint change
|
||||
// event
|
||||
}
|
||||
|
||||
void breakpointChanged(TraceBreakpointLocation tb) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
calculateBreakpointHits();
|
||||
calculateHelperMaps();
|
||||
});
|
||||
}
|
||||
|
||||
void breakpointDeleted(TraceBreakpointLocation tb) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
calculateBreakpointHits();
|
||||
calculateHelperMaps();
|
||||
});
|
||||
}
|
||||
|
||||
void breakpointLifespanChanged(TraceBreakpointLocation tb) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
calculateBreakpointHits();
|
||||
calculateHelperMaps();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String OPTIONS_NAME = "Breakpoint Hit Timeline";
|
||||
|
||||
private PluginTool tool;
|
||||
|
||||
Trace currentTrace;
|
||||
TimeOverviewColorComponent overviewComponent;
|
||||
DialogComponentProvider legendDialog;
|
||||
BreakTypeOverviewLegendPanel legendPanel;
|
||||
TimeOverviewColorPlugin plugin;
|
||||
|
||||
private final BreakpointTimeOverviewEventListener eventListener =
|
||||
new BreakpointTimeOverviewEventListener();
|
||||
|
||||
private final Map<CellType, Color> colorMap = new HashMap<>();
|
||||
private final Map<Integer, Long> indexToSnap = new HashMap<>();
|
||||
private final Map<Long, Color> snapToColor = new HashMap<>();
|
||||
private final Map<Long, String> snapToTooltip = new HashMap<>();
|
||||
private final Map<Long, ULongSpan> snapToRange = new HashMap<>();
|
||||
|
||||
List<BreakpointEvent> snapsWithBreakpointsHit = new ArrayList<>();
|
||||
|
||||
Lifespan bounds;
|
||||
private DebuggerTraceManagerService debuggerTraceManagerService;
|
||||
|
||||
protected void calculateBreakpointHits() {
|
||||
snapsWithBreakpointsHit.clear();
|
||||
|
||||
// LATER: Check if breakpoint is enabled after GP-6441 is done
|
||||
for (final TraceBreakpointLocation breakpointLocation : getTrace().getBreakpointManager()
|
||||
.getAllBreakpointLocations()) {
|
||||
|
||||
for (final TraceObjectValue traceVal : breakpointLocation.getObject()
|
||||
.getValues(Lifespan.ALL, TraceBreakpointLocation.KEY_RANGE)) {
|
||||
if (!(traceVal.getValue() instanceof final AddressRange range)) {
|
||||
continue;
|
||||
}
|
||||
for (final TraceObjectValue specValue : breakpointLocation.getSpecification()
|
||||
.getObject()
|
||||
.getValues(Lifespan.ALL, TraceBreakpointSpec.KEY_KINDS)) {
|
||||
|
||||
if (!(specValue.getValue() instanceof final String kind)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
findAndAddAllBreakpointHitsAtLocation(breakpointLocation, range, kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void calculateHelperMaps() {
|
||||
indexToSnap.clear();
|
||||
snapToColor.clear();
|
||||
if (bounds == null) {
|
||||
return;
|
||||
}
|
||||
final long splits = overviewComponent.getOverviewPixelCount();
|
||||
final long snapSpanPerCell = Math.max((bounds.lmax() - bounds.lmin()) / splits, 1);
|
||||
|
||||
int cellIndex = 0;
|
||||
for (long i = 0; i < bounds.lmax(); i += snapSpanPerCell) {
|
||||
boolean hasBreakpointHit = false;
|
||||
Color cellColor = Colors.BACKGROUND;
|
||||
|
||||
for (final BreakpointEvent event : snapsWithBreakpointsHit) {
|
||||
if ((i <= event.snap()) && (event.snap() <= (i + snapSpanPerCell))) {
|
||||
hasBreakpointHit = true;
|
||||
indexToSnap.put(cellIndex, event.snap());
|
||||
snapToTooltip.put(event.snap(), """
|
||||
Snapshot %d
|
||||
Break type %s""".formatted(event.snap(), event.breakType()));
|
||||
snapToRange.put(event.snap(), ULongSpan.span(i, i + snapSpanPerCell));
|
||||
|
||||
cellColor =
|
||||
ColorUtils.addColors(cellColor, event.breakType().getDefaultColor());
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBreakpointHit) {
|
||||
// Just use the first snap of this span
|
||||
indexToSnap.put(cellIndex, i);
|
||||
snapToRange.put(i, ULongSpan.span(i, i + snapSpanPerCell));
|
||||
snapToTooltip.put(i, "Snapshot %d".formatted(i));
|
||||
}
|
||||
|
||||
snapToColor.put(indexToSnap.get(cellIndex), cellColor);
|
||||
|
||||
cellIndex++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void findAndAddAllBreakpointHitsAtLocation(TraceBreakpointLocation breakpointLocation,
|
||||
AddressRange range, String kind) {
|
||||
for (final TraceBreakpointKind breakpointKind : TraceBreakpointKindSet.decode(kind,
|
||||
false)) {
|
||||
switch (breakpointKind) {
|
||||
case HW_EXECUTE, SW_EXECUTE -> findAndAddExecuteBreakpointHits(breakpointLocation,
|
||||
range);
|
||||
case READ, WRITE -> findAndAddMemoryBreakpointHits(breakpointLocation, range,
|
||||
breakpointKind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findAndAddExecuteBreakpointHits(TraceBreakpointLocation breakpointLocation,
|
||||
AddressRange range) {
|
||||
final Collection<? extends TraceObjectValue> intersecting = currentTrace.getObjectManager()
|
||||
.getValuesIntersecting(Lifespan.ALL, range, TraceStackFrame.KEY_PC);
|
||||
for (final TraceObjectValue tov : intersecting) {
|
||||
snapsWithBreakpointsHit
|
||||
.add(new BreakpointEvent(tov.getMinSnap(), CellType.INSTRUCTION_EXECUTED));
|
||||
}
|
||||
}
|
||||
|
||||
private void findAndAddMemoryBreakpointHits(TraceBreakpointLocation breakpointLocation,
|
||||
AddressRange range, TraceBreakpointKind kind) {
|
||||
for (final TraceReference reference : currentTrace.getReferenceManager()
|
||||
.getReferencesToRange(Lifespan.ALL, range)) {
|
||||
|
||||
if ((reference.getReferenceType() == RefType.READ) &&
|
||||
(kind == TraceBreakpointKind.READ)) {
|
||||
snapsWithBreakpointsHit
|
||||
.add(new BreakpointEvent(reference.getStartSnap(), CellType.MEMORY_READ));
|
||||
}
|
||||
|
||||
if ((reference.getReferenceType() == RefType.WRITE) &&
|
||||
(kind == TraceBreakpointKind.WRITE)) {
|
||||
snapsWithBreakpointsHit.add(
|
||||
new BreakpointEvent(reference.getStartSnap(), CellType.MEMORY_WRITTEN));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingActionIf> getActions() {
|
||||
final 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 Lifespan getBounds() {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color associated with the given {@link CellType}
|
||||
*
|
||||
* @param breakType the span type for which to get a color.
|
||||
* @return the color associated with the given {@link CellType}
|
||||
*/
|
||||
public Color getColor(CellType breakType) {
|
||||
final Color color = colorMap.get(breakType);
|
||||
if (color == null) {
|
||||
colorMap.put(breakType, breakType.getDefaultColor());
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor(Long snap) {
|
||||
final Color c = Colors.BACKGROUND;
|
||||
|
||||
if (snap != null) {
|
||||
final ULongSpan range = snapToRange.get(snap);
|
||||
final long currentSnap = debuggerTraceManagerService.getCurrentSnap();
|
||||
if ((range != null) && (currentSnap > range.min()) && (currentSnap < range.max())) {
|
||||
return Color.GREEN;
|
||||
}
|
||||
return snapToColor.get(snap);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HelpLocation getHelpLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private DialogComponentProvider getLegendDialog() {
|
||||
if (legendDialog == null) {
|
||||
legendPanel = new BreakTypeOverviewLegendPanel(this);
|
||||
|
||||
legendDialog =
|
||||
new OverviewColorLegendDialog("Overview Legend", legendPanel, getHelpLocation());
|
||||
}
|
||||
return legendDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return OPTIONS_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getSnap(int pixelIndex) {
|
||||
final BigInteger bigHeight = BigInteger.valueOf(overviewComponent.getOverviewPixelCount());
|
||||
final BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex);
|
||||
|
||||
final BigInteger span = BigInteger.valueOf(indexToSnap.size());
|
||||
final BigInteger offset = span.multiply(bigPixelIndex).divide(bigHeight);
|
||||
return indexToSnap.get(offset.intValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(Long snap) {
|
||||
return snapToTooltip.getOrDefault(snap, "Snapshot %d".formatted(snap));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return currentTrace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(PluginTool pluginTool) {
|
||||
tool = pluginTool;
|
||||
debuggerTraceManagerService = tool.getService(DebuggerTraceManagerService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBounds(Lifespan bounds) {
|
||||
if (currentTrace != null) {
|
||||
this.bounds = Lifespan.span(0, getTrace().getTimeManager().getMaxSnap());
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor(CellType type, Color newColor) {
|
||||
final ToolOptions options = tool.getOptions(OPTIONS_NAME);
|
||||
options.setColor(type.getDescription(), newColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndices(TreeSet<Long> set) {
|
||||
// Empty because we do not change indices once a trace is set
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOverviewComponent(TimeOverviewColorComponent component) {
|
||||
overviewComponent = component;
|
||||
overviewComponent.addComponentListener(new ComponentAdapter() {
|
||||
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
SwingUtilities.invokeLater(() -> calculateHelperMaps());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlugin(TimeOverviewColorPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrace(Trace trace) {
|
||||
if ((trace != null) && (trace != currentTrace)) {
|
||||
if (currentTrace != null) {
|
||||
currentTrace.removeListener(eventListener);
|
||||
}
|
||||
currentTrace = trace;
|
||||
currentTrace.addListener(eventListener);
|
||||
SwingUtilities.invokeLater(this::calculateHelperMaps);
|
||||
bounds = Lifespan.span(0, getTrace().getTimeManager().getMaxSnap());
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* 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.breakpoint;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import generic.theme.GColor;
|
||||
|
||||
/**
|
||||
* An enum for the different types that are represented by unique colors by the
|
||||
* {@link BreakpointTimeOverviewColorService}
|
||||
*/
|
||||
public enum CellType {
|
||||
INSTRUCTION_EXECUTED("Instruction Executed", new GColor(
|
||||
"color.debugger.plugin.timeoverview.box.type.instructions")),
|
||||
MEMORY_READ("Memory Read", new GColor(
|
||||
"color.debugger.plugin.timeoverview.box.type.read.memory")),
|
||||
MEMORY_WRITTEN("Memory Written", new GColor(
|
||||
"color.debugger.plugin.timeoverview.box.type.write.memory")),
|
||||
CURRENT_LOCATION("Current Location", new GColor("color.palette.green"));
|
||||
|
||||
final private String description;
|
||||
final private Color color;
|
||||
|
||||
CellType(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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user