mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-24 13:11:47 +00:00
Merge remote-tracking branch 'origin/GP-80_Dan_dynamicBytesViewer'
This commit is contained in:
commit
7671a4726d
@ -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());
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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(
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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()]);
|
||||
}
|
||||
|
||||
|
@ -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 ) );
|
||||
}
|
||||
|
@ -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) {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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++) {
|
||||
|
@ -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?!?!?
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user