GP-80: Add Dynamic bytes (raw memory) viewer

This commit is contained in:
Dan 2021-09-27 10:40:40 -04:00
parent a1dba97a10
commit 48ba18306e
54 changed files with 5937 additions and 2330 deletions

View file

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

View file

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

View file

@ -32,13 +32,12 @@ import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.listener.*;
import docking.widgets.fieldpanel.support.*;
import ghidra.app.plugin.core.format.*;
import ghidra.program.model.address.AddressOutOfBoundsException;
import ghidra.program.model.address.*;
import ghidra.util.Msg;
/**
* FieldViewer to show data formatted according to the DataFormatModel that
* is passed in to the constructor. The source of the data is an array
* of ByteBlocks that is managed by an IndexMap.
* FieldViewer to show data formatted according to the DataFormatModel that is passed in to the
* constructor. The source of the data is an array of ByteBlocks that is managed by an IndexMap.
*/
public class ByteViewerComponent extends FieldPanel implements FieldMouseListener,
FieldLocationListener, FieldSelectionListener, FieldInputListener {
@ -70,14 +69,14 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
/**
* Constructor
* @param vpanel the byte viewer panel that this component lives in
*
* @param 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;
@ -742,9 +751,8 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
enableHelp();
}
/**
* Enable help for this component; used the model name as part of
* the help ID.
/**
* Enable help for this component; used the model name as part of the help ID.
*/
private void enableHelp() {
HelpService helpService = Help.getHelpService();
@ -814,7 +822,7 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
/**
* Create a byte block selection from the field selection.
*/
private ByteBlockSelection processFieldSelection(FieldSelection selection) {
protected ByteBlockSelection processFieldSelection(FieldSelection selection) {
ByteBlockSelection sel = new ByteBlockSelection();
int count = selection.getNumRanges();
@ -865,6 +873,17 @@ public class ByteViewerComponent extends FieldPanel implements FieldMouseListene
return null;
}
public AddressSetView getView() {
AddressSet result = new AddressSet();
if (blockSet != null) {
for (ByteBlock block : blockSet.getBlocks()) {
Address start = blockSet.getBlockStart(block);
result.add(start, start.add(block.getLength().longValue() - 1));
}
}
return result;
}
private class ByteViewerBackgroundColorModel implements BackgroundColorModel {
private Color defaultBackgroundColor = Color.WHITE;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,15 +33,15 @@ import ghidra.program.util.ProgramSelection;
/**
* ByteBlockSet implementation for a Program object.
*/
class ProgramByteBlockSet implements ByteBlockSet {
public class ProgramByteBlockSet implements ByteBlockSet {
private MemoryBlock[] memBlocks;
private Program program;
protected final Program program;
private ByteBlockChangeManager bbcm;
private ByteBlock[] blocks;
private final ProgramByteViewerComponentProvider provider;
ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program,
protected ProgramByteBlockSet(ProgramByteViewerComponentProvider provider, Program program,
ByteBlockChangeManager bbcm) {
this.provider = provider;
@ -53,7 +53,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
this.bbcm = new ByteBlockChangeManager(this, bbcm);
}
getMemoryBlocks();
newMemoryBlocks();
}
/**
@ -66,6 +66,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
/**
* Get the appropriate plugin event for the given block selection.
*
* @param source source to use in the event
* @param selection selection to use to generate the event
*/
@ -86,6 +87,7 @@ class ProgramByteBlockSet implements ByteBlockSet {
/**
* Get a plugin event for the given block and offset.
*
* @param source source to use in the event
* @param block block to use to generate the event
* @param offset offset into the block
@ -105,38 +107,54 @@ 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();
for (int i = 0; i < blocks.length; i++) {
Address blockStart = memBlocks[i].getStart();
Address blockEnd = memBlocks[i].getEnd();
AddressRange intersection =
range.intersect(new AddressRangeImpl(blockStart, blockEnd));
if (intersection != null) {
ByteBlockInfo startInfo = getByteBlockInfo(intersection.getMinAddress());
ByteBlockInfo endInfo = getByteBlockInfo(intersection.getMaxAddress());
ByteBlockRange br = new ByteBlockRange(startInfo.getBlock(),
startInfo.getOffset(), endInfo.getOffset());
list.add(br);
}
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();
AddressRange intersection =
range.intersect(new AddressRangeImpl(blockStart, blockEnd));
if (intersection != null) {
ByteBlockInfo startInfo = getByteBlockInfo(intersection.getMinAddress());
ByteBlockInfo endInfo = getByteBlockInfo(intersection.getMaxAddress());
ByteBlockRange br = new ByteBlockRange(startInfo.getBlock(),
startInfo.getOffset(), endInfo.getOffset());
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 block byte block
* @param index offset into the block
* @param length number of bytes in question
*/
@Override
@ -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,8 +243,9 @@ class ProgramByteBlockSet implements ByteBlockSet {
long off = address.subtract(memBlocks[i].getStart());
BigInteger offset =
(off < 0)
? BigInteger.valueOf(off + 0x8000000000000000L).subtract(
BigInteger.valueOf(0x8000000000000000L))
? BigInteger.valueOf(off + 0x8000000000000000L)
.subtract(
BigInteger.valueOf(0x8000000000000000L))
: BigInteger.valueOf(off);
return new ByteBlockInfo(blocks[i], offset);
}
@ -236,15 +256,15 @@ class ProgramByteBlockSet implements ByteBlockSet {
return null;
}
Address getBlockStart(ByteBlock block) {
protected Address getBlockStart(ByteBlock block) {
return getAddress(block, BigInteger.ZERO);
}
Address getBlockStart(int blockNumber) {
protected Address getBlockStart(int blockNumber) {
return memBlocks[blockNumber].getStart();
}
int getByteBlockNumber(Address blockStartAddr) {
protected int getByteBlockNumber(Address blockStartAddr) {
for (int i = 0; i < memBlocks.length; i++) {
if (memBlocks[i].getStart().compareTo(blockStartAddr) == 0) {
return i;
@ -253,8 +273,8 @@ class ProgramByteBlockSet implements ByteBlockSet {
return -1;
}
AddressSet getAddressSet(ByteBlockSelection selection) {
@Override
public AddressSet getAddressSet(ByteBlockSelection selection) {
AddressSet addrSet = new AddressSet();
for (int i = 0; i < selection.getNumberOfRanges(); i++) {
@ -267,15 +287,19 @@ class ProgramByteBlockSet implements ByteBlockSet {
return addrSet;
}
private void getMemoryBlocks() {
protected void newMemoryBlocks() {
Memory memory = program.getMemory();
memBlocks = program.getMemory().getBlocks();
memBlocks = memory.getBlocks();
blocks = new ByteBlock[memBlocks.length];
for (int i = 0; i < memBlocks.length; i++) {
blocks[i] = new MemoryByteBlock(program, memory, memBlocks[i]);
blocks[i] = newMemoryByteBlock(memory, memBlocks[i]);
}
}
protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
return new MemoryByteBlock(program, memory, memBlock);
}
@Override
public void dispose() {
// nothing to do?!?!?

View file

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

View file

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

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,46 +19,63 @@ 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>();
}
/**
* Constructor
*/
public ByteBlockSelection(ByteBlockRange[] ranges) {
List<ByteBlockRange> l = Arrays.asList(ranges);
list = new ArrayList<ByteBlockRange>(l);
}
/**
* Construct an empty selection.
*/
public ByteBlockSelection() {
this(new ArrayList<>());
}
/**
* Add a range to the selection.
*/
public void add(ByteBlockRange range) {
list.add(range);
}
/**
* Construct a selection from an array of ranges
*
* @param ranges the ranges
*/
public ByteBlockSelection(ByteBlockRange[] ranges) {
this(Arrays.asList(ranges));
}
/**
* Get the number of byte block ranges in this selection.
*
* @return int
*/
public int getNumberOfRanges() {
return list.size();
}
/**
* Add a range to the selection.
*
* @param range the range to add
*/
public void add(ByteBlockRange range) {
list.add(range);
}
/**
* Get the byte block range at the given index.
*/
public ByteBlockRange getRange(int index) {
return list.get(index);
}
/**
* Get the number of byte block ranges in this selection.
*
* @return the number of (disjoint) ranges
*/
public int getNumberOfRanges() {
return list.size();
}
/**
* Get the byte block range at the given index.
*
* @param index the index of the range to get
* @return the requested range
*/
public ByteBlockRange getRange(int index) {
return list.get(index);
}
}

View file

@ -19,32 +19,35 @@ 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
* @param column the column within the UI byte field
* @return the event
* @return the event
*/
public ProgramLocationPluginEvent getPluginEvent(String source, ByteBlock block,
BigInteger offset, int column);
/**
* 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,8 +56,9 @@ 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 block byte block
* @param index offset into the block
* @param length number of bytes in question
* @return true if changed
*/
@ -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);
}