From b17f2e6b96221cc316ea162473c7f1d3e2ab1a84 Mon Sep 17 00:00:00 2001 From: ghidra_red Date: Wed, 10 Jun 2026 14:10:14 +0000 Subject: [PATCH] GP-5896: Add actions to create new debugger stack viewer and to convert current debugger byte view into a stack one --- .../DebuggerMemoryBytesActionContext.java | 12 +++---- .../gui/memory/DebuggerMemoryBytesPlugin.java | 35 +++++++++++++++++-- .../memory/DebuggerMemoryBytesProvider.java | 33 ++++++++++++++++- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesActionContext.java index d0bd1936c9..1acda28ca2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesActionContext.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesActionContext.java @@ -4,9 +4,9 @@ * 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. @@ -15,16 +15,16 @@ */ package ghidra.app.plugin.core.debug.gui.memory; -import ghidra.app.plugin.core.byteviewer.ByteViewerActionContext; -import ghidra.app.plugin.core.byteviewer.ProgramByteViewerComponentProvider; +import ghidra.app.plugin.core.byteviewer.*; import ghidra.app.plugin.core.debug.gui.action.DebuggerProgramLocationActionContext; import ghidra.trace.model.program.TraceProgramView; public class DebuggerMemoryBytesActionContext extends ByteViewerActionContext implements DebuggerProgramLocationActionContext { - public DebuggerMemoryBytesActionContext(ProgramByteViewerComponentProvider provider) { - super(provider); + public DebuggerMemoryBytesActionContext(ProgramByteViewerComponentProvider provider, + ByteViewerComponent currentComponent) { + super(provider, currentComponent); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java index 158127c9df..f614fa1b05 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesPlugin.java @@ -16,15 +16,17 @@ package ghidra.app.plugin.core.debug.gui.memory; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import org.jdom2.Element; +import docking.ActionContext; import docking.action.DockingAction; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.app.plugin.core.byteviewer.AbstractByteViewerPlugin; -import ghidra.app.plugin.core.byteviewer.ByteViewerComponentProvider; +import ghidra.app.plugin.core.byteviewer.*; +import ghidra.app.plugin.core.debug.gui.action.SPLocationTrackingSpec; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.gui.DebuggerResources.NewMemoryAction; @@ -73,6 +75,7 @@ public class DebuggerMemoryBytesPlugin private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider"; protected DockingAction actionNewMemory; + private DockingAction actionNewStackView; @AutoServiceConsumed private ProgramManager programManager; @@ -99,6 +102,12 @@ public class DebuggerMemoryBytesPlugin .enabled(true) .onAction(c -> connectedProvider.cloneWindow()) .buildAndInstall(tool); + + actionNewStackView = NewMemoryAction.builder(this) + .enabled(true) + .menuPath("Window", DebuggerPluginPackage.NAME, "New Stack View") + .onAction(this::newStackViewActivated) + .buildAndInstall(tool); } public DebuggerMemoryBytesProvider createViewerIfMissing(LocationTrackingSpec spec, @@ -277,4 +286,26 @@ public class DebuggerMemoryBytesPlugin int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0); ensureProviders(disconnectedCount, true, saveState); } + + private void newStackViewActivated(ActionContext c) { + DebuggerMemoryBytesProvider provider = createNewDisconnectedProvider(); + provider.setTrackingSpec(SPLocationTrackingSpec.INSTANCE); + provider.setFollowsCurrentThread(true); + ByteViewerConfigOptions options = new ByteViewerConfigOptions(); + options.setHexGroupSize(1); + String hexColumn = "Hex"; + int bytesPerLine = 8; + if (current != DebuggerCoordinates.NOWHERE) { + int pointerSize = current.getTrace().getProgramView().getMinAddress().getPointerSize(); + hexColumn = switch (pointerSize) { + case 2 -> "Hex Short"; + case 4 -> "Hex Integer"; + case 8 -> "Hex Long"; + default -> "Hex"; + }; + bytesPerLine = pointerSize; + } + options.setBytesPerLine(bytesPerLine); + provider.updateConfigOptions(options, Set.of(hexColumn, "Chars")); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index 559e0d42a1..21b680d5ed 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -28,6 +28,7 @@ import java.util.concurrent.*; import javax.swing.*; +import docking.action.builder.ActionBuilder; import org.apache.commons.lang3.StringUtils; import docking.ActionContext; @@ -230,6 +231,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi protected MultiStateDockingAction actionAutoReadMemory; protected DockingAction actionRefreshSelectedMemory; protected MultiStateDockingAction actionTrackLocation; + protected DockingAction actionConvertToStackView; protected ForMemoryBytesGoToTrait goToTrait; protected ForMemoryBytesTrackingTrait trackingTrait; @@ -493,7 +495,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi @Override protected ByteViewerActionContext newByteViewerActionContext() { - return new DebuggerMemoryBytesActionContext(this); + return new DebuggerMemoryBytesActionContext(this, panel.getCurrentComponent()); } @Override @@ -522,6 +524,15 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi .onAction( ctx -> doSetFollowsCurrentThread(actionFollowsCurrentThread.isSelected())) .buildAndInstallLocal(this); + + actionConvertToStackView = + new ActionBuilder("Convert To Stack View", plugin.getName()).description( + "Convert current byte viewer into a stack view") + .enabled(true) + .menuPath("Convert To Stack View") + .menuGroup("aa") + .onAction(this::convertToStackViewActivated) + .buildAndInstallLocal(this); } actionGoTo = goToTrait.installAction(); @@ -805,4 +816,24 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi CompletableFuture getLastAutoRead() { return readsMemTrait.getLastRead(); } + + private void convertToStackViewActivated(ActionContext c) { + setTrackingSpec(SPLocationTrackingSpec.INSTANCE); + ByteViewerConfigOptions options = new ByteViewerConfigOptions(); + options.setHexGroupSize(1); + String hexColumn = "Hex"; + int bytesPerLine = 8; + if (current != DebuggerCoordinates.NOWHERE) { + int pointerSize = current.getTrace().getProgramView().getMinAddress().getPointerSize(); + hexColumn = switch (pointerSize) { + case 2 -> "Hex Short"; + case 4 -> "Hex Integer"; + case 8 -> "Hex Long"; + default -> "Hex"; + }; + bytesPerLine = pointerSize; + } + options.setBytesPerLine(bytesPerLine); + updateConfigOptions(options, Set.of(hexColumn, "Chars")); + } }