GP-5886 Added ability to toggle the display of function variables in the listing.

This commit is contained in:
ghidragon 2025-09-11 13:43:40 -04:00
parent 1bc7bbfe8d
commit 8fc93d0d50
25 changed files with 1165 additions and 361 deletions

View file

@ -15,7 +15,7 @@
*/
package ghidra.app.plugin.core.debug.gui.listing;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.GROUP_TRANSIENT_VIEWS;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import java.util.List;
import java.util.function.Consumer;
@ -32,7 +32,8 @@ import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractNewListingAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.DebuggerProgramLocationActionContext;
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
import ghidra.app.services.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
@ -81,7 +82,8 @@ import ghidra.trace.model.program.TraceProgramView;
},
servicesProvided = {
DebuggerListingService.class,
})
}
)
public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerListingProvider>
implements DebuggerListingService {
private static final String KEY_CONNECTED_PROVIDER = "connectedProvider";
@ -450,6 +452,7 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
@Override
public void writeConfigState(SaveState saveState) {
super.writeConfigState(saveState);
SaveState connectedProviderState = new SaveState();
connectedProvider.writeConfigState(connectedProviderState);
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, connectedProviderState.saveToXml());
@ -470,6 +473,7 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
@Override
public void readConfigState(SaveState saveState) {
super.readConfigState(saveState);
Element connectedProviderElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
if (connectedProviderElement != null) {
SaveState connectedProviderState = new SaveState(connectedProviderElement);

View file

@ -100,7 +100,6 @@ data/typeinfo/golang/go1.20.0.json||GHIDRA||||END|
data/typeinfo/golang/go1.21.0.json||GHIDRA||||END|
data/typeinfo/golang/go1.22.0.json||GHIDRA||||END|
data/typeinfo/golang/go1.23.0.json||GHIDRA||||END|
data/typeinfo/golang/go1.23.2.json||GHIDRA||||END|
data/typeinfo/golang/go1.24.0.json||GHIDRA||||END|
data/typeinfo/golang/go1.25.0.json||GHIDRA||||END|
data/typeinfo/golang/golang_1.15_anybit_any.gdt||GHIDRA||||END|

View file

@ -108,6 +108,7 @@ color.bg.listing.comparison.code.units.unmatched = color.palette.lightskyblue
color.bg.listing.error = color.palette.lightcoral
font.listing.base = font.monospaced
font.listing.base.hidden.field = font.listing.base[italic]
font.listing.header = SansSerif-PLAIN-11

View file

@ -655,6 +655,29 @@
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Opening/Closing Function Variables Display</H2>
<BLOCKQUOTE>
The display of parameters and local variables for a function can be toggled on or off.
Normally, these are displayed just below the function signature, but they can be turned off
to conserve screen space. There is an open/close control (+/-)
on the first parameter/variable line that can be used to toggle them on or off.</P>
<H3>Actions for Opening/Closing Function Variables Display</H3>
<BLOCKQUOTE>
<P>In addition to the control widget in the display, there are also several actions that
can be used to control the display of function variables.</P>
<UL>
<LI><A name="Show_All_Variables"></A><B>Show/Hide All Variables</B> - This toggle
action can be used to globally control whether or not function variables are displayed.
Individual functions can still be toggled on or off, but this sets the default for all
functions.</LI>
<LI><A name="Show_Variables"></A><B>Show/Hide Variables</B> - This action toggles
the display of the variables. If they are showing, this will turn them off. And if they
are not showing, this action will turn them on. The default keybinding is
<CODE>Space</CODE> so if you are on a function or variable, pressing the space bar will
toggle the display state.</LI>
</UL>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="cursorTextHighlight"></A> Cursor Text Highlight</H2>
<BLOCKQUOTE>

View file

@ -181,6 +181,7 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
public P createNewDisconnectedProvider() {
P newProvider = createProvider(formatMgr.createClone(), false);
newProvider.setClipboardService(tool.getService(ClipboardService.class));
disconnectedProviders.add(newProvider);
if (dndProvider != null) {
newProvider.addProgramDropProvider(dndProvider);

View file

@ -274,12 +274,14 @@ public class CodeBrowserPlugin extends AbstractCodeBrowserPlugin<CodeViewerProvi
@Override
public void writeConfigState(SaveState saveState) {
super.writeConfigState(saveState);
formatMgr.saveState(saveState);
connectedProvider.saveState(saveState);
}
@Override
public void readConfigState(SaveState saveState) {
super.readConfigState(saveState);
formatMgr.readState(saveState);
connectedProvider.readState(saveState);
}

View file

@ -20,7 +20,8 @@ import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.awt.event.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import javax.swing.Icon;
@ -31,6 +32,7 @@ import javax.swing.event.ChangeListener;
import docking.*;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.actions.PopupActionProvider;
import docking.dnd.*;
import docking.widgets.EventTrigger;
@ -41,6 +43,7 @@ import docking.widgets.fieldpanel.support.*;
import docking.widgets.tab.GTabPanel;
import generic.theme.GIcon;
import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.nav.ListingPanelContainer;
import ghidra.app.nav.LocationMemento;
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
@ -68,6 +71,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
implements ProgramLocationListener, ProgramSelectionListener, Draggable, Droppable,
ChangeListener, StringSelectionListener, PopupActionProvider {
private static final String SHOW_FUNCITON_VARS_OPTIONS_NAME = "SHOW_FUNCITON_VARS";
private static final String OLD_NAME = "CodeBrowserPlugin";
private static final String NAME = "Listing";
private static final String TITLE = NAME + ": ";
@ -126,6 +130,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
private FieldNavigator fieldNavigator;
private MultiListingLayoutModel multiModel;
private ToggleDockingAction toggleVariablesAction;
public CodeViewerProvider(CodeBrowserPluginInterface plugin, FormatManager formatMgr,
boolean isConnected) {
@ -151,6 +156,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
listingPanel = new ListingPanel(formatMgr);
listingPanel.enablePropertyBasedColorModel(true);
decorationPanel = new ListingPanelContainer(listingPanel, isConnected);
ListingMiddleMouseHighlightProvider listingHighlighter =
createListingHighlighter(listingPanel, tool, decorationPanel);
@ -429,6 +435,11 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
updateTitle();
listingPanel.setProgram(program);
ListingModel listingModel = listingPanel.getListingModel();
if (listingModel != null) {
boolean shouldShowVariables = toggleVariablesAction.isSelected();
listingModel.setAllFunctionVariablesOpen(shouldShowVariables);
}
codeViewerClipboardProvider.setProgram(program);
codeViewerClipboardProvider.setListingLayoutModel(listingPanel.getListingModel());
if (coordinatedListingPanelListener != null) {
@ -470,17 +481,67 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
action = new GotoNextFunctionAction(tool, plugin.getName());
tool.addAction(action);
toggleVariablesAction = new ToggleActionBuilder("Show Function Variables", plugin.getName())
.popupMenuPath("Show/Hide All Variables")
.popupMenuGroup("Variables")
.helpLocation(new HelpLocation("CodeBrowserPlugin", "Show_All_Variables"))
.selected(true)
.withContext(ProgramLocationActionContext.class)
.enabledWhen(this::isInFunctionArea)
.onAction(c -> showVariablesForAllFunctions(toggleVariablesAction.isSelected()))
.buildAndInstallLocal(this);
new ActionBuilder("Toggle Show Function Variables", plugin.getName())
.popupMenuPath("Show/Hide Variables")
.popupMenuGroup("Variables")
.helpLocation(new HelpLocation("CodeBrowserPlugin", "Show_Variables"))
.keyBinding("SPACE")
.withContext(ProgramLocationActionContext.class)
.enabledWhen(this::isInFunctionArea)
.onAction(c -> toggleShowVariables(c.getAddress()))
.buildAndInstallLocal(this);
buildQuickTogleFieldActions();
}
private void toggleShowVariables(Address address) {
ListingModel model = listingPanel.getListingModel();
boolean open = model.areFunctionVariablesOpen(address);
model.setAllFunctionVariablesOpen(!open);
setLocation(new VariablesOpenCloseLocation(program, address));
}
private void showVariablesForAllFunctions(boolean selected) {
ListingModel model = listingPanel.getListingModel();
model.setAllFunctionVariablesOpen(selected);
}
private boolean isInFunctionArea(ProgramLocationActionContext context) {
ProgramLocation location = context.getLocation();
return location instanceof FunctionLocation ||
location instanceof VariablesOpenCloseLocation;
}
private void buildQuickTogleFieldActions() {
List<String> quickToggleFieldNames = formatMgr.getQuickToggleFieldNames();
int count = 0;
for (String fieldName : quickToggleFieldNames) {
DockingAction toggleAction = new ActionBuilder("Toggle " + fieldName, plugin.getName())
String keyBinding = null;
if (count < 5) {
char c = (char) ('1' + count);
keyBinding = "control shift " + c;
}
else {
Msg.debug(this,
"Excessive Field Toggle actions . No keybinding assigned for field: " +
fieldName);
}
new ActionBuilder("Toggle " + fieldName, plugin.getName())
.popupMenuPath("Toggle Field", fieldName)
.popupMenuGroup("Field", "" + count)
.keyBinding(keyBinding)
.helpLocation(new HelpLocation("CodeBrowserPlugin", "Toggle_Field"))
// only show this action when over the listing field header
.popupWhen(c -> c.getContextObject() instanceof FieldHeaderLocation)
@ -488,19 +549,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
.buildAndInstallLocal(this);
// automatically assign keybindings to the first 5 toggle fields.
if (count < 5) {
char c = (char) ('1' + count);
toggleAction.setKeyBindingData(
new KeyBindingData(c, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
}
else {
Msg.debug(this,
"Excessive Field Toggle actions . No keybinding assigned for field: " +
fieldName);
}
count++;
}
tool.setMenuGroup(new String[] { "Toggle Field" }, "Disassembly");
}
public ListingPanel getListingPanel() {
@ -820,12 +872,19 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
void saveState(SaveState saveState) {
saveState.putInt(DIVIDER_LOCATION, getListingPanel().getDividerLocation());
saveState.putBoolean(HOVER_MODE, toggleHoverAction.isSelected());
saveState.putBoolean(SHOW_FUNCITON_VARS_OPTIONS_NAME, toggleVariablesAction.isSelected());
}
void readState(SaveState saveState) {
getListingPanel().setDividerLocation(
saveState.getInt(DIVIDER_LOCATION, ListingPanel.DEFAULT_DIVIDER_LOCATION));
toggleHoverAction.setSelected(saveState.getBoolean(HOVER_MODE, true));
boolean showVariables = saveState.getBoolean(SHOW_FUNCITON_VARS_OPTIONS_NAME, true);
toggleVariablesAction.setSelected(showVariables);
ListingModel listingModel = listingPanel.getListingModel();
if (listingModel != null) {
listingModel.setAllFunctionVariablesOpen(showVariables);
}
}
private void setHoverEnabled(boolean enabled) {
@ -951,9 +1010,12 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
// (its done in an invoke later)
Swing.runLater(() -> {
newProvider.doSetProgram(program);
SaveState saveState = new SaveState();
saveState(saveState);
newProvider.readState(saveState);
newProvider.setLocation(currentLocation);
newProvider.listingPanel.getFieldPanel()
.setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset());
newProvider.setLocation(currentLocation);
});
}

View file

@ -0,0 +1,258 @@
/* ###
* 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.util.viewer.field;
import java.awt.*;
import docking.widgets.fieldpanel.support.*;
import generic.theme.GIcon;
import ghidra.app.util.viewer.proxy.EmptyProxy;
import ghidra.app.util.viewer.proxy.ProxyObj;
/**
* FactoryField class for displaying the open/close field.
*/
public abstract class AbstractOpenCloseField implements ListingField {
protected static final GIcon OPEN_ICON =
new GIcon("icon.base.util.viewer.fieldfactory.openclose.open");
protected static final GIcon CLOSED_ICON =
new GIcon("icon.base.util.viewer.fieldfactory.openclose.closed");
private FieldFactory factory;
protected int startX;
protected int startY;
protected int fieldWidth;
protected int heightAbove;
protected int heightBelow;
protected ProxyObj<?> proxy;
protected boolean isOpen;
protected int toggleHandleSize;
/**
* Constructor
* @param factory the FieldFactory that created this field.
* @param proxy the object associated with this field.
* @param metrics the FontMetrics used to render this field.
* @param x the starting x position of this field.
* @param width the width of this field.
*/
public AbstractOpenCloseField(FieldFactory factory, ProxyObj<?> proxy,
FontMetrics metrics, int x, int width) {
this.factory = factory;
this.proxy = proxy;
this.fieldWidth = width;
this.startX = x;
this.heightAbove = metrics.getAscent();
this.heightBelow = metrics.getLeading() + metrics.getDescent();
this.toggleHandleSize = AbstractOpenCloseField.getOpenCloseHandleSize();
}
@Override
public FieldFactory getFieldFactory() {
return factory;
}
@Override
public ProxyObj<?> getProxy() {
if (proxy == null) {
return EmptyProxy.EMPTY_PROXY;
}
return proxy;
}
@Override
public int getHeightAbove() {
return heightAbove;
}
@Override
public int getHeightBelow() {
return heightBelow;
}
/**
* Sets the yPos relative to the overall layout.
* @param yPos the starting Y position of the layout row.
* @param heightAbove the heightAbove the alignment line in the layout row.
* @param heightBelow the heightBelow the alignment line in the layout row.
*/
public void setYPos(int yPos, int heightAbove, int heightBelow) {
this.startY = yPos;
this.heightAbove = heightAbove;
this.heightBelow = heightBelow;
}
@Override
public int getPreferredWidth() {
return getWidth(); // does the width of this field vary?
}
@Override
public int getHeight() {
return heightAbove + heightBelow;
}
@Override
public int getStartX() {
return startX;
}
/**
* Returns the vertical position of this field.
* @return the position
*/
public int getStartY() {
return startY;
}
/**
* Sets the starting vertical position of this field.
* @param startY the starting vertical position.
*/
public void setStartY(int startY) {
this.startY = startY;
}
protected void paintCursor(Graphics g, Color cursorColor, RowColLocation cursorLoc) {
if (cursorLoc != null) {
g.setColor(cursorColor);
Rectangle cursorBounds = getCursorBounds(cursorLoc.row(), cursorLoc.col());
g.fillRect(cursorBounds.x, cursorBounds.y, cursorBounds.width, cursorBounds.height);
}
}
@Override
public boolean contains(int x, int y) {
if ((x < startX) || (x >= startX + fieldWidth) || (y < startY) ||
(y >= startY + heightAbove + heightBelow)) {
return false;
}
return true;
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
public int getNumRows() {
return 1;
}
@Override
public int getNumCols(int row) {
return 0;
}
@Override
public int getX(int row, int col) {
return startX;
}
@Override
public int getY(int row) {
return startY;
}
@Override
public int getRow(int y) {
return 0;
}
@Override
public int getCol(int row, int x) {
return 0;
}
@Override
public boolean isValid(int row, int col) {
return ((row == 0) && (col == 0));
}
@Override
public Rectangle getCursorBounds(int row, int col) {
if (!isValid(row, col)) {
return null;
}
return new Rectangle(startX, -heightAbove, 2, heightAbove + heightBelow);
}
@Override
public int getScrollableUnitIncrement(int topOfScreen, int direction, int max) {
if ((topOfScreen < startY) || (topOfScreen > startY + heightAbove + heightBelow)) {
return max;
}
if (direction > 0) { // if scrolling down
return heightAbove + heightBelow - (topOfScreen - startY);
}
return startY - topOfScreen;
}
@Override
public boolean isPrimary() {
return false;
}
@Override
public void rowHeightChanged(int newHeightAbove, int newHeightBelow) {
this.heightAbove = newHeightAbove;
this.heightBelow = newHeightBelow;
}
@Override
public String getText() {
return "";
}
@Override
public String getTextWithLineSeparators() {
return "";
}
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
return new DefaultRowColLocation();
}
@Override
public int screenLocationToTextOffset(int row, int col) {
return 0;
}
@Override
public Object getClickedObject(FieldLocation fieldLocation) {
return this;
}
/**
* Toggles the open state of this field.
*/
public abstract void toggleOpenCloseState();
//==================================================================================================
// Static Methods
//==================================================================================================
static int getOpenCloseHandleSize() {
return OPEN_ICON.getIconWidth();
}
}

View file

@ -68,7 +68,7 @@ public class IndentField implements ListingField {
// this class is dependent upon the OpenClosedField in that they work together to perform
// painting
toggleHandleSize = OpenCloseField.getOpenCloseHandleSize();
toggleHandleSize = AbstractOpenCloseField.getOpenCloseHandleSize();
}
/**

View file

@ -15,41 +15,25 @@
*/
package ghidra.app.util.viewer.field;
import static ghidra.app.util.viewer.field.AbstractOpenCloseField.*;
import java.awt.*;
import javax.swing.JComponent;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager;
import docking.widgets.fieldpanel.internal.PaintContext;
import docking.widgets.fieldpanel.support.*;
import generic.theme.GIcon;
import docking.widgets.fieldpanel.support.RowColLocation;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.util.viewer.proxy.EmptyProxy;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.program.model.listing.Data;
/**
* FactoryField class for displaying the open/close field.
*/
public class OpenCloseField implements ListingField {
private static final GIcon OPEN_ICON =
new GIcon("icon.base.util.viewer.fieldfactory.openclose.open");
private static final GIcon CLOSED_ICON =
new GIcon("icon.base.util.viewer.fieldfactory.openclose.closed");
private FieldFactory factory;
private int startX;
private int startY;
private int fieldWidth;
private int heightAbove;
private int heightBelow;
private ProxyObj<?> proxy;
private boolean isOpen;
public class OpenCloseField extends AbstractOpenCloseField {
private int indentLevel;
private boolean isLast;
private int toggleHandleSize;
private int insetSpace = 1;
/**
@ -64,51 +48,10 @@ public class OpenCloseField implements ListingField {
*/
public OpenCloseField(FieldFactory factory, ProxyObj<?> proxy, int indentLevel,
FontMetrics metrics, int x, int width, boolean isLast) {
this.factory = factory;
this.proxy = proxy;
super(factory, proxy, metrics, x, width);
this.isOpen = proxy.getListingLayoutModel().isOpen((Data) proxy.getObject());
this.fieldWidth = width;
this.startX = x;
this.indentLevel = indentLevel;
this.isLast = isLast;
this.heightAbove = metrics.getAscent();
this.heightBelow = metrics.getLeading() + metrics.getDescent();
this.toggleHandleSize = OpenCloseField.getOpenCloseHandleSize();
}
@Override
public FieldFactory getFieldFactory() {
return factory;
}
@Override
public ProxyObj<?> getProxy() {
if (proxy == null) {
return EmptyProxy.EMPTY_PROXY;
}
return proxy;
}
@Override
public int getHeightAbove() {
return heightAbove;
}
@Override
public int getHeightBelow() {
return heightBelow;
}
/**
* Sets the yPos relative to the overall layout.
* @param yPos the starting Y position of the layout row.
* @param heightAbove the heightAbove the alignment line in the layout row.
* @param heightBelow the heightBelow the alignment line in the layout row.
*/
public void setYPos(int yPos, int heightAbove, int heightBelow) {
this.startY = yPos;
this.heightAbove = heightAbove;
this.heightBelow = heightBelow;
}
@Override
@ -116,37 +59,6 @@ public class OpenCloseField implements ListingField {
return (indentLevel + 1) * fieldWidth;
}
@Override
public int getPreferredWidth() {
return getWidth(); // does the width of this field vary?
}
@Override
public int getHeight() {
return heightAbove + heightBelow;
}
@Override
public int getStartX() {
return startX;
}
/**
* Returns the vertical position of this field.
* @return the position
*/
public int getStartY() {
return startY;
}
/**
* Sets the starting vertical position of this field.
* @param startY the starting vertical position.
*/
public void setStartY(int startY) {
this.startY = startY;
}
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc,
@ -211,132 +123,11 @@ public class OpenCloseField implements ListingField {
paintCursor(g, context.getCursorColor(), cursorLoc);
}
private void paintCursor(Graphics g, Color cursorColor, RowColLocation cursorLoc) {
if (cursorLoc != null) {
g.setColor(cursorColor);
Rectangle cursorBounds = getCursorBounds(cursorLoc.row(), cursorLoc.col());
g.fillRect(cursorBounds.x, cursorBounds.y, cursorBounds.width, cursorBounds.height);
}
}
@Override
public boolean contains(int x, int y) {
if ((x < startX) || (x >= startX + fieldWidth) || (y < startY) ||
(y >= startY + heightAbove + heightBelow)) {
return false;
}
return true;
}
@Override
public int getNumDataRows() {
return 1;
}
@Override
public int getNumRows() {
return 1;
}
@Override
public int getNumCols(int row) {
return 0;
}
@Override
public int getX(int row, int col) {
return startX;
}
@Override
public int getY(int row) {
return startY;
}
@Override
public int getRow(int y) {
return 0;
}
@Override
public int getCol(int row, int x) {
return 0;
}
@Override
public boolean isValid(int row, int col) {
return ((row == 0) && (col == 0));
}
@Override
public Rectangle getCursorBounds(int row, int col) {
if (!isValid(row, col)) {
return null;
}
return new Rectangle(startX, -heightAbove, 2, heightAbove + heightBelow);
}
@Override
public int getScrollableUnitIncrement(int topOfScreen, int direction, int max) {
if ((topOfScreen < startY) || (topOfScreen > startY + heightAbove + heightBelow)) {
return max;
}
if (direction > 0) { // if scrolling down
return heightAbove + heightBelow - (topOfScreen - startY);
}
return startY - topOfScreen;
}
@Override
public boolean isPrimary() {
return false;
}
@Override
public void rowHeightChanged(int newHeightAbove, int newHeightBelow) {
this.heightAbove = newHeightAbove;
this.heightBelow = newHeightBelow;
}
@Override
public String getText() {
return "";
}
@Override
public String getTextWithLineSeparators() {
return "";
}
@Override
public RowColLocation textOffsetToScreenLocation(int textOffset) {
return new DefaultRowColLocation();
}
@Override
public int screenLocationToTextOffset(int row, int col) {
return 0;
}
@Override
public Object getClickedObject(FieldLocation fieldLocation) {
return this;
}
/**
* Toggles the open state of this field.
*/
@Override
public void toggleOpenCloseState() {
proxy.getListingLayoutModel().toggleOpen((Data) proxy.getObject());
}
//==================================================================================================
// Static Methods
//==================================================================================================
static int getOpenCloseHandleSize() {
return OPEN_ICON.getIconWidth();
}
}

View file

@ -15,14 +15,15 @@
*/
package ghidra.app.util.viewer.field;
import java.awt.event.MouseEvent;
import ghidra.app.nav.Navigatable;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.util.ProgramLocation;
import java.awt.event.MouseEvent;
public class OpenCloseFieldMouseHandler implements FieldMouseHandlerExtension {
private final static Class<?>[] SUPPORTED_CLASSES = new Class[] { OpenCloseField.class };
private final static Class<?>[] SUPPORTED_CLASSES =
new Class[] { OpenCloseField.class, VariableOpenCloseField.class };
@Override
public boolean fieldElementClicked(Object clickedObject, Navigatable sourceNavigatable,
@ -32,7 +33,7 @@ public class OpenCloseFieldMouseHandler implements FieldMouseHandlerExtension {
return false;
}
OpenCloseField field = (OpenCloseField) clickedObject;
AbstractOpenCloseField field = (AbstractOpenCloseField) clickedObject;
field.toggleOpenCloseState();
return true;
}

View file

@ -0,0 +1,109 @@
/* ###
* 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.util.viewer.field;
import java.awt.*;
import javax.swing.JComponent;
import docking.widgets.fieldpanel.internal.FieldBackgroundColorManager;
import docking.widgets.fieldpanel.internal.PaintContext;
import docking.widgets.fieldpanel.support.RowColLocation;
import generic.theme.GThemeDefaults.Colors.Palette;
import generic.theme.Gui;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.app.util.viewer.proxy.VariableProxy;
import ghidra.program.model.address.Address;
/**
* FactoryField class for displaying the open/close field widget for function variables.
*/
public class VariableOpenCloseField extends AbstractOpenCloseField {
private static final Font HIDDEN_FONT = Gui.getFont("font.listing.base.hidden.field");
/**
* Constructor
* @param factory the FieldFactory that created this field.
* @param proxy the object associated with this field.
* @param metrics the FontMetrics used to render this field.
* @param x the starting x position of this field.
* @param width the width of this field.
*/
public VariableOpenCloseField(FieldFactory factory, ProxyObj<?> proxy,
FontMetrics metrics, int x, int width) {
super(factory, proxy, metrics, x, width);
if (proxy instanceof VariableProxy variableProxy) {
Address functionAddress = variableProxy.getFunctionAddress();
this.isOpen = proxy.getListingLayoutModel().areFunctionVariablesOpen(functionAddress);
}
}
@Override
public int getWidth() {
if (isOpen) {
return fieldWidth;
}
return fieldWidth + 200;
}
@Override
public void paint(JComponent c, Graphics g, PaintContext context,
Rectangle clip, FieldBackgroundColorManager map, RowColLocation cursorLoc,
int rowHeight) {
// center in the heightAbove area (negative, since 0 is the baseline of text, which is at
// the bottom of the heightAbove)
int toggleHandleStartY = -((heightAbove / 2) + (toggleHandleSize / 2));
int toggleHandleStartX = startX;
// If we're in printing mode, trying to render these open/close images
// causes the JVM to bomb. We'd like to eventually figure out why but in
// the meantime we can safely comment this out and still generate an acceptable
// image.
//
if (!context.isPrinting()) {
if (isOpen) {
g.drawImage(OPEN_ICON.getImageIcon().getImage(), toggleHandleStartX,
toggleHandleStartY, context.getBackground(), null);
}
else {
g.drawImage(CLOSED_ICON.getImageIcon().getImage(), toggleHandleStartX,
toggleHandleStartY, context.getBackground(), null);
}
}
g.setColor(Palette.LIGHT_GRAY);
if (!isOpen) {
Font font = g.getFont();
g.setFont(HIDDEN_FONT);
g.drawString("Variables", startX + fieldWidth + 10, 0);
g.setFont(font);
}
paintCursor(g, context.getCursorColor(), cursorLoc);
}
/**
* Toggles the open state of this field.
*/
@Override
public void toggleOpenCloseState() {
if (proxy instanceof VariableProxy variableProxy) {
Address functionAddress = variableProxy.getFunctionAddress();
proxy.getListingLayoutModel().setFunctionVariablesOpen(functionAddress, !isOpen);
}
}
}

View file

@ -0,0 +1,114 @@
/* ###
* 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.util.viewer.field;
import java.math.BigInteger;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.util.ListingHighlightProvider;
import ghidra.app.util.viewer.format.FieldFormatModel;
import ghidra.app.util.viewer.proxy.ProxyObj;
import ghidra.app.util.viewer.proxy.VariableProxy;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.Variable;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.VariablesOpenCloseLocation;
/**
* Generates Open/Close Fields for variables under functions.
*/
public class VariablesOpenCloseFieldFactory extends FieldFactory {
public static final String FIELD_NAME = "+";
public VariablesOpenCloseFieldFactory() {
super(FIELD_NAME);
}
/**
* Constructor
* @param model the model that the field belongs to.
* @param hlProvider the HightLightStringProvider.
* @param displayOptions the Options for display properties.
* @param fieldOptions the Options for field specific properties.
*/
private VariablesOpenCloseFieldFactory(FieldFormatModel model,
ListingHighlightProvider hlProvider,
Options displayOptions, Options fieldOptions) {
super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions);
servicesChanged();
}
/**
* Returns the FactoryField for the given object at index index.
* @param varWidth the amount of variable width spacing for any fields
* before this one.
* @param proxy the object whose properties should be displayed.
*/
@Override
public ListingField getField(ProxyObj<?> proxy, int varWidth) {
if (!enabled) {
return null;
}
if (proxy instanceof VariableProxy variableProxy) {
if (variableProxy.isFirst()) {
return new VariableOpenCloseField(this, proxy, getMetrics(), startX + varWidth,
width);
}
}
return null;
}
@Override
public ProgramLocation getProgramLocation(int row, int col, ListingField bf) {
ProxyObj<?> proxy = bf.getProxy();
if (proxy instanceof VariableProxy variableProxy) {
Program program = variableProxy.getProgram();
Address functionAddress = variableProxy.getFunctionAddress();
return new VariablesOpenCloseLocation(program, functionAddress);
}
return null;
}
@Override
public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum,
ProgramLocation programLoc) {
if (!(programLoc instanceof VariablesOpenCloseLocation)) {
return null;
}
return new FieldLocation(index, fieldNum, 0, 0);
}
@Override
public boolean acceptsType(int category, Class<?> proxyObjectClass) {
if (!Variable.class.isAssignableFrom(proxyObjectClass)) {
return false;
}
return (category == FieldFormatModel.FUNCTION_VARS);
}
@Override
public FieldFactory newInstance(FieldFormatModel fieldModel, ListingHighlightProvider provider,
ToolOptions displayOptions, ToolOptions fieldOptions) {
return new VariablesOpenCloseFieldFactory(fieldModel, provider, displayOptions,
fieldOptions);
}
}

View file

@ -516,7 +516,13 @@ public class FormatManager implements OptionsChangeListener {
Element rowElem = new Element("ROW");
Element colElem = new Element("FIELD");
colElem.setAttribute("WIDTH", "90");
colElem.setAttribute("NAME", "+");
colElem.setAttribute("WIDTH", "20");
colElem.setAttribute("ENABLED", "true");
rowElem.addContent(colElem);
colElem = new Element("FIELD");
colElem.setAttribute("WIDTH", "70");
colElem.setAttribute("ENABLED", "true");
rowElem.addContent(colElem);

View file

@ -73,6 +73,21 @@ public class EmptyListingModel implements ListingModel {
// stub
}
@Override
public void setFunctionVariablesOpen(Address functionAddress, boolean open) {
// stub
}
@Override
public void setAllFunctionVariablesOpen(boolean open) {
// stub
}
@Override
public boolean areFunctionVariablesOpen(Address FunctionAddress) {
return false;
}
@Override
public void openAllData(Data data, TaskMonitor monitor) {
// stub

View file

@ -15,6 +15,7 @@
*/
package ghidra.app.util.viewer.listingpanel;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.Layout;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.framework.options.Options;
@ -23,6 +24,9 @@ import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
/**
* Model displaying program data in a {@link FieldPanel}
*/
public interface ListingModel {
static final String FUNCTION_POINTER_OPTION_GROUP_NAME = "Function Pointers";
@ -42,6 +46,9 @@ public interface ListingModel {
public Layout getLayout(Address address, boolean isGapAddress);
/**
* {@return the width of the longest layout this model can produce.}
*/
public int getMaxWidth();
/**
@ -59,6 +66,28 @@ public interface ListingModel {
*/
public void toggleOpen(Data data);
/**
* Sets whether or not to display function variables for the function at the given address.
* @param FunctionAddress the address of the function
* @param open if true, the variables are displayed, otherwise they are hidden
*/
public void setFunctionVariablesOpen(Address FunctionAddress, boolean open);
/**
* Checks if the function variables are being displayed at the given address
* @param FunctionAddress the address of the function
* @return true if the variables are being displayed for the function at the given address
*/
public boolean areFunctionVariablesOpen(Address FunctionAddress);
/**
* Sets the display of variables for all functions. This basically sets the default state,
* but the state can be overridden for individual functions. Changing this value erases all
* individually set values.
* @param open if true, show function variables
*/
public void setAllFunctionVariablesOpen(boolean open);
/**
* Opens the given data, but not any sub-components.
*
@ -106,18 +135,45 @@ public interface ListingModel {
*/
public void closeAllData(AddressSetView addresses, TaskMonitor monitor);
/**
* Adds a listener for changes to this model.
* @param listener the listener to be notified
*/
public void addListener(ListingModelListener listener);
/**
* Removes a listener from those being notified of model changes.
* @param listener the listener to be removed
*/
public void removeListener(ListingModelListener listener);
/**
* {@return the program being displayed by this model.}
*/
public Program getProgram();
/**
* {@return true if the program being displayed by this listing has been closed (and therefor
* the model is invalid.)}
*/
public boolean isClosed();
/**
* Sets the {@link FormatManager} for this model which determines the layout of the fields.
* @param formatManager the new FormatManager to use
*/
public void setFormatManager(FormatManager formatManager);
/**
* Disposes this model
*/
public void dispose();
/**
* Adjusts each range in the given address set to be on code unit boundaries.
* @param addressSet the address set to be adjusted
* @return a new AddressSet where each range is on a code unit boundary
*/
public AddressSet adjustAddressSetToCodeUnitBoundaries(AddressSet addressSet);
/**

View file

@ -27,7 +27,7 @@ import ghidra.app.util.viewer.field.DummyFieldFactory;
import ghidra.app.util.viewer.field.ListingField;
import ghidra.app.util.viewer.format.*;
import ghidra.app.util.viewer.proxy.*;
import ghidra.app.util.viewer.util.OpenCloseManager;
import ghidra.app.util.viewer.util.ProgramOpenCloseManager;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.options.OptionsChangeListener;
@ -43,7 +43,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
DomainObjectListener, ChangeListener, OptionsChangeListener {
protected final Program program;
private OpenCloseManager openCloseMgr = new OpenCloseManager();
private ProgramOpenCloseManager openCloseMgr = new ProgramOpenCloseManager();
private FormatManager formatMgr;
private ToolOptions fieldOptions;
private boolean showExternalFunctionPointerFormat;
@ -172,17 +172,17 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
if (function != null) {
format = formatMgr.getFunctionFormat();
format.addLayouts(list, 0, new FunctionProxy(this, program, addr, function));
Parameter[] params = function.getParameters();
format = formatMgr.getFunctionVarFormat();
format.addLayouts(list, 0,
new VariableProxy(this, program, addr, function, function.getReturn()));
for (Parameter param : params) {
format.addLayouts(list, 0, new VariableProxy(this, program, addr, function, param));
boolean variablesOpen = openCloseMgr.isFunctionVariablesOpen(function.getEntryPoint());
if (variablesOpen) {
addReturn(addr, list, format, function);
addParameters(addr, list, format, function);
addLocals(addr, list, format, function);
}
Variable[] vars = function.getLocalVariables();
for (Variable var : vars) {
format.addLayouts(list, 0, new VariableProxy(this, program, addr, function, var));
else {
format.addLayouts(list, 0, new ClosedVariableProxy(this, program, addr, function));
}
}
if (cu != null) {
format = formatMgr.getCodeUnitFormat();
@ -213,6 +213,30 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
}
private void addReturn(Address addr, List<RowLayout> list, FieldFormatModel format,
Function function) {
format.addLayouts(list, 0,
new VariableProxy(this, program, addr, function, function.getReturn(), true));
}
private void addLocals(Address addr, List<RowLayout> list, FieldFormatModel format,
Function function) {
Variable[] vars = function.getLocalVariables();
for (Variable var : vars) {
format.addLayouts(list, 0,
new VariableProxy(this, program, addr, function, var, false));
}
}
private void addParameters(Address addr, List<RowLayout> list, FieldFormatModel format,
Function function) {
Parameter[] params = function.getParameters();
for (Parameter param : params) {
format.addLayouts(list, 0,
new VariableProxy(this, program, addr, function, param, false));
}
}
private Function getPointerReferencedFunction(Data data) {
Reference ref = data.getPrimaryReference(0);
@ -239,7 +263,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
if (cu instanceof Data) {
Data data = (Data) cu;
if (data.getNumComponents() > 0) {
if (openCloseMgr.isOpen(data.getMinAddress())) {
if (openCloseMgr.isDataOpen(data.getMinAddress())) {
Address openAddr = findOpenDataAfter(address, data);
if (openAddr != null) {
return openAddr;
@ -255,8 +279,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
Data data;
DataType dt = parent.getBaseDataType();
if (dt instanceof Union) {
int index =
openCloseMgr.getOpenIndex(parent.getMinAddress(), parent.getComponentPath());
int index = openCloseMgr.getOpenDataIndex(parent);
if (index < 0) {
return null;
}
@ -288,7 +311,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
return null;
}
if (data.getNumComponents() > 0) {
if (openCloseMgr.isOpen(data.getMinAddress(), data.getComponentPath())) {
if (openCloseMgr.isDataOpen(data)) {
Address openAddr = findOpenDataAfter(address, data);
if (openAddr != null) {
return openAddr;
@ -344,7 +367,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
if (cu instanceof Data) {
Data data = (Data) cu;
if (data.getNumComponents() > 0) {
if (openCloseMgr.isOpen(data.getMinAddress())) {
if (openCloseMgr.isDataOpen(data.getMinAddress())) {
return true;
}
}
@ -359,7 +382,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
Data data;
if (parent.getBaseDataType() instanceof Union) {
int index =
openCloseMgr.getOpenIndex(parent.getMinAddress(), parent.getComponentPath());
openCloseMgr.getOpenDataIndex(parent);
if (index < 0) {
return null;
}
@ -376,7 +399,7 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
}
if (data.getNumComponents() > 0) {
if (openCloseMgr.isOpen(data.getMinAddress(), data.getComponentPath())) {
if (openCloseMgr.isDataOpen(data)) {
Address openAddr = findOpenDataBefore(addr, data);
if (openAddr != null) {
return openAddr;
@ -396,11 +419,11 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
private void addOpenData(List<Data> list, Data data, Address addr) {
Address dataAddr = data.getMinAddress();
if (openCloseMgr.isOpen(dataAddr, data.getComponentPath())) {
if (openCloseMgr.isDataOpen(data)) {
DataType dt = data.getBaseDataType();
if (dt instanceof Union) {
int openIndex =
openCloseMgr.getOpenIndex(data.getMinAddress(), data.getComponentPath());
openCloseMgr.getOpenDataIndex(data);
int numComps = ((Union) dt).getNumComponents();
if (openIndex < 0) {
openIndex = numComps;
@ -437,8 +460,8 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
DataType dt = data.getBaseDataType();
if (dt instanceof Union) {
Address dataAddr = data.getMinAddress();
if (openCloseMgr.isOpen(dataAddr, data.getComponentPath())) {
int openIndex = openCloseMgr.getOpenIndex(dataAddr, data.getComponentPath());
if (openCloseMgr.isDataOpen(data)) {
int openIndex = openCloseMgr.getOpenDataIndex(data);
int i = openIndex;
int numComps = ((Union) dt).getNumComponents();
if (i < 0) {
@ -459,22 +482,37 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
@Override
public boolean isOpen(Data data) {
return openCloseMgr.isOpen(data);
return openCloseMgr.isDataOpen(data);
}
@Override
public void toggleOpen(Data data) {
openCloseMgr.toggleOpen(data);
openCloseMgr.toggleDataOpen(data);
}
@Override
public void setFunctionVariablesOpen(Address functionAddress, boolean open) {
openCloseMgr.setFunctionVariablesOpen(functionAddress, open);
}
@Override
public void setAllFunctionVariablesOpen(boolean open) {
openCloseMgr.setAllFunctionVariablesOpen(open);
}
@Override
public boolean areFunctionVariablesOpen(Address FunctionAddress) {
return openCloseMgr.isFunctionVariablesOpen(FunctionAddress);
}
@Override
public void openAllData(Data data, TaskMonitor monitor) {
openCloseMgr.openAllData(data, monitor);
openCloseMgr.openDataRecursively(data, monitor);
}
@Override
public void closeAllData(Data data, TaskMonitor monitor) {
openCloseMgr.closeAllData(data, monitor);
openCloseMgr.closeDataRecursively(data, monitor);
}
@Override
@ -494,8 +532,8 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener
@Override
public boolean openData(Data data) {
return openCloseMgr.openData(data);
openCloseMgr.openData(data);
return true;
}
protected void notifyDataChanged(boolean updateImmediately) {

View file

@ -177,6 +177,21 @@ public class ListingModelConverter implements ListingModel {
model.toggleOpen(data);
}
@Override
public void setFunctionVariablesOpen(Address functionAddress, boolean open) {
model.setFunctionVariablesOpen(functionAddress, open);
}
@Override
public void setAllFunctionVariablesOpen(boolean open) {
model.setAllFunctionVariablesOpen(open);
}
@Override
public boolean areFunctionVariablesOpen(Address FunctionAddress) {
return model.areFunctionVariablesOpen(FunctionAddress);
}
@Override
public AddressSet adjustAddressSetToCodeUnitBoundaries(AddressSet addressSet) {
AddressSet compatibleAddressSet = DiffUtility.getCompatibleAddressSet(addressSet, program);

View file

@ -271,6 +271,21 @@ public class MultiListingLayoutModel implements ListingModelListener, FormatMode
models[modelID].toggleOpen(data);
}
@Override
public void setFunctionVariablesOpen(Address functionAddress, boolean open) {
models[modelID].setFunctionVariablesOpen(functionAddress, open);
}
@Override
public void setAllFunctionVariablesOpen(boolean open) {
models[modelID].setAllFunctionVariablesOpen(open);
}
@Override
public boolean areFunctionVariablesOpen(Address FunctionAddress) {
return models[modelID].areFunctionVariablesOpen(FunctionAddress);
}
@Override
public boolean openData(Data data) {
return models[modelID].openData(data);

View file

@ -0,0 +1,34 @@
/* ###
* 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.util.viewer.proxy;
import ghidra.app.util.viewer.listingpanel.ListingModel;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
/**
* Variable Proxy for when the function variables aren't being shown. It doesn't include
* a variable object which prevents the various variable field factories from triggering.
*/
public class ClosedVariableProxy extends VariableProxy {
public ClosedVariableProxy(ListingModel model, Program program, Address locationAddr,
Function fun) {
super(model, program, locationAddr, fun, null, true);
}
}

View file

@ -37,6 +37,7 @@ public class VariableProxy extends ProxyObj<Variable> {
private int firstUseOffset;
private Variable var;
private int ordinal = -1;
private boolean isFirst;
/**
* Constructs a proxy for a variable.
@ -45,21 +46,26 @@ public class VariableProxy extends ProxyObj<Variable> {
* @param locationAddr the listing address at which the function exists or was inferred via reference
* @param fun the function containing the variable.
* @param var the variable to proxy.
* @param isFirst true if this is the first parameter or variable
*/
public VariableProxy(ListingModel model, Program program, Address locationAddr, Function fun,
Variable var) {
Variable var, boolean isFirst) {
super(model);
this.program = program;
this.locationAddr = locationAddr;
this.var = var;
this.isFirst = isFirst;
this.functionAddr = fun.getEntryPoint();
if (var != null) {
if (var instanceof Parameter) {
ordinal = ((Parameter) var).getOrdinal();
}
Varnode firstVarnode = var.getFirstStorageVarnode();
storageAddr = firstVarnode != null ? firstVarnode.getAddress() : null;
firstUseOffset = var.getFirstUseOffset();
}
}
@Override
public Variable getObject() {
@ -126,6 +132,10 @@ public class VariableProxy extends ProxyObj<Variable> {
return functionAddr;
}
public Program getProgram() {
return program;
}
@Override
public boolean contains(Address a) {
Variable v = getObject();
@ -134,4 +144,8 @@ public class VariableProxy extends ProxyObj<Variable> {
}
return Objects.equals(v.getMinAddress(), a);
}
public boolean isFirst() {
return isFirst;
}
}

View file

@ -0,0 +1,93 @@
/* ###
* 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.util.viewer.util;
import java.util.HashSet;
import java.util.Set;
import ghidra.program.model.address.Address;
/**
* Class to maintain a simple open close/state for address locations. The default open/close
* state can be set and then a set of address is kept for the locations that are the opposite
* of the default.
*/
public class AddressBasedOpenCloseManager {
private boolean openByDefault = true;
private Set<Address> addresses = new HashSet<>();
/**
* Checks if the state is "open" for the given address.
* @param address the address to test
* @return true if the state of the given address is "open"
*/
public boolean isOpen(Address address) {
boolean contains = addresses.contains(address);
return openByDefault ? !contains : contains;
}
/**
* Sets the state at the given address to be "open".
* @param address the address to set "open"
*/
public void open(Address address) {
if (openByDefault) {
addresses.remove(address);
}
else {
addresses.add(address);
}
}
/**
* Sets the state at the given address to be "closed".
* @param address the address to set "closed"
*/
public void close(Address address) {
if (openByDefault) {
addresses.add(address);
}
else {
addresses.remove(address);
}
}
/**
* Checks if the default state is "open".
* @return true if the default state for addresses is "open"
*/
public boolean isOpenByDefault() {
return openByDefault;
}
/**
* Sets all address to "open" (Makes "open" the default state and clears all individual
* settings.
*/
public void openAll() {
openByDefault = true;
addresses.clear();
}
/**
* Sets all address to "closed" (Makes "closed" the default state and clears all individual
* settings.
*/
public void closeAll() {
openByDefault = false;
addresses.clear();
}
}

View file

@ -15,9 +15,8 @@
*/
package ghidra.app.util.viewer.util;
import java.util.*;
import javax.swing.event.ChangeListener;
import java.util.HashMap;
import java.util.Map;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
@ -27,33 +26,27 @@ import ghidra.util.task.TaskMonitorAdapter;
/**
* Manages the open/close state of structures and arrays at specific addresses.
*/
public class OpenCloseManager {
public class DataOpenCloseManager {
/**
* The map stores an int[] for each address that has something open.
* If map.get(address) returns null then outermost level is closed.
*/
private Map<Address, int[]> map = new HashMap<>();
private List<ChangeListener> listeners = new ArrayList<>();
/**
* Marks the given data as open. This method notifies listeners of changes.
* @param data The data to open.
* @return true if the data location was opened (false if already open or can't be opened)
*/
public boolean openData(Data data) {
public void openData(Data data) {
if (data.getComponent(0) == null) {
return false;
return;
}
Address addr = data.getMinAddress();
int[] path = data.getComponentPath();
if (isOpen(addr, path)) {
return false;
if (!isDataOpen(addr, path)) {
openData(addr, path);
}
open(addr, path);
notifyDataToggled();
return true;
}
/**
@ -66,11 +59,10 @@ public class OpenCloseManager {
}
Address addr = data.getMinAddress();
int[] path = data.getComponentPath();
if (!isOpen(addr, path)) {
if (!isDataOpen(addr, path)) {
return;
}
close(addr, path);
notifyDataToggled();
}
/**
@ -78,11 +70,11 @@ public class OpenCloseManager {
* @param address the address to open
* @param path the data component path to open
*/
private void open(Address address, int[] path) {
private void openData(Address address, int[] path) {
int pathSize = path.length;
int[] levels = map.get(address);
if ((levels == null) || (pathSize >= levels.length)) {
exactOpen(address, path);
exactOpenData(address, path);
return;
}
levels[0] = 0;
@ -90,7 +82,7 @@ public class OpenCloseManager {
for (; i < pathSize; i++) {
if (levels[i + 1] != path[i]) {
if (levels[i + 1] != -1) {
exactOpen(address, path);
exactOpenData(address, path);
return;
}
levels[i + 1] = path[i];
@ -99,7 +91,7 @@ public class OpenCloseManager {
map.put(address, levels);
}
private void exactOpen(Address address, int[] path) {
private void exactOpenData(Address address, int[] path) {
int pathSize = path.length;
int[] newLevels = new int[pathSize + 1];
newLevels[0] = 0;
@ -158,20 +150,13 @@ public class OpenCloseManager {
return 0;
}
/**
* Tests if the data at the given address is open
* @param address the address to test if open
*/
public boolean isOpen(Address address) {
return isOpen(address, null);
}
/**
* Test is the data at the given address and component path is open
* @param address the address to test
* @param path the component path to test.
* @return true if the data with the given component path is open
*/
public boolean isOpen(Address address, int[] path) {
public boolean isDataOpen(Address address, int[] path) {
int[] levels = map.get(address);
if (levels == null) {
return false;
@ -199,8 +184,9 @@ public class OpenCloseManager {
* Returns the index of the component that is open at the given address.
* @param address the address to find the open index.
* @param path the component path.
* @return the index of the component that is open at the given address.
*/
public int getOpenIndex(Address address, int[] path) {
public int getOpenDataIndex(Address address, int[] path) {
int[] levels = map.get(address);
if ((levels == null) || (levels.length == 0)) {
return -1;
@ -216,14 +202,8 @@ public class OpenCloseManager {
return -1;
}
public boolean isOpen(Data data) {
return isOpen(data.getMinAddress(), data.getComponentPath());
}
public void toggleOpen(Data data) {
toggleTopLevelData(data);
notifyDataToggled();
}
private void toggleTopLevelData(Data data) {
@ -233,17 +213,11 @@ public class OpenCloseManager {
Address addr = data.getMinAddress();
int[] path = data.getComponentPath();
if (isOpen(addr, path)) {
if (isDataOpen(addr, path)) {
close(addr, path);
}
else {
open(addr, path);
}
}
private void notifyDataToggled() {
for (ChangeListener l : listeners) {
l.stateChanged(null);
openData(addr, path);
}
}
@ -276,32 +250,22 @@ public class OpenCloseManager {
long progress = addresses.getNumAddresses() - unprocessed.getNumAddresses();
monitor.setProgress(progress);
}
notifyDataToggled();
}
public void openAllData(Data data, TaskMonitor monitor) {
toggleDataRecursively(data, true, monitor);
notifyDataToggled();
}
public void closeAllData(Program program, AddressSetView addresses, TaskMonitor monitor) {
toggleAllDataInAddresses(false, program, addresses, monitor);
}
public void closeAllData(Data data, TaskMonitor monitor) {
toggleDataRecursively(data, false, monitor);
notifyDataToggled();
public boolean isDataOpen(Data data) {
return isDataOpen(data.getMinAddress(), data.getComponentPath());
}
private void toggleDataRecursively(Data data, boolean openState, TaskMonitor monitor) {
public void toggleDataRecursively(Data data, boolean openState, TaskMonitor monitor) {
if (data == null && !monitor.isCancelled()) {
return;
}
if (isOpen(data) != openState) {
if (isDataOpen(data) != openState) {
toggleTopLevelData(data);
}
int componentCount = data.getNumComponents();
@ -326,7 +290,7 @@ public class OpenCloseManager {
return;
}
if (isOpen(data) != openState) {
if (isDataOpen(data) != openState) {
toggleTopLevelData(data);
}
@ -336,22 +300,6 @@ public class OpenCloseManager {
}
}
/**
* Adds a change listener to be notified when a location is open or closed.
* @param l the listener to be notified.
*/
public void addChangeListener(ChangeListener l) {
listeners.add(l);
}
/**
* Removes the listener.
* @param l the listener to remove.
*/
public void removeChangeListener(ChangeListener l) {
listeners.remove(l);
}
//==================================================================================================
// Inner Classes
//==================================================================================================

View file

@ -0,0 +1,167 @@
/* ###
* 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.util.viewer.util;
import java.util.ArrayList;
import java.util.List;
import javax.swing.event.ChangeListener;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
/**
* Manages the open/close state of various listing regions. This includes structures, arrays, and
* function variables.
*/
public class ProgramOpenCloseManager {
private DataOpenCloseManager dataOpenCloseManager = new DataOpenCloseManager();
private List<ChangeListener> listeners = new ArrayList<>();
private AddressBasedOpenCloseManager variablesOpenCloseManager = new AddressBasedOpenCloseManager();
/**
* Sets whether or not to display function variables at the given address.
* @param functionAddress the address of the function
* @param open true to display function variables, false to to hide them
*/
public void setFunctionVariablesOpen(Address functionAddress, boolean open) {
if (open) {
variablesOpenCloseManager.open(functionAddress);
}
else {
variablesOpenCloseManager.close(functionAddress);
}
notifyListeners();
}
/**
* Checks if the function variables are being shown at the given function address.
* @param functionAddress the address of the function to check
* @return true if the variables are being displayed
*/
public boolean isFunctionVariablesOpen(Address functionAddress) {
return variablesOpenCloseManager.isOpen(functionAddress);
}
/**
* Sets whether or not function variables are being shown by globally. This essentially sets
* the default state, but the state can be overridden at specific functions.
* @param open if true, then the function variables are displayed
*/
public void setAllFunctionVariablesOpen(boolean open) {
if (open) {
variablesOpenCloseManager.openAll();
}
else {
variablesOpenCloseManager.closeAll();
}
notifyListeners();
}
public boolean isAllFunctionVariablesOpen() {
return variablesOpenCloseManager.isOpenByDefault();
}
/**
* Marks the given data as open. This method notifies listeners of changes.
* @param data The data to open.
*/
public void openData(Data data) {
dataOpenCloseManager.openData(data);
notifyListeners();
}
/**
* Marks the given data as open. This method notifies listeners of changes.
* @param data The data to open.
*/
public void closeData(Data data) {
dataOpenCloseManager.closeData(data);
notifyListeners();
}
/**
* Tests if the data at the given address is open
* @param address the address to test if open
* @return true if the data at the address is open.
*/
public boolean isDataOpen(Address address) {
return dataOpenCloseManager.isDataOpen(address, null);
}
public boolean isDataOpen(Data data) {
return dataOpenCloseManager.isDataOpen(data);
}
/**
* Returns the index of the component that is open at the given address.
* @param data the data to get the index for
* @return the index of the component that is open for the given data
*/
public int getOpenDataIndex(Data data) {
return dataOpenCloseManager.getOpenDataIndex(data.getMinAddress(), data.getComponentPath());
}
public void toggleDataOpen(Data data) {
dataOpenCloseManager.toggleOpen(data);
notifyListeners();
}
public void openAllData(Program program, AddressSetView addresses, TaskMonitor monitor) {
dataOpenCloseManager.openAllData(program, addresses, monitor);
notifyListeners();
}
public void closeAllData(Program program, AddressSetView addresses, TaskMonitor monitor) {
dataOpenCloseManager.closeAllData(program, addresses, monitor);
notifyListeners();
}
public void openDataRecursively(Data data, TaskMonitor monitor) {
dataOpenCloseManager.toggleDataRecursively(data, true, monitor);
notifyListeners();
}
public void closeDataRecursively(Data data, TaskMonitor monitor) {
dataOpenCloseManager.toggleDataRecursively(data, false, monitor);
notifyListeners();
}
/**
* Adds a change listener to be notified when a location is open or closed.
* @param l the listener to be notified.
*/
public void addChangeListener(ChangeListener l) {
listeners.add(l);
}
/**
* Removes the listener.
* @param l the listener to remove.
*/
public void removeChangeListener(ChangeListener l) {
listeners.remove(l);
}
private void notifyListeners() {
for (ChangeListener l : listeners) {
l.stateChanged(null);
}
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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.program.util;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
/**
* ProgramLocation that represents the cursor being on the variables open/close widget
*/
public class VariablesOpenCloseLocation extends CodeUnitLocation {
/**
* Constructor
*
* @param program the program of the location
* @param addr address of the location
*/
public VariablesOpenCloseLocation(Program program, Address addr) {
super(program, addr, null, 0, 0, 0);
}
public VariablesOpenCloseLocation() {
}
}