Merge remote-tracking branch 'origin/GP-80_Dan_dynamicBytesViewer'

This commit is contained in:
Ryan Kurtz 2021-09-27 11:05:00 -04:00
commit 7671a4726d
54 changed files with 5937 additions and 2330 deletions

View File

@ -0,0 +1,196 @@
/* ###
* 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;
import java.util.*;
import javax.swing.JLabel;
import org.apache.commons.collections4.ComparatorUtils;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.program.model.address.Address;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.*;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.Swing;
public class DebuggerLocationLabel extends JLabel {
protected class ForLocationLabelTraceListener extends TraceDomainObjectListener {
private final AsyncDebouncer<Void> updateLabelDebouncer =
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100);
public ForLocationLabelTraceListener() {
updateLabelDebouncer
.addListener(__ -> Swing.runIfSwingOrRunLater(() -> doUpdateLabel()));
listenFor(TraceMemoryRegionChangeType.ADDED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.DELETED, this::regionChanged);
listenFor(TraceModuleChangeType.CHANGED, this::moduleChanged);
listenFor(TraceModuleChangeType.LIFESPAN_CHANGED, this::moduleChanged);
listenFor(TraceModuleChangeType.DELETED, this::moduleChanged);
listenFor(TraceSectionChangeType.ADDED, this::sectionChanged);
listenFor(TraceSectionChangeType.CHANGED, this::sectionChanged);
listenFor(TraceSectionChangeType.DELETED, this::sectionChanged);
}
private void doUpdateLabel() {
updateLabel();
}
private void regionChanged(TraceMemoryRegion region) {
updateLabelDebouncer.contact(null);
}
private void moduleChanged(TraceModule module) {
updateLabelDebouncer.contact(null);
}
private void sectionChanged(TraceSection section) {
updateLabelDebouncer.contact(null);
}
}
protected final ForLocationLabelTraceListener listener = new ForLocationLabelTraceListener();
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private Address address = null;
protected boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getView(), b.getView())) {
return false; // Subsumes trace
}
if (!Objects.equals(a.getTime(), b.getTime())) {
return false;
}
return true;
}
protected void addNewListeners() {
Trace trace = current.getTrace();
if (trace != null) {
trace.addListener(listener);
}
}
protected void removeOldListeners() {
Trace trace = current.getTrace();
if (trace != null) {
trace.removeListener(listener);
}
}
public void goToCoordinates(DebuggerCoordinates coordinates) {
if (sameCoordinates(current, coordinates)) {
current = coordinates;
return;
}
boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace());
if (doListeners) {
removeOldListeners();
}
current = coordinates;
if (doListeners) {
addNewListeners();
}
updateLabel();
}
public void goToAddress(Address address) {
this.address = address;
updateLabel();
}
protected TraceSection getNearestSectionContaining() {
if (current.getView() == null) {
return null;
}
Trace trace = current.getTrace();
List<TraceSection> sections =
new ArrayList<>(trace.getModuleManager().getSectionsAt(current.getSnap(), address));
if (sections.isEmpty()) {
return null;
}
// TODO: DB's R-Tree could probably do this natively
sections.sort(ComparatorUtils.chainedComparator(List.of(
Comparator.comparing(s -> s.getRange().getMinAddress()),
Comparator.comparing(s -> -s.getRange().getLength()))));
return sections.get(sections.size() - 1);
}
protected TraceModule getNearestModuleContaining() {
if (current.getView() == null) {
return null;
}
Trace trace = current.getTrace();
List<TraceModule> modules =
new ArrayList<>(trace.getModuleManager().getModulesAt(current.getSnap(), address));
if (modules.isEmpty()) {
return null;
}
// TODO: DB's R-Tree could probably do this natively
modules.sort(ComparatorUtils.chainedComparator(List.of(
Comparator.comparing(m -> m.getRange().getMinAddress()),
Comparator.comparing(m -> -m.getRange().getLength()))));
return modules.get(modules.size() - 1);
}
protected TraceMemoryRegion getRegionContaining() {
if (current.getView() == null) {
return null;
}
Trace trace = current.getTrace();
return trace.getMemoryManager().getRegionContaining(current.getSnap(), address);
}
protected String computeLocationString() {
TraceProgramView view = current.getView();
if (view == null) {
return "";
}
if (address == null) {
return "(nowhere)";
}
TraceSection section = getNearestSectionContaining();
if (section != null) {
return section.getModule().getName() + ":" + section.getName();
}
TraceModule module = getNearestModuleContaining();
if (module != null) {
return module.getName();
}
TraceMemoryRegion region = getRegionContaining();
if (region != null) {
return region.getName();
}
return "(unknown)";
}
public void updateLabel() {
setText(computeLocationString());
}
}

View File

@ -257,9 +257,7 @@ public interface DebuggerResources {
Color DEFAULT_COLOR_BACKGROUND_ERROR = new Color(1.0f, 0.75f, 0.75f);
int PRIORITY_REGISTER_MARKER = 10;
// TODO: Is this the right name? Used by Location Tracking, which could be anything
// Close enough for now
String OPTION_NAME_COLORS_REGISTER_MARKERS = "Colors.Register Markers";
String OPTION_NAME_COLORS_TRACKING_MARKERS = "Colors.Tracking Markers";
Color DEFAULT_COLOR_REGISTER_MARKERS = new Color(0.75f, 0.875f, 0.75f);
ImageIcon ICON_REGISTER_MARKER = ResourceManager.loadImage("images/register-marker.png");

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.listing;
package ghidra.app.plugin.core.debug.gui.action;
import java.awt.BorderLayout;
import java.util.List;
@ -34,14 +34,15 @@ import ghidra.util.Msg;
public class DebuggerGoToDialog extends DialogComponentProvider {
private final DebuggerListingProvider provider;
final JTextField textExpression;
final JComboBox<String> comboSpaces;
private final DebuggerGoToTrait trait;
private final DefaultComboBoxModel<String> modelSpaces;
protected DebuggerGoToDialog(DebuggerListingProvider provider) {
final JTextField textExpression;
final JComboBox<String> comboSpaces;
public DebuggerGoToDialog(DebuggerGoToTrait trait) {
super("Go To", true, true, true, false);
this.provider = provider;
this.trait = trait;
textExpression = new JTextField();
modelSpaces = new DefaultComboBoxModel<>();
@ -91,11 +92,11 @@ public class DebuggerGoToDialog extends DialogComponentProvider {
}
}
@Override
protected void okCallback() {
@Override // public for tests
public void okCallback() {
CompletableFuture<Boolean> future;
try {
future = provider.goToSleigh((String) comboSpaces.getSelectedItem(),
future = trait.goToSleigh((String) comboSpaces.getSelectedItem(),
textExpression.getText());
}
catch (Throwable t) {
@ -117,12 +118,16 @@ public class DebuggerGoToDialog extends DialogComponentProvider {
}
@Override
protected void cancelCallback() {
public void cancelCallback() {
close();
}
public void show(SleighLanguage language) {
populateSpaces(language);
provider.getTool().showDialog(this);
trait.tool.showDialog(this);
}
public void setExpression(String expression) {
textExpression.setText(expression);
}
}

View File

@ -0,0 +1,104 @@
/* ###
* 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.action;
import java.util.concurrent.CompletableFuture;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingAction;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.GoToAction;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.program.TraceProgramView;
public abstract class DebuggerGoToTrait {
protected DockingAction action;
protected final PluginTool tool;
protected final Plugin plugin;
protected final ComponentProvider provider;
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected final DebuggerGoToDialog goToDialog;
public DebuggerGoToTrait(PluginTool tool, Plugin plugin, ComponentProvider provider) {
this.tool = tool;
this.plugin = plugin;
this.provider = provider;
goToDialog = new DebuggerGoToDialog(this);
}
protected abstract boolean goToAddress(Address address);
public void goToCoordinates(DebuggerCoordinates coordinates) {
current = coordinates;
}
public DockingAction installAction() {
action = GoToAction.builder(plugin)
.enabledWhen(ctx -> current.getView() != null)
.onAction(this::activatedGoTo)
.buildAndInstallLocal(provider);
action.setEnabled(false);
return action;
}
private void activatedGoTo(ActionContext context) {
TraceProgramView view = current.getView();
if (view == null) {
return;
}
Language language = view.getLanguage();
if (!(language instanceof SleighLanguage)) {
return;
}
goToDialog.show((SleighLanguage) language);
}
public CompletableFuture<Boolean> goToSleigh(String spaceName, String expression) {
Language language = current.getView().getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalStateException("Current trace does not use Sleigh");
}
SleighLanguage slang = (SleighLanguage) language;
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName);
if (space == null) {
throw new IllegalArgumentException("No such address space: " + spaceName);
}
SleighExpression expr = SleighProgramCompiler.compileExpression(slang, expression);
return goToSleigh(space, expr);
}
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, SleighExpression expression) {
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
CompletableFuture<byte[]> result = expression.evaluate(executor);
return result.thenApply(offset -> {
Address address = space.getAddress(
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
return goToAddress(address);
});
}
}

View File

@ -0,0 +1,271 @@
/* ###
* 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.action;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingAction;
import docking.action.ToolBarData;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractCaptureSelectedMemoryAction;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
import ghidra.app.services.TraceRecorder;
import ghidra.app.services.TraceRecorderListener;
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Msg;
import ghidra.util.Swing;
public abstract class DebuggerReadsMemoryTrait {
protected static final AutoConfigState.ClassHandler<DebuggerReadsMemoryTrait> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(DebuggerReadsMemoryTrait.class, MethodHandles.lookup());
protected class CaptureSelectedMemoryAction extends AbstractCaptureSelectedMemoryAction {
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
public CaptureSelectedMemoryAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP));
setEnabled(false);
}
@Override
public void actionPerformed(ActionContext context) {
AddressSetView selection = getSelection();
if (selection == null || selection.isEmpty() || !current.isAliveAndReadsPresent()) {
return;
}
Trace trace = current.getTrace();
TraceRecorder recorder = current.getRecorder();
BackgroundUtils.async(tool, trace, NAME, true, true, false,
(__, monitor) -> recorder.captureProcessMemory(selection, monitor, false));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
AddressSetView selection = getSelection();
if (selection == null || selection.isEmpty() || !current.isAliveAndReadsPresent()) {
return false;
}
TraceRecorder recorder = current.getRecorder();
// TODO: Either allow partial, or provide action to intersect with accessible
if (!recorder.getAccessibleProcessMemory().contains(selection)) {
return false;
}
return true;
}
public void updateEnabled(ActionContext context) {
setEnabled(isEnabledForContext(context));
}
}
protected class ForCaptureEnabledTraceListener extends TraceDomainObjectListener {
public ForCaptureEnabledTraceListener() {
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
}
private void snapshotAdded(TraceSnapshot snapshot) {
actionCaptureSelected.updateEnabled(null);
}
}
protected class ForAccessRecorderListener implements TraceRecorderListener {
@Override
public void processMemoryAccessibilityChanged(TraceRecorder recorder) {
Swing.runIfSwingOrRunLater(() -> {
actionCaptureSelected.updateEnabled(null);
});
}
}
protected class ForVisibilityListener implements AddressSetDisplayListener {
@Override
public void visibleAddressesChanged(AddressSetView visibleAddresses) {
if (Objects.equals(visible, visibleAddresses)) {
return;
}
visible = visibleAddresses;
doAutoRead();
}
}
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoRead;
protected CaptureSelectedMemoryAction actionCaptureSelected;
private final AutoReadMemorySpec defaultAutoSpec =
AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
@AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class)
protected AutoReadMemorySpec autoSpec = defaultAutoSpec;
protected final PluginTool tool;
protected final Plugin plugin;
protected final ComponentProvider provider;
protected final ForCaptureEnabledTraceListener traceListener =
new ForCaptureEnabledTraceListener();
protected final ForAccessRecorderListener recorderListener = new ForAccessRecorderListener();
protected final ForVisibilityListener displayListener = new ForVisibilityListener();
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected AddressSetView visible;
public DebuggerReadsMemoryTrait(PluginTool tool, Plugin plugin, ComponentProvider provider) {
this.tool = tool;
this.plugin = plugin;
this.provider = provider;
}
protected boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getView(), b.getView())) {
return false; // Subsumes trace
}
if (!Objects.equals(a.getTime(), b.getTime())) {
return false;
}
if (!Objects.equals(a.getRecorder(), b.getRecorder())) {
return false;
}
return true;
}
protected void addNewTraceListener() {
if (current.getTrace() != null) {
current.getTrace().addListener(traceListener);
}
}
protected void removeOldTraceListener() {
if (current.getTrace() != null) {
current.getTrace().removeListener(traceListener);
}
}
protected void addNewRecorderListener() {
if (current.getRecorder() != null) {
current.getRecorder().addListener(recorderListener);
}
}
protected void removeOldRecorderListener() {
if (current.getRecorder() != null) {
current.getRecorder().removeListener(recorderListener);
}
}
public void goToCoordinates(DebuggerCoordinates coordinates) {
if (sameCoordinates(current, coordinates)) {
current = coordinates;
return;
}
boolean doTraceListener = !Objects.equals(current.getTrace(), coordinates.getTrace());
boolean doRecListener = !Objects.equals(current.getRecorder(), coordinates.getRecorder());
if (doTraceListener) {
removeOldTraceListener();
}
if (doRecListener) {
removeOldRecorderListener();
}
current = coordinates;
if (doTraceListener) {
addNewTraceListener();
}
if (doRecListener) {
addNewRecorderListener();
}
doAutoRead();
// NB. provider should call contextChanged, updating actions
}
protected void doAutoRead() {
autoSpec.readMemory(tool, current, visible).exceptionally(ex -> {
Msg.error(this, "Could not auto-read memory: " + ex);
return null;
});
}
public MultiStateDockingAction<AutoReadMemorySpec> installAutoReadAction() {
actionAutoRead = DebuggerAutoReadMemoryAction.builder(plugin)
.onAction(this::activatedAutoRead)
.onActionStateChanged(this::changedAutoReadMemory)
.buildAndInstallLocal(provider);
actionAutoRead.setCurrentActionStateByUserData(defaultAutoSpec);
return actionAutoRead;
}
protected void activatedAutoRead(ActionContext ctx) {
doAutoRead();
}
protected void changedAutoReadMemory(ActionState<AutoReadMemorySpec> newState,
EventTrigger trigger) {
doSetAutoRead(newState.getUserData());
}
protected void doSetAutoRead(AutoReadMemorySpec spec) {
this.autoSpec = spec;
if (visible != null) {
doAutoRead();
}
}
public DockingAction installCaptureSelectedAction() {
actionCaptureSelected = new CaptureSelectedMemoryAction();
provider.addLocalAction(actionCaptureSelected);
return actionCaptureSelected;
}
public AddressSetDisplayListener getDisplayListener() {
return displayListener;
}
public void writeConfigState(SaveState saveState) {
CONFIG_STATE_HANDLER.writeConfigState(this, saveState);
}
public void readConfigState(SaveState saveState) {
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
actionAutoRead.setCurrentActionStateByUserData(autoSpec);
}
public void setAutoSpec(AutoReadMemorySpec autoSpec) {
// TODO: What if action == null?
actionAutoRead.setCurrentActionStateByUserData(autoSpec);
}
public AutoReadMemorySpec getAutoSpec() {
return autoSpec;
}
protected abstract AddressSetView getSelection();
}

View File

@ -20,6 +20,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
import ghidra.framework.plugintool.Plugin;
public interface DebuggerTrackLocationAction extends TrackLocationAction {
// TODO: Update the action when new specs enter the class path?
static MultiStateActionBuilder<LocationTrackingSpec> builder(Plugin owner) {
MultiStateActionBuilder<LocationTrackingSpec> builder = TrackLocationAction.builder(owner);

View File

@ -0,0 +1,305 @@
/* ###
* 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.action;
import java.awt.Color;
import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.support.BackgroundColorModel;
import docking.widgets.fieldpanel.support.FieldSelection;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec;
import ghidra.app.plugin.core.debug.gui.colors.*;
import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgroundColorManager.ColoredFieldSelection;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerTrackedRegisterListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
import ghidra.trace.model.Trace.TraceStackChangeType;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceAddressSpace;
public class DebuggerTrackLocationTrait {
protected static final AutoConfigState.ClassHandler<DebuggerTrackLocationTrait> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(DebuggerTrackLocationTrait.class, MethodHandles.lookup());
protected class ForTrackingListener extends TraceDomainObjectListener {
public ForTrackingListener() {
listenFor(TraceMemoryBytesChangeType.CHANGED, this::registersChanged);
listenFor(TraceStackChangeType.CHANGED, this::stackChanged);
}
private void registersChanged(TraceAddressSpace space, TraceAddressSnapRange range,
byte[] oldValue, byte[] newValue) {
if (current.getView() == null || spec == null) {
// Should only happen during transitional times, if at all.
return;
}
if (!spec.affectedByRegisterChange(space, range, current)) {
return;
}
doTrack();
}
private void stackChanged(TraceStack stack) {
if (current.getView() == null || spec == null) {
// Should only happen during transitional times, if at all.
return;
}
if (!spec.affectedByStackChange(stack, current)) {
return;
}
doTrack();
}
}
// TODO: This may already be deprecated....
protected class ColorModel extends DebuggerTrackedRegisterBackgroundColorModel {
public ColorModel() {
super(plugin);
}
@Override
protected ProgramLocation getTrackedLocation() {
return trackedLocation;
}
}
protected class ListingColorModel
extends DebuggerTrackedRegisterListingBackgroundColorModel {
public ListingColorModel(ListingPanel listingPanel) {
super(plugin, listingPanel);
}
@Override
protected ProgramLocation getTrackedLocation() {
return trackedLocation;
}
}
protected class TrackSelectionGenerator implements SelectionGenerator {
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS)
private Color trackingColor;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
public TrackSelectionGenerator() {
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
}
@Override
public void addSelections(BigInteger layoutIndex, SelectionTranslator translator,
List<ColoredFieldSelection> selections) {
if (trackedLocation == null || trackingColor == null) {
return;
}
FieldSelection fieldSel =
translator.convertAddressToField(trackedLocation.getAddress());
selections.add(new ColoredFieldSelection(fieldSel, trackingColor));
}
}
protected MultiStateDockingAction<LocationTrackingSpec> action;
private final LocationTrackingSpec defaultSpec =
LocationTrackingSpec.fromConfigName(PCLocationTrackingSpec.CONFIG_NAME);
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
protected LocationTrackingSpec spec = defaultSpec;
protected final PluginTool tool;
protected final Plugin plugin;
protected final ComponentProvider provider;
protected final ForTrackingListener listener = new ForTrackingListener();
protected final ColorModel colorModel;
protected final TrackSelectionGenerator selectionGenerator;
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected ProgramLocation trackedLocation;
public DebuggerTrackLocationTrait(PluginTool tool, Plugin plugin, ComponentProvider provider) {
this.tool = tool;
this.plugin = plugin;
this.provider = provider;
this.colorModel = new ColorModel();
this.selectionGenerator = new TrackSelectionGenerator();
}
public BackgroundColorModel getBackgroundColorModel() {
return colorModel;
}
public ListingBackgroundColorModel createListingBackgroundColorModel(
ListingPanel listingPanel) {
return new ListingColorModel(listingPanel);
}
public SelectionGenerator getSelectionGenerator() {
return selectionGenerator;
}
protected boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getView(), b.getView())) {
return false; // Subsumes trace
}
if (!Objects.equals(a.getTime(), b.getTime())) {
return false;
}
if (!Objects.equals(a.getThread(), b.getThread())) {
return false;
}
if (!Objects.equals(a.getFrame(), b.getFrame())) {
return false;
}
return true;
}
public void setSpec(LocationTrackingSpec spec) {
// TODO: What if action == null?
action.setCurrentActionStateByUserData(spec);
}
public LocationTrackingSpec getSpec() {
return spec;
}
public ProgramLocation getTrackedLocation() {
return trackedLocation;
}
public MultiStateDockingAction<LocationTrackingSpec> installAction() {
// TODO: Add "other" option, and present most-recent in menu, too
// TODO: "other" as in arbitrary expression?
// Only those applicable to the current thread's registers, though.
action = DebuggerTrackLocationAction.builder(plugin)
.onAction(this::clickedSpecButton)
.onActionStateChanged(this::clickedSpecMenu)
.buildAndInstallLocal(provider);
action.setCurrentActionStateByUserData(defaultSpec);
return action;
}
protected void clickedSpecButton(ActionContext ctx) {
doTrack();
}
protected void clickedSpecMenu(ActionState<LocationTrackingSpec> newState,
EventTrigger trigger) {
doSetSpec(newState.getUserData());
}
protected void doSetSpec(LocationTrackingSpec spec) {
if (this.spec != spec) {
this.spec = spec;
specChanged();
}
doTrack();
}
protected ProgramLocation computeTrackedLocation() {
// Change of register values (for current frame)
// Change of stack pc (for current frame)
// Change of current view (if not caused by goTo)
// Change of current thread
// Change of current snap
// Change of current frame
// Change of tracking settings
DebuggerCoordinates cur = current;
TraceThread thread = cur.getThread();
if (thread == null || spec == null) {
return null;
}
// NB: view's snap may be forked for emulation
Address address = spec.computeTraceAddress(tool, cur, current.getView().getSnap());
return address == null ? null : new ProgramLocation(current.getView(), address);
}
protected void doTrack() {
trackedLocation = computeTrackedLocation();
locationTracked();
}
protected void addNewListeners() {
Trace trace = current.getTrace();
if (trace != null) {
trace.addListener(listener);
}
}
protected void removeOldListeners() {
Trace trace = current.getTrace();
if (trace != null) {
trace.removeListener(listener);
}
}
public void goToCoordinates(DebuggerCoordinates coordinates) {
if (sameCoordinates(current, coordinates)) {
current = coordinates;
return;
}
boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace());
if (doListeners) {
removeOldListeners();
}
current = coordinates;
if (doListeners) {
addNewListeners();
}
doTrack();
}
public void writeConfigState(SaveState saveState) {
CONFIG_STATE_HANDLER.writeConfigState(this, saveState);
}
public void readConfigState(SaveState saveState) {
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
action.setCurrentActionStateByUserData(spec);
}
protected void locationTracked() {
// Listener method
}
protected void specChanged() {
// Listener method
}
}

View File

@ -0,0 +1,78 @@
/* ###
* 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.colors;
import java.awt.Color;
import java.math.BigInteger;
import docking.widgets.fieldpanel.support.BackgroundColorModel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
public abstract class DebuggerTrackedRegisterBackgroundColorModel implements BackgroundColorModel {
protected Color defaultBackgroundColor;
protected Program program;
protected AddressIndexMap addressIndexMap;
// TODO: Seems I should at least rename this option
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS)
Color trackingColor;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
public DebuggerTrackedRegisterBackgroundColorModel(Plugin plugin) {
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
}
/**
* Get the location which is to be highlighted as "tracked."
*
* @return the location
*/
protected abstract ProgramLocation getTrackedLocation();
@Override
public Color getBackgroundColor(BigInteger index) {
if (addressIndexMap == null) {
return defaultBackgroundColor;
}
ProgramLocation loc = getTrackedLocation();
if (loc == null) {
return defaultBackgroundColor;
}
Address address = addressIndexMap.getAddress(index);
if (!loc.getAddress().equals(address)) {
return defaultBackgroundColor;
}
return trackingColor;
}
@Override
public Color getDefaultBackgroundColor() {
return defaultBackgroundColor;
}
@Override
public void setDefaultBackgroundColor(Color c) {
defaultBackgroundColor = c;
}
}

View File

@ -0,0 +1,269 @@
/* ###
* 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.colors;
import java.awt.Color;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
import docking.widgets.fieldpanel.internal.*;
import docking.widgets.fieldpanel.support.*;
import ghidra.util.ColorUtils.ColorBlender;
public class MultiSelectionBlendedLayoutBackgroundColorManager
implements LayoutBackgroundColorManager {
public static class ColoredFieldSelection {
FieldSelection selection;
Color color;
public ColoredFieldSelection(FieldSelection selection, Color color) {
this.selection = Objects.requireNonNull(selection);
this.color = Objects.requireNonNull(color);
}
public ColoredFieldSelection intersect(BigInteger index) {
return new ColoredFieldSelection(selection.intersect(index), color);
}
public boolean isTotal(BigInteger index) {
return selection.getNumRanges() == 1 &&
selection.getFieldRange(0).containsEntirely(index);
}
public boolean isEmpty() {
return selection.isEmpty();
}
public boolean contains(FieldLocation loc) {
return selection.contains(loc);
}
public boolean containsEntirely(FieldRange range) {
return selection.containsEntirely(range);
}
public boolean excludesEntirely(FieldRange range) {
return selection.excludesEntirely(range);
}
}
public static LayoutBackgroundColorManager getLayoutColorMap(BigInteger index,
Collection<ColoredFieldSelection> selections, Color backgroundColor,
boolean isBackgroundDefault) {
List<ColoredFieldSelection> intersections =
selections.stream().map(cfs -> cfs.intersect(index)).collect(Collectors.toList());
List<ColoredFieldSelection> empties =
intersections.stream().filter(cfs -> cfs.isEmpty()).collect(Collectors.toList());
// Check for completely empty, i.e., use the background
if (empties.size() == intersections.size()) {
return new EmptyLayoutBackgroundColorManager(backgroundColor);
}
ColorBlender blender = new ColorBlender();
if (!isBackgroundDefault) {
blender.add(backgroundColor);
}
List<ColoredFieldSelection> totals =
intersections.stream().filter(cfs -> cfs.isTotal(index)).collect(Collectors.toList());
if (totals.size() + empties.size() == intersections.size()) {
totals.forEach(cfs -> blender.add(cfs.color));
return new EmptyLayoutBackgroundColorManager(blender.getColor(backgroundColor));
}
FieldLocation startOfLine = new FieldLocation(index, 0, 0, 0);
FieldLocation endOfLine =
new FieldLocation(index, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
for (ColoredFieldSelection cfs : intersections) {
if (cfs.contains(startOfLine)) {
blender.add(cfs.color);
}
}
ColorBlender blenderR = new ColorBlender();
if (!isBackgroundDefault) {
blenderR.add(backgroundColor);
}
for (ColoredFieldSelection cfs : intersections) {
if (cfs.contains(endOfLine)) {
blenderR.add(cfs.color);
}
}
return new MultiSelectionBlendedLayoutBackgroundColorManager(index, intersections,
backgroundColor,
blender.getColor(backgroundColor), blenderR.getColor(backgroundColor));
}
public static class MultiSelectionBlendedFieldBackgroundColorManager
implements FieldBackgroundColorManager {
private final BigInteger index;
private final int fieldNum;
private final MultiSelectionBlendedLayoutBackgroundColorManager layoutSelection;
private final List<ColoredFieldSelection> selections;
private final Color backgroundColor;
public MultiSelectionBlendedFieldBackgroundColorManager(BigInteger index, int fieldNum,
MultiSelectionBlendedLayoutBackgroundColorManager layoutSelection,
List<ColoredFieldSelection> selections, Color backgroundColor) {
this.index = index;
this.fieldNum = fieldNum;
this.layoutSelection = layoutSelection;
this.selections = selections;
this.backgroundColor = backgroundColor;
}
@Override
public Color getBackgroundColor() {
return layoutSelection.dontPaintBg(backgroundColor);
}
@Override
public List<Highlight> getSelectionHighlights(int row) {
FieldLocation start = new FieldLocation(index, fieldNum, row, 0);
FieldLocation end = new FieldLocation(index, fieldNum, row + 1, 0);
FieldRange range = new FieldRange(start, end);
List<Highlight> highlights = new ArrayList<>();
for (ColoredFieldSelection cfs : selections) {
FieldSelection intersect = cfs.selection.intersect(range);
for (int i = 0; i < intersect.getNumRanges(); i++) {
FieldRange rng = intersect.getFieldRange(i);
int min = rng.getStart().col;
int max = rng.getEnd().row == row ? range.getEnd().col : Integer.MAX_VALUE;
highlights.add(new Highlight(min, max, cfs.color));
}
}
return highlights;
}
@Override
public Color getPaddingColor(int padIndex) {
return layoutSelection.getPaddingColor(padIndex);
}
}
private final BigInteger index;
private final List<ColoredFieldSelection> selections;
private final Color backgroundColor;
private final Color leftBorderColor;
private final Color rightBorderColor;
public MultiSelectionBlendedLayoutBackgroundColorManager(BigInteger index,
List<ColoredFieldSelection> selections, Color backgroundColor, Color leftBorderColor,
Color rightBorderColor) {
this.index = index;
this.selections = selections;
this.backgroundColor = backgroundColor;
this.leftBorderColor = leftBorderColor;
this.rightBorderColor = rightBorderColor;
}
@Override
public Color getBackgroundColor() {
return backgroundColor;
}
protected Color dontPaintBg(Color color) {
return color == backgroundColor ? null : color;
}
@Override
public Color getPaddingColor(int padIndex) {
if (padIndex == 0) {
return dontPaintBg(leftBorderColor);
}
if (padIndex == -1) {
return dontPaintBg(rightBorderColor);
}
return dontPaintBg(getPaddingColorBetweenFields(padIndex));
}
protected Color getPaddingColorBetweenFields(int padIndex) {
FieldLocation start =
new FieldLocation(index, padIndex - 1, Integer.MAX_VALUE, Integer.MAX_VALUE);
FieldLocation end = new FieldLocation(index, padIndex, 0, 0);
FieldRange range = new FieldRange(start, end);
ColorBlender blender = new ColorBlender();
for (ColoredFieldSelection cfs : selections) {
if (cfs.containsEntirely(range)) {
blender.add(cfs.color);
}
}
return blender.getColor(backgroundColor);
}
protected boolean excludedByAll(FieldRange range) {
for (ColoredFieldSelection cfs : selections) {
if (!cfs.excludesEntirely(range)) {
return false;
}
}
return true;
}
protected Color computeSolidColor(FieldRange range) {
ColorBlender blender = new ColorBlender();
for (ColoredFieldSelection cfs : selections) {
if (cfs.containsEntirely(range)) {
blender.add(cfs.color);
continue;
}
if (cfs.excludesEntirely(range)) {
// good, but don't add color
continue;
}
// Field is not a solid color
return null;
}
return blender.getColor(backgroundColor);
}
@Override
public FieldBackgroundColorManager getFieldBackgroundColorManager(int fieldNum) {
FieldLocation start = new FieldLocation(index, fieldNum, 0, 0);
FieldLocation end = new FieldLocation(index, fieldNum + 1, 0, 0);
FieldRange range = new FieldRange(start, end);
if (excludedByAll(range)) {
return EmptyFieldBackgroundColorManager.EMPTY_INSTANCE;
}
Color solidColor = computeSolidColor(range);
if (solidColor != null) {
return new FullySelectedFieldBackgroundColorManager(solidColor);
}
// Could separate out solid colors, but at the expense of constructing a collection....
// Leave fieldBackgroudColor the same as backgroundColor, and pass all selections in
return new MultiSelectionBlendedFieldBackgroundColorManager(index, fieldNum, this,
selections, backgroundColor);
}
@Override
public Color getBackgroundColor(FieldLocation location) {
ColorBlender blender = new ColorBlender();
for (ColoredFieldSelection cfs : selections) {
if (cfs.contains(location)) {
blender.add(cfs.color);
}
}
return blender.getColor(backgroundColor);
}
}

View File

@ -0,0 +1,26 @@
/* ###
* 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.colors;
import java.math.BigInteger;
import java.util.List;
import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgroundColorManager.ColoredFieldSelection;
public interface SelectionGenerator {
void addSelections(BigInteger layoutIndex, SelectionTranslator translator,
List<ColoredFieldSelection> selections);
}

View File

@ -0,0 +1,31 @@
/* ###
* 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.colors;
import docking.widgets.fieldpanel.support.FieldSelection;
import ghidra.program.model.address.*;
public interface SelectionTranslator {
AddressSetView convertFieldToAddress(FieldSelection fieldSelection);
FieldSelection convertAddressToField(AddressSetView addresses);
FieldSelection convertAddressToField(AddressRange range);
default FieldSelection convertAddressToField(Address address) {
return convertAddressToField(new AddressRangeImpl(address, address));
}
}

View File

@ -28,7 +28,7 @@ import docking.ActionContext;
import docking.action.MenuData;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.AbstractCodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
@ -46,7 +46,6 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.program.TraceProgramView;
@ -89,7 +88,8 @@ import utilities.util.SuppressableCallback.Suppression;
servicesProvided = {
DebuggerListingService.class,
})
public class DebuggerListingPlugin extends CodeBrowserPlugin implements DebuggerListingService {
public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerListingProvider>
implements DebuggerListingService {
private static final String KEY_CONNECTED_PROVIDER = "connectedProvider";
private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount";
private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider";
@ -124,14 +124,14 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
//private GoToService goToService;
@AutoServiceConsumed
private ProgramManager programManager;
// NOTE: ListingPlugin doesn't extend AbstractDebuggerPlugin
// NOTE: This plugin doesn't extend AbstractDebuggerPlugin
@SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring;
@AutoOptionDefined( //
name = OPTION_NAME_COLORS_STALE_MEMORY, //
@AutoOptionDefined(
name = OPTION_NAME_COLORS_STALE_MEMORY,
description = "Color of memory addresses whose content is not known in the view's " +
"snap", //
"snap",
help = @HelpInfo(anchor = "colors"))
private Color staleMemoryColor = DEFAULT_COLOR_BACKGROUND_STALE;
@AutoOptionDefined( //
@ -142,7 +142,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
private Color errorMemoryColor = DEFAULT_COLOR_BACKGROUND_ERROR;
// NOTE: Static programs are marked via markerSet. Dynamic are marked via custom color model
@AutoOptionDefined( //
name = OPTION_NAME_COLORS_REGISTER_MARKERS, //
name = OPTION_NAME_COLORS_TRACKING_MARKERS, //
description = "Background color for locations referred to by a tracked register", //
help = @HelpInfo(anchor = "colors"))
private Color trackingColor = DEFAULT_COLOR_REGISTER_MARKERS;
@ -162,15 +162,6 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
createActions();
}
protected DebuggerListingProvider getConnectedProvider() {
return (DebuggerListingProvider) connectedProvider;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected List<DebuggerListingProvider> getDisconnectedProviders() {
return (List) disconnectedProviders;
}
@Override
protected DebuggerListingProvider createProvider(FormatManager formatManager,
boolean isConnected) {
@ -184,7 +175,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
public DebuggerListingProvider createListingIfMissing(LocationTrackingSpec spec,
boolean followsCurrentThread) {
synchronized (disconnectedProviders) {
for (DebuggerListingProvider provider : getDisconnectedProviders()) {
for (DebuggerListingProvider provider : disconnectedProviders) {
if (provider.getTrackingSpec() != spec) {
continue;
}
@ -201,11 +192,6 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
}
}
@Override
public DebuggerListingProvider createNewDisconnectedProvider() {
return (DebuggerListingProvider) super.createNewDisconnectedProvider();
}
@Override
protected void viewChanged(AddressSetView addrSet) {
TraceProgramView view = current.getView();
@ -239,6 +225,12 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
firePluginEvent(new TraceSelectionPluginEvent(getName(), selection, view));
}
@Override
public void highlightChanged(CodeViewerProvider codeViewerProvider,
ProgramSelection highlight) {
// TODO Nothing, yet
}
protected boolean heedLocationEvent(ProgramLocationPluginEvent ev) {
PluginEvent trigger = ev.getTriggerEvent();
/*Msg.debug(this, "Location event");
@ -267,12 +259,11 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
@Override
public void processEvent(PluginEvent event) {
// Do not call super here. I intend to prevent it from seeing events.
if (event instanceof ProgramLocationPluginEvent) {
cbProgramLocationEvents.invoke(() -> {
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
if (heedLocationEvent(ev)) {
getConnectedProvider().staticProgramLocationChanged(ev.getLocation());
connectedProvider.staticProgramLocationChanged(ev.getLocation());
}
});
}
@ -291,6 +282,9 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
}
if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
if (current.getTrace() == ev.getTrace()) {
current = DebuggerCoordinates.NOWHERE;
}
allProviders(p -> p.traceClosed(ev.getTrace()));
}
// TODO: Sync selection and highlights?
@ -308,34 +302,29 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
}
protected void allProviders(Consumer<DebuggerListingProvider> action) {
action.accept(getConnectedProvider());
for (DebuggerListingProvider provider : getDisconnectedProviders()) {
action.accept(connectedProvider);
for (DebuggerListingProvider provider : disconnectedProviders) {
action.accept(provider);
}
}
@Override
protected void programClosed(Program program) {
// Immaterial
}
@AutoServiceConsumed
public void setTraceManager(DebuggerTraceManagerService traceManager) {
DebuggerListingProvider provider = getConnectedProvider();
DebuggerListingProvider provider = connectedProvider;
if (provider == null || traceManager == null) {
return;
}
provider.coordinatesActivated(traceManager.getCurrent());
provider.coordinatesActivated(current = traceManager.getCurrent());
}
@Override
public void setTrackingSpec(LocationTrackingSpec spec) {
getConnectedProvider().setTrackingSpec(spec);
connectedProvider.setTrackingSpec(spec);
}
@Override
public void setCurrentSelection(ProgramSelection selection) {
getConnectedProvider().setSelection(selection);
connectedProvider.setSelection(selection);
}
@Override
@ -345,7 +334,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
return false;
}
//cbGoTo.invoke(() -> {
DebuggerListingProvider provider = getConnectedProvider();
DebuggerListingProvider provider = connectedProvider;
provider.doSyncToStatic(location);
provider.doCheckCurrentModuleMissing();
//});
@ -354,7 +343,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
@Override
public boolean goTo(Address address, boolean centerOnScreen) {
TraceProgramView view = getConnectedProvider().current.getView();
TraceProgramView view = connectedProvider.current.getView();
if (view == null) {
return false;
}
@ -392,17 +381,17 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
@Override
public void writeDataState(SaveState saveState) {
SaveState connectedProviderState = new SaveState();
getConnectedProvider().writeDataState(connectedProviderState);
connectedProvider.writeDataState(connectedProviderState);
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
/**
* Arrange the follows ones first, so that we reload them into corresponding providers
* restored from config state
*/
List<DebuggerListingProvider> disconnected = getDisconnectedProviders().stream()
List<DebuggerListingProvider> disconnected = disconnectedProviders.stream()
.filter(p -> p.isFollowsCurrentThread())
.collect(Collectors.toList());
for (DebuggerListingProvider p : getDisconnectedProviders()) {
for (DebuggerListingProvider p : disconnectedProviders) {
if (!disconnected.contains(p)) {
disconnected.add(p);
}
@ -419,8 +408,8 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
}
protected void ensureProviders(int count, boolean followCurrentThread, SaveState configState) {
while (getDisconnectedProviders().size() < count) {
int index = getDisconnectedProviders().size();
while (disconnectedProviders.size() < count) {
int index = disconnectedProviders.size();
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
DebuggerListingProvider provider = createNewDisconnectedProvider();
provider.setFollowsCurrentThread(false);
@ -442,13 +431,13 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
if (connectedProviderElement != null) {
SaveState connectedProviderState = new SaveState(connectedProviderElement);
getConnectedProvider().readDataState(connectedProviderState);
connectedProvider.readDataState(connectedProviderState);
}
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
ensureProviders(disconnectedCount, false, saveState);
List<DebuggerListingProvider> disconnected = getDisconnectedProviders();
List<DebuggerListingProvider> disconnected = disconnectedProviders;
for (int index = 0; index < disconnectedCount; index++) {
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
Element providerElement = saveState.getXmlElement(stateName);
@ -463,10 +452,10 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
@Override
public void writeConfigState(SaveState saveState) {
SaveState connectedProviderState = new SaveState();
getConnectedProvider().writeConfigState(connectedProviderState);
connectedProvider.writeConfigState(connectedProviderState);
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
List<DebuggerListingProvider> disconnected = getDisconnectedProviders().stream()
List<DebuggerListingProvider> disconnected = disconnectedProviders.stream()
.filter(p -> p.isFollowsCurrentThread())
.collect(Collectors.toList());
int disconnectedCount = disconnected.size();
@ -485,7 +474,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
if (connectedProviderElement != null) {
SaveState connectedProviderState = new SaveState(connectedProviderElement);
getConnectedProvider().readConfigState(connectedProviderState);
connectedProvider.readConfigState(connectedProviderState);
}
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);

View File

@ -16,49 +16,46 @@
package ghidra.app.plugin.core.debug.gui.listing;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.ICON_REGISTER_MARKER;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS;
import java.awt.Color;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JLabel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.collections4.ComparatorUtils;
import org.apache.commons.lang3.StringUtils;
import org.jdom.Element;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.*;
import docking.menu.ActionState;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.nav.ListingPanelContainer;
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.plugin.core.debug.utils.*;
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.app.plugin.core.exporter.ExporterDialog;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingDisplayListener;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.framework.model.DomainFile;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState;
@ -67,31 +64,24 @@ import ghidra.framework.plugintool.AutoConfigState;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.*;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceVariableSnapProgramView;
import ghidra.trace.model.stack.TraceStack;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.*;
import ghidra.util.HTMLUtilities;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.*;
import utilities.util.SuppressableCallback;
import utilities.util.SuppressableCallback.Suppression;
public class DebuggerListingProvider extends CodeViewerProvider implements ListingDisplayListener {
public class DebuggerListingProvider extends CodeViewerProvider {
private static final AutoConfigState.ClassHandler<DebuggerListingProvider> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(DebuggerListingProvider.class, MethodHandles.lookup());
@ -116,50 +106,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
return true;
}
protected class CaptureSelectedMemoryAction extends AbstractCaptureSelectedMemoryAction {
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
public CaptureSelectedMemoryAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP));
addLocalAction(this);
setEnabled(false);
}
@Override
public void actionPerformed(ActionContext context) {
if (!current.isAliveAndReadsPresent()) {
return;
}
Trace trace = current.getTrace();
TraceRecorder recorder = current.getRecorder();
BackgroundUtils.async(plugin.getTool(), trace, NAME, true, true, false,
(__, monitor) -> recorder.captureProcessMemory(
getListingPanel().getProgramSelection(), monitor, false));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!current.isAliveAndReadsPresent()) {
return false;
}
TraceRecorder recorder = current.getRecorder();
ProgramSelection selection = getSelection();
if (selection == null || selection.isEmpty()) {
return false;
}
// TODO: Either allow partial, or provide action to intersect with accessible
if (!recorder.getAccessibleProcessMemory().contains(selection)) {
return false;
}
return true;
}
public void updateEnabled(ActionContext context) {
setEnabled(isEnabledForContext(context));
}
}
protected class SyncToStaticListingAction extends AbstractSyncToStaticListingAction {
public SyncToStaticListingAction() {
super(plugin);
@ -190,19 +136,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
protected class TrackedLocationBackgroundColorModel
extends DebuggerTrackedRegisterListingBackgroundColorModel {
public TrackedLocationBackgroundColorModel(DebuggerListingPlugin plugin,
ListingPanel listingPanel) {
super(plugin, listingPanel);
}
@Override
protected ProgramLocation getTrackedLocation() {
return trackedLocation;
}
}
protected class MarkerSetChangeListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
@ -233,89 +166,41 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
protected class ForTrackingAndLabelingTraceListener extends TraceDomainObjectListener {
private final AsyncDebouncer<Void> updateLabelDebouncer =
new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100);
public ForTrackingAndLabelingTraceListener() {
updateLabelDebouncer
.addListener(__ -> Swing.runIfSwingOrRunLater(() -> doUpdateLabel()));
listenFor(TraceSnapshotChangeType.ADDED, this::snapshotAdded);
listenFor(TraceMemoryBytesChangeType.CHANGED, this::registersChanged);
listenFor(TraceStackChangeType.CHANGED, this::stackChanged);
listenFor(TraceMemoryRegionChangeType.ADDED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.LIFESPAN_CHANGED, this::regionChanged);
listenFor(TraceMemoryRegionChangeType.DELETED, this::regionChanged);
listenFor(TraceModuleChangeType.CHANGED, this::moduleChanged);
listenFor(TraceModuleChangeType.LIFESPAN_CHANGED, this::moduleChanged);
listenFor(TraceModuleChangeType.DELETED, this::moduleChanged);
listenFor(TraceSectionChangeType.ADDED, this::sectionChanged);
listenFor(TraceSectionChangeType.CHANGED, this::sectionChanged);
listenFor(TraceSectionChangeType.DELETED, this::sectionChanged);
protected class ForListingGoToTrait extends DebuggerGoToTrait {
public ForListingGoToTrait() {
super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin,
DebuggerListingProvider.this);
}
private void snapshotAdded(TraceSnapshot snapshot) {
actionCaptureSelectedMemory.updateEnabled(null);
}
private void registersChanged(TraceAddressSpace space, TraceAddressSnapRange range,
byte[] oldValue, byte[] newValue) {
if (current.getView() == null || trackingSpec == null) {
// Should only happen during transitional times, if at all.
return;
}
if (!trackingSpec.affectedByRegisterChange(space, range, current)) {
return;
}
doTrackSpec();
}
private void stackChanged(TraceStack stack) {
if (current.getView() == null || trackingSpec == null) {
// Should only happen during transitional times, if at all.
return;
}
if (!trackingSpec.affectedByStackChange(stack, current)) {
return;
}
doTrackSpec();
}
private void doUpdateLabel() {
updateLocationLabel();
}
private void regionChanged(TraceMemoryRegion region) {
updateLabelDebouncer.contact(null);
}
private void moduleChanged(TraceModule module) {
updateLabelDebouncer.contact(null);
}
private void sectionChanged(TraceSection section) {
updateLabelDebouncer.contact(null);
}
}
protected class ForAccessRecorderListener implements TraceRecorderListener {
@Override
public void processMemoryAccessibilityChanged(TraceRecorder recorder) {
Swing.runIfSwingOrRunLater(() -> {
actionCaptureSelectedMemory.updateEnabled(null);
});
protected boolean goToAddress(Address address) {
return getListingPanel().goTo(address);
}
}
private final LocationTrackingSpec defaultTrackingSpec =
LocationTrackingSpec.fromConfigName(PCLocationTrackingSpec.CONFIG_NAME);
private final AutoReadMemorySpec defaultReadMemorySpec =
AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
protected class ForListingTrackingTrait extends DebuggerTrackLocationTrait {
public ForListingTrackingTrait() {
super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin,
DebuggerListingProvider.this);
}
@Override
protected void locationTracked() {
doGoToTracked();
}
}
protected class ForListingReadsMemoryTrait extends DebuggerReadsMemoryTrait {
public ForListingReadsMemoryTrait() {
super(DebuggerListingProvider.this.tool, DebuggerListingProvider.this.plugin,
DebuggerListingProvider.this);
}
@Override
protected AddressSetView getSelection() {
return DebuggerListingProvider.this.getSelection();
}
}
private final DebuggerListingPlugin plugin;
@ -334,46 +219,37 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS)
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS)
private Color trackingColor;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected AddressSetView visible;
protected TraceRecorder currentRecorder;
protected ProgramLocation trackedLocation;
protected Program markedProgram;
protected Address markedAddress;
protected MarkerSet trackingMarker;
protected CaptureSelectedMemoryAction actionCaptureSelectedMemory;
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
protected DockingAction actionGoTo;
protected SyncToStaticListingAction actionSyncToStaticListing;
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionCaptureSelectedMemory;
protected DockingAction actionExportView;
protected DockingAction actionOpenProgram;
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
protected final DebuggerGoToDialog goToDialog;
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
protected LocationTrackingSpec trackingSpec = defaultTrackingSpec;
@AutoConfigStateField
protected boolean syncToStaticListing;
@AutoConfigStateField
protected boolean followsCurrentThread = true;
@AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class)
protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec;
// TODO: followsCurrentSnap
// TODO: followsCurrentSnap?
protected ForTrackingAndLabelingTraceListener forTrackingTraceListener =
new ForTrackingAndLabelingTraceListener();
protected ForAccessRecorderListener forAccessRecorderListener = new ForAccessRecorderListener();
protected DebuggerGoToTrait goToTrait;
protected ForListingTrackingTrait trackingTrait;
protected ForListingReadsMemoryTrait readsMemTrait;
protected final JLabel locationLabel = new JLabel();
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
protected final MultiBlendedListingBackgroundColorModel colorModel;
protected final MarkerSetChangeListener markerChangeListener = new MarkerSetChangeListener();
@ -392,11 +268,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
this.plugin = plugin;
this.isMainListing = isConnected;
goToDialog = new DebuggerGoToDialog(this);
goToTrait = new ForListingGoToTrait();
trackingTrait = new ForListingTrackingTrait();
readsMemTrait = new ForListingReadsMemoryTrait();
ListingPanel listingPanel = getListingPanel();
colorModel = new MultiBlendedListingBackgroundColorModel();
colorModel.addModel(new TrackedLocationBackgroundColorModel(plugin, listingPanel));
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel));
colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel));
listingPanel.setBackgroundColorModel(colorModel);
@ -408,12 +286,15 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
setVisible(true);
createActions();
doTrackSpec();
goToTrait.goToCoordinates(current);
trackingTrait.goToCoordinates(current);
readsMemTrait.goToCoordinates(current);
locationLabel.goToCoordinates(current);
// TODO: An icon to distinguish dynamic from static
//getComponent().setBorder(BorderFactory.createEmptyBorder());
addListingDisplayListener(this);
addDisplayListener(readsMemTrait.getDisplayListener());
this.setNorthComponent(locationLabel);
if (isConnected) {
@ -454,6 +335,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
return isMainListing;
}
@Override
public boolean isReadOnly() {
return current.isAliveAndPresent();
}
@Override
public String getWindowGroup() {
//TODO: Overriding this to align disconnected providers
@ -486,6 +372,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
saveState.putXmlElement("formatManager", formatManagerState.saveToXml());
CONFIG_STATE_HANDLER.writeConfigState(this, saveState);
trackingTrait.writeConfigState(saveState);
readsMemTrait.writeConfigState(saveState);
}
void readConfigState(SaveState saveState) {
@ -498,8 +386,9 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
trackingTrait.readConfigState(saveState);
readsMemTrait.readConfigState(saveState);
actionTrackLocation.setCurrentActionStateByUserData(trackingSpec);
if (isMainListing()) {
actionSyncToStaticListing.setSelected(syncToStaticListing);
followsCurrentThread = true;
@ -509,7 +398,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
actionFollowsCurrentThread.setSelected(followsCurrentThread);
updateBorder();
}
actionAutoReadMemory.setCurrentActionStateByUserData(autoReadMemorySpec);
}
@Override
@ -522,6 +410,28 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
super.addToTool();
}
@Override
protected CodeBrowserClipboardProvider newClipboardProvider() {
/**
* TODO: Ensure memory writes via paste are properly directed to the target process. In the
* meantime, just prevent byte pastes altogether. I cannot disable the clipboard altogether,
* because there are still excellent cases for copying from the dynamic listing, and we
* should still permit pastes of annotations.
*/
return new CodeBrowserClipboardProvider(tool, this) {
@Override
protected boolean pasteBytes(Transferable pasteData)
throws UnsupportedFlavorException, IOException {
return false;
}
@Override
protected boolean pasteByteString(String string) {
return false;
}
};
}
protected void updateMarkerServiceColorModel() {
colorModel.removeModel(markerServiceColorModel);
if (markerService != null) {
@ -564,7 +474,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
@AutoOptionConsumed(name = OPTION_NAME_COLORS_REGISTER_MARKERS)
@AutoOptionConsumed(name = OPTION_NAME_COLORS_TRACKING_MARKERS)
private void setTrackingColor(Color trackingColor) {
if (trackingMarker != null) {
trackingMarker.setMarkerColor(trackingColor);
@ -638,20 +548,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
protected void addNewListeners() {
Trace trace = current.getTrace();
if (trace != null) {
trace.addListener(forTrackingTraceListener);
}
}
protected void removeOldListeners() {
Trace trace = current.getTrace();
if (trace != null) {
trace.removeListener(forTrackingTraceListener);
}
}
@Override
protected void doSetProgram(Program newProgram) {
if (newProgram != null && newProgram != current.getView()) {
@ -666,25 +562,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
setSelection(new ProgramSelection());
super.doSetProgram(newProgram);
updateTitle();
updateLocationLabel();
}
protected void doSetRecorder(TraceRecorder newRecorder) {
if (currentRecorder == newRecorder) {
return;
}
if (currentRecorder != null) {
currentRecorder.removeListener(forAccessRecorderListener);
}
currentRecorder = newRecorder;
if (currentRecorder != null) {
currentRecorder.addListener(forAccessRecorderListener);
}
locationLabel.updateLabel();
}
protected String computeSubTitle() {
TraceProgramView view = current.getView();
List<String> parts = new ArrayList<>();
LocationTrackingSpec trackingSpec = trackingTrait == null ? null : trackingTrait.getSpec();
if (trackingSpec != null) {
String specTitle = trackingSpec.computeTitle(current);
if (specTitle != null) {
@ -703,104 +587,18 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
setSubTitle(computeSubTitle());
}
protected TraceSection getNearestSectionContaining(Address address) {
if (current.getView() == null) {
return null;
}
Trace trace = current.getTrace();
List<TraceSection> sections =
new ArrayList<>(trace.getModuleManager().getSectionsAt(current.getSnap(), address));
if (sections.isEmpty()) {
return null;
}
// TODO: DB's R-Tree could probably do this natively
sections.sort(ComparatorUtils.chainedComparator(List.of(
Comparator.comparing(s -> s.getRange().getMinAddress()),
Comparator.comparing(s -> -s.getRange().getLength()))));
return sections.get(sections.size() - 1);
}
protected TraceModule getNearestModuleContaining(Address address) {
if (current.getView() == null) {
return null;
}
Trace trace = current.getTrace();
List<TraceModule> modules =
new ArrayList<>(trace.getModuleManager().getModulesAt(current.getSnap(), address));
if (modules.isEmpty()) {
return null;
}
// TODO: DB's R-Tree could probably do this natively
modules.sort(ComparatorUtils.chainedComparator(List.of(
Comparator.comparing(m -> m.getRange().getMinAddress()),
Comparator.comparing(m -> -m.getRange().getLength()))));
return modules.get(modules.size() - 1);
}
protected TraceMemoryRegion getRegionContaining(Address address) {
if (current.getView() == null) {
return null;
}
Trace trace = current.getTrace();
return trace.getMemoryManager().getRegionContaining(current.getSnap(), address);
}
protected String computeLocationString() {
TraceProgramView view = current.getView();
if (view == null) {
return "";
}
ProgramLocation location = getListingPanel().getProgramLocation();
if (location == null) {
return "(nowhere)";
}
Address address = location.getAddress();
TraceSection section = getNearestSectionContaining(address);
if (section != null) {
return section.getModule().getName() + ":" + section.getName();
}
TraceModule module = getNearestModuleContaining(address);
if (module != null) {
return module.getName();
}
TraceMemoryRegion region = getRegionContaining(address);
if (region != null) {
return region.getName();
}
return "(unknown)";
}
protected void updateLocationLabel() {
locationLabel.setText(computeLocationString());
}
protected void createActions() {
// TODO: Add "other" option, and present most-recent in menu, too
// TODO: "other" as in arbitrary expression?
// Only those applicable to the current thread's registers, though.
actionTrackLocation = DebuggerTrackLocationAction.builder(plugin)
.onAction(this::activatedLocationTracking)
.onActionStateChanged(this::changedLocationTracking)
.buildAndInstallLocal(this);
actionTrackLocation.setCurrentActionStateByUserData(defaultTrackingSpec);
actionGoTo = GoToAction.builder(plugin)
.enabledWhen(ctx -> current.getView() != null)
.onAction(this::activatedGoTo)
.buildAndInstallLocal(this);
if (isMainListing()) {
actionSyncToStaticListing = new SyncToStaticListingAction();
}
else {
actionFollowsCurrentThread = new FollowsCurrentThreadAction();
}
actionCaptureSelectedMemory = new CaptureSelectedMemoryAction();
actionAutoReadMemory = DebuggerAutoReadMemoryAction.builder(plugin)
.onAction(this::activatedAutoReadMemory)
.onActionStateChanged(this::changedAutoReadMemory)
.buildAndInstallLocal(this);
actionAutoReadMemory.setCurrentActionStateByUserData(defaultReadMemorySpec);
actionGoTo = goToTrait.installAction();
actionTrackLocation = trackingTrait.installAction();
actionAutoReadMemory = readsMemTrait.installAutoReadAction();
actionCaptureSelectedMemory = readsMemTrait.installCaptureSelectedAction();
actionExportView = ExportTraceViewAction.builder(plugin)
.enabledWhen(ctx -> current.getView() != null)
@ -815,18 +613,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
contextChanged();
}
private void activatedGoTo(ActionContext context) {
TraceProgramView view = current.getView();
if (view == null) {
return;
}
Language language = view.getLanguage();
if (!(language instanceof SleighLanguage)) {
return;
}
goToDialog.show((SleighLanguage) language);
}
private void activatedExportView(ActionContext context) {
if (current.getView() == null) {
return;
@ -846,24 +632,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
ProgramManager.OPEN_CURRENT);
}
protected void activatedLocationTracking(ActionContext ctx) {
doTrackSpec();
}
protected void changedLocationTracking(ActionState<LocationTrackingSpec> newState,
EventTrigger trigger) {
doSetTrackingSpec(newState.getUserData());
}
protected void activatedAutoReadMemory(ActionContext ctx) {
doAutoReadMemory();
}
protected void changedAutoReadMemory(ActionState<AutoReadMemorySpec> newState,
EventTrigger trigger) {
doSetAutoReadMemory(newState.getUserData());
}
protected boolean isEffectivelyDifferent(ProgramLocation cur, ProgramLocation dest) {
if (Objects.equals(cur, dest)) {
return false;
@ -897,6 +665,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
@Override
public void stateChanged(ChangeEvent e) {
super.stateChanged(e);
ProgramLocation trackedLocation = trackingTrait.getTrackedLocation();
if (trackedLocation != null && !isEffectivelyDifferent(getLocation(), trackedLocation)) {
cbGoTo.invoke(() -> getListingPanel().goTo(trackedLocation, true));
}
@ -934,7 +703,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
@Override
public void programLocationChanged(ProgramLocation location, EventTrigger trigger) {
updateLocationLabel();
locationLabel.goToAddress(location.getAddress());
if (traceManager != null) {
location = ProgramLocationUtils.fixLocation(location, false);
}
@ -945,30 +714,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
public CompletableFuture<Boolean> goToSleigh(String spaceName, String expression) {
Language language = current.getView().getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalStateException("Current trace does not use Sleigh");
}
SleighLanguage slang = (SleighLanguage) language;
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceName);
if (space == null) {
throw new IllegalArgumentException("No such address space: " + spaceName);
}
SleighExpression expr = SleighProgramCompiler.compileExpression(slang, expression);
return goToSleigh(space, expr);
}
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, SleighExpression expression) {
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
CompletableFuture<byte[]> result = expression.evaluate(executor);
return result.thenApply(offset -> {
Address address = space.getAddress(
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
return getListingPanel().goTo(address);
});
}
protected void doSyncToStatic(ProgramLocation location) {
if (isSyncToStaticListing() && location != null) {
ProgramLocation staticLoc = mappingService.getStaticLocationFromDynamic(location);
@ -1102,19 +847,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
public void setTrackingSpec(LocationTrackingSpec spec) {
actionTrackLocation.setCurrentActionStateByUserData(spec);
}
protected void doSetTrackingSpec(LocationTrackingSpec spec) {
if (trackingSpec != spec) {
trackingSpec = spec;
updateTitle();
}
doTrackSpec();
trackingTrait.setSpec(spec);
}
public LocationTrackingSpec getTrackingSpec() {
return trackingSpec;
return trackingTrait.getSpec();
}
public void setSyncToStaticListing(boolean sync) {
@ -1162,41 +899,16 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
return followsCurrentThread;
}
protected void doSetAutoReadMemory(AutoReadMemorySpec spec) {
this.autoReadMemorySpec = spec;
if (visible != null) {
// HACK: Calling listener method directly
doAutoReadMemory();
}
}
public void setAutoReadMemorySpec(AutoReadMemorySpec spec) {
actionAutoReadMemory.setCurrentActionStateByUserData(spec);
readsMemTrait.setAutoSpec(spec);
}
public AutoReadMemorySpec getAutoReadMemorySpec() {
return autoReadMemorySpec;
}
protected ProgramLocation computeTrackedLocation() {
// Change of register values (for current frame)
// Change of stack pc (TODO) (for current frame)
// Change of current view (if not caused by goTo)
// Change of current thread
// Change of current snap (TODO)
// Change of current frame (TODO)
// Change of tracking settings
DebuggerCoordinates cur = current;
TraceThread thread = cur.getThread();
if (thread == null || trackingSpec == null) {
return null;
}
// NB: view's snap may be forked for emulation
Address address = trackingSpec.computeTraceAddress(tool, cur, current.getView().getSnap());
return address == null ? null : new ProgramLocation(current.getView(), address);
return readsMemTrait.getAutoSpec();
}
protected ProgramLocation doMarkTrackedLocation() {
ProgramLocation trackedLocation = trackingTrait.getTrackedLocation();
if (trackedLocation == null) {
markTrackedStaticLocation(null);
return null;
@ -1207,8 +919,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
return trackedStatic;
}
protected void doTrackSpec() {
ProgramLocation loc = trackedLocation = computeTrackedLocation();
protected void doGoToTracked() {
ProgramLocation loc = trackingTrait.getTrackedLocation();
ProgramLocation trackedStatic = doMarkTrackedLocation();
if (loc == null) {
return;
@ -1232,7 +944,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
@Override
public void dispose() {
super.dispose();
removeOldListeners();
if (consoleService != null) {
if (actionOpenProgram != null) {
consoleService.removeResolutionAction(actionOpenProgram);
@ -1240,22 +951,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
}
}
@Override
public void visibleAddressesChanged(AddressSetView visibleAddresses) {
if (Objects.equals(this.visible, visibleAddresses)) {
return;
}
this.visible = visibleAddresses;
doAutoReadMemory();
}
protected void doAutoReadMemory() {
autoReadMemorySpec.readMemory(tool, current, visible).exceptionally(ex -> {
Msg.error(this, "Could not auto-read memory: " + ex);
return null;
});
}
public void staticProgramLocationChanged(ProgramLocation location) {
TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap)
if (!isSyncToStaticListing() || view == null || location == null) {
@ -1281,18 +976,12 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
current = coordinates;
return;
}
boolean doListeners = !Objects.equals(current.getTrace(), coordinates.getTrace());
if (doListeners) {
removeOldListeners();
}
current = coordinates;
if (doListeners) {
addNewListeners();
}
doSetProgram(current.getView());
doSetRecorder(current.getRecorder());
doTrackSpec();
doAutoReadMemory();
goToTrait.goToCoordinates(coordinates);
trackingTrait.goToCoordinates(coordinates);
readsMemTrait.goToCoordinates(coordinates);
locationLabel.goToCoordinates(coordinates);
contextChanged();
}

View File

@ -15,68 +15,23 @@
*/
package ghidra.app.plugin.core.debug.gui.listing;
import java.awt.Color;
import java.math.BigInteger;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.colors.DebuggerTrackedRegisterBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.framework.plugintool.Plugin;
public abstract class DebuggerTrackedRegisterListingBackgroundColorModel
implements ListingBackgroundColorModel {
private Color defaultBackgroundColor;
private Program program;
private AddressIndexMap addressIndexMap;
extends DebuggerTrackedRegisterBackgroundColorModel implements ListingBackgroundColorModel {
// TODO: Seems I should at least rename this option
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS)
Color trackingColor;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
public DebuggerTrackedRegisterListingBackgroundColorModel(DebuggerListingPlugin plugin,
public DebuggerTrackedRegisterListingBackgroundColorModel(Plugin plugin,
ListingPanel listingPanel) {
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
super(plugin);
modelDataChanged(listingPanel);
}
@Override
public Color getBackgroundColor(BigInteger index) {
if (program == null) {
return defaultBackgroundColor;
}
ProgramLocation loc = getTrackedLocation();
if (loc == null) {
return defaultBackgroundColor;
}
Address address = addressIndexMap.getAddress(index);
if (!loc.getAddress().equals(address)) {
return defaultBackgroundColor;
}
return trackingColor;
}
@Override
public Color getDefaultBackgroundColor() {
return defaultBackgroundColor;
}
@Override
public void setDefaultBackgroundColor(Color c) {
defaultBackgroundColor = c;
}
@Override
public void modelDataChanged(ListingPanel listingPanel) {
this.program = listingPanel == null ? null : listingPanel.getProgram();
this.addressIndexMap = listingPanel == null ? null : listingPanel.getAddressIndexMap();
}
protected abstract ProgramLocation getTrackedLocation();
}

View File

@ -20,29 +20,31 @@ import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.fieldpanel.support.BackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.util.ColorUtils.ColorBlender;
public class MultiBlendedListingBackgroundColorModel implements ListingBackgroundColorModel {
private final List<ListingBackgroundColorModel> models = new ArrayList<>();
private final List<BackgroundColorModel> models = new ArrayList<>();
private final List<Color> toBlend = new ArrayList<>();
private final ColorBlender blender = new ColorBlender();
public MultiBlendedListingBackgroundColorModel() {
}
public void addModel(ListingBackgroundColorModel m) {
public void addModel(BackgroundColorModel m) {
models.add(m);
}
public void removeModel(ListingBackgroundColorModel m) {
public void removeModel(BackgroundColorModel m) {
models.remove(m);
}
@Override
public Color getBackgroundColor(BigInteger index) {
toBlend.clear();
for (ListingBackgroundColorModel m : models) {
blender.clear();
for (BackgroundColorModel m : models) {
Color c = m.getBackgroundColor(index);
if (c == null) {
continue;
@ -50,31 +52,9 @@ public class MultiBlendedListingBackgroundColorModel implements ListingBackgroun
if (c.equals(m.getDefaultBackgroundColor())) {
continue;
}
toBlend.add(c);
blender.add(c);
}
int size = toBlend.size();
if (size == 0) {
return getDefaultBackgroundColor();
}
if (size == 1) {
return toBlend.get(0);
}
return blend();
}
protected Color blend() {
int r = 0;
int g = 0;
int b = 0;
int ta = 0;
for (Color c : toBlend) {
int a = c.getAlpha();
ta += a;
r += a * c.getRed();
g += a * c.getGreen();
b += a * c.getBlue();
}
return ta == 0 ? getDefaultBackgroundColor() : new Color(r / ta, g / ta, b / ta);
return blender.getColor(getDefaultBackgroundColor());
}
@Override
@ -87,15 +67,19 @@ public class MultiBlendedListingBackgroundColorModel implements ListingBackgroun
@Override
public void setDefaultBackgroundColor(Color c) {
for (ListingBackgroundColorModel m : models) {
for (BackgroundColorModel m : models) {
m.setDefaultBackgroundColor(c);
}
}
@Override
public void modelDataChanged(ListingPanel listingPanel) {
for (ListingBackgroundColorModel m : models) {
m.modelDataChanged(listingPanel);
for (BackgroundColorModel m : models) {
if (!(m instanceof ListingBackgroundColorModel)) {
continue;
}
ListingBackgroundColorModel lm = (ListingBackgroundColorModel) m;
lm.modelDataChanged(listingPanel);
}
}
}

View File

@ -0,0 +1,181 @@
/* ###
* 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.memory;
import java.awt.Color;
import java.awt.FontMetrics;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import docking.widgets.fieldpanel.internal.LayoutBackgroundColorManager;
import docking.widgets.fieldpanel.support.FieldSelection;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.colors.*;
import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgroundColorManager.ColoredFieldSelection;
import ghidra.app.plugin.core.format.DataFormatModel;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.memory.TraceMemoryState;
public class DebuggerMemoryByteViewerComponent extends ByteViewerComponent
implements SelectionTranslator {
protected class SelectionHighlightSelectionGenerator implements SelectionGenerator {
@Override
public void addSelections(BigInteger layoutIndex, SelectionTranslator translator,
List<ColoredFieldSelection> selections) {
Color selectionColor = paintContext.getSelectionColor();
Color highlightColor = paintContext.getHighlightColor();
selections.add(new ColoredFieldSelection(getSelection(), selectionColor));
selections.add(new ColoredFieldSelection(getHighlight(), highlightColor));
}
}
protected class TraceMemoryStateSelectionGenerator implements SelectionGenerator {
@Override
public void addSelections(BigInteger layoutIndex, SelectionTranslator translator,
List<ColoredFieldSelection> selections) {
FieldSelection lineFieldSel = new FieldSelection();
lineFieldSel.addRange(layoutIndex, layoutIndex.add(BigInteger.ONE));
DebuggerMemoryBytesProvider provider = panel.getProvider();
DebuggerCoordinates coordinates = provider.current;
if (coordinates.getView() == null) {
return;
}
Trace trace = coordinates.getTrace();
// TODO: Mimic the listing's background, or factor into common
long snap = coordinates.getSnap();
// TODO: Span out and cache?
AddressSetView lineAddresses = translator.convertFieldToAddress(lineFieldSel);
// Because UNKNOWN need not be explicitly recorded, compute it by subtracting others
AddressSet unknown = new AddressSet(lineAddresses);
for (AddressRange range : lineAddresses) {
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : trace.getMemoryManager()
.getStates(snap, range)) {
if (entry.getValue() != TraceMemoryState.UNKNOWN) {
unknown.delete(entry.getKey().getRange());
}
Color color = colorForState(entry.getValue());
if (color == null) {
continue;
}
// NOTE: Only TraceMemoryState.ERROR should reach here
FieldSelection resultFieldSel =
translator.convertAddressToField(entry.getKey().getRange());
if (!resultFieldSel.isEmpty()) {
selections.add(new ColoredFieldSelection(resultFieldSel, color));
}
}
}
if (unknownColor == null) {
return;
}
for (AddressRange unk : unknown) {
FieldSelection resultFieldSel = translator.convertAddressToField(unk);
if (!resultFieldSel.isEmpty()) {
selections.add(new ColoredFieldSelection(resultFieldSel, unknownColor));
}
}
}
}
private final DebuggerMemoryBytesPanel panel;
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_ERROR_MEMORY)
private Color errorColor;
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_STALE_MEMORY)
private Color unknownColor;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
private final List<SelectionGenerator> selectionGenerators;
public DebuggerMemoryByteViewerComponent(DebuggerMemoryBytesPanel vpanel,
ByteViewerLayoutModel layoutModel, DataFormatModel model, int bytesPerLine,
FontMetrics fm) {
super(vpanel, layoutModel, model, bytesPerLine, fm);
// TODO: I don't care much for this reverse path
this.panel = vpanel;
autoOptionsWiring = AutoOptions.wireOptionsConsumed(vpanel.getProvider().getPlugin(), this);
selectionGenerators = List.of(
new SelectionHighlightSelectionGenerator(),
new TraceMemoryStateSelectionGenerator(),
vpanel.getProvider().trackingTrait.getSelectionGenerator());
// NOTE: Cursor, being line-by-line, is done via background color model in super
}
protected Color colorForState(TraceMemoryState state) {
switch (state) {
case ERROR:
return errorColor;
case KNOWN:
return null;
case UNKNOWN:
return unknownColor;
}
throw new AssertionError();
}
@Override
protected LayoutBackgroundColorManager getLayoutSelectionMap(BigInteger layoutIndex) {
Color backgroundColor = backgroundColorModel.getBackgroundColor(layoutIndex);
boolean isBackgroundDefault =
backgroundColorModel.getDefaultBackgroundColor().equals(backgroundColor);
List<ColoredFieldSelection> selections = new ArrayList<>(3);
for (SelectionGenerator sg : selectionGenerators) {
sg.addSelections(layoutIndex, this, selections);
}
return MultiSelectionBlendedLayoutBackgroundColorManager.getLayoutColorMap(
layoutIndex, selections, backgroundColor, isBackgroundDefault);
}
@Override
public AddressSetView convertFieldToAddress(FieldSelection fieldSelection) {
ProgramByteBlockSet blockSet = getBlockSet();
if (blockSet == null) {
return new AddressSet();
}
return blockSet.getAddressSet(processFieldSelection(fieldSelection));
}
@Override
public FieldSelection convertAddressToField(AddressSetView addresses) {
ProgramByteBlockSet blockSet = getBlockSet();
if (blockSet == null) {
return new FieldSelection();
}
return getFieldSelection(blockSet.getBlockSelection(addresses));
}
@Override
public FieldSelection convertAddressToField(AddressRange range) {
ProgramByteBlockSet blockSet = getBlockSet();
if (blockSet == null) {
return new FieldSelection();
}
return getFieldSelection(blockSet.getBlockSelection(range));
}
}

View File

@ -0,0 +1,42 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.memory;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.app.plugin.core.format.DataFormatModel;
public class DebuggerMemoryBytesPanel extends ByteViewerPanel {
private final DebuggerMemoryBytesProvider provider;
public DebuggerMemoryBytesPanel(DebuggerMemoryBytesProvider provider) {
super(provider);
// TODO: Would rather not provide this reverse path
this.provider = provider;
}
/**
* TODO: I don't care for this
*/
public DebuggerMemoryBytesProvider getProvider() {
return provider;
}
@Override
protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) {
return new DebuggerMemoryByteViewerComponent(this, new ByteViewerLayoutModel(), model,
getBytesPerLine(), getFontMetrics());
}
}

View File

@ -15,21 +15,285 @@
*/
package ghidra.app.plugin.core.debug.gui.memory;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.byteviewer.ByteViewerPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.framework.plugintool.PluginTool;
import java.awt.Color;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class DebuggerMemoryBytesPlugin /*extends Plugin*/ {
import org.jdom.Element;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
import ghidra.app.services.*;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramSelection;
@PluginInfo(
shortDescription = "View bytes of trace (possibly live) memory",
description = "Provides the memory bytes display window. Functions similarly to " +
"the main program bytes display window, but for traces. If the trace is the " +
"destination of a live recording, the view(s) retrieve live memory on demand.",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
// ProgramSelectionPluginEvent.class, // TODO: Later or remove
// ProgramHighlightPluginEvent.class, // TODO: Later or remove
TraceActivatedPluginEvent.class, // Trace/thread activation and register tracking
TraceClosedPluginEvent.class,
},
eventsProduced = {
TraceLocationPluginEvent.class,
TraceSelectionPluginEvent.class,
},
servicesRequired = {
DebuggerModelService.class, // For memory capture
ClipboardService.class,
})
public class DebuggerMemoryBytesPlugin
extends AbstractByteViewerPlugin<DebuggerMemoryBytesProvider> {
private static final String KEY_CONNECTED_PROVIDER = "connectedProvider";
private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount";
private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider";
@AutoServiceConsumed
private ProgramManager programManager;
// NOTE: This plugin doesn't extend AbstractDebuggerPlugin
@SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring;
@AutoOptionConsumed(name = OPTION_NAME_COLORS_STALE_MEMORY)
private Color staleMemoryColor;
@AutoOptionConsumed(name = OPTION_NAME_COLORS_ERROR_MEMORY)
private Color errorMemoryColor;
@AutoOptionConsumed(name = OPTION_NAME_COLORS_TRACKING_MARKERS)
private Color trackingColor;
@SuppressWarnings("unused")
private AutoOptions.Wiring autoOptionsWiring;
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
public DebuggerMemoryBytesPlugin(PluginTool tool) {
super(tool);
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
autoOptionsWiring = AutoOptions.wireOptions(this);
createActions();
}
@Override
protected DebuggerMemoryBytesProvider createProvider(boolean isConnected) {
return new DebuggerMemoryBytesProvider(tool, this, isConnected);
}
private void createActions() {
// TODO
}
public DebuggerMemoryBytesProvider createViewerIfMissing(LocationTrackingSpec spec,
boolean followsCurrentThread) {
synchronized (disconnectedProviders) {
for (DebuggerMemoryBytesProvider provider : disconnectedProviders) {
if (provider.getTrackingSpec() != spec) {
continue;
}
if (provider.isFollowsCurrentThread() != followsCurrentThread) {
continue;
}
return provider;
}
DebuggerMemoryBytesProvider provider = createNewDisconnectedProvider();
provider.setTrackingSpec(spec);
provider.setFollowsCurrentThread(followsCurrentThread);
provider.goToCoordinates(current);
return provider;
}
}
@Override
protected void updateLocation(
ProgramByteViewerComponentProvider programByteViewerComponentProvider,
ProgramLocationPluginEvent event, boolean export) {
// TODO
}
@Override
protected void fireProgramLocationPluginEvent(
ProgramByteViewerComponentProvider programByteViewerComponentProvider,
ProgramLocationPluginEvent pluginEvent) {
// TODO
}
@Override
public void updateSelection(ByteViewerComponentProvider provider,
ProgramSelectionPluginEvent event, Program program) {
// TODO
}
@Override
public void highlightChanged(ByteViewerComponentProvider provider, ProgramSelection highlight) {
// TODO
}
protected void allProviders(Consumer<DebuggerMemoryBytesProvider> action) {
action.accept(connectedProvider);
for (DebuggerMemoryBytesProvider provider : disconnectedProviders) {
action.accept(provider);
}
}
@Override
public void processEvent(PluginEvent event) {
if (event instanceof TraceActivatedPluginEvent) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
current = ev.getActiveCoordinates();
allProviders(p -> p.coordinatesActivated(current));
}
if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
if (current.getTrace() == ev.getTrace()) {
current = DebuggerCoordinates.NOWHERE;
}
allProviders(p -> p.traceClosed(ev.getTrace()));
}
// TODO: Sync among dynamic providers?
}
@AutoServiceConsumed
public void setTraceManager(DebuggerTraceManagerService traceManager) {
DebuggerMemoryBytesProvider provider = connectedProvider;
if (provider == null || traceManager == null) {
return;
}
provider.coordinatesActivated(current = traceManager.getCurrent());
}
@Override
public Object getTransientState() {
// Not needed, since I'm not coordinated with ProgramManager
return new Object[] {};
}
@Override
public void restoreTransientState(Object objectState) {
// Not needed, since I'm not coordinated with ProgramManager
}
@Override
public void writeDataState(SaveState saveState) {
SaveState connectedProviderState = new SaveState();
connectedProvider.writeDataState(connectedProviderState);
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
/**
* TODO: This will likely require a refactor of the existing {@link ByteViewerPlugin} to provide
* an abstract one that we can inherit from, in the same vein as the
* {@link DebuggerListingPlugin}.
* Arrange the follows ones first, so that we reload them into corresponding providers
* restored from config state
*/
protected DebuggerMemoryBytesPlugin(PluginTool tool) {
//super(tool);
TODO();
List<DebuggerMemoryBytesProvider> disconnected = disconnectedProviders.stream()
.filter(p -> p.isFollowsCurrentThread())
.collect(Collectors.toList());
for (DebuggerMemoryBytesProvider p : disconnectedProviders) {
if (!disconnected.contains(p)) {
disconnected.add(p);
}
}
int disconnectedCount = disconnected.size();
saveState.putInt(KEY_DISCONNECTED_COUNT, disconnectedCount);
for (int index = 0; index < disconnectedCount; index++) {
DebuggerMemoryBytesProvider provider = disconnected.get(index);
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
SaveState providerState = new SaveState();
provider.writeDataState(providerState);
saveState.putXmlElement(stateName, providerState.saveToXml());
}
}
protected void ensureProviders(int count, boolean followCurrentThread, SaveState configState) {
while (disconnectedProviders.size() < count) {
int index = disconnectedProviders.size();
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
DebuggerMemoryBytesProvider provider = createNewDisconnectedProvider();
provider.setFollowsCurrentThread(false);
Element providerElement = configState.getXmlElement(stateName);
// Read transient configs, which are not saved in tool
if (providerElement != null) {
SaveState providerState = new SaveState(providerElement);
provider.readConfigState(providerState); // Yes, config
}
else {
provider.setTrackingSpec(
LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME));
}
}
}
@Override
public void readDataState(SaveState saveState) {
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
if (connectedProviderElement != null) {
SaveState connectedProviderState = new SaveState(connectedProviderElement);
connectedProvider.readDataState(connectedProviderState);
}
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
ensureProviders(disconnectedCount, false, saveState);
List<DebuggerMemoryBytesProvider> disconnected = disconnectedProviders;
for (int index = 0; index < disconnectedCount; index++) {
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
Element providerElement = saveState.getXmlElement(stateName);
if (providerElement != null) {
SaveState providerState = new SaveState(providerElement);
DebuggerMemoryBytesProvider provider = disconnected.get(index);
provider.readDataState(providerState);
}
}
}
@Override
public void writeConfigState(SaveState saveState) {
SaveState connectedProviderState = new SaveState();
connectedProvider.writeConfigState(connectedProviderState);
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
List<DebuggerMemoryBytesProvider> disconnected = disconnectedProviders.stream()
.filter(p -> p.isFollowsCurrentThread())
.collect(Collectors.toList());
int disconnectedCount = disconnected.size();
saveState.putInt(KEY_DISCONNECTED_COUNT, disconnectedCount);
for (int index = 0; index < disconnectedCount; index++) {
DebuggerMemoryBytesProvider provider = disconnected.get(index);
String stateName = PREFIX_DISCONNECTED_PROVIDER + index;
SaveState providerState = new SaveState();
provider.writeConfigState(providerState);
saveState.putXmlElement(stateName, providerState.saveToXml());
}
}
@Override
public void readConfigState(SaveState saveState) {
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
if (connectedProviderElement != null) {
SaveState connectedProviderState = new SaveState(connectedProviderElement);
connectedProvider.readConfigState(connectedProviderState);
}
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
ensureProviders(disconnectedCount, true, saveState);
}
}

View File

@ -0,0 +1,439 @@
/* ###
* 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.memory;
import java.awt.BorderLayout;
import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.action.*;
import docking.menu.MultiStateDockingAction;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerLocationLabel;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
import ghidra.app.plugin.core.format.ByteBlock;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.Swing;
public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvider {
private static final AutoConfigState.ClassHandler<ProgramByteViewerComponentProvider> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(ProgramByteViewerComponentProvider.class,
MethodHandles.lookup());
private static final String KEY_DEBUGGER_COORDINATES = "DebuggerCoordinates";
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getView(), b.getView())) {
return false; // Subsumes trace
}
if (!Objects.equals(a.getRecorder(), b.getRecorder())) {
return false; // For capture memory action
}
if (!Objects.equals(a.getTime(), b.getTime())) {
return false;
}
if (!Objects.equals(a.getThread(), b.getThread())) {
return false; // for reg/pc tracking
}
if (!Objects.equals(a.getFrame(), b.getFrame())) {
return false; // for reg/pc tracking
}
return true;
}
protected class FollowsCurrentThreadAction extends AbstractFollowsCurrentThreadAction {
public FollowsCurrentThreadAction() {
super(plugin);
setMenuBarData(new MenuData(new String[] { NAME }));
setSelected(true);
addLocalAction(this);
setEnabled(true);
}
@Override
public void actionPerformed(ActionContext context) {
doSetFollowsCurrentThread(isSelected());
}
}
protected class ForMemoryBytesGoToTrait extends DebuggerGoToTrait {
public ForMemoryBytesGoToTrait() {
super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin,
DebuggerMemoryBytesProvider.this);
}
@Override
protected boolean goToAddress(Address address) {
TraceProgramView view = current.getView();
if (view == null) {
return false;
}
return goTo(view, new ProgramLocation(view, address));
}
}
protected class ForMemoryBytesTrackingTrait extends DebuggerTrackLocationTrait {
public ForMemoryBytesTrackingTrait() {
super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin,
DebuggerMemoryBytesProvider.this);
}
@Override
protected void locationTracked() {
doGoToTracked();
}
}
protected class ForMemoryBytesReadsMemoryTrait extends DebuggerReadsMemoryTrait {
public ForMemoryBytesReadsMemoryTrait() {
super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin,
DebuggerMemoryBytesProvider.this);
}
@Override
protected AddressSetView getSelection() {
return DebuggerMemoryBytesProvider.this.getSelection();
}
}
private final AutoReadMemorySpec defaultReadMemorySpec =
AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
private final DebuggerMemoryBytesPlugin myPlugin;
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
protected DockingAction actionGoTo;
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionCaptureSelectedMemory;
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
protected ForMemoryBytesGoToTrait goToTrait;
protected ForMemoryBytesTrackingTrait trackingTrait;
protected ForMemoryBytesReadsMemoryTrait readsMemTrait;
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
@AutoConfigStateField
protected boolean followsCurrentThread = true;
@AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class)
protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec;
// TODO: followsCurrentSnap?
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected final boolean isMainViewer;
protected DebuggerMemoryBytesProvider(PluginTool tool, DebuggerMemoryBytesPlugin plugin,
boolean isConnected) {
super(tool, plugin, "Memory", isConnected);
this.myPlugin = plugin;
this.isMainViewer = isConnected;
autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
createActions();
addDisplayListener(readsMemTrait.getDisplayListener());
decorationComponent.add(locationLabel, BorderLayout.NORTH);
goToTrait.goToCoordinates(current);
trackingTrait.goToCoordinates(current);
readsMemTrait.goToCoordinates(current);
locationLabel.goToCoordinates(current);
}
/**
* TODO: I'd rather this not be here
*/
protected Plugin getPlugin() {
return plugin;
}
protected void initTraits() {
if (goToTrait == null) {
goToTrait = new ForMemoryBytesGoToTrait();
}
if (trackingTrait == null) {
trackingTrait = new ForMemoryBytesTrackingTrait();
}
if (readsMemTrait == null) {
readsMemTrait = new ForMemoryBytesReadsMemoryTrait();
}
}
@Override
protected ByteViewerPanel newByteViewerPanel() {
initTraits();
return new DebuggerMemoryBytesPanel(this);
}
// For testing access
@Override
protected ByteViewerPanel getByteViewerPanel() {
return super.getByteViewerPanel();
}
/**
* Deal with the fact that initialization order is hard to control
*/
protected DebuggerCoordinates getCurrent() {
return current == null ? DebuggerCoordinates.NOWHERE : current;
}
protected String computeSubTitle() {
// TODO: This should be factored in a common place
DebuggerCoordinates current = getCurrent();
TraceProgramView view = current == null ? null : current.getView();
List<String> parts = new ArrayList<>();
LocationTrackingSpec trackingSpec = trackingTrait == null ? null : trackingTrait.getSpec();
if (trackingSpec != null) {
String specTitle = trackingSpec.computeTitle(current);
if (specTitle != null) {
parts.add(specTitle);
}
}
if (view != null) {
parts.add(current.getTrace().getDomainFile().getName());
}
return StringUtils.join(parts, ", ");
}
@Override
protected void updateTitle() {
setSubTitle(computeSubTitle());
}
protected void createActions() {
initTraits();
if (!isMainViewer()) {
actionFollowsCurrentThread = new FollowsCurrentThreadAction();
}
actionGoTo = goToTrait.installAction();
actionTrackLocation = trackingTrait.installAction();
actionAutoReadMemory = readsMemTrait.installAutoReadAction();
actionCaptureSelectedMemory = readsMemTrait.installCaptureSelectedAction();
}
@Override
protected void doSetProgram(Program newProgram) {
if (newProgram != null && newProgram != current.getView()) {
throw new AssertionError();
}
if (getProgram() == newProgram) {
return;
}
if (newProgram != null && !(newProgram instanceof TraceProgramView)) {
throw new IllegalArgumentException("Dynamic Listings require trace views");
}
super.doSetProgram(newProgram);
if (newProgram != null) {
setSelection(new ProgramSelection());
}
updateTitle();
locationLabel.updateLabel();
}
protected DebuggerCoordinates adjustCoordinates(DebuggerCoordinates coordinates) {
if (followsCurrentThread) {
return coordinates;
}
// Because the view's snap is changing with or without us.... So go with.
return current.withTime(coordinates.getTime());
}
public void goToCoordinates(DebuggerCoordinates coordinates) {
if (sameCoordinates(current, coordinates)) {
current = coordinates;
return;
}
current = coordinates;
doSetProgram(current.getView());
goToTrait.goToCoordinates(coordinates);
trackingTrait.goToCoordinates(coordinates);
readsMemTrait.goToCoordinates(coordinates);
locationLabel.goToCoordinates(coordinates);
contextChanged();
}
public void coordinatesActivated(DebuggerCoordinates coordinates) {
DebuggerCoordinates adjusted = adjustCoordinates(coordinates);
goToCoordinates(adjusted);
}
public void traceClosed(Trace trace) {
if (current.getTrace() == trace) {
goToCoordinates(DebuggerCoordinates.NOWHERE);
}
}
public void setFollowsCurrentThread(boolean follows) {
if (isMainViewer()) {
throw new IllegalStateException(
"The main memory bytes viewer always follows the current trace and thread");
}
actionFollowsCurrentThread.setSelected(follows);
doSetFollowsCurrentThread(follows);
}
protected void doSetFollowsCurrentThread(boolean follows) {
this.followsCurrentThread = follows;
updateBorder();
updateTitle();
coordinatesActivated(traceManager.getCurrent());
}
protected void updateBorder() {
decorationComponent.setConnected(followsCurrentThread);
}
public boolean isFollowsCurrentThread() {
return followsCurrentThread;
}
public void setAutoReadMemorySpec(AutoReadMemorySpec spec) {
readsMemTrait.setAutoSpec(spec);
}
public AutoReadMemorySpec getAutoReadMemorySpec() {
return readsMemTrait.getAutoSpec();
}
protected void doGoToTracked() {
ProgramLocation loc = trackingTrait.getTrackedLocation();
if (loc == null) {
return;
}
TraceProgramView curView = current.getView();
Swing.runIfSwingOrRunLater(() -> {
goTo(curView, loc);
});
}
public void setTrackingSpec(LocationTrackingSpec spec) {
trackingTrait.setSpec(spec);
}
public LocationTrackingSpec getTrackingSpec() {
return trackingTrait.getSpec();
}
public boolean isMainViewer() {
return isMainViewer;
}
@Override
protected void writeConfigState(SaveState saveState) {
super.writeConfigState(saveState);
}
@Override
protected void readConfigState(SaveState saveState) {
super.readConfigState(saveState);
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
trackingTrait.readConfigState(saveState);
if (isMainViewer()) {
followsCurrentThread = true;
}
else {
actionFollowsCurrentThread.setSelected(followsCurrentThread);
updateBorder();
}
// TODO: actionAutoReadMemory
}
@Override
protected void writeDataState(SaveState saveState) {
if (!isMainViewer()) {
current.writeDataState(tool, saveState, KEY_DEBUGGER_COORDINATES);
}
super.writeDataState(saveState);
}
@Override
protected void readDataState(SaveState saveState) {
if (!isMainViewer()) {
DebuggerCoordinates coordinates =
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES, true);
coordinatesActivated(coordinates);
}
super.readDataState(saveState);
}
@Override
protected void updateLocation(ByteBlock block, BigInteger blockOffset, int column,
boolean export) {
super.updateLocation(block, blockOffset, column, export);
locationLabel.goToAddress(currentLocation == null ? null : currentLocation.getAddress());
}
@Override
public void cloneWindow() {
final DebuggerMemoryBytesProvider newProvider = myPlugin.createNewDisconnectedProvider();
SaveState saveState = new SaveState();
writeConfigState(saveState);
newProvider.readConfigState(saveState);
newProvider.goToCoordinates(current);
newProvider.setLocation(currentLocation);
newProvider.panel.setViewerPosition(panel.getViewerPosition());
}
@Override
protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
if (program == null) {
return null;
}
return new WritesTargetProgramByteBlockSet(this, program, changeManager);
}
@Override
public void addLocalAction(DockingActionIf action) {
/**
* TODO This is a terrible hack, but it's temporary. We do not yet support writing target
* memory from the bytes provider. Once we do, we should obviously take this hack out. I
* don't think we'll forget, because the only way to get the write toggle button back is to
* delete this override.
*/
if (action == editModeAction) {
return;
}
super.addLocalAction(action);
}
}

View File

@ -0,0 +1,179 @@
/* ###
* 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.memory;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import ghidra.app.plugin.core.byteviewer.MemoryByteBlock;
import ghidra.app.plugin.core.format.ByteBlockAccessException;
import ghidra.app.services.TraceRecorder;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
/**
* An extension of MemoryByteBlock that redirects applicable writes to a debug target
*/
public class WritesTargetMemoryByteBlock extends MemoryByteBlock {
protected final WritesTargetProgramByteBlockSet blockSet;
/**
* Constructor.
*
* @param blockSet the containing block set
* @param program the trace program view for the block
* @param memory the view's memory
* @param block the view's memory block
*/
public WritesTargetMemoryByteBlock(WritesTargetProgramByteBlockSet blockSet, Program program,
Memory memory, MemoryBlock block) {
super(program, memory, block);
this.blockSet = blockSet;
}
/**
* Check writes should be redirected, based on the provider's coordinates.
*
* <p>
* Note that redirecting the write prevents the edit from being written (dirtectly) into the
* trace. If the edit is successful, the trace recorder will record it to the trace.
*
* @return true to redirect
*/
protected boolean shouldWriteTarget() {
return blockSet.provider.current.isAliveAndPresent();
}
/**
* Get the recorder.
*
* <p>
* This should only be used for redirected writes. If we're not live, this will return null.
*
* @return the recorder
*/
protected TraceRecorder getTraceRecorder() {
return blockSet.provider.current.getRecorder();
}
@Override
public void setByte(BigInteger index, byte value)
throws ByteBlockAccessException {
if (!shouldWriteTarget()) {
super.setByte(index, value);
return;
}
Address addr = getAddress(index);
writeTargetByte(addr, value);
}
@Override
public void setInt(BigInteger index, int value)
throws ByteBlockAccessException {
if (!shouldWriteTarget()) {
super.setInt(index, value);
return;
}
Address addr = getAddress(index);
writeTargetInt(addr, value, isBigEndian());
}
@Override
public void setLong(BigInteger index, long value)
throws ByteBlockAccessException {
if (!shouldWriteTarget()) {
super.setLong(index, value);
return;
}
Address addr = getAddress(index);
writeTargetLong(addr, value, isBigEndian());
}
/**
* Write an array of bytes to the target's memory.
*
* @param addr the starting address
* @param data the data to write, prepared in correct endianness
*/
public void writeTarget(Address addr, byte[] data) {
TraceRecorder recorder = getTraceRecorder();
recorder.writeProcessMemory(addr, data);
}
/**
* Allocate a buffer for encoding values into bytes.
*
* @param size the number of bytes to allocate
* @param bigEndian true to order the buffer in {@link ByteOrder#BIG_ENDIAN}.
* @return the buffer, allocated and configured.
*/
protected ByteBuffer newBuffer(int size, boolean bigEndian) {
ByteBuffer buf = ByteBuffer.allocate(size);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
return buf;
}
/**
* Write a single byte to the target.
*
* <p>
* Endianness is meaningless
*
* @param addr the address
* @param value the byte
*/
public void writeTargetByte(Address addr, byte value) {
writeTarget(addr, new byte[] { value });
}
/**
* Write a single int to the target
*
* @param addr the minimum address to modify
* @param value the integer
* @param bigEndian true for big endian, false for little
*/
public void writeTargetInt(Address addr, int value, boolean bigEndian) {
ByteBuffer buf = newBuffer(Integer.BYTES, bigEndian);
buf.putInt(value);
writeTarget(addr, buf.array());
}
/**
* Write a single long to the target
*
* @param addr the minimum address to modify
* @param value the long
* @param bigEndian true for big endian, false for little
*/
public void writeTargetLong(Address addr, long value, boolean bigEndian) {
ByteBuffer buf = newBuffer(Long.BYTES, bigEndian);
buf.putLong(value);
writeTarget(addr, buf.array());
}
@Override
protected boolean editAllowed(Address addr, long length) {
/**
* Traces are much more permissive when it comes to writes. The instruction will just get
* clobbered, from this time forward.
*/
return true;
}
}

View File

@ -0,0 +1,36 @@
/* ###
* 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.memory;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
public class WritesTargetProgramByteBlockSet extends ProgramByteBlockSet {
protected final DebuggerMemoryBytesProvider provider;
public WritesTargetProgramByteBlockSet(DebuggerMemoryBytesProvider provider,
Program program, ByteBlockChangeManager bbcm) {
super(provider, program, bbcm);
this.provider = provider;
}
@Override
protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
return new WritesTargetMemoryByteBlock(this, program, memory, memBlock);
}
}

View File

@ -23,6 +23,7 @@ import com.google.common.collect.Range;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.program.model.lang.RegisterValue;
@ -135,7 +136,7 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator
performAction(listingProvider.actionGoTo, false);
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
dialog.textExpression.setText("RAX");
dialog.setExpression("RAX");
captureDialog(dialog);
}

View File

@ -69,7 +69,7 @@ import ghidra.util.task.TaskMonitor;
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
static LocationTrackingSpec getLocationTrackingSpec(String name) {
return /*waitForValue(() ->*/ LocationTrackingSpec.fromConfigName(name)/*)*/;
return LocationTrackingSpec.fromConfigName(name);
}
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
@ -261,7 +261,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
createAndOpenTrace();
TraceThread thread1;
TraceThread thread2;
DebuggerListingProvider extraListing = SwingExecutorService.INSTANCE.submit(
DebuggerListingProvider extraProvider = SwingExecutorService.INSTANCE.submit(
() -> listingPlugin.createListingIfMissing(trackPc, true)).get();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager memory = tb.trace.getMemoryManager();
@ -283,15 +283,15 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.activateThread(thread1);
waitForSwing();
loc = extraListing.getLocation();
loc = extraProvider.getLocation();
assertEquals(tb.trace.getProgramView(), loc.getProgram());
assertEquals(tb.addr(0x00401234), loc.getAddress());
extraListing.setFollowsCurrentThread(false);
extraProvider.setFollowsCurrentThread(false);
traceManager.activateThread(thread2);
waitForSwing();
loc = extraListing.getLocation();
loc = extraProvider.getLocation();
assertEquals(tb.trace.getProgramView(), loc.getProgram());
assertEquals(tb.addr(0x00401234), loc.getAddress());
}
@ -432,6 +432,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
loc = listingProvider.getLocation();
assertEquals(b1.trace.getProgramView(), loc.getProgram());
assertEquals(b1.addr(0x00400000), loc.getAddress());
// TODO: Assert thread?
traceManager.activateThread(t2);
waitForSwing();
@ -820,7 +821,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
performAction(listingProvider.actionGoTo, false);
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
dialog.textExpression.setText("r0");
dialog.setExpression("r0");
dialog.okCallback();
waitForPass(
@ -828,7 +829,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
performAction(listingProvider.actionGoTo, false);
dialog = waitForDialogComponent(DebuggerGoToDialog.class);
dialog.textExpression.setText("*:4 r0");
dialog.setExpression("*:4 r0");
dialog.okCallback();
waitForPass(

View File

@ -15,6 +15,7 @@
*/
package ghidra.dbg.model;
import java.nio.ByteBuffer;
import java.util.function.Function;
import ghidra.program.model.address.Address;
@ -55,6 +56,18 @@ public class TestDebuggerModelBuilder {
return testModel.range(min, max);
}
public byte[] arr(int... e) {
byte[] result = new byte[e.length];
for (int i = 0; i < e.length; i++) {
result[i] = (byte) e[i];
}
return result;
}
public ByteBuffer buf(int... e) {
return ByteBuffer.wrap(arr(e));
}
public void createTestProcessesAndThreads() {
testProcess1 = testModel.addProcess(1);
testThread1 = testProcess1.addThread(1);

View File

@ -40,11 +40,15 @@ public class TestTargetMemory
), "Initialized");
}
public void getMemory(Address address, byte[] data) {
assertEquals(getModel().ram, address.getAddressSpace());
memory.getData(address.getOffset(), data);
}
@Override
public CompletableFuture<byte[]> readMemory(Address address, int length) {
assertEquals(getModel().ram, address.getAddressSpace());
byte[] data = new byte[length];
memory.getData(address.getOffset(), data);
getMemory(address, data);
CompletableFuture<byte[]> future = getModel().future(data);
future.thenAccept(__ -> {
listeners.fire.memoryUpdated(this, address, data);

View File

@ -86,6 +86,7 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
public MemoryBlock[] getBlocks() {
List<MemoryBlock> result = new ArrayList<>();
forVisibleRegions(reg -> result.add(getBlock(reg)));
Collections.sort(result, Comparator.comparing(b -> b.getStart()));
return result.toArray(new MemoryBlock[result.size()]);
}

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,10 +25,10 @@ public class DecoratorPanel extends JPanel {
public DecoratorPanel(JComponent component, boolean isConnected) {
setLayout(new BorderLayout());
add(component);
setConnnected( isConnected );
setConnected( isConnected );
}
public void setConnnected( boolean isConnected ) {
public void setConnected( boolean isConnected ) {
if ( !isConnected ) {
setBorder( BorderFactory.createLineBorder( Color.ORANGE, 2 ) );
}

View File

@ -275,6 +275,9 @@ public class AssembleDockingAction extends DockingAction {
return;
}
prepareLayout(context);
if (cv.isReadOnly()) {
return;
}
ListingActionContext lac = (ListingActionContext) context;
ProgramLocation cur = lac.getLocation();
@ -371,6 +374,13 @@ public class AssembleDockingAction extends DockingAction {
}
ListingActionContext lac = (ListingActionContext) context;
ComponentProvider cp = lac.getComponentProvider();
if (cp instanceof CodeViewerProvider) {
CodeViewerProvider cv = (CodeViewerProvider) cp;
if (cv.isReadOnly()) {
return false;
}
}
Program program = lac.getProgram();
if (program == null) {

View File

@ -24,16 +24,21 @@ import ghidra.program.util.ProgramSelection;
public interface CodeBrowserPluginInterface {
PluginTool getTool();
String getName();
void providerClosed(CodeViewerProvider codeViewerProvider);
boolean isDisposed();
void locationChanged(CodeViewerProvider codeViewerProvider, ProgramLocation loc);
void selectionChanged(CodeViewerProvider codeViewerProvider, ProgramSelection currentSelection);
void highlightChanged(CodeViewerProvider codeViewerProvider, ProgramSelection highlight);
ViewManagerService getViewManager(CodeViewerProvider codeViewerProvider);
CodeViewerProvider createNewDisconnectedProvider();
}

View File

@ -158,10 +158,14 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
listingPanel.setStringSelectionListener(this);
listingPanel.addIndexMapChangeListener(this);
codeViewerClipboardProvider = new CodeBrowserClipboardProvider(tool, this);
codeViewerClipboardProvider = newClipboardProvider();
tool.addPopupActionProvider(this);
}
protected CodeBrowserClipboardProvider newClipboardProvider() {
return new CodeBrowserClipboardProvider(tool, this);
}
@Override
public boolean isSnapshot() {
// we are a snapshot when we are 'disconnected'
@ -175,6 +179,17 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
return false;
}
/**
* TODO: Remove or rename this to something that accommodates redirecting writes, e.g., to a
* debug target process, particularly for assembly, which may involve code unit modification
* after a successful write, reported asynchronously :/ .
*
* @return true if this listing represents a read-only view
*/
public boolean isReadOnly() {
return false;
}
private ListingHighlightProvider createListingHighlighter(ListingPanel panel,
PluginTool pluginTool, Component repaintComponent) {
ListingHighlightProvider listingHighlighter =
@ -1051,20 +1066,20 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
}
/**
* Add the ListingDisplayListener to the listing panel
* Add the {@link AddressSetDisplayListener} to the listing panel
*
* @param listener the listener to add
*/
public void addListingDisplayListener(ListingDisplayListener listener) {
listingPanel.addListingDisplayListener(listener);
public void addDisplayListener(AddressSetDisplayListener listener) {
listingPanel.addDisplayListener(listener);
}
/**
* Remove the ListingDisplayListener from the listing panel
* Remove the {@link AddressSetDisplayListener} from the listing panel
*
* @param listener the listener to remove
*/
public void removeListingDisplayListener(ListingDisplayListener listener) {
listingPanel.removeListingDisplayListener(listener);
public void removeDisplayListener(AddressSetDisplayListener listener) {
listingPanel.removeDisplayListener(listener);
}
}

View File

@ -219,11 +219,11 @@ public interface CodeViewerService {
* Adds a listener to be notified when the set of visible addresses change.
* @param listener the listener to be notified;
*/
public void addListingDisplayListener(ListingDisplayListener listener);
public void addListingDisplayListener(AddressSetDisplayListener listener);
/**
* Removes listener from being notified when the set of visible addresses change.
* @param listener the listener to be notified;
*/
public void removeListingDisplayListener(ListingDisplayListener listener);
public void removeListingDisplayListener(AddressSetDisplayListener listener);
}

View File

@ -20,7 +20,7 @@ import ghidra.program.model.address.AddressSetView;
/**
* Interface for being notified whenever the set of visible addresses change in the listing.
*/
public interface ListingDisplayListener {
public interface AddressSetDisplayListener {
/**
* Callback whenever the set of visible addresses change in the listing.
* @param visibleAddresses the current set of visible addresses in the listing. If no

View File

@ -89,7 +89,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
// don't care
}
};
private List<ListingDisplayListener> displayListeners = new ArrayList<>();
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
private String currentTextSelection;
@ -479,12 +479,12 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
element.setPixelMap(pixmap);
}
for (ListingDisplayListener listener : displayListeners) {
for (AddressSetDisplayListener listener : displayListeners) {
notifyDisplayListener(listener);
}
}
private void notifyDisplayListener(ListingDisplayListener listener) {
private void notifyDisplayListener(AddressSetDisplayListener listener) {
AddressSetView displayAddresses = pixmap.getAddressSet();
try {
listener.visibleAddressesChanged(displayAddresses);
@ -1160,11 +1160,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc
layoutModel.dataChanged(true);
}
public void addListingDisplayListener(ListingDisplayListener listener) {
public void addDisplayListener(AddressSetDisplayListener listener) {
displayListeners.add(listener);
}
public void removeListingDisplayListener(ListingDisplayListener listener) {
public void removeDisplayListener(AddressSetDisplayListener listener) {
displayListeners.remove(listener);
}
}

View File

@ -307,7 +307,7 @@ public class ListingPanelTest extends AbstractGhidraHeadedIntegrationTest {
AtomicReference<AddressSetView> addresses = new AtomicReference<>();
CodeViewerService cvs = tool.getService(CodeViewerService.class);
cvs.addListingDisplayListener(new ListingDisplayListener() {
cvs.addListingDisplayListener(new AddressSetDisplayListener() {
@Override
public void visibleAddressesChanged(AddressSetView visibleAddresses) {
addresses.set(visibleAddresses);

View File

@ -0,0 +1,308 @@
/* ###
* 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.byteviewer;
import java.util.*;
import org.jdom.Element;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.services.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import utility.function.Callback;
public abstract class AbstractByteViewerPlugin<P extends ProgramByteViewerComponentProvider>
extends Plugin {
protected Program currentProgram;
private boolean areEventsDisabled;
protected ProgramLocation currentLocation;
protected P connectedProvider;
protected List<P> disconnectedProviders = new ArrayList<>();
public AbstractByteViewerPlugin(PluginTool tool) {
super(tool);
connectedProvider = createProvider(true);
}
protected abstract P createProvider(boolean isConnected);
protected void showConnectedProvider() {
tool.showComponentProvider(connectedProvider, true);
}
public P createNewDisconnectedProvider() {
P newProvider = createProvider(false);
disconnectedProviders.add(newProvider);
tool.showComponentProvider(newProvider, true);
return newProvider;
}
@Override
protected void init() {
ClipboardService clipboardService = tool.getService(ClipboardService.class);
if (clipboardService != null) {
connectedProvider.setClipboardService(clipboardService);
for (P provider : disconnectedProviders) {
provider.setClipboardService(clipboardService);
}
}
}
/**
* Tells a plugin that it is no longer needed. The plugin should remove itself from anything
* that it is registered to and release any resources.
*/
@Override
public void dispose() {
removeProvider(connectedProvider);
for (P provider : disconnectedProviders) {
removeProvider(provider);
}
disconnectedProviders.clear();
}
/**
* Process the plugin event; delegates the processing to the byte block.
*/
/**
* Tells a Plugin to write any data-independent (preferences) properties to the output stream.
*/
@Override
public void writeConfigState(SaveState saveState) {
connectedProvider.writeConfigState(saveState);
}
/**
* Tells the Plugin to read its data-independent (preferences) properties from the input stream.
*/
@Override
public void readConfigState(SaveState saveState) {
connectedProvider.readConfigState(saveState);
}
/**
* Read data state; called after readConfigState(). Events generated by plugins we depend on
* should have been already been thrown by the time this method is called.
*/
@Override
public void readDataState(SaveState saveState) {
doWithEventsDisabled(() -> {
ProgramManager programManagerService = tool.getService(ProgramManager.class);
connectedProvider.readDataState(saveState);
int numDisconnected = saveState.getInt("Num Disconnected", 0);
for (int i = 0; i < numDisconnected; i++) {
Element xmlElement = saveState.getXmlElement("Provider" + i);
SaveState providerSaveState = new SaveState(xmlElement);
String programPath = providerSaveState.getString("Program Path", "");
DomainFile file = tool.getProject().getProjectData().getFile(programPath);
if (file == null) {
continue;
}
Program program = programManagerService.openProgram(file);
if (program != null) {
P provider = createProvider(false);
provider.doSetProgram(program);
provider.readConfigState(providerSaveState);
provider.readDataState(providerSaveState);
tool.showComponentProvider(provider, true);
addProvider(provider);
}
}
});
}
/**
* Tells the Plugin to write any data-dependent state to the output stream.
*/
@Override
public void writeDataState(SaveState saveState) {
connectedProvider.writeDataState(saveState);
saveState.putInt("Num Disconnected", disconnectedProviders.size());
int i = 0;
for (P provider : disconnectedProviders) {
SaveState providerSaveState = new SaveState();
DomainFile df = provider.getProgram().getDomainFile();
if (df.getParent() == null) {
continue; // not contained within project
}
String programPathname = df.getPathname();
providerSaveState.putString("Program Path", programPathname);
provider.writeConfigState(providerSaveState);
provider.writeDataState(providerSaveState);
String elementName = "Provider" + i;
saveState.putXmlElement(elementName, providerSaveState.saveToXml());
i++;
}
}
@Override
public Object getUndoRedoState(DomainObject domainObject) {
Map<Long, Object> stateMap = new HashMap<>();
addUndoRedoState(stateMap, domainObject, connectedProvider);
for (P provider : disconnectedProviders) {
addUndoRedoState(stateMap, domainObject, provider);
}
if (stateMap.isEmpty()) {
return null;
}
return stateMap;
}
private void addUndoRedoState(Map<Long, Object> stateMap, DomainObject domainObject,
P provider) {
if (provider == null) {
return;
}
Object state = provider.getUndoRedoState(domainObject);
if (state != null) {
stateMap.put(provider.getInstanceID(), state);
}
}
@SuppressWarnings("unchecked")
@Override
public void restoreUndoRedoState(DomainObject domainObject, Object state) {
Map<Long, Object> stateMap = (Map<Long, Object>) state;
restoreUndoRedoState(stateMap, domainObject, connectedProvider);
for (P provider : disconnectedProviders) {
restoreUndoRedoState(stateMap, domainObject, provider);
}
}
private void restoreUndoRedoState(Map<Long, Object> stateMap, DomainObject domainObject,
P provider) {
if (provider == null) {
return;
}
Object state = stateMap.get(provider.getInstanceID());
if (state != null) {
provider.restoreUndoRedoState(domainObject, state);
}
}
@Override
public Object getTransientState() {
Object[] state = new Object[2];
SaveState ss = new SaveState();
connectedProvider.writeDataState(ss);
state[0] = ss;
state[1] = connectedProvider.getCurrentSelection();
return state;
}
@Override
public void restoreTransientState(Object objectState) {
doWithEventsDisabled(() -> {
Object[] state = (Object[]) objectState;
connectedProvider.restoreLocation((SaveState) state[0]);
connectedProvider.setSelection((ProgramSelection) state[1]);
});
}
private void doWithEventsDisabled(Callback callback) {
areEventsDisabled = true;
try {
callback.call();
}
finally {
areEventsDisabled = false;
}
}
protected boolean eventsDisabled() {
return areEventsDisabled;
}
void setStatusMessage(String msg) {
tool.setStatusInfo(msg);
}
void addProvider(P provider) {
disconnectedProviders.add(provider);
provider.setClipboardService(tool.getService(ClipboardService.class));
}
Program getProgram() {
return currentProgram;
}
// Silly Junits - only public until we move to the new multi-view system
public P getProvider() {
return connectedProvider;
}
public abstract void updateSelection(ByteViewerComponentProvider provider,
ProgramSelectionPluginEvent event, Program program);
public abstract void highlightChanged(ByteViewerComponentProvider provider,
ProgramSelection highlight);
public void closeProvider(ByteViewerComponentProvider provider) {
if (provider == connectedProvider) {
tool.showComponentProvider(provider, false);
}
else {
disconnectedProviders.remove(provider);
removeProvider(provider);
}
}
protected void exportLocation(Program program, ProgramLocation location) {
GoToService service = tool.getService(GoToService.class);
if (service != null) {
service.goTo(location, program);
}
}
protected void removeProvider(ByteViewerComponentProvider provider) {
tool.removeComponentProvider(provider);
provider.dispose();
}
protected abstract void updateLocation(
ProgramByteViewerComponentProvider programByteViewerComponentProvider,
ProgramLocationPluginEvent event, boolean export);
protected abstract void fireProgramLocationPluginEvent(
ProgramByteViewerComponentProvider programByteViewerComponentProvider,
ProgramLocationPluginEvent pluginEvent);
}

View File

@ -25,10 +25,10 @@ import ghidra.framework.options.SaveState;
import ghidra.program.model.address.Address;
/**
* Helper class to manage changes within byte blocks; determines what offsets
* have changed so the changes can be rendered properly in the Byte Viewer.
* Helper class to manage changes within byte blocks; determines what offsets have changed so the
* changes can be rendered properly in the Byte Viewer.
*/
class ByteBlockChangeManager {
public class ByteBlockChangeManager {
private ProgramByteBlockSet blockSet;
private List<ByteEditInfo> changeList; // list of changes for this tool
@ -57,6 +57,7 @@ class ByteBlockChangeManager {
/**
* Add a change to the change list.
*
* @param edit edit object that has the old value and new value
*
*/
@ -117,12 +118,12 @@ class ByteBlockChangeManager {
}
/**
* Return true if any offset in the range offset to offset+unitByteSize-1
* is in either of the change lists.
* Return true if any offset in the range offset to offset+unitByteSize-1 is in either of the
* change lists.
*
* @param block block in question
* @param offset offset into the block
* @param unitByteSize number of bytes in the unit (dictated by the
* data format model)
* @param unitByteSize number of bytes in the unit (dictated by the data format model)
*
* @return boolean true if an offset in the range was found
*/
@ -140,6 +141,7 @@ class ByteBlockChangeManager {
//////////////////////////////////////////////////////////////////////
/**
* Return true if the block and offset are in the list.
*
* @param list either the local change list or the external change list
* @param block block in question
* @param offset offset into the block

View File

@ -32,13 +32,12 @@ import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.*;
import docking.widgets.fieldpanel.support.*;
import ghidra.app.plugin.core.format.*;
import ghidra.program.model.address.AddressOutOfBoundsException;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
/**
* FieldViewer to show data formatted according to the DataFormatModel that
* is passed in to the constructor. The source of the data is an array
* of ByteBlocks that is managed by an IndexMap.
* FieldViewer to show data formatted according to the DataFormatModel that is passed in to the
* constructor. The source of the data is an array of ByteBlocks that is managed by an IndexMap.
*/
public class ByteViewerComponent extends FieldPanel implements FieldMouseListener,
FieldLocationListener, FieldSelectionListener, FieldInputListener {
@ -70,14 +69,14 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
/**
* Constructor
*
* @param vpanel the byte viewer panel that this component lives in
* @param layoutModel the layout model for this component
* @param model data format model that knows how the data should be
* displayed
* @param model data format model that knows how the data should be displayed
* @param bytesPerLine number of bytes displayed in a row
* @param fm the font metrics used for drawing
*/
ByteViewerComponent(ByteViewerPanel vpanel, ByteViewerLayoutModel layoutModel,
protected ByteViewerComponent(ByteViewerPanel vpanel, ByteViewerLayoutModel layoutModel,
DataFormatModel model, int bytesPerLine, FontMetrics fm) {
super(layoutModel);
@ -357,6 +356,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
/**
* Set the color for the component that has focus.
*
* @param c the color to set
*/
void setCurrentCursorColor(Color c) {
@ -366,6 +366,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
/**
* Set the background color for the line containing the cursor.
*
* @param c the color to set
*/
void setCurrentCursorLineColor(Color c) {
@ -374,6 +375,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
/**
* Set the color for showing gaps in indexes.
*
* @param c the color to set
*/
void setSeparatorColor(Color c) {
@ -415,8 +417,17 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
updatingIndexMap = false;
}
protected IndexMap getIndexMap() {
return indexMap;
}
protected ProgramByteBlockSet getBlockSet() {
return blockSet;
}
/**
* Set the new group size
*
* @param groupSize the group size
* @throws UnsupportedOperationException if model for this view does not support groups
*/
@ -442,7 +453,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
}
}
private FieldSelection getFieldSelection(ByteBlockSelection selection) {
protected FieldSelection getFieldSelection(ByteBlockSelection selection) {
FieldSelection fsel = new FieldSelection();
for (int i = 0; i < selection.getNumberOfRanges(); i++) {
ByteBlockRange r = selection.getRange(i);
@ -487,8 +498,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
* @param block the block
* @param index the index
* @param characterOffset the offset into the UI field
* @return index of the location; return -1 if there was an error
* setting the cursor location
* @return index of the location; return -1 if there was an error setting the cursor location
*/
int setViewerCursorLocation(ByteBlock block, BigInteger index, int characterOffset) {
if (indexMap == null) {
@ -649,10 +659,9 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
}
/**
* Set the edit mode according to the given param if the model
* for this view supports editing.
* @param editMode true means to enable editing, and change the cursor
* color.
* Set the edit mode according to the given param if the model for this view supports editing.
*
* @param editMode true means to enable editing, and change the cursor color.
*/
void setEditMode(boolean editMode) {
consumeKeyStrokes = editMode;
@ -743,8 +752,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
}
/**
* Enable help for this component; used the model name as part of
* the help ID.
* Enable help for this component; used the model name as part of the help ID.
*/
private void enableHelp() {
HelpService helpService = Help.getHelpService();
@ -814,7 +822,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
/**
* Create a byte block selection from the field selection.
*/
private ByteBlockSelection processFieldSelection(FieldSelection selection) {
protected ByteBlockSelection processFieldSelection(FieldSelection selection) {
ByteBlockSelection sel = new ByteBlockSelection();
int count = selection.getNumRanges();
@ -865,6 +873,17 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return null;
}
public AddressSetView getView() {
AddressSet result = new AddressSet();
if (blockSet != null) {
for (ByteBlock block : blockSet.getBlocks()) {
Address start = blockSet.getBlockStart(block);
result.add(start, start.add(block.getLength().longValue() - 1));
}
}
return result;
}
private class ByteViewerBackgroundColorModel implements BackgroundColorModel {
private Color defaultBackgroundColor = Color.WHITE;

View File

@ -28,6 +28,7 @@ import ghidra.GhidraOptions;
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
import ghidra.app.plugin.core.format.*;
import ghidra.app.services.MarkerService;
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool;
@ -87,25 +88,26 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
private ToggleEditAction editModeAction;
protected ToggleEditAction editModeAction;
protected OptionsAction setOptionsAction;
protected ProgramByteBlockSet blockSet;
protected final ByteViewerPlugin plugin;
protected final AbstractByteViewerPlugin<?> plugin;
protected SwingUpdateManager updateManager;
private Map<String, Class<? extends DataFormatModel>> dataFormatModelClassMap;
protected ByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin, String name,
protected ByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin<?> plugin,
String name,
Class<?> contextType) {
super(tool, name, plugin.getName(), contextType);
this.plugin = plugin;
initializedDataFormatModelClassMap();
panel = new ByteViewerPanel(this);
panel = newByteViewerPanel();
bytesPerLine = DEFAULT_BYTES_PER_LINE;
setIcon(ResourceManager.loadImage("images/binaryData.gif"));
setOptions();
@ -118,6 +120,10 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
setWindowMenuGroup("Byte Viewer");
}
protected ByteViewerPanel newByteViewerPanel() {
return new ByteViewerPanel(this);
}
private void initializedDataFormatModelClassMap() {
dataFormatModelClassMap = new HashMap<>();
Set<? extends DataFormatModel> models = getDataFormatModels();
@ -150,6 +156,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
/**
* Notification that an option changed.
*
* @param options options object containing the property that changed
* @param group
* @param optionName name of option that changed
@ -338,7 +345,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
}
void writeConfigState(SaveState saveState) {
protected void writeConfigState(SaveState saveState) {
DataModelInfo info = panel.getDataModelInfo();
saveState.putStrings(VIEW_NAMES, info.getNames());
saveState.putInt(HEX_VIEW_GROUPSIZE, hexGroupSize);
@ -346,7 +353,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
saveState.putInt(OFFSET_NAME, offset);
}
void readConfigState(SaveState saveState) {
protected void readConfigState(SaveState saveState) {
String[] names = saveState.getStrings(VIEW_NAMES, new String[0]);
hexGroupSize = saveState.getInt(HEX_VIEW_GROUPSIZE, 1);
restoreViews(names, false);
@ -412,10 +419,10 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
abstract void updateLocation(ByteBlock block, BigInteger blockOffset, int column,
protected abstract void updateLocation(ByteBlock block, BigInteger blockOffset, int column,
boolean export);
abstract void updateSelection(ByteBlockSelection selection);
protected abstract void updateSelection(ByteBlockSelection selection);
void dispose() {
updateManager.dispose();
@ -445,7 +452,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
}
ByteViewerPanel getByteViewerPanel() {
protected ByteViewerPanel getByteViewerPanel() {
return panel;
}
@ -481,7 +488,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
return null;
}
try {
return classy.newInstance();
return classy.getConstructor().newInstance();
}
catch (Exception e) {
// cannot happen, since we only get the value from valid class that we put into the map
@ -493,4 +500,22 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
public MarkerService getMarkerService() {
return tool.getService(MarkerService.class);
}
/**
* Add the {@link AddressSetDisplayListener} to the byte viewer panel
*
* @param listener the listener to add
*/
public void addDisplayListener(AddressSetDisplayListener listener) {
panel.addDisplayListener(listener);
}
/**
* Remove the {@link AddressSetDisplayListener} from the byte viewer panel
*
* @param listener the listener to remove
*/
public void removeDisplayListener(AddressSetDisplayListener listener) {
panel.removeDisplayListener(listener);
}
}

View File

@ -33,15 +33,14 @@ import ghidra.app.plugin.core.format.DataFormatModel;
/**
* Implements the LayoutModel for ByteViewer Components.
*/
class ByteViewerLayoutModel implements LayoutModel {
public class ByteViewerLayoutModel implements LayoutModel {
private int width;
private IndexMap indexMap;
private List<LayoutModelListener> listeners;
private FieldFactory[] factorys;
private BigInteger numIndexes;
ByteViewerLayoutModel() {
public ByteViewerLayoutModel() {
factorys = new FieldFactory[0];
listeners = new ArrayList<LayoutModelListener>(1);
numIndexes = BigInteger.ZERO;

View File

@ -28,25 +28,27 @@ import docking.help.HelpService;
import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.EmptyTextField;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.IndexMapper;
import docking.widgets.fieldpanel.listener.LayoutModelListener;
import docking.widgets.fieldpanel.support.SingleRowLayout;
import docking.widgets.fieldpanel.support.ViewerPosition;
import docking.widgets.fieldpanel.listener.*;
import docking.widgets.fieldpanel.support.*;
import docking.widgets.indexedscrollpane.*;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import ghidra.app.plugin.core.format.*;
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.layout.HorizontalLayout;
import ghidra.util.layout.PairLayout;
/**
* Top level component that contains has a scrolled pane for the panel of
* components that show the view for each format.
* Top level component that contains has a scrolled pane for the panel of components that show the
* view for each format.
*/
public class ByteViewerPanel extends JPanel implements TableColumnModelListener, LayoutModel {
public class ByteViewerPanel extends JPanel
implements TableColumnModelListener, LayoutModel, LayoutListener {
// private ByteViewerPlugin plugin;
private List<ByteViewerComponent> viewList; // list of field viewers
private FieldPanel indexPanel; // panel for showing indexes
@ -79,10 +81,12 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
// changes while this flag is true
private final ByteViewerComponentProvider provider;
private List<AddressSetDisplayListener> displayListeners = new ArrayList<>();
/**
* Constructor
*/
ByteViewerPanel(ByteViewerComponentProvider provider) {
protected ByteViewerPanel(ByteViewerComponentProvider provider) {
super();
this.provider = provider;
bytesPerLine = ByteViewerComponentProvider.DEFAULT_BYTES_PER_LINE;
@ -240,8 +244,8 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Set the byte blocks and create an new IndexMap object that will be
* passed to the index panel and to each component that shows a format.
* Set the byte blocks and create an new IndexMap object that will be passed to the index panel
* and to each component that shows a format.
*/
void setByteBlocks(ByteBlockSet blockSet) {
this.blockSet = blockSet;
@ -323,6 +327,15 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
}
/**
* Set the background color model for all the views.
*/
public void setViewerBackgroundColorModel(BackgroundColorModel colorModel) {
for (ByteViewerComponent c : viewList) {
c.setBackgroundColorModel(colorModel);
}
}
/**
* Get the current highlight.
*
@ -363,8 +376,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
/**
* Get the data format model of the view that is in focus.
*
* @return DataFormatModel model of the view in focus; return null
* if no views are shown
* @return DataFormatModel model of the view in focus; return null if no views are shown
*/
DataFormatModel getCurrentModel() {
if (currentView == null) {
@ -376,17 +388,21 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
/**
* Returns the currently focused view.
*/
ByteViewerComponent getCurrentComponent() {
public ByteViewerComponent getCurrentComponent() {
return currentView;
}
protected ByteViewerComponent newByteViewerComponent(DataFormatModel model) {
return new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine, fm);
}
/**
* Add a view to the panel.
*
* @param viewName name of the format, e.g., Hex, Ascii, etc.
* @param model model that understands the format
* @param editMode true if edit mode is on
* @param updateViewPosition true if the view position should be
* set
* @param updateViewPosition true if the view position should be set
*/
ByteViewerComponent addView(String viewName, DataFormatModel model, boolean editMode,
boolean updateViewPosition) {
@ -398,8 +414,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
// create new ByteViewerComponent
ByteViewerComponent c =
new ByteViewerComponent(this, new ByteViewerLayoutModel(), model, bytesPerLine, fm);
ByteViewerComponent c = newByteViewerComponent(model);
c.setEditColor(editColor);
c.setNonFocusCursorColor(cursorColor);
c.setCurrentCursorColor(currentCursorColor);
@ -471,9 +486,8 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Set the given component to be the current view; called by the
* mouse listener in the ByteViewerComponent when the user clicks in the
* panel.
* Set the given component to be the current view; called by the mouse listener in the
* ByteViewerComponent when the user clicks in the panel.
*/
void setCurrentView(ByteViewerComponent c) {
if (currentView != null && currentView != c) {
@ -483,8 +497,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Set the cursor color on the current view to show that it is in
* edit mode.
* Set the cursor color on the current view to show that it is in edit mode.
*/
void setEditMode(boolean editMode) {
for (int i = 0; i < viewList.size(); i++) {
@ -537,8 +550,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Set the bytes per line. Bytes per line dictates the number of fields
* displayed in a row.
* Set the bytes per line. Bytes per line dictates the number of fields displayed in a row.
*/
void setBytesPerLine(int bytesPerLine) {
@ -555,10 +567,9 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Check that each model for the views can support the given
* bytes per line value.
* @throws InvalidInputException if a model cannot support the
* bytesPerLine value
* Check that each model for the views can support the given bytes per line value.
*
* @throws InvalidInputException if a model cannot support the bytesPerLine value
*/
void checkBytesPerLine(int numBytesPerLine) throws InvalidInputException {
for (int i = 0; i < viewList.size(); i++) {
@ -576,6 +587,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
/**
* Set the group size on the current view.
*
* @param groupSize new group size
*/
void setCurrentGroupSize(int groupSize) {
@ -596,9 +608,9 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Set the insertion field and tell other views to change location;
* called when the ByteViewerComponent receives a notification that
* the cursor location has changed.
* Set the insertion field and tell other views to change location; called when the
* ByteViewerComponent receives a notification that the cursor location has changed.
*
* @param source source of the change
* @param block block for the new location
* @param offset offset into the block
@ -632,8 +644,9 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Called from the ByteViewerComponent when it received a notification
* that the selection has changed.
* Called from the ByteViewerComponent when it received a notification that the selection has
* changed.
*
* @param source source of the change
* @param selection selection
*/
@ -657,8 +670,8 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Return array of names of views in the order that they appear in the
* panel. The name array includes an entry for the index panel.
* Return array of names of views in the order that they appear in the panel. The name array
* includes an entry for the index panel.
*/
DataModelInfo getDataModelInfo() {
@ -689,11 +702,11 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
*
* @return ViewerPosition top viewer position
*/
ViewerPosition getViewerPosition() {
public ViewerPosition getViewerPosition() {
return indexPanel.getViewerPosition();
}
void setViewerPosition(ViewerPosition pos) {
public void setViewerPosition(ViewerPosition pos) {
indexPanel.setViewerPosition(pos.getIndex(), pos.getXOffset(), pos.getYOffset());
}
@ -718,6 +731,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
/**
* Restore the configuration of the plugin.
*
* @param fontMetrics font metrics
* @param newEditColor color for showing edits
*/
@ -781,10 +795,14 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
/**
* Get the font metrics that the panel is using.
*/
FontMetrics getFontMetrics() {
protected FontMetrics getFontMetrics() {
return fm;
}
protected int getBytesPerLine() {
return bytesPerLine;
}
/**
* Create the components for this top level panel.
*/
@ -804,6 +822,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
indexPanel.enableSelection(false);
indexPanel.setCursorOn(false);
indexPanel.setFocusable(false);
indexPanel.addLayoutListener(this);
compPanel = new CompositePanel(indexPanel);
@ -885,8 +904,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
}
/**
* Create a new index map and update the map in the index field adapter
* and all the views.
* Create a new index map and update the map in the index field adapter and all the views.
*/
private void updateIndexMap() {
if (blockSet == null) {
@ -1027,6 +1045,7 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
/***
* Getter for the list of ByteViewer Components
*
* @return viewList the list of ByteViewerComponents
*/
public List<ByteViewerComponent> getViewList() {
@ -1046,6 +1065,43 @@ public class ByteViewerPanel extends JPanel implements TableColumnModelListener,
public void flushChanges() {
// nothing to do
}
protected AddressSetView computeVisibleAddresses(List<AnchoredLayout> layouts) {
// Kind of gross, but current component will do
ByteViewerComponent component = getCurrentComponent();
if (component == null || blockSet == null) {
return new AddressSet();
}
BigInteger startIndex = layouts.get(0).getIndex();
BigInteger endIndex = layouts.get(layouts.size() - 1).getIndex();
FieldSelection fieldSel = new FieldSelection();
fieldSel.addRange(startIndex, endIndex.add(BigInteger.ONE));
ByteBlockSelection blockSel = component.processFieldSelection(fieldSel);
return blockSet.getAddressSet(blockSel);
}
@Override
public void layoutsChanged(List<AnchoredLayout> layouts) {
AddressSetView visible = computeVisibleAddresses(layouts);
for (AddressSetDisplayListener listener : displayListeners) {
try {
listener.visibleAddressesChanged(visible);
}
catch (Throwable t) {
Msg.showError(this, indexPanel, "Error in Display Listener",
"Exception encountered when notifying listeners of change in display", t);
}
}
}
public void addDisplayListener(AddressSetDisplayListener listener) {
displayListeners.add(listener);
}
public void removeDisplayListener(AddressSetDisplayListener listener) {
displayListeners.add(listener);
}
}
class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollListener {
@ -1211,4 +1267,5 @@ class CompositePanel extends JPanel implements IndexedScrollable, IndexScrollLis
processingIndexRangeChanged = false;
}
}
}

View File

@ -15,29 +15,21 @@
*/
package ghidra.app.plugin.core.byteviewer;
import java.util.*;
import org.jdom.Element;
import java.util.Iterator;
import ghidra.app.CorePluginPackage;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.SystemUtilities;
import utility.function.Callback;
/**
* Visible Plugin to show ByteBlock data in various formats.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
@ -46,63 +38,31 @@ import utility.function.Callback;
description = "Provides a component for showing the bytes in memory. " +
"Additional plugins provide capabilites for this plugin" +
" to show the bytes in various formats (e.g., hex, octal, decimal)." +
" The hex format plugin is loaded by default when this " + "plugin is loaded.",
servicesRequired = { ProgramManager.class, GoToService.class, NavigationHistoryService.class, ClipboardService.class },
" The hex format plugin is loaded by default when this plugin is loaded.",
servicesRequired = {
ProgramManager.class, GoToService.class, NavigationHistoryService.class,
ClipboardService.class,
},
eventsConsumed = {
ProgramLocationPluginEvent.class, ProgramActivatedPluginEvent.class,
ProgramSelectionPluginEvent.class, ProgramHighlightPluginEvent.class, ProgramClosedPluginEvent.class,
ByteBlockChangePluginEvent.class },
eventsProduced = { ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class, ByteBlockChangePluginEvent.class }
)
//@formatter:on
public class ByteViewerPlugin extends Plugin {
private Program currentProgram;
private boolean areEventsDisabled;
private ProgramLocation currentLocation;
private ProgramByteViewerComponentProvider connectedProvider;
private List<ProgramByteViewerComponentProvider> disconnectedProviders = new ArrayList<>();
ProgramSelectionPluginEvent.class, ProgramHighlightPluginEvent.class,
ProgramClosedPluginEvent.class, ByteBlockChangePluginEvent.class,
},
eventsProduced = {
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
ByteBlockChangePluginEvent.class,
})
public class ByteViewerPlugin extends AbstractByteViewerPlugin<ProgramByteViewerComponentProvider> {
public ByteViewerPlugin(PluginTool tool) {
super(tool);
connectedProvider = new ProgramByteViewerComponentProvider(tool, this, true);
}
protected void showConnectedProvider() {
tool.showComponentProvider(connectedProvider, true);
}
@Override
protected void init() {
ClipboardService clipboardService = tool.getService(ClipboardService.class);
if (clipboardService != null) {
connectedProvider.setClipboardService(clipboardService);
for (ProgramByteViewerComponentProvider provider : disconnectedProviders) {
provider.setClipboardService(clipboardService);
}
}
protected ProgramByteViewerComponentProvider createProvider(boolean isConnected) {
return new ProgramByteViewerComponentProvider(tool, this, isConnected);
}
/**
* Tells a plugin that it is no longer needed. The plugin should remove
* itself from anything that it is registered to and release any resources.
*/
@Override
public void dispose() {
removeProvider(connectedProvider);
for (ProgramByteViewerComponentProvider provider : disconnectedProviders) {
removeProvider(provider);
}
disconnectedProviders.clear();
}
/**
* Process the plugin event; delegates the processing to the
* byte block.
*/
@Override
public void processEvent(PluginEvent event) {
if (event instanceof ProgramClosedPluginEvent) {
@ -132,229 +92,7 @@ public class ByteViewerPlugin extends Plugin {
}
}
public void fireProgramLocationPluginEvent(ProgramByteViewerComponentProvider provider,
ProgramLocationPluginEvent event) {
if (SystemUtilities.isEqual(event.getLocation(), currentLocation)) {
return;
}
currentLocation = event.getLocation();
if (provider == connectedProvider) {
firePluginEvent(event);
}
}
/**
* Tells a Plugin to write any data-independent (preferences)
* properties to the output stream.
*/
@Override
public void writeConfigState(SaveState saveState) {
connectedProvider.writeConfigState(saveState);
}
/**
* Tells the Plugin to read its data-independent (preferences)
* properties from the input stream.
*/
@Override
public void readConfigState(SaveState saveState) {
connectedProvider.readConfigState(saveState);
}
/**
* Read data state; called after readConfigState(). Events generated
* by plugins we depend on should have been already been thrown by the
* time this method is called.
*/
@Override
public void readDataState(SaveState saveState) {
doWithEventsDisabled(() -> {
ProgramManager programManagerService = tool.getService(ProgramManager.class);
connectedProvider.readDataState(saveState);
int numDisconnected = saveState.getInt("Num Disconnected", 0);
for (int i = 0; i < numDisconnected; i++) {
Element xmlElement = saveState.getXmlElement("Provider" + i);
SaveState providerSaveState = new SaveState(xmlElement);
String programPath = providerSaveState.getString("Program Path", "");
DomainFile file = tool.getProject().getProjectData().getFile(programPath);
if (file == null) {
continue;
}
Program program = programManagerService.openProgram(file);
if (program != null) {
ProgramByteViewerComponentProvider provider =
new ProgramByteViewerComponentProvider(tool, this, false);
provider.doSetProgram(program);
provider.readConfigState(providerSaveState);
provider.readDataState(providerSaveState);
tool.showComponentProvider(provider, true);
addProvider(provider);
}
}
});
}
/**
* Tells the Plugin to write any data-dependent state to the
* output stream.
*/
@Override
public void writeDataState(SaveState saveState) {
connectedProvider.writeDataState(saveState);
saveState.putInt("Num Disconnected", disconnectedProviders.size());
int i = 0;
for (ProgramByteViewerComponentProvider provider : disconnectedProviders) {
SaveState providerSaveState = new SaveState();
DomainFile df = provider.getProgram().getDomainFile();
if (df.getParent() == null) {
continue; // not contained within project
}
String programPathname = df.getPathname();
providerSaveState.putString("Program Path", programPathname);
provider.writeConfigState(providerSaveState);
provider.writeDataState(providerSaveState);
String elementName = "Provider" + i;
saveState.putXmlElement(elementName, providerSaveState.saveToXml());
i++;
}
}
@Override
public Object getUndoRedoState(DomainObject domainObject) {
Map<Long, Object> stateMap = new HashMap<>();
addUndoRedoState(stateMap, domainObject, connectedProvider);
for (ProgramByteViewerComponentProvider provider : disconnectedProviders) {
addUndoRedoState(stateMap, domainObject, provider);
}
if (stateMap.isEmpty()) {
return null;
}
return stateMap;
}
private void addUndoRedoState(Map<Long, Object> stateMap, DomainObject domainObject,
ProgramByteViewerComponentProvider provider) {
if (provider == null) {
return;
}
Object state = provider.getUndoRedoState(domainObject);
if (state != null) {
stateMap.put(provider.getInstanceID(), state);
}
}
@SuppressWarnings("unchecked")
@Override
public void restoreUndoRedoState(DomainObject domainObject, Object state) {
Map<Long, Object> stateMap = (Map<Long, Object>) state;
restoreUndoRedoState(stateMap, domainObject, connectedProvider);
for (ProgramByteViewerComponentProvider provider : disconnectedProviders) {
restoreUndoRedoState(stateMap, domainObject, provider);
}
}
private void restoreUndoRedoState(Map<Long, Object> stateMap, DomainObject domainObject,
ProgramByteViewerComponentProvider provider) {
if (provider == null) {
return;
}
Object state = stateMap.get(provider.getInstanceID());
if (state != null) {
provider.restoreUndoRedoState(domainObject, state);
}
}
@Override
public Object getTransientState() {
Object[] state = new Object[2];
SaveState ss = new SaveState();
connectedProvider.writeDataState(ss);
state[0] = ss;
state[1] = connectedProvider.getCurrentSelection();
return state;
}
@Override
public void restoreTransientState(Object objectState) {
doWithEventsDisabled(() -> {
Object[] state = (Object[]) objectState;
connectedProvider.restoreLocation((SaveState) state[0]);
connectedProvider.setSelection((ProgramSelection) state[1]);
});
}
private void doWithEventsDisabled(Callback callback) {
areEventsDisabled = true;
try {
callback.call();
}
finally {
areEventsDisabled = false;
}
}
private boolean eventsDisabled() {
return areEventsDisabled;
}
void setStatusMessage(String msg) {
tool.setStatusInfo(msg);
}
void addProvider(ProgramByteViewerComponentProvider provider) {
disconnectedProviders.add(provider);
provider.setClipboardService(tool.getService(ClipboardService.class));
}
Program getProgram() {
return currentProgram;
}
// Silly Junits - only public until we move to the new multi-view system
public ProgramByteViewerComponentProvider getProvider() {
return connectedProvider;
}
public void updateSelection(ProgramByteViewerComponentProvider provider,
ProgramSelectionPluginEvent event, Program program) {
if (provider == connectedProvider) {
firePluginEvent(event);
}
}
public void highlightChanged(ProgramByteViewerComponentProvider provider,
ProgramSelection highlight) {
if (provider == connectedProvider) {
tool.firePluginEvent(new ProgramHighlightPluginEvent(getName(), highlight,
connectedProvider.getProgram()));
}
}
public void closeProvider(ProgramByteViewerComponentProvider provider) {
if (provider == connectedProvider) {
tool.showComponentProvider(provider, false);
}
else {
disconnectedProviders.remove(provider);
removeProvider(provider);
}
}
public void updateLocation(ProgramByteViewerComponentProvider provider,
ProgramLocationPluginEvent event, boolean export) {
@ -370,16 +108,34 @@ public class ByteViewerPlugin extends Plugin {
}
}
private void exportLocation(Program program, ProgramLocation location) {
GoToService service = tool.getService(GoToService.class);
if (service != null) {
service.goTo(location, program);
@Override
public void fireProgramLocationPluginEvent(ProgramByteViewerComponentProvider provider,
ProgramLocationPluginEvent event) {
if (SystemUtilities.isEqual(event.getLocation(), currentLocation)) {
return;
}
currentLocation = event.getLocation();
if (provider == connectedProvider) {
firePluginEvent(event);
}
}
private void removeProvider(ProgramByteViewerComponentProvider provider) {
tool.removeComponentProvider(provider);
provider.dispose();
@Override
public void updateSelection(ByteViewerComponentProvider provider,
ProgramSelectionPluginEvent event, Program program) {
if (provider == connectedProvider) {
firePluginEvent(event);
}
}
@Override
public void highlightChanged(ByteViewerComponentProvider provider,
ProgramSelection highlight) {
if (provider == connectedProvider) {
tool.firePluginEvent(new ProgramHighlightPluginEvent(getName(), highlight,
connectedProvider.getProgram()));
}
}
}

View File

@ -20,6 +20,7 @@ import java.math.BigInteger;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.core.format.*;
import ghidra.program.model.address.AddressSet;
public class EmptyByteBlockSet implements ByteBlockSet {
@ -53,4 +54,8 @@ public class EmptyByteBlockSet implements ByteBlockSet {
byte[] newValue) {
}
@Override
public AddressSet getAddressSet(ByteBlockSelection selection) {
return null;
}
}

View File

@ -23,6 +23,7 @@ import java.util.List;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.app.plugin.core.format.*;
import ghidra.program.model.address.AddressSet;
/**
* ByteBlockSet for a File object.
@ -148,4 +149,10 @@ class FileByteBlockSet implements ByteBlockSet {
this.index = index;
}
}
@Override
public AddressSet getAddressSet(ByteBlockSelection selection) {
// Not applicable
return null;
}
}

View File

@ -38,11 +38,12 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Constructor
*
* @param program program used in generating plugin events
* @param memory memory from a program
* @param block block from memory
*/
MemoryByteBlock(Program program, Memory memory, MemoryBlock block) {
protected MemoryByteBlock(Program program, Memory memory, MemoryBlock block) {
this.program = program;
this.memory = memory;
this.block = block;
@ -53,6 +54,7 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Get the location representation for the given index.
*
* @param index byte index into this block
* @throws IndexOutOfBoundsException if index in not in this block.
*/
@ -77,8 +79,7 @@ public class MemoryByteBlock implements ByteBlock {
}
/**
* Return the name to be used for describing the indexes into the
* byte block.
* Return the name to be used for describing the indexes into the byte block.
*/
@Override
public String getIndexName() {
@ -93,7 +94,8 @@ public class MemoryByteBlock implements ByteBlock {
long size = block.getSize();
if (size < 0) {
return BigInteger.valueOf(size + 0x8000000000000000L).subtract(
return BigInteger.valueOf(size + 0x8000000000000000L)
.subtract(
BigInteger.valueOf(0x8000000000000000L));
}
return BigInteger.valueOf(size);
@ -101,11 +103,11 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Set the byte at the given index.
*
* @param index byte index
* @param value value to set
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public void setByte(BigInteger index, byte value) throws ByteBlockAccessException {
@ -121,10 +123,10 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Get the byte at the given index.
*
* @param index byte index
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public byte getByte(BigInteger index) throws ByteBlockAccessException {
@ -145,11 +147,11 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Set the long at the given index.
*
* @param index byte index
* @param value value to set
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public void setLong(BigInteger index, long value) throws ByteBlockAccessException {
@ -165,10 +167,10 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Get the long at the given index.
*
* @param index byte index
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public long getLong(BigInteger index) throws ByteBlockAccessException {
@ -183,6 +185,7 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Set the block according to the bigEndian parameter.
*
* @param bigEndian true means big endian; false means little endian
*/
@Override
@ -193,10 +196,10 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Get the int value at the given index.
*
* @param index byte index
* @throws ByteBlockAccessException if the block cannot be read
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public int getInt(BigInteger index) throws ByteBlockAccessException {
@ -211,11 +214,11 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Set the int at the given index.
*
* @param index byte index
* @param value value to set
* @throws ByteBlockAccessException if the block cannot be updated
* @throws IndexOutOfBoundsException if the given index is not in this
* block.
* @throws IndexOutOfBoundsException if the given index is not in this block.
*/
@Override
public void setInt(BigInteger index, int value) throws ByteBlockAccessException {
@ -239,6 +242,7 @@ public class MemoryByteBlock implements ByteBlock {
/**
* Return true if the block is big endian.
*
* @return false if the block is little endian
*/
@Override
@ -247,12 +251,11 @@ public class MemoryByteBlock implements ByteBlock {
}
/**
* Returns the natural alignment (offset) for the given radix. If there is
* no natural alignment, it should return 0. A natural alignment only exists if
* there is some underlying indexing structure that isn't based at 0. For example,
* if the underlying structure is address based and the starting address is not 0,
* then the natural alignment is the address offset mod the radix (if the starting
* address is 10 and the radix is 4, then then the alignment is 2)).
* Returns the natural alignment (offset) for the given radix. If there is no natural alignment,
* it should return 0. A natural alignment only exists if there is some underlying indexing
* structure that isn't based at 0. For example, if the underlying structure is address based
* and the starting address is not 0, then the natural alignment is the address offset mod the
* radix (if the starting address is 10 and the radix is 4, then then the alignment is 2)).
*/
@Override
public int getAlignment(int radix) {
@ -309,11 +312,10 @@ public class MemoryByteBlock implements ByteBlock {
/////////////////////////////////////////////////////////////////////
/**
* Check for whether edits are allowed at the given address; edits are
* not allowed if a code unit (other than undefined data) exists at the
* given address.
* Check for whether edits are allowed at the given address; edits are not allowed if a code
* unit (other than undefined data) exists at the given address.
*/
private void checkEditsAllowed(Address addr, long length) throws ByteBlockAccessException {
protected void checkEditsAllowed(Address addr, long length) throws ByteBlockAccessException {
if (!editAllowed(addr, length)) {
String msg = "Instruction exists at address " + addr;
if (length > 1) {
@ -330,10 +332,10 @@ public class MemoryByteBlock implements ByteBlock {
}
/**
* Return true if undefined data exists at the given address; return
* false if code unit exists, thus editing is not allowed.
* Return true if undefined data exists at the given address; return false if code unit exists,
* thus editing is not allowed.
*/
private boolean editAllowed(Address addr, long length) {
protected boolean editAllowed(Address addr, long length) {
Listing listing = program.getListing();
Address a = addr;
for (int i = 0; i < length; i++) {

View File

@ -33,15 +33,15 @@ import ghidra.program.util.ProgramSelection;
/**
* ByteBlockSet implementation for a Program object.
*/
class ProgramByteBlockSet implements ByteBlockSet {
public class ProgramByteBlockSet implements ByteBlockSet {
private MemoryBlock[] memBlocks;
private Program program;
protected final Program program;
private ByteBlockChangeManager bbcm;
private ByteBlock[] blocks;
private final ProgramByteViewerComponentProvider provider;
ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program,
protected ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program,
ByteBlockChangeManager bbcm) {
this.provider = provider;
@ -53,7 +53,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
this.bbcm = new ByteBlockChangeManager(this, bbcm);
}
getMemoryBlocks();
newMemoryBlocks();
}
/**
@ -66,6 +66,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
/**
* Get the appropriate plugin event for the given block selection.
*
* @param source source to use in the event
* @param selection selection to use to generate the event
*/
@ -86,6 +87,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
/**
* Get a plugin event for the given block and offset.
*
* @param source source to use in the event
* @param block block to use to generate the event
* @param offset offset into the block
@ -105,14 +107,12 @@ class ProgramByteBlockSet implements ByteBlockSet {
}
}
ByteBlockSelection getBlockSelection(ProgramSelection selection) {
AddressRangeIterator iter = selection.getAddressRanges();
List<ByteBlockRange> list = new ArrayList<ByteBlockRange>(3);
while (iter.hasNext()) {
AddressRange range = iter.next();
protected void collectBlockSelection(AddressRange range, List<ByteBlockRange> result) {
// TODO: These blocks really ought to be indexed by start address
// Use a NavigableMap
// I'll wait to assess speed before worrying about tree indexing
// Use entries that groups the relevant objects instead of co-indexed arrays
// Though a nicety, it becomes necessary if indexing/sorting by start address
for (int i = 0; i < blocks.length; i++) {
Address blockStart = memBlocks[i].getStart();
Address blockEnd = memBlocks[i].getEnd();
@ -123,18 +123,36 @@ class ProgramByteBlockSet implements ByteBlockSet {
ByteBlockInfo endInfo = getByteBlockInfo(intersection.getMaxAddress());
ByteBlockRange br = new ByteBlockRange(startInfo.getBlock(),
startInfo.getOffset(), endInfo.getOffset());
list.add(br);
result.add(br);
}
}
}
ByteBlockRange[] bRange = new ByteBlockRange[list.size()];
bRange = list.toArray(bRange);
return new ByteBlockSelection(bRange);
public ByteBlockSelection getBlockSelection(AddressRange range) {
List<ByteBlockRange> list = new ArrayList<>();
collectBlockSelection(range, list);
return new ByteBlockSelection(list);
}
public ByteBlockSelection getBlockSelection(AddressRangeIterator iter) {
List<ByteBlockRange> list = new ArrayList<>();
while (iter.hasNext()) {
collectBlockSelection(iter.next(), list);
}
return new ByteBlockSelection(list);
}
public ByteBlockSelection getBlockSelection(AddressSetView addresses) {
return getBlockSelection(addresses.getAddressRanges());
}
public ByteBlockSelection getBlockSelection(ProgramSelection selection) {
return getBlockSelection(selection.getAddressRanges());
}
/**
* Return true if the block has been changed at the given index.
*
* @param block byte block
* @param index offset into the block
* @param length number of bytes in question
@ -150,6 +168,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
/**
* Send a notification that a byte block edit occurred.
*
* @param block block being edited
* @param index offset into the block
* @param oldValue old byte values
@ -171,21 +190,21 @@ class ProgramByteBlockSet implements ByteBlockSet {
return bbcm.getUndoRedoState();
}
void restoreUndoReoState(SaveState saveState) {
void restoreUndoRedoState(SaveState saveState) {
bbcm.restoreUndoRedoState(saveState);
}
/**
* Get the byte block change manager
*/
ByteBlockChangeManager getByteBlockChangeManager() {
protected ByteBlockChangeManager getByteBlockChangeManager() {
return bbcm;
}
/**
* Get the address for the given block and offset.
*/
Address getAddress(ByteBlock block, BigInteger offset) {
protected Address getAddress(ByteBlock block, BigInteger offset) {
for (int i = 0; i < blocks.length; i++) {
if (blocks[i] != block) {
@ -207,7 +226,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
/**
* Given an address, get the byte block info.
*/
ByteBlockInfo getByteBlockInfo(Address address) {
protected ByteBlockInfo getByteBlockInfo(Address address) {
if (!program.getMemory().contains(address)) {
// this block set is out of date...eventually a new
@ -224,7 +243,8 @@ class ProgramByteBlockSet implements ByteBlockSet {
long off = address.subtract(memBlocks[i].getStart());
BigInteger offset =
(off < 0)
? BigInteger.valueOf(off + 0x8000000000000000L).subtract(
? BigInteger.valueOf(off + 0x8000000000000000L)
.subtract(
BigInteger.valueOf(0x8000000000000000L))
: BigInteger.valueOf(off);
return new ByteBlockInfo(blocks[i], offset);
@ -236,15 +256,15 @@ class ProgramByteBlockSet implements ByteBlockSet {
return null;
}
Address getBlockStart(ByteBlock block) {
protected Address getBlockStart(ByteBlock block) {
return getAddress(block, BigInteger.ZERO);
}
Address getBlockStart(int blockNumber) {
protected Address getBlockStart(int blockNumber) {
return memBlocks[blockNumber].getStart();
}
int getByteBlockNumber(Address blockStartAddr) {
protected int getByteBlockNumber(Address blockStartAddr) {
for (int i = 0; i < memBlocks.length; i++) {
if (memBlocks[i].getStart().compareTo(blockStartAddr) == 0) {
return i;
@ -253,8 +273,8 @@ class ProgramByteBlockSet implements ByteBlockSet {
return -1;
}
AddressSet getAddressSet(ByteBlockSelection selection) {
@Override
public AddressSet getAddressSet(ByteBlockSelection selection) {
AddressSet addrSet = new AddressSet();
for (int i = 0; i < selection.getNumberOfRanges(); i++) {
@ -267,15 +287,19 @@ class ProgramByteBlockSet implements ByteBlockSet {
return addrSet;
}
private void getMemoryBlocks() {
protected void newMemoryBlocks() {
Memory memory = program.getMemory();
memBlocks = program.getMemory().getBlocks();
memBlocks = memory.getBlocks();
blocks = new ByteBlock[memBlocks.length];
for (int i = 0; i < memBlocks.length; i++) {
blocks[i] = new MemoryByteBlock(program, memory, memBlocks[i]);
blocks[i] = newMemoryByteBlock(memory, memBlocks[i]);
}
}
protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
return new MemoryByteBlock(program, memory, memBlock);
}
@Override
public void dispose() {
// nothing to do?!?!?

View File

@ -68,10 +68,14 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
private boolean disposed;
public ProgramByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin,
public ProgramByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin<?> plugin,
boolean isConnected) {
super(tool, plugin, "Bytes", ByteViewerActionContext.class);
this(tool, plugin, "Bytes", isConnected);
}
protected ProgramByteViewerComponentProvider(PluginTool tool,
AbstractByteViewerPlugin<?> plugin, String name, boolean isConnected) {
super(tool, plugin, name, ByteViewerActionContext.class);
this.isConnected = isConnected;
setIcon(ResourceManager.loadImage("images/binaryData.gif"));
if (!isConnected) {
@ -215,7 +219,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
clipboardProvider.setPasteEnabled(enabled);
}
void doSetProgram(Program newProgram) {
protected void doSetProgram(Program newProgram) {
setOptionsAction.setEnabled(newProgram != null);
cloneByteViewerAction.setEnabled(newProgram != null);
@ -239,7 +243,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
updateTitle();
}
private void updateTitle() {
protected void updateTitle() {
String title =
"Bytes: " + (program == null ? "No Program" : program.getDomainFile().getName());
if (!isConnected()) {
@ -428,7 +432,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
return loc;
}
void setLocation(ProgramLocation location) {
protected void setLocation(ProgramLocation location) {
setLocation(location, false);
}
@ -559,7 +563,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
}
}
private ProgramByteBlockSet getByteBlockSet(ByteBlockChangeManager changeManager) {
protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
if (program == null) {
return null;
}
@ -572,12 +576,12 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
blockSet.dispose();
}
blockSet = getByteBlockSet(changeManager);
blockSet = newByteBlockSet(changeManager);
panel.setByteBlocks(blockSet);
}
@Override
void updateSelection(ByteBlockSelection selection) {
protected void updateSelection(ByteBlockSelection selection) {
ProgramSelectionPluginEvent event = blockSet.getPluginEvent(plugin.getName(), selection);
currentSelection = event.getSelection();
plugin.updateSelection(this, event, program);
@ -586,7 +590,8 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
}
@Override
void updateLocation(ByteBlock block, BigInteger blockOffset, int column, boolean export) {
protected void updateLocation(ByteBlock block, BigInteger blockOffset, int column,
boolean export) {
ProgramLocationPluginEvent event =
blockSet.getPluginEvent(plugin.getName(), block, blockOffset, column);
currentLocation = event.getLocation();
@ -595,7 +600,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
contextChanged();
}
void readDataState(SaveState saveState) {
protected void readDataState(SaveState saveState) {
unRegisterNavigatable();
initializeInstanceID(saveState.getLong("NAV_ID", getInstanceID()));
registerNavigatable();
@ -632,10 +637,10 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
return;
}
SaveState saveState = (SaveState) state;
blockSet.restoreUndoReoState(saveState);
blockSet.restoreUndoRedoState(saveState);
}
void writeDataState(SaveState saveState) {
protected void writeDataState(SaveState saveState) {
saveState.putLong("NAV_ID", getInstanceID());
ByteBlockInfo info = panel.getCursorLocation();
int blockNumber = -1;
@ -708,6 +713,22 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
return dataFormatModels;
}
public void cloneWindow() {
ProgramByteViewerComponentProvider newProvider = plugin.createNewDisconnectedProvider();
SaveState saveState = new SaveState();
writeConfigState(saveState);
newProvider.readConfigState(saveState);
newProvider.doSetProgram(program);
newProvider.setLocation(currentLocation);
newProvider.setSelection(currentSelection, false);
newProvider.setHighlight(currentHighlight);
ViewerPosition viewerPosition = panel.getViewerPosition();
newProvider.panel.setViewerPosition(viewerPosition);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
@ -727,23 +748,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi
@Override
public void actionPerformed(ActionContext context) {
ProgramByteViewerComponentProvider newProvider =
new ProgramByteViewerComponentProvider(tool, plugin, false);
plugin.addProvider(newProvider);
SaveState saveState = new SaveState();
writeConfigState(saveState);
newProvider.readConfigState(saveState);
tool.showComponentProvider(newProvider, true);
newProvider.doSetProgram(program);
newProvider.setLocation(currentLocation);
newProvider.setSelection(currentSelection, false);
newProvider.setHighlight(currentHighlight);
ViewerPosition viewerPosition = panel.getViewerPosition();
newProvider.panel.setViewerPosition(viewerPosition);
cloneWindow();
}
}

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,32 +15,33 @@
*/
package ghidra.app.plugin.core.byteviewer;
import ghidra.framework.plugintool.Plugin;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import resources.ResourceManager;
import docking.ActionContext;
import docking.action.*;
import ghidra.framework.plugintool.Plugin;
import resources.ResourceManager;
class ToggleEditAction extends ToggleDockingAction {
class ToggleEditAction extends ToggleDockingAction {
private final ByteViewerComponentProvider provider;
public ToggleEditAction(ByteViewerComponentProvider provider, Plugin plugin) {
super("Enable/Disable Byteviewer Editing", plugin.getName());
this.provider = provider;
setToolBarData( new ToolBarData(
ResourceManager.loadImage( "images/editbytes.gif" ), "Byteviewer" ) );
setKeyBindingData( new KeyBindingData(
KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK ) );
setToolBarData(new ToolBarData(
ResourceManager.loadImage("images/editbytes.gif"), "Byteviewer"));
setKeyBindingData(new KeyBindingData(
KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK));
setDescription("Enable/Disable editing of bytes in Byte Viewer panels.");
setSelected(false);
setEnabled(true);
}
@Override
public void actionPerformed(ActionContext context) {
boolean isSelected = isSelected();
provider.setEditMode(isSelected);
}
}
}

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,27 +19,41 @@ import java.util.*;
/**
* Defines a selection for byte blocks.
*
* The selection is a list of disjoint ranges.
*/
public class ByteBlockSelection {
private final List<ByteBlockRange> list;
private List<ByteBlockRange> list;
/**
* Construct a selection from a list of ranges
*
* @param ranges the ranges
*/
public ByteBlockSelection(List<ByteBlockRange> ranges) {
list = new ArrayList<>(ranges);
}
/**
* Construct an empty selection.
*/
public ByteBlockSelection() {
list = new ArrayList<ByteBlockRange>();
this(new ArrayList<>());
}
/**
* Constructor
* Construct a selection from an array of ranges
*
* @param ranges the ranges
*/
public ByteBlockSelection(ByteBlockRange[] ranges) {
List<ByteBlockRange> l = Arrays.asList(ranges);
list = new ArrayList<ByteBlockRange>(l);
this(Arrays.asList(ranges));
}
/**
* Add a range to the selection.
*
* @param range the range to add
*/
public void add(ByteBlockRange range) {
list.add(range);
@ -49,7 +62,7 @@ public class ByteBlockSelection {
/**
* Get the number of byte block ranges in this selection.
*
* @return int
* @return the number of (disjoint) ranges
*/
public int getNumberOfRanges() {
return list.size();
@ -57,6 +70,9 @@ public class ByteBlockSelection {
/**
* Get the byte block range at the given index.
*
* @param index the index of the range to get
* @return the requested range
*/
public ByteBlockRange getRange(int index) {
return list.get(index);

View File

@ -19,21 +19,23 @@ import java.math.BigInteger;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
import ghidra.program.model.address.AddressSet;
/**
* Interface to define methods for getting byte blocks and translating
* events.
* Interface to define methods for getting byte blocks and translating events.
*/
public interface ByteBlockSet {
/**
* Get the blocks in this set.
*
* @return the blocks
*/
public ByteBlock[] getBlocks();
/**
* Get a plugin event for the given block and offset.
*
* @param source source to use in the event
* @param block block to use to generate the event
* @param offset offset into the block
@ -45,6 +47,7 @@ public interface ByteBlockSet {
/**
* Get the appropriate plugin event for the given block selection.
*
* @param source source to use in the event
* @param selection selection to use to generate the event
* @return the event
@ -53,6 +56,7 @@ public interface ByteBlockSet {
/**
* Return true if the block has been changed at the given index.
*
* @param block byte block
* @param index offset into the block
* @param length number of bytes in question
@ -62,6 +66,7 @@ public interface ByteBlockSet {
/**
* Send a notification that a byte block edit occurred.
*
* @param block block being edited
* @param index offset into the block
* @param oldValue old byte values
@ -72,7 +77,14 @@ public interface ByteBlockSet {
/**
* Release resources that this object may be using.
*
*/
public void dispose();
/**
* Convert the byte block selection to the address set it covers
*
* @param selection the selection from the byte block perspective
* @return the selection from the address perspective
*/
public AddressSet getAddressSet(ByteBlockSelection selection);
}

View File

@ -50,9 +50,9 @@ public class FieldPanel extends JPanel
private boolean repaintPosted;
private boolean inFocus;
private BackgroundColorModel backgroundColorModel =
protected BackgroundColorModel backgroundColorModel =
new DefaultBackgroundColorModel(Color.WHITE);
private PaintContext paintContext = new PaintContext();
protected PaintContext paintContext = new PaintContext();
private AnchoredLayoutHandler layoutHandler;
private CursorHandler cursorHandler = new CursorHandler();
@ -1079,7 +1079,7 @@ public class FieldPanel extends JPanel
}
}
private LayoutBackgroundColorManager getLayoutSelectionMap(BigInteger layoutIndex) {
protected LayoutBackgroundColorManager getLayoutSelectionMap(BigInteger layoutIndex) {
Color backgroundColor = backgroundColorModel.getBackgroundColor(layoutIndex);
Color defaultBackColor = backgroundColorModel.getDefaultBackgroundColor();
boolean isDefault = backgroundColor.equals(defaultBackColor);

View File

@ -129,8 +129,8 @@ public class ColorUtils {
}
/**
* A method to produce a color (either black or white) that contrasts with the given color.
* This is useful for finding a readable foreground color for a given background.
* A method to produce a color (either black or white) that contrasts with the given color. This
* is useful for finding a readable foreground color for a given background.
*
* @param color the color for which to find a contrast.
* @return the contrasting color.
@ -158,9 +158,9 @@ public class ColorUtils {
}
/**
* Takes the first color, blending into it the second color, using the given ratio. A
* lower ratio (say .1f) signals to use very little of the first color; a larger ratio
* signals to use more of the first color.
* Takes the first color, blending into it the second color, using the given ratio. A lower
* ratio (say .1f) signals to use very little of the first color; a larger ratio signals to use
* more of the first color.
*
* @param c1 the first color
* @param c2 the second color
@ -185,4 +185,50 @@ public class ColorUtils {
return color;
}
/**
* Blender of colors
*/
public static class ColorBlender {
int r = 0;
int g = 0;
int b = 0;
int a = 0;
/**
* Add a color into the mixture, in a quantity proportional to its alpha value
*
* @param color the color to mix
*/
public void add(Color color) {
int ca = color.getAlpha();
a += ca;
r += ca * color.getRed();
g += ca * color.getGreen();
b += ca * color.getBlue();
}
/**
* Reset the mixture
*/
public void clear() {
r = 0;
g = 0;
b = 0;
a = 0;
}
/**
* Get the color of the current mixture
*
* @param defaultColor the default (background) color, if the mixture has no color
* @return the resulting color
*/
public Color getColor(Color defaultColor) {
if (a == 0) {
return defaultColor;
}
return new Color(r / a, g / a, b / a);
}
}
}