GP-6692 - Added an option to limit the number of symbols displayed at an address

This commit is contained in:
dragonmacher
2026-04-28 18:17:58 -04:00
parent 7ff28d30bc
commit f9f71fe576
32 changed files with 1424 additions and 600 deletions
@@ -112,14 +112,18 @@
certain Navigation behaviors.</P> certain Navigation behaviors.</P>
<H3>Xrefs</H3>
<BLOCKQUOTE>
<A name="Show_Xrefs"></A> <A name="Show_Xrefs"></A>
<P>In the XRef field, sometimes there are too many addresses to display so the field will <P>In the XRef field, sometimes there are too many addresses to display so the field will
display "[more]" to indicate that one or more cross-reference addresses are not shown.</P> display "[more]" to indicate that one or more cross-reference addresses are not shown.</P>
<BLOCKQUOTE> <BLOCKQUOTE>
<P><IMG border="0" src="help/shared/tip.png" alt=""> Double-clicking on the "<FONT color= <P><IMG border="0" src="help/shared/tip.png" alt=""> Double-clicking on the "<FONT color=
"green"><TT>XREF[n]:</TT></FONT> or <FONT><TT>[more]</TT></FONT>" text will cause a "green"><TT>XREF[n]:</TT></FONT> or <FONT color="green"><TT>[more]</TT></FONT>"
dialog containing all the Xrefs to appear.</P> text will cause a dialog containing all the Xrefs to appear.</P>
<P> <P>
This differs from the <A href= This differs from the <A href=
"help/topics/LocationReferencesPlugin/Location_References.html#LocationReferencesPlugin"> "help/topics/LocationReferencesPlugin/Location_References.html#LocationReferencesPlugin">
@@ -179,6 +183,51 @@
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE>
<H3>Labels</H3>
<BLOCKQUOTE>
<A name="Show_Labels"></A>
<P>In the Labels field, sometimes there are too many labels to display so the field will
display "[more]" to indicate that one or more labels are not shown.</P>
<BLOCKQUOTE>
<P><IMG border="0" src="help/shared/tip.png" alt=""> Double-clicking on the
"<FONT><TT>[more]</TT></FONT>" text will cause a dialog containing all the
labels to appear.</P>
</BLOCKQUOTE>
<H3><A NAME="Refresh_Labels"></A>Refresh
<IMG border="0" src="Icons.REFRESH_ICON" alt=""></H3>
<BLOCKQUOTE>
<P>
This action will refresh the table of labels. This table does not respond to
program changes, such as adding or deleting lables. Thus, if you would like
the table to update to the current state of the program, then you can press
the refresh button.
</P>
</BLOCKQUOTE>
<H3><A NAME="Delete_Label"></A>Delete Label
<IMG border="0" src="Icons.DELETE_ICON" alt=""></H3>
<BLOCKQUOTE>
<P>
This action will delete all selected labels from the database. This differs
from the <A HREF="help/topics/Search/Query_Results_Dialog.htm#Remove_Items">
Remove Items </A> action, which will simply remove items from the table.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2><A NAME="Keyboard_Controls">Keyboard Controls</H2> <H2><A NAME="Keyboard_Controls">Keyboard Controls</H2>
@@ -848,6 +848,10 @@
this off, the function name will only appear in the function signature. If it's on, the this off, the function name will only appear in the function signature. If it's on, the
function name will also appear as a label below the function header.</P> function name will also appear as a label below the function header.</P>
<P><B>Maximum Number of Labels to Display -</B> Sets the maximum number of labels to
display. If the max is reached, a '[more]' field will appear. You can double-click that
field to see all labels at that address.</P>
<P><B>Display Non-local Namespace -</B> Select this option to prepend the namespace to all <P><B>Display Non-local Namespace -</B> Select this option to prepend the namespace to all
labels that are not in the current Function's namespace.&nbsp; Currently, this would only labels that are not in the current Function's namespace.&nbsp; Currently, this would only
affect a label that is not global, but is in a namespace other than the function that affect a label that is not global, but is in a namespace other than the function that
@@ -254,20 +254,12 @@
the address using the <I>Set Label</I> dialog.</P> the address using the <I>Set Label</I> dialog.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<TABLE x-use-null-cells="" width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG src="images/SetLabel.png" border="0"></TD>
</TR>
</TBODY>
</TABLE><BR>
<BR>
<BLOCKQUOTE> <BLOCKQUOTE>
<H3>Label</H3> <H3>Label</H3>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>The list in the combo box will show all symbols associated with the address shown in the <P>The drop-down list will show all symbols associated with the address shown in the
dialog title. Choosing a label from the list will cause that symbol to be associated with dialog title. Choosing a label from the list will cause that symbol to be associated with
the operand reference being modified. Typing in a new name will cause a new symbol to be the operand reference being modified. Typing in a new name will cause a new symbol to be
created at the target address before associating it with the operand reference.</P> created at the target address before associating it with the operand reference.</P>
@@ -45,6 +45,15 @@
</P> </P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A NAME="Set_Primary">Set Primary</A></H3>
<BLOCKQUOTE>
<P>
Sets the selected symbol to be the primary symbol at the symbol's address. The primary
symbol is the symbol displayed by default for the 'from' side of an xref.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
@@ -60,6 +69,9 @@
</LI> </LI>
<LI> <LI>
<A href="help/topics/SymbolTreePlugin/SymbolTree.htm">Symbol Tree</A> <A href="help/topics/SymbolTreePlugin/SymbolTree.htm">Symbol Tree</A>
</LI>
<LI>
<A href="help/topics/CodeBrowserPlugin/CodeBrowser.htm#Show_Labels">Show All Labels</A>
</LI> </LI>
</UL> </UL>
</BLOCKQUOTE> </BLOCKQUOTE>
@@ -102,10 +102,6 @@ public class LabelMgrPlugin extends Plugin {
return new EditFieldNameDialog("", tool); return new EditFieldNameDialog("", tool);
} }
OperandLabelDialog getOperandLabelDialog() {
return new OperandLabelDialog(this);
}
/** /**
* Removes the label or alias that the cursor is over from the current label field. If an * Removes the label or alias that the cursor is over from the current label field. If an
* exception is caught during the removal of the label or alias, a message is written to the * exception is caught during the removal of the label or alias, a message is written to the
@@ -171,17 +167,18 @@ public class LabelMgrPlugin extends Plugin {
} }
void setOperandLabelCallback(ListingActionContext context) { void setOperandLabelCallback(ListingActionContext context) {
getOperandLabelDialog().setOperandLabel(context);
SymbolChooserDialog dialog = new SymbolChooserDialog(this, context);
dialog.show();
} }
Symbol getSymbol(ListingActionContext context) { Symbol getSymbol(ListingActionContext context) {
ProgramLocation location = context.getLocation(); ProgramLocation location = context.getLocation();
if (location instanceof LabelFieldLocation) { if (location instanceof LabelFieldLocation lfl) {
LabelFieldLocation lfl = (LabelFieldLocation) location;
return lfl.getSymbol(); return lfl.getSymbol();
} }
else if (location instanceof OperandFieldLocation) { else if (location instanceof OperandFieldLocation ofl) {
VariableOffset variableOffset = ((OperandFieldLocation) location).getVariableOffset(); VariableOffset variableOffset = ofl.getVariableOffset();
if (variableOffset != null) { if (variableOffset != null) {
Variable var = variableOffset.getVariable(); Variable var = variableOffset.getVariable();
if (var != null) { if (var != null) {
@@ -1,178 +0,0 @@
/* ###
* 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.label;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GDLabel;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.refs.AssociateSymbolCmd;
import ghidra.app.context.ListingActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.OperandFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.layout.PairLayout;
public class OperandLabelDialog extends DialogComponentProvider {
private JLabel label;
private GhidraComboBox<String> myChoice;
private LabelMgrPlugin plugin;
private ListingActionContext programActionContext;
public OperandLabelDialog(LabelMgrPlugin plugin) {
super("");
this.plugin = plugin;
setHelpLocation(new HelpLocation(HelpTopics.LABEL, "OperandLabelDialog"));
addWorkPanel(buildMainPanel());
addOKButton();
addCancelButton();
}
/**
* Define the Main panel for the dialog here.
* @return JPanel the completed Main Panel
*/
protected JPanel buildMainPanel() {
JPanel mainPanel = new JPanel(new PairLayout(5, 5));
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
label = new GDLabel("Label: ");
myChoice = new GhidraComboBox<>();
myChoice.setName("MYCHOICE");
myChoice.setEditable(true);
myChoice.getAccessibleContext().setAccessibleName("My Choice");
mainPanel.add(label);
mainPanel.add(myChoice);
mainPanel.getAccessibleContext().setAccessibleName("Operand Label");
return mainPanel;
}
/**
* This method gets called when the user clicks on the Ok Button. The base
* class calls this method.
*/
@Override
protected void okCallback() {
Program program = programActionContext.getProgram();
ProgramLocation loc = programActionContext.getLocation();
OperandFieldLocation location = (OperandFieldLocation) loc;
Symbol sym = getSymbol(programActionContext);
String currentLabel = myChoice.getText();
if (currentLabel.equals(sym.getName(true))) {
close();
return;
}
ReferenceManager refMgr = program.getReferenceManager();
SymbolTable symTable = program.getSymbolTable();
int opIndex = location.getOperandIndex();
Address addr = location.getAddress();
Address symAddr = sym.getAddress();
Reference ref = refMgr.getReference(addr, symAddr, opIndex);
CompoundCmd<Program> cmd = new CompoundCmd<>("Set Label");
Namespace scope = null;
Symbol newSym = findSymbol(symTable, currentLabel, symAddr);
if (newSym == null) {
cmd.add(new AddLabelCmd(symAddr, currentLabel, SourceType.USER_DEFINED));
}
else {
scope = newSym.getParentNamespace();
currentLabel = newSym.getName();
}
cmd.add(new AssociateSymbolCmd(ref, currentLabel, scope));
if (!plugin.getTool().execute(cmd, program)) {
setStatusText(cmd.getStatusMsg());
return;
}
close();
}
// Find and return the first symbol at the address with the given name. Since this is about
// the presentation at the call or jump instruction, it doesn't matter which symbol of the
// same name you pick.
private Symbol findSymbol(SymbolTable symTable, String currentLabel, Address symAddr) {
SymbolIterator symbols = symTable.getSymbolsAsIterator(symAddr);
for (Symbol symbol : symbols) {
if (symbol.getName(true).equals(currentLabel)) {
return symbol;
}
}
return null;
}
/**
* This method gets called when the user clicks on the Cancel Button. The base
* class calls this method.
*/
@Override
protected void cancelCallback() {
close();
}
@Override
public void close() {
programActionContext = null;
super.close();
}
public void setOperandLabel(ListingActionContext context) {
programActionContext = context;
setStatusText("");
myChoice.clearModel();
Symbol s = getSymbol(context);
Symbol[] symbols = context.getProgram().getSymbolTable().getSymbols(s.getAddress());
for (Symbol symbol : symbols) {
myChoice.addToModel(symbol.getName(true));
}
setTitle("Set Label at " + s.getAddress());
myChoice.setSelectedItem(s.getName(true));
PluginTool tool = plugin.getTool();
tool.showDialog(this);
}
private Symbol getSymbol(ListingActionContext context) {
Program program = context.getProgram();
OperandFieldLocation location = (OperandFieldLocation) context.getLocation();
Address address = location.getAddress();
int opIndex = location.getOperandIndex();
ReferenceManager refMgr = program.getReferenceManager();
Reference ref = refMgr.getPrimaryReferenceFrom(address, opIndex);
if (ref != null) {
SymbolTable st = program.getSymbolTable();
return st.getSymbol(ref);
}
return null;
}
}
@@ -26,20 +26,12 @@ import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ListingContextAction; import ghidra.app.context.ListingContextAction;
import ghidra.program.util.OperandFieldLocation; import ghidra.program.util.OperandFieldLocation;
/**
* <CODE>AddLabelAction</CODE> allows the user to add a label.
*/
class SetOperandLabelAction extends ListingContextAction { class SetOperandLabelAction extends ListingContextAction {
private LabelMgrPlugin plugin; private LabelMgrPlugin plugin;
private static final String[] POPUP_PATH = { "Set Associated Label..." }; private static final String[] POPUP_PATH = { "Set Associated Label..." };
private static final KeyStroke KEYBINDING = private static final KeyStroke KEYBINDING =
KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_MASK | InputEvent.ALT_MASK); KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK);
/**
* Creates a new instance of the action.
*
* @param plugin Label Manager Plugin instance
*/
SetOperandLabelAction(LabelMgrPlugin plugin) { SetOperandLabelAction(LabelMgrPlugin plugin) {
super("Set Operand Label", plugin.getName()); super("Set Operand Label", plugin.getName());
@@ -59,13 +51,8 @@ class SetOperandLabelAction extends ListingContextAction {
plugin.isOnSymbol(context); plugin.isOnSymbol(context);
} }
/**
* Method called when the action is invoked.
* @param ActionEvent details regarding the invocation of this action
*/
@Override @Override
public void actionPerformed(ListingActionContext context) { public void actionPerformed(ListingActionContext context) {
plugin.setOperandLabelCallback(context); plugin.setOperandLabelCallback(context);
} }
} }
@@ -0,0 +1,192 @@
/* ###
* 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.label;
import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.DefaultDropDownSelectionDataModel;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.DropDownTextFieldDataModel.SearchMode;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.refs.AssociateSymbolCmd;
import ghidra.app.context.ListingActionContext;
import ghidra.framework.cmd.CompoundCmd;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.program.util.OperandFieldLocation;
public class SymbolChooserDialog extends DialogComponentProvider {
private List<String> names;
private DropDownSelectionTextField<String> textField;
private LabelMgrPlugin plugin;
private ListingActionContext context;
public SymbolChooserDialog(LabelMgrPlugin plugin, ListingActionContext context) {
super("Choose Label");
this.plugin = plugin;
this.context = context;
Symbol symbol = getOperandLabel();
Program program = context.getProgram();
SymbolTable st = program.getSymbolTable();
Address address = symbol.getAddress();
Symbol[] symbols = st.getSymbols(address);
names = Arrays.stream(symbols)
.map(s -> s.getName())
.collect(Collectors.toList());
addWorkPanel(buildWorkPanel());
addOKButton();
addCancelButton();
}
public void show() {
DockingWindowManager.showDialog(this);
}
private JComponent buildWorkPanel() {
DefaultDropDownSelectionDataModel<String> model =
DefaultDropDownSelectionDataModel.getStringModel(names);
textField = new DropDownSelectionTextField<>(model);
textField.setShowMatchingListOnEmptyText(true);
textField.setSearchMode(SearchMode.CONTAINS);
Document doc = textField.getDocument();
doc.addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
updateOk();
}
@Override
public void insertUpdate(DocumentEvent e) {
updateOk();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateOk();
}
});
JPanel panel = new JPanel(new BorderLayout());
panel.add(textField, BorderLayout.NORTH);
return panel;
}
private void updateOk() {
setOkEnabled(!textField.getText().isEmpty());
}
@Override
protected void okCallback() {
Program program = context.getProgram();
OperandFieldLocation location = (OperandFieldLocation) context.getLocation();
Symbol currentSymbol = getOperandLabel();
String newLabel = textField.getSelectedValue();
if (newLabel == null) {
setStatusText("Please choose a label");
return;
}
if (newLabel.equals(currentSymbol.getName(true))) {
close();
return;
}
ReferenceManager refMgr = program.getReferenceManager();
SymbolTable symTable = program.getSymbolTable();
int opIndex = location.getOperandIndex();
Address addr = location.getAddress();
Address symAddr = currentSymbol.getAddress();
Reference ref = refMgr.getReference(addr, symAddr, opIndex);
CompoundCmd<Program> cmd = new CompoundCmd<>("Set Label");
Namespace scope = null;
Symbol newSym = findSymbol(symTable, newLabel, symAddr);
if (newSym == null) {
cmd.add(new AddLabelCmd(symAddr, newLabel, SourceType.USER_DEFINED));
}
else {
scope = newSym.getParentNamespace();
newLabel = newSym.getName();
}
cmd.add(new AssociateSymbolCmd(ref, newLabel, scope));
if (!plugin.getTool().execute(cmd, program)) {
setStatusText(cmd.getStatusMsg());
return;
}
close();
}
// Find and return the first symbol at the address with the given name. Since this is about
// the presentation at the call or jump instruction, it doesn't matter which symbol of the
// same name you pick.
private Symbol findSymbol(SymbolTable symTable, String currentLabel, Address symAddr) {
SymbolIterator symbols = symTable.getSymbolsAsIterator(symAddr);
for (Symbol symbol : symbols) {
if (symbol.getName(true).equals(currentLabel)) {
return symbol;
}
}
return null;
}
private Symbol getOperandLabel() {
Program program = context.getProgram();
OperandFieldLocation location = (OperandFieldLocation) context.getLocation();
Address address = location.getAddress();
int opIndex = location.getOperandIndex();
ReferenceManager refMgr = program.getReferenceManager();
Reference ref = refMgr.getPrimaryReferenceFrom(address, opIndex);
if (ref != null) {
SymbolTable st = program.getSymbolTable();
return st.getSymbol(ref);
}
return null;
}
String getChoice() {
return textField.getSelectedValue();
}
void setSelectedItem(String value) {
textField.setSelectedValue(value);
}
}
@@ -291,7 +291,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
goToExternalAction.setEnabled(false); goToExternalAction.setEnabled(false);
CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this); CloneSymbolTreeAction cloneAction = new CloneSymbolTreeAction(plugin, this);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin); CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin.getTool());
SetSymbolPrimaryAction primaryAction = new SetSymbolPrimaryAction();
tool.addLocalAction(this, createImportAction); tool.addLocalAction(this, createImportAction);
tool.addLocalAction(this, setExternalProgramAction); tool.addLocalAction(this, setExternalProgramAction);
@@ -311,6 +312,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
tool.addLocalAction(this, goToExternalAction); tool.addLocalAction(this, goToExternalAction);
tool.addLocalAction(this, cloneAction); tool.addLocalAction(this, cloneAction);
tool.addLocalAction(this, tableAction); tool.addLocalAction(this, tableAction);
tool.addLocalAction(this, primaryAction);
} }
//================================================================================================== //==================================================================================================
@@ -22,6 +22,7 @@ import javax.swing.table.TableColumnModel;
import docking.action.KeyBindingType; import docking.action.KeyBindingType;
import docking.action.MenuData; import docking.action.MenuData;
import docking.tool.ToolConstants;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import docking.widgets.table.threaded.GThreadedTablePanel; import docking.widgets.table.threaded.GThreadedTablePanel;
import ghidra.app.context.ProgramSymbolActionContext; import ghidra.app.context.ProgramSymbolActionContext;
@@ -32,8 +33,8 @@ import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.app.util.SymbolInspector; import ghidra.app.util.SymbolInspector;
import ghidra.app.util.query.TableService; import ghidra.app.util.query.TableService;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
@@ -43,11 +44,11 @@ import ghidra.util.table.GhidraThreadedTablePanel;
public class CreateSymbolTableAction extends ProgramSymbolContextAction { public class CreateSymbolTableAction extends ProgramSymbolContextAction {
private Plugin plugin; private ServiceProvider services;
public CreateSymbolTableAction(Plugin plugin) { public CreateSymbolTableAction(ServiceProvider services) {
super("Create Table", plugin.getName(), KeyBindingType.SHARED); super("Create Table", ToolConstants.SHARED_OWNER, KeyBindingType.SHARED);
this.plugin = plugin; this.services = services;
setPopupMenuData(new MenuData(new String[] { "Create Table" }, setPopupMenuData(new MenuData(new String[] { "Create Table" },
SymbolTreeContextAction.MIDDLE_MENU_GROUP)); SymbolTreeContextAction.MIDDLE_MENU_GROUP));
@@ -72,25 +73,39 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction {
rowObjects.add(new SymbolRowObject(symbol)); rowObjects.add(new SymbolRowObject(symbol));
} }
PluginTool tool = plugin.getTool();
Program program = context.getProgram(); Program program = context.getProgram();
TransientSymbolTableModel model = new TransientSymbolTableModel(tool, program, rowObjects); TransientSymbolTableModel model =
new TransientSymbolTableModel(services, program, rowObjects);
showTransientTable(services, "Symbols", context.getProgram(), model);
}
/**
* A utility method to show a table of symbols.
* @param services the service provider
* @param title the provider's title
* @param program the program
* @param model the model
* @return the new provider
*/
public static TableComponentProvider<SymbolRowObject> showTransientTable(
ServiceProvider services, String title, Program program,
TransientSymbolTableModel model) {
TableService service = services.getService(TableService.class);
if (service == null) {
Msg.showError(CreateSymbolTableAction.class, null, "Table Service Not Installed",
"You must have a Table Service installed to create a Symbol Table");
return null;
}
Navigatable navigatable = null; Navigatable navigatable = null;
GoToService goToService = tool.getService(GoToService.class); GoToService goToService = services.getService(GoToService.class);
if (goToService != null) { if (goToService != null) {
navigatable = goToService.getDefaultNavigatable(); navigatable = goToService.getDefaultNavigatable();
} }
TableService service = tool.getService(TableService.class);
if (service == null) {
Msg.showError(this, null, "Table Service Not Installed",
"You must have a Table Service installed to create a Symbol Table");
return;
}
TableComponentProvider<SymbolRowObject> provider = TableComponentProvider<SymbolRowObject> provider =
service.showTable("Symbols", "Symbols", model, "Symbols", navigatable); service.showTable(title, "Symbols", model, "Symbols", navigatable);
provider.setActionContextProvider(mouseEvent -> { provider.setActionContextProvider(mouseEvent -> {
@@ -101,32 +116,38 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction {
return new ProgramSymbolActionContext(provider, program, selectedSymbols, table); return new ProgramSymbolActionContext(provider, program, selectedSymbols, table);
}); });
// replace the generic provider help with this action's help // replace the generic provider help
provider.setHelpLocation(getHelpLocation()); provider.setHelpLocation(new HelpLocation("SymbolTablePlugin", "Temporary_Symbol_Table"));
addActions(provider, model); addActions(services, provider, model);
GhidraThreadedTablePanel<SymbolRowObject> tablePanel = provider.getThreadedTablePanel(); GhidraThreadedTablePanel<SymbolRowObject> tablePanel = provider.getThreadedTablePanel();
GhidraTable table = tablePanel.getTable(); GhidraTable table = tablePanel.getTable();
configureSymbolTable(tool, table, model, program); configureSymbolTable(services, table, model, program);
return provider;
} }
private void addActions(TableComponentProvider<SymbolRowObject> provider, private static void addActions(ServiceProvider services,
TransientSymbolTableModel model) { TableComponentProvider<SymbolRowObject> provider, TransientSymbolTableModel model) {
provider.installRemoveItemsAction(); provider.installRemoveItemsAction();
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin); CreateSymbolTableAction tableAction = new CreateSymbolTableAction(services);
provider.getTool().addLocalAction(provider, tableAction); PluginTool tool = provider.getTool();
tool.addLocalAction(provider, tableAction);
SetSymbolPrimaryAction primaryAction = new SetSymbolPrimaryAction();
tool.addLocalAction(provider, primaryAction);
} }
private void configureSymbolTable(PluginTool tool, GhidraTable table, private static void configureSymbolTable(ServiceProvider services, GhidraTable table,
TransientSymbolTableModel model, Program program) { TransientSymbolTableModel model, Program program) {
new TransientSymbolTableDnDAdapter(table, model); new TransientSymbolTableDnDAdapter(table, model);
SymbolInspector symbolInspector = new SymbolInspector(tool, table); SymbolInspector symbolInspector = new SymbolInspector(services, table);
SymbolRenderer renderer = model.getSymbolRenderer(); SymbolRenderer renderer = model.getSymbolRenderer();
renderer.setSymbolInspector(symbolInspector); renderer.setSymbolInspector(symbolInspector);
@@ -141,7 +162,7 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction {
} }
} }
private List<Symbol> getSelectedSymbols(GTable table, TransientSymbolTableModel model) { private static List<Symbol> getSelectedSymbols(GTable table, TransientSymbolTableModel model) {
List<Symbol> list = new ArrayList<>(); List<Symbol> list = new ArrayList<>();
int[] rows = table.getSelectedRows(); int[] rows = table.getSelectedRows();
for (int row : rows) { for (int row : rows) {
@@ -0,0 +1,86 @@
/* ###
* 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.symboltree.actions;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.tool.ToolConstants;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
import ghidra.app.context.ProgramSymbolActionContext;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.task.TaskLauncher;
public class SetSymbolPrimaryAction extends DockingAction {
private static final String NAME = "Set Label Primary";
public SetSymbolPrimaryAction() {
super(NAME, ToolConstants.SHARED_OWNER);
// Note: the group '2' is that of the PinSymbolAction. That group seems like a nice place.
setPopupMenuData(new MenuData(new String[] { "Set Primary" }, "2"));
setHelpLocation(new HelpLocation("SymbolTablePlugin", "Set_Primary"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof ProgramSymbolActionContext psac)) {
return false;
}
int n = psac.getSymbolCount();
if (n != 1) {
return false;
}
Symbol s = psac.getFirstSymbol();
return !s.isPrimary();
}
@Override
public void actionPerformed(ActionContext context) {
ProgramSymbolActionContext psac = (ProgramSymbolActionContext) context;
Symbol s = psac.getFirstSymbol();
Namespace ns = s.getParentNamespace();
String name = s.getName();
Address addr = s.getAddress();
SetLabelPrimaryCmd cmd = new SetLabelPrimaryCmd(addr, name, ns);
TaskLauncher.launchModal(NAME, () -> {
Program p = psac.getProgram();
p.withTransaction(NAME, () -> {
cmd.applyTo(p);
});
});
String errorMessage = cmd.getStatusMsg();
if (!StringUtils.isBlank(errorMessage)) {
Msg.showError(getClass(), null, "Unable to Set Label Primary", errorMessage);
}
}
}
@@ -23,8 +23,8 @@ import ghidra.app.cmd.label.DeleteLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd; import ghidra.app.cmd.label.RenameLabelCmd;
import ghidra.app.util.template.TemplateSimplifier; import ghidra.app.util.template.TemplateSimplifier;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.framework.cmd.Command;
import ghidra.framework.cmd.CompoundCmd; import ghidra.framework.cmd.CompoundCmd;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
@@ -33,9 +33,11 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.AddressBasedTableModel; import ghidra.util.table.AddressBasedTableModel;
import ghidra.util.table.column.*; import ghidra.util.table.column.*;
import ghidra.util.table.field.*; import ghidra.util.table.field.*;
import ghidra.util.task.*;
public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<SymbolRowObject> { public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
@@ -51,7 +53,6 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
public static final int SOURCE_COL = 5; public static final int SOURCE_COL = 5;
public static final int REFS_COL = 6; public static final int REFS_COL = 6;
private PluginTool tool;
protected SymbolTable symbolTable; protected SymbolTable symbolTable;
protected ReferenceManager refMgr; protected ReferenceManager refMgr;
protected SymbolRowObject lastSymbol; protected SymbolRowObject lastSymbol;
@@ -59,9 +60,8 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
protected SymbolRenderer symbolRenderer = new SymbolRenderer(); protected SymbolRenderer symbolRenderer = new SymbolRenderer();
AbstractSymbolTableModel(PluginTool tool) { AbstractSymbolTableModel(ServiceProvider sp) {
super("Symbols", tool, null, null); super("Symbols", sp, null, null);
this.tool = tool;
this.filter = new NewSymbolFilter(); this.filter = new NewSymbolFilter();
} }
@@ -166,9 +166,11 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
return; return;
} }
RenameLabelCmd cmd = new RenameLabelCmd(symbol, newName, SourceType.USER_DEFINED); RenameTask task = new RenameTask(symbol, newName);
if (!tool.execute(cmd, getProgram())) { TaskLauncher.launch(task);
Msg.showError(getClass(), null, "Error Renaming Symbol", cmd.getStatusMsg());
if (!task.success()) {
Msg.showError(getClass(), null, "Error Renaming Symbol", task.status());
} }
} }
@@ -229,15 +231,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
void delete(List<Symbol> rowObjects) { protected void delete(List<Symbol> symbols) {
if (rowObjects == null || rowObjects.isEmpty()) { if (symbols == null || symbols.isEmpty()) {
return; return;
} }
tool.setStatusInfo("");
List<Symbol> deleteList = new LinkedList<>(); List<Symbol> deleteList = new LinkedList<>();
CompoundCmd<Program> cmd = new CompoundCmd<>("Delete symbol(s)"); CompoundCmd<Program> cmd = new CompoundCmd<>("Delete symbol(s)");
for (Symbol symbol : rowObjects) { for (Symbol symbol : symbols) {
if (symbol.isDynamic()) { if (symbol.isDynamic()) {
continue; // can't delete dynamic symbols... continue; // can't delete dynamic symbols...
} }
@@ -262,16 +263,19 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
return; return;
} }
if (tool.execute(cmd, getProgram())) { DeleteTask task = new DeleteTask(cmd);
TaskLauncher.launch(task);
if (!task.success()) {
reload();
Msg.showError(getClass(), null, "Error Deleting Symbol(s)", task.status());
}
else {
for (Symbol s : deleteList) { for (Symbol s : deleteList) {
removeObject(new SymbolRowObject(s)); removeObject(new SymbolRowObject(s));
} }
updateNow(); updateNow();
} }
else {
tool.setStatusInfo(cmd.getStatusMsg());
reload();
}
} }
public SymbolFilter getFilter() { public SymbolFilter getFilter() {
@@ -328,16 +332,102 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
return symbolRenderer; return symbolRenderer;
} }
//==================================================================================================
// Task Classes
//==================================================================================================
private class DeleteTask extends Task {
private CompoundCmd<Program> compoundCmd;
private boolean success;
private String status;
DeleteTask(CompoundCmd<Program> compoundCmd) {
super("Delete Symbols");
this.compoundCmd = compoundCmd;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
program.withTransaction(getName(), () -> {
doRun(monitor);
});
}
private void doRun(TaskMonitor monitor) throws CancelledException {
monitor.initialize(compoundCmd.size());
success = true;
List<Command<Program>> commands = compoundCmd.getCommands();
for (Command<Program> cmd : commands) {
monitor.increment();
if (!cmd.applyTo(program)) {
success = false;
status = cmd.getStatusMsg();
break;
}
}
}
boolean success() {
return success;
}
String status() {
return status;
}
}
private class RenameTask extends Task {
private Symbol symbol;
private String newName;
private boolean success;
private String status;
public RenameTask(Symbol symbol, String newName) {
super("Rename Symbol");
this.symbol = symbol;
this.newName = newName;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
program.withTransaction(getName(), () -> {
RenameLabelCmd cmd = new RenameLabelCmd(symbol, newName, SourceType.USER_DEFINED);
success = cmd.applyTo(program);
});
}
boolean success() {
return success;
}
String status() {
return status;
}
}
//================================================================================================== //==================================================================================================
// Table Column Classes // Table Column Classes
//================================================================================================== //==================================================================================================
private class NameTableColumn protected class NameTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Symbol> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Symbol> {
public static final String NAME = "Name";
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Name"; return NAME;
} }
@Override @Override
@@ -347,7 +437,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class PinnedTableColumn protected class PinnedTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Boolean> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Boolean> {
private PinnedRenderer renderer = new PinnedRenderer(); private PinnedRenderer renderer = new PinnedRenderer();
@@ -378,7 +468,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class LocationTableColumn protected class LocationTableColumn
extends AbstractProgramLocationTableColumn<SymbolRowObject, AddressBasedLocation> { extends AbstractProgramLocationTableColumn<SymbolRowObject, AddressBasedLocation> {
@Override @Override
@@ -404,12 +494,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class SymbolTypeTableColumn protected class SymbolTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
public static final String NAME = "Type";
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Type"; return NAME;
} }
@Override @Override
@@ -426,14 +518,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class VariableSymbolLocation extends AddressBasedLocation { protected class VariableSymbolLocation extends AddressBasedLocation {
VariableSymbolLocation(Variable variable) { VariableSymbolLocation(Variable variable) {
super(variable.getSymbol().getAddress(), variable.getVariableStorage().toString()); super(variable.getSymbol().getAddress(), variable.getVariableStorage().toString());
} }
} }
private class DataTypeTableColumn protected class DataTypeTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override @Override
@@ -470,7 +562,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class NamespaceTableColumn protected class NamespaceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override @Override
@@ -489,9 +581,11 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class SourceTableColumn protected class SourceTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SourceType> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, SourceType> {
public static final String NAME = "Source";
private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<>() { private GColumnRenderer<SourceType> renderer = new AbstractGColumnRenderer<>() {
@Override @Override
protected String getText(Object value) { protected String getText(Object value) {
@@ -509,7 +603,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Source"; return NAME;
} }
@Override @Override
@@ -529,14 +623,16 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class ReferenceCountTableColumn protected class ReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
public static final String NAME = "Ref Count";
private ReferenceCountRenderer renderer = new ReferenceCountRenderer(); private ReferenceCountRenderer renderer = new ReferenceCountRenderer();
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Reference Count"; return NAME;
} }
@Override @Override
@@ -562,7 +658,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class OffcutReferenceCountTableColumn protected class OffcutReferenceCountTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, Integer> {
private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer(); private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer();
@@ -613,7 +709,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class UserTableColumn protected class UserTableColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override @Override
@@ -650,7 +746,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
class OriginalNameColumn protected class OriginalNameColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
@Override @Override
@@ -688,7 +784,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel<Sy
} }
} }
private class SimplifiedNameColumn protected class SimplifiedNameColumn
extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> { extends AbstractProgramBasedDynamicTableColumn<SymbolRowObject, String> {
private TemplateSimplifier simplifier = new TemplateSimplifier(); private TemplateSimplifier simplifier = new TemplateSimplifier();
@@ -36,6 +36,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable; import ghidra.program.model.symbol.SymbolTable;
import ghidra.program.util.LabelFieldLocation;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.table.*; import ghidra.util.table.*;
@@ -126,12 +127,17 @@ class SymbolPanel extends JPanel {
Program program = location.getProgram(); Program program = location.getProgram();
SymbolTable symbolTable = program.getSymbolTable(); SymbolTable symbolTable = program.getSymbolTable();
Address address = location.getAddress(); Address address = location.getAddress();
Symbol primarySymbol = symbolTable.getPrimarySymbol(address);
if (primarySymbol == null) { Symbol symbol = null;
return; if (location instanceof LabelFieldLocation lfl) {
symbol = lfl.getSymbol();
} }
SymbolRowObject rowObject = new SymbolRowObject(primarySymbol); if (symbol == null) {
symbol = symbolTable.getPrimarySymbol(address);
}
SymbolRowObject rowObject = new SymbolRowObject(symbol);
int index = symbolModel.getRowIndex(rowObject); int index = symbolModel.getRowIndex(rowObject);
if (index >= 0) { if (index >= 0) {
gTable.selectRow(index); gTable.selectRow(index);
@@ -21,16 +21,19 @@ import static ghidra.program.util.ProgramEvent.*;
import java.awt.Component; import java.awt.Component;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.stream.Collectors;
import javax.swing.Icon; import javax.swing.Icon;
import docking.ActionContext; import docking.ActionContext;
import docking.action.*; import docking.action.*;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import docking.tool.ToolConstants;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.OptionDialogBuilder; import docking.widgets.OptionDialogBuilder;
import docking.widgets.table.DynamicTableColumn;
import docking.widgets.table.TableColumnDescriptor;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.cmd.refs.RemoveReferenceCmd; import ghidra.app.cmd.refs.RemoveReferenceCmd;
@@ -39,17 +42,20 @@ import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.symboltree.actions.*; import ghidra.app.plugin.core.symboltree.actions.*;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.services.BlockModelService; import ghidra.app.services.BlockModelService;
import ghidra.app.services.GoToService; import ghidra.app.services.GoToService;
import ghidra.app.util.HelpTopics;
import ghidra.app.util.SymbolInspector; import ghidra.app.util.SymbolInspector;
import ghidra.app.util.viewer.field.LabelFieldSymbolLoader;
import ghidra.app.util.viewer.field.LabelFieldSymbolLoader.Symbols;
import ghidra.framework.model.DomainObjectListener; import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.model.DomainObjectListenerBuilder; import ghidra.framework.model.DomainObjectListenerBuilder;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramChangeRecord;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
@@ -79,12 +85,13 @@ import resources.Icons;
"allows symbols to be renamed and deleted. This plugin also " + "allows symbols to be renamed and deleted. This plugin also " +
"shows references to a symbol. Filters can be set " + "shows references to a symbol. Filters can be set " +
"to show subsets of the symbols.", "to show subsets of the symbols.",
servicesProvided = { SymbolTableService.class },
servicesRequired = { GoToService.class, BlockModelService.class }, servicesRequired = { GoToService.class, BlockModelService.class },
eventsProduced = { ProgramLocationPluginEvent.class }, eventsProduced = { ProgramLocationPluginEvent.class },
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class } eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class }
) )
//@formatter:on //@formatter:on
public class SymbolTablePlugin extends Plugin { public class SymbolTablePlugin extends Plugin implements SymbolTableService {
private static final String NAVIGATE_ON_INCOMING_EVENT_KEY = "NAVIGATE_ON_INCOMING_EVENT"; private static final String NAVIGATE_ON_INCOMING_EVENT_KEY = "NAVIGATE_ON_INCOMING_EVENT";
private static final String NAVIGATE_ON_OUTGOING_EVENT_KEY = "NAVIGATE_ON_OUTGOING_EVENT"; private static final String NAVIGATE_ON_OUTGOING_EVENT_KEY = "NAVIGATE_ON_OUTGOING_EVENT";
@@ -106,6 +113,10 @@ public class SymbolTablePlugin extends Plugin {
private BlockModelService blockModelService; private BlockModelService blockModelService;
private SwingUpdateManager swingMgr; private SwingUpdateManager swingMgr;
// providers shown by the service interface
private Map<String, TableComponentProvider<SymbolRowObject>> transientTableProviders =
new HashMap<>();
private DomainObjectListener domainObjectListener = createDomainObjectListener(); private DomainObjectListener domainObjectListener = createDomainObjectListener();
/** /**
@@ -126,6 +137,7 @@ public class SymbolTablePlugin extends Plugin {
@Override @Override
protected void init() { protected void init() {
gotoService = tool.getService(GoToService.class); gotoService = tool.getService(GoToService.class);
blockModelService = tool.getService(BlockModelService.class); blockModelService = tool.getService(BlockModelService.class);
@@ -138,11 +150,6 @@ public class SymbolTablePlugin extends Plugin {
inspector = new SymbolInspector(getTool(), symProvider.getComponent()); inspector = new SymbolInspector(getTool(), symProvider.getComponent());
} }
/**
* 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 @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
@@ -456,10 +463,14 @@ public class SymbolTablePlugin extends Plugin {
DockingAction clearPinnedAction = new ClearPinSymbolAction(getName(), pinnedPopupGroup); DockingAction clearPinnedAction = new ClearPinSymbolAction(getName(), pinnedPopupGroup);
tool.addAction(clearPinnedAction); tool.addAction(clearPinnedAction);
CreateSymbolTableAction tableAction = new CreateSymbolTableAction(this); CreateSymbolTableAction tableAction = new CreateSymbolTableAction(getTool());
tableAction.getPopupMenuData().setMenuGroup(popupGroup); tableAction.getPopupMenuData().setMenuGroup(popupGroup);
tool.addLocalAction(symProvider, tableAction); tool.addLocalAction(symProvider, tableAction);
SetSymbolPrimaryAction primaryAction = new SetSymbolPrimaryAction();
primaryAction.getPopupMenuData().setMenuGroup(popupGroup);
tool.addLocalAction(symProvider, primaryAction);
//@formatter:off //@formatter:off
String bottomGroup = "ShowReferencesTo" + 1; String bottomGroup = "ShowReferencesTo" + 1;
new ActionBuilder("Delete All References", getName()) new ActionBuilder("Delete All References", getName())
@@ -641,6 +652,163 @@ public class SymbolTablePlugin extends Plugin {
action.setSelected(true); action.setSelected(true);
} }
//=================================================================================================
// Service Methods
//=================================================================================================
@Override
public TableComponentProvider<SymbolRowObject> showSymbols(CodeUnit codeUnit) {
Objects.requireNonNull(codeUnit);
Program program = codeUnit.getProgram();
Address addr = codeUnit.getMinAddress();
String title = "Labels at " + addr;
TableComponentProvider<SymbolRowObject> provider = transientTableProviders.get(title);
if (provider != null) {
if (provider.isShowing()) {
LabelFieldSymbolModel model = (LabelFieldSymbolModel) provider.getModel();
reload(codeUnit, model);
provider.toFront();
return provider;
}
transientTableProviders.remove(title);
}
LabelFieldSymbolLoader loader =
new LabelFieldSymbolLoader(codeUnit, Integer.MAX_VALUE, true);
Symbols symbols = loader.getSymbols();
List<Symbol> list = symbols.getAllSymbols();
HashSet<SymbolRowObject> rowObjects = list.stream()
.map(s -> new SymbolRowObject(s))
.collect(Collectors.toCollection(HashSet::new));
LabelFieldSymbolModel model =
new LabelFieldSymbolModel(tool, program, rowObjects);
provider = CreateSymbolTableAction.showTransientTable(tool, title, program, model);
if (provider == null) {
return null;
}
provider.setClosedCallback(() -> {
transientTableProviders.remove(title);
});
addActions(provider, model, codeUnit);
transientTableProviders.put(title, provider);
return provider;
}
private void addActions(TableComponentProvider<SymbolRowObject> provider,
LabelFieldSymbolModel model, CodeUnit cu) {
new ActionBuilder("Refresh", ToolConstants.SHARED_OWNER)
.toolBarGroup("_", "1") // first
.toolBarIcon(Icons.REFRESH_ICON)
.helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Refresh_Labels"))
.onAction(c -> {
reload(cu, model);
})
.buildAndInstallLocal(provider);
new ActionBuilder("Delete", ToolConstants.SHARED_OWNER)
.toolBarGroup("_", "2") // first
.toolBarIcon(Icons.DELETE_ICON)
.helpLocation(new HelpLocation(HelpTopics.CODE_BROWSER, "Delete_Label"))
.enabledWhen(c -> {
GhidraTable table = provider.getTable();
return table.getSelectedRowCount() > 0;
})
.onAction(c -> {
deleteSymbols(provider, model);
})
.buildAndInstallLocal(provider);
}
private void deleteSymbols(TableComponentProvider<SymbolRowObject> provider,
LabelFieldSymbolModel model) {
List<Symbol> symbols = new ArrayList<>();
GhidraTable table = provider.getTable();
int[] rows = table.getSelectedRows();
for (int row : rows) {
SymbolRowObject ro = model.getRowObject(row);
Symbol symbol = ro.getSymbol();
if (symbol.isDeleted()) {
// this symbol was deleted outside of the table and the table did not update
model.removeObject(ro);
continue;
}
symbols.add(symbol);
}
model.delete(symbols);
}
private void reload(CodeUnit cu, LabelFieldSymbolModel model) {
LabelFieldSymbolLoader loader =
new LabelFieldSymbolLoader(cu, Integer.MAX_VALUE, true);
Symbols symbols = loader.getSymbols();
List<Symbol> list = symbols.getAllSymbols();
HashSet<SymbolRowObject> rowObjects = list.stream()
.map(s -> new SymbolRowObject(s))
.collect(Collectors.toCollection(HashSet::new));
model.setData(rowObjects);
}
private class LabelFieldSymbolModel extends TransientSymbolTableModel {
public LabelFieldSymbolModel(ServiceProvider sp, Program program,
HashSet<SymbolRowObject> rowObjects) {
super(sp, program, rowObjects);
}
public void setData(HashSet<SymbolRowObject> rowObjects) {
this.rowObjects = rowObjects;
reload();
}
@Override
protected void delete(List<Symbol> symbols) {
super.delete(symbols);
}
@Override
protected TableColumnDescriptor<SymbolRowObject> createTableColumnDescriptor() {
TableColumnDescriptor<SymbolRowObject> descriptor = super.createTableColumnDescriptor();
//@formatter:off
Set<String> visibleNames = new HashSet<>(Set.of(
NameTableColumn.NAME,
SymbolTypeTableColumn.NAME,
SourceTableColumn.NAME,
ReferenceCountTableColumn.NAME));
//@formatter:on
List<DynamicTableColumn<SymbolRowObject, ?, ?>> allColumns = descriptor.getAllColumns();
for (DynamicTableColumn<SymbolRowObject, ?, ?> column : allColumns) {
String columnName = column.getColumnName();
boolean visible = visibleNames.contains(columnName);
descriptor.setVisible(columnName, visible);
}
return descriptor;
}
}
//================================================================================================== //==================================================================================================
// Table Update Jobs // Table Update Jobs
//================================================================================================== //==================================================================================================
@@ -0,0 +1,33 @@
/* ###
* 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.symtable;
import ghidra.app.plugin.core.table.TableComponentProvider;
import ghidra.app.plugin.processors.sleigh.symbol.Symbol;
import ghidra.program.model.listing.CodeUnit;
/**
* Service for showing {@link Symbol}s in a table.
*/
public interface SymbolTableService {
/**
* Shows all symbols and offcut symbols contained in the given code unit.
* @param codeUnit the code unit
* @return the table provider that is shown
*/
public TableComponentProvider<SymbolRowObject> showSymbols(CodeUnit codeUnit);
}
@@ -22,7 +22,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import ghidra.framework.model.DomainObjectListenerBuilder; import ghidra.framework.model.DomainObjectListenerBuilder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramChangeRecord;
import ghidra.util.datastruct.Accumulator; import ghidra.util.datastruct.Accumulator;
@@ -36,13 +36,13 @@ import ghidra.util.task.TaskMonitor;
*/ */
public class TransientSymbolTableModel extends AbstractSymbolTableModel { public class TransientSymbolTableModel extends AbstractSymbolTableModel {
private HashSet<SymbolRowObject> rowObjects; protected HashSet<SymbolRowObject> rowObjects;
private SwingUpdateManager updater = new SwingUpdateManager(this::fireTableDataChanged); private SwingUpdateManager updater = new SwingUpdateManager(this::fireTableDataChanged);
public TransientSymbolTableModel(PluginTool tool, Program program, public TransientSymbolTableModel(ServiceProvider sp, Program program,
HashSet<SymbolRowObject> rowObjects) { HashSet<SymbolRowObject> rowObjects) {
super(tool); super(sp);
this.rowObjects = rowObjects; this.rowObjects = rowObjects;
setProgram(program); setProgram(program);
symbolTable = program.getSymbolTable(); symbolTable = program.getSymbolTable();
@@ -34,17 +34,20 @@ public class LabelCodeUnitFormat extends BrowserCodeUnitFormat {
@Override @Override
protected String getOffcutLabelStringForInstruction(Address offcutAddress, protected String getOffcutLabelStringForInstruction(Address offcutAddress,
Instruction instruction, Address markupAddress) { Instruction instruction, Address markupAddress, Symbol symbol) {
if (markupAddress != null) { if (markupAddress != null) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
Program program = instruction.getProgram(); Program program = instruction.getProgram();
Symbol offsym = program.getSymbolTable().getPrimarySymbol(offcutAddress);
if (symbol == null) {
symbol = program.getSymbolTable().getPrimarySymbol(offcutAddress);
}
Address instructionAddress = instruction.getMinAddress(); Address instructionAddress = instruction.getMinAddress();
long diff = offcutAddress.subtract(instructionAddress); long diff = offcutAddress.subtract(instructionAddress);
boolean decorate = !offsym.isDynamic(); boolean decorate = !symbol.isDynamic();
boolean simplify = true; boolean simplify = true;
return getDefaultOffcutString(offsym, instruction, diff, decorate, simplify); return getDefaultOffcutString(symbol, instruction, diff, decorate, simplify);
} }
@Override @Override
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
/* ###
* 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.event.MouseEvent;
import docking.widgets.fieldpanel.field.FieldElement;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.symtable.SymbolTableService;
import ghidra.app.services.GoToService;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.util.Msg;
/**
* A handler to process Label field clicks
*/
public class LabelFieldMouseHandler implements FieldMouseHandlerExtension {
private final static Class<?>[] SUPPORTED_CLASSES =
new Class<?>[] { LabelFieldLocation.class, MoreLabelFieldLocation.class };
@Override
public boolean fieldElementClicked(Object clickedObject, Navigatable sourceNavigatable,
ProgramLocation location, MouseEvent mouseEvent, ServiceProvider serviceProvider) {
if (mouseEvent.getClickCount() != 2 || mouseEvent.getButton() != MouseEvent.BUTTON1) {
return false;
}
GoToService goToService = serviceProvider.getService(GoToService.class);
if (goToService == null) {
Msg.error(this, GoToService.class.getSimpleName() + " not installed!");
return false;
}
SymbolTableService service = serviceProvider.getService(SymbolTableService.class);
if (service == null) {
Msg.error(this, SymbolTableService.class.getSimpleName() + " not installed!");
return false;
}
String clickedText = getText(clickedObject);
if (MoreLabelFieldLocation.MORE_LABELS_STRING.equals(clickedText)) {
showLabelsDialog(service, location);
return true;
}
if (location instanceof LabelFieldLocation) {
// Allow double-clicking of any label to show the label dialog. This allows the user to
// use the dialog even when the [more] is not showing.
showLabelsDialog(service, location);
return true;
}
return false;
}
private void showLabelsDialog(SymbolTableService service, ProgramLocation location) {
Address addr = location.getAddress();
Program program = location.getProgram();
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitAt(addr);
service.showSymbols(cu);
}
private String getText(Object clickedObject) {
if (clickedObject instanceof FieldElement) {
FieldElement fieldElement = (FieldElement) clickedObject;
return fieldElement.getText();
}
return clickedObject.toString();
}
@Override
public Class<?>[] getSupportedProgramLocations() {
return SUPPORTED_CLASSES;
}
}
@@ -0,0 +1,223 @@
/* ###
* 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.util.*;
import generic.json.Json;
import ghidra.program.database.symbol.FunctionSymbol;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressIterator;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
/**
* A simple class to load all symbols for a given code unit
*/
public class LabelFieldSymbolLoader {
private boolean displayFunctionLabel;
private Symbols symbols;
private boolean hasMore;
public LabelFieldSymbolLoader(CodeUnit cu, int max, boolean displayFunctionLabel) {
this.displayFunctionLabel = displayFunctionLabel;
symbols = new Symbols();
gatherRealSymbols(cu, max);
gatherOffcutSymbols(cu, max);
}
public Symbols getSymbols() {
return symbols;
}
public boolean hasMore() {
return hasMore;
}
private void gatherRealSymbols(CodeUnit cu, int max) {
Address addr = cu.getMinAddress();
Program program = cu.getProgram();
//
// Place the primary symbol to the front so that it is always rendered, even if we hit the
// symbol limit. Also, remove the function symbol if the user doesn't want to see it.
//
SymbolTable st = program.getSymbolTable();
SymbolIterator it = st.getSymbolsAsIterator(addr);
Symbol primary = st.getPrimarySymbol(addr);
if (primary == null) {
return;
}
while (it.hasNext()) {
Symbol s = it.next();
if (s.isPrimary()) {
continue;
}
if ((max - 1) == symbols.size()) { // -1 to save space for primary
hasMore = true;
break;
}
if (s instanceof FunctionSymbol && !displayFunctionLabel) {
continue;
}
symbols.add(s);
}
symbols.add(primary);
}
private void gatherOffcutSymbols(CodeUnit cu, int max) {
if (max == 0) {
return;
}
Address startAddr = cu.getMinAddress();
if (!startAddr.isMemoryAddress()) {
return;
}
Program program = cu.getProgram();
if (cu.getLength() == 1) {
return;
}
Address nextAddr = startAddr.next();
if (nextAddr == null) {
return;
}
SymbolTable symbolTable = program.getSymbolTable();
Address endAddress = cu.getMaxAddress();
ReferenceManager referenceManager = program.getReferenceManager();
AddressIterator it = referenceManager.getReferenceDestinationIterator(nextAddr, true);
while (it.hasNext()) {
Address addr = it.next();
if (addr.compareTo(endAddress) > 0 ||
// Note: check for wrapping - temporary work-around
addr.compareTo(cu.getMinAddress()) <= 0) {
break;
}
if (max == symbols.size()) {
hasMore = true;
return;
}
Symbol s = symbolTable.getPrimarySymbol(addr);
symbols.addOffcut(s);
}
SymbolIterator symIter = symbolTable.getSymbolIterator(nextAddr, true);
while (symIter.hasNext()) {
Symbol s = symIter.next();
Address addr = s.getAddress();
if (addr.compareTo(endAddress) > 0 ||
// Note: check for wrapping - temporary work-around
addr.compareTo(cu.getMinAddress()) <= 0) {
break;
}
if (max == symbols.size()) {
hasMore = true;
return;
}
// remove to handle the case where this symbol was added in the above loop
symbols.removeOffct(s);
symbols.addOffcut(s);
}
}
/**
* A simple class to hold all real and offcut symbols for a given code unit. The client will
* limit the number of symbols added to this class. The real symbols are loaded first, with any
* remaining space filled with existing offcut symbols.
*/
public class Symbols {
private List<Symbol> realSymbols = new ArrayList<>();
private List<Symbol> offcutSymbols = new ArrayList<>();
void addOffcut(Symbol s) {
offcutSymbols.add(s);
}
void removeOffct(Symbol s) {
offcutSymbols.remove(s);
}
void add(Symbol s) {
realSymbols.add(s);
}
void add(int index, Symbol s) {
realSymbols.add(index, s);
}
void remove(Symbol s) {
realSymbols.remove(s);
}
int size() {
return offcutSymbols.size() + realSymbols.size();
}
Symbol get(int index) {
if (index < offcutSymbols.size()) {
return offcutSymbols.get(index);
}
int updatedIndex = index - offcutSymbols.size();
return realSymbols.get(updatedIndex);
}
public List<Symbol> getAllSymbols() {
List<Symbol> list = new ArrayList<>();
list.addAll(realSymbols);
list.addAll(offcutSymbols);
return list;
}
List<Symbol> getOffcuts() {
return Collections.unmodifiableList(offcutSymbols);
}
void reverseSymbols() {
realSymbols = realSymbols.reversed();
}
@Override
public String toString() {
return Json.toString(this);
}
}
}
@@ -30,7 +30,7 @@ import ghidra.program.model.symbol.Reference;
import ghidra.program.util.*; import ghidra.program.util.*;
/** /**
* A handler to process {@link XRefFieldMouseHandler} clicks * A handler to process XRef field clicks
*/ */
public class XRefFieldMouseHandler implements FieldMouseHandlerExtension { public class XRefFieldMouseHandler implements FieldMouseHandlerExtension {
@@ -1367,7 +1367,7 @@ public class CodeUnitFormat {
if (symbolAddress.isMemoryAddress()) { if (symbolAddress.isMemoryAddress()) {
CodeUnit cu = program.getListing().getCodeUnitContaining(symbolAddress); CodeUnit cu = program.getListing().getCodeUnitContaining(symbolAddress);
if (isOffcut(symbolAddress, cu)) { if (isOffcut(symbolAddress, cu)) {
return getOffcutLabelString(symbolAddress, cu, markupAddress); return getOffcutLabelString(symbolAddress, cu, markupAddress, symbol);
} }
else if (isStringData(cu)) { else if (isStringData(cu)) {
return getLabelStringForStringData((Data) cu, symbol); return getLabelStringForStringData((Data) cu, symbol);
@@ -1408,10 +1408,11 @@ public class CodeUnitFormat {
return prefix + UNDERSCORE + SymbolUtilities.getAddressString(symbol.getAddress()); return prefix + UNDERSCORE + SymbolUtilities.getAddressString(symbol.getAddress());
} }
public String getOffcutLabelString(Address offcutAddress, CodeUnit cu, Address markupAddress) { public String getOffcutLabelString(Address offcutAddress, CodeUnit cu, Address markupAddress,
Symbol symbol) {
if (cu instanceof Instruction) { if (cu instanceof Instruction) {
return getOffcutLabelStringForInstruction(offcutAddress, (Instruction) cu, return getOffcutLabelStringForInstruction(offcutAddress, (Instruction) cu,
markupAddress); markupAddress, symbol);
} }
return getOffcutDataString(offcutAddress, (Data) cu); return getOffcutDataString(offcutAddress, (Data) cu);
} }
@@ -1460,17 +1461,22 @@ public class CodeUnitFormat {
* @param offcutAddress address for which generated label represents * @param offcutAddress address for which generated label represents
* @param instruction instruction containing offcut address * @param instruction instruction containing offcut address
* @param markupAddress address where a label will be referenced from (may be null) * @param markupAddress address where a label will be referenced from (may be null)
* @param symbol an optional symbol that is used to generate the symbol name
* @return generated offcut label * @return generated offcut label
*/ */
protected String getOffcutLabelStringForInstruction(Address offcutAddress, protected String getOffcutLabelStringForInstruction(Address offcutAddress,
Instruction instruction, Address markupAddress) { Instruction instruction, Address markupAddress, Symbol symbol) {
Program program = instruction.getProgram(); Program program = instruction.getProgram();
Symbol offsym = program.getSymbolTable().getPrimarySymbol(offcutAddress);
if (symbol == null) {
symbol = program.getSymbolTable().getPrimarySymbol(offcutAddress);
}
Address instructionAddress = instruction.getMinAddress(); Address instructionAddress = instruction.getMinAddress();
long diff = offcutAddress.subtract(instructionAddress); long diff = offcutAddress.subtract(instructionAddress);
boolean decorate = false; // we never decorate in the operand field boolean decorate = false; // we never decorate in the operand field
boolean simplify = true; // we always simplify names of instruction labels boolean simplify = true; // we always simplify names of instruction labels
if (offsym.isDynamic()) { if (symbol.isDynamic()) {
Symbol containingSymbol = program.getSymbolTable().getPrimarySymbol(instructionAddress); Symbol containingSymbol = program.getSymbolTable().getPrimarySymbol(instructionAddress);
if (containingSymbol != null) { if (containingSymbol != null) {
String displayName = containingSymbol.getName(); String displayName = containingSymbol.getName();
@@ -1481,7 +1487,7 @@ public class CodeUnitFormat {
return simplifyTemplate(displayName) + PLUS + SymbolUtilities.getDiffString(diff); return simplifyTemplate(displayName) + PLUS + SymbolUtilities.getDiffString(diff);
} }
} }
return getDefaultOffcutString(offsym, instruction, diff, decorate, simplify); return getDefaultOffcutString(symbol, instruction, diff, decorate, simplify);
} }
protected String addOffcutInformation(String prefix, String addressString, int diff, protected String addOffcutInformation(String prefix, String addressString, int diff,
@@ -20,7 +20,6 @@ import static org.junit.Assert.*;
import org.junit.*; import org.junit.*;
import docking.ActionContext; import docking.ActionContext;
import docking.widgets.combobox.GhidraComboBox;
import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@@ -74,10 +73,9 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest
performAction(setLabelAction, context, false); performAction(setLabelAction, context, false);
waitForSwing(); waitForSwing();
OperandLabelDialog dialog = waitForDialogComponent(OperandLabelDialog.class); SymbolChooserDialog dialog = waitForDialogComponent(SymbolChooserDialog.class);
GhidraComboBox<?> combo = (GhidraComboBox<?>) findComponentByName(dialog, "MYCHOICE");
setSelectedItem(combo, "bob"); setSelectedItem(dialog, "bob");
pressButtonByText(dialog, "OK"); pressButtonByText(dialog, "OK");
waitForSwing(); waitForSwing();
@@ -91,10 +89,9 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest
performAction(setLabelAction, context, false); performAction(setLabelAction, context, false);
waitForSwing(); waitForSwing();
dialog = waitForDialogComponent(OperandLabelDialog.class); dialog = waitForDialogComponent(SymbolChooserDialog.class);
combo = (GhidraComboBox<?>) findComponentByName(dialog, "MYCHOICE");
setSelectedItem(combo, "b"); setSelectedItem(dialog, "b");
pressButtonByText(dialog, "OK"); pressButtonByText(dialog, "OK");
program.flushEvents(); program.flushEvents();
@@ -108,7 +105,7 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest
assertEquals("dword ptr [b]", cb.getCurrentFieldText()); assertEquals("dword ptr [b]", cb.getCurrentFieldText());
} }
private void setSelectedItem(GhidraComboBox<?> combo, String s) { private void setSelectedItem(SymbolChooserDialog dialog, String item) {
runSwing(() -> combo.setSelectedItem(s)); runSwing(() -> dialog.setSelectedItem(item));
} }
} }
@@ -169,8 +169,9 @@ public class LabelFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
ListingTextField tf = (ListingTextField) cb.getCurrentField(); ListingTextField tf = (ListingTextField) cb.getCurrentField();
// 4 offcut labels and one dynamic label // 4 offcut labels and one dynamic label
String actual = tf.getText();
assertEquals("LAB_01002000+1 LAB_01002000+2 LAB_01002000+3 LAB_01002000+4 LAB_01002000", assertEquals("LAB_01002000+1 LAB_01002000+2 LAB_01002000+3 LAB_01002000+4 LAB_01002000",
tf.getText()); // bad offcut put on instruction actual); // bad offcut put on instruction
assertEquals(5, tf.getNumRows()); assertEquals(5, tf.getNumRows());
} }
@@ -93,6 +93,13 @@ public class FieldLocation implements Comparable<FieldLocation> {
this(BigInteger.valueOf(index), fieldNum, row, col); this(BigInteger.valueOf(index), fieldNum, row, col);
} }
/**
* Construct a new FieldLocation with the given index,fieldNum,row, and col.
* @param index the index of the layout containing the location
* @param fieldNum the index of the field in the layout containing the location
* @param row the text row in the field containing the location.
* @param col the character position in the row containing the location.
*/
public FieldLocation(BigInteger index, int fieldNum, int row, int col) { public FieldLocation(BigInteger index, int fieldNum, int row, int col) {
this.index = index; this.index = index;
this.fieldNum = fieldNum; this.fieldNum = fieldNum;
@@ -38,4 +38,21 @@ public interface DynamicColumnTableModel<ROW_TYPE>
* @return the model index * @return the model index
*/ */
public int getColumnIndex(DynamicTableColumn<ROW_TYPE, ?, ?> column); public int getColumnIndex(DynamicTableColumn<ROW_TYPE, ?, ?> column);
/**
* Allows table models to create their own preference key.
* <p>
* The preference key is used to save the column state of this configurable model. All models
* that share a preference key will share the same visible column state.
* The {@link GTableColumnModel} manages this state for the framework. The column model will
* ask the table for its preference key, which will ask the model when not key has been set on
* the table. In the case that no key is set in the table or the model, a key will be created
* based on the classname and default columns.
*
* @return the preference key; the default value is null
*/
public default String getPreferenceKey() {
return null;
}
} }
@@ -15,10 +15,10 @@
*/ */
package docking.widgets.table; package docking.widgets.table;
import static docking.DockingUtils.CONTROL_KEY_MODIFIER_MASK; import static docking.DockingUtils.*;
import static docking.action.MenuData.NO_MNEMONIC; import static docking.action.MenuData.*;
import static java.awt.event.InputEvent.SHIFT_DOWN_MASK; import static java.awt.event.InputEvent.*;
import static javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION; import static javax.swing.ListSelectionModel.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
@@ -636,7 +636,7 @@ public class GTable extends JTable {
} }
/** /**
* {@return the underlying ConfigurableColumnTableModel if one is in-use} * {@return the underlying ConfigurableColumnTableModel if one is in use}
*/ */
public ConfigurableColumnTableModel getConfigurableColumnTableModel() { public ConfigurableColumnTableModel getConfigurableColumnTableModel() {
TableModel model = getUnwrappedTableModel(); TableModel model = getUnwrappedTableModel();
@@ -646,6 +646,17 @@ public class GTable extends JTable {
return null; return null;
} }
/**
* {@return the underlying DynamicColumnTableModel if one is in use}
*/
public DynamicColumnTableModel<?> getDynamicTableModel() {
TableModel model = getUnwrappedTableModel();
if (model instanceof DynamicColumnTableModel<?>) {
return (DynamicColumnTableModel<?>) model;
}
return null;
}
/** /**
* Unrolls the current model by checking if the current model is inside of a wrapper table * Unrolls the current model by checking if the current model is inside of a wrapper table
* model. * model.
@@ -912,9 +923,19 @@ public class GTable extends JTable {
* @see #setPreferenceKey(String) * @see #setPreferenceKey(String)
*/ */
public String getPreferenceKey() { public String getPreferenceKey() {
if (preferenceKey != null) {
// prefer the key that has been set programmatically
return preferenceKey; return preferenceKey;
} }
DynamicColumnTableModel<?> dynamicModel = getDynamicTableModel();
if (dynamicModel != null) {
return dynamicModel.getPreferenceKey();
}
return null;
}
/** /**
* Signals that the preferences of this table (visible columns, sort order, etc.) should be * Signals that the preferences of this table (visible columns, sort order, etc.) should be
* saved. * saved.
@@ -15,9 +15,9 @@
*/ */
package ghidra.framework.cmd; package ghidra.framework.cmd;
import ghidra.framework.model.DomainObject; import java.util.*;
import java.util.ArrayList; import ghidra.framework.model.DomainObject;
/** /**
* Implementation for multiple commands that are done as a unit. * Implementation for multiple commands that are done as a unit.
@@ -28,7 +28,7 @@ import java.util.ArrayList;
* @param <T> {@link DomainObject} implementation interface * @param <T> {@link DomainObject} implementation interface
*/ */
public class CompoundCmd<T extends DomainObject> implements Command<T> { public class CompoundCmd<T extends DomainObject> implements Command<T> {
private ArrayList<Command<T>> cmds; private List<Command<T>> cmds;
private String statusMsg; private String statusMsg;
private String name; private String name;
@@ -81,4 +81,10 @@ public class CompoundCmd<T extends DomainObject> implements Command<T> {
return cmds.size(); return cmds.size();
} }
/**
* {@return the commands in this compound command}
*/
public List<Command<T>> getCommands() {
return Collections.unmodifiableList(cmds);
}
} }
@@ -65,7 +65,7 @@ public interface SymbolTable {
* @param name the name of the symbol * @param name the name of the symbol
* @param source the source of this symbol. In general, a source of {@link SourceType#DEFAULT} * @param source the source of this symbol. In general, a source of {@link SourceType#DEFAULT}
* should never be specified using this method. * should never be specified using this method.
* @return new labe or function symbol * @return new label or function symbol
* @throws InvalidInputException if name contains white space, is zero length, or is null for * @throws InvalidInputException if name contains white space, is zero length, or is null for
* non-default source * non-default source
* @throws IllegalArgumentException if {@link SourceType#DEFAULT} is improperly specified, or * @throws IllegalArgumentException if {@link SourceType#DEFAULT} is improperly specified, or
@@ -463,7 +463,7 @@ public interface SymbolTable {
/** /**
* Get all the symbols of the given type within the given address set. * Get all the symbols of the given type within the given address set.
* <p> * <p>
* <b>NOTE:</b> All external symbols will be omiitted unless the full * <b>NOTE:</b> All external symbols will be omitted unless the full
* {@link AddressSpace#EXTERNAL_SPACE} range is included within the specified address set * {@link AddressSpace#EXTERNAL_SPACE} range is included within the specified address set
* or a null addressSet is specified. All global dynamic label symbols will be omitted. * or a null addressSet is specified. All global dynamic label symbols will be omitted.
* *
@@ -36,8 +36,8 @@ public class CodeUnitLocation extends ProgramLocation {
* @param componentPath if this is not null it is the path to a data * @param componentPath if this is not null it is the path to a data
* component inside of another data component * component inside of another data component
* @param row the row within the field. * @param row the row within the field.
* @param col - the display item index on the given row. (Note most fields only have one display item per row) * @param col the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset - the character offset within the display item. * @param charOffset the character offset within the display item.
* *
*/ */
public CodeUnitLocation(Program program, Address addr, int[] componentPath, int row, int col, public CodeUnitLocation(Program program, Address addr, int[] componentPath, int row, int col,
@@ -53,8 +53,8 @@ public class CodeUnitLocation extends ProgramLocation {
* @param componentPath if this is not null it is the path to a data * @param componentPath if this is not null it is the path to a data
* component inside of another data component * component inside of another data component
* @param row the row within the field. * @param row the row within the field.
* @param col - the display item index on the given row. (Note most fields only have one display item per row) * @param col the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset - the character offset within the display item. * @param charOffset the character offset within the display item.
* *
*/ */
protected CodeUnitLocation(Program program, Address addr, Address byteAddr, int[] componentPath, protected CodeUnitLocation(Program program, Address addr, Address byteAddr, int[] componentPath,
@@ -70,8 +70,8 @@ public class CodeUnitLocation extends ProgramLocation {
* @param program the program for obtaining the code unit * @param program the program for obtaining the code unit
* @param addr address of the location; should not be null * @param addr address of the location; should not be null
* @param row the row within the field. * @param row the row within the field.
* @param col - the display item index on the given row. (Note most fields only have one display item per row) * @param col the display item index on the given row. (Note most fields only have one display item per row)
* @param charOffset - the character offset within the display item. * @param charOffset the character offset within the display item.
* *
*/ */
public CodeUnitLocation(Program program, Address addr, int row, int col, int charOffset) { public CodeUnitLocation(Program program, Address addr, int row, int col, int charOffset) {
@@ -0,0 +1,40 @@
/* ###
* 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;
/**
* Represents the '[more]' text used by the label field factory.
*/
public class MoreLabelFieldLocation extends CodeUnitLocation {
public static final String MORE_LABELS_STRING = "[more]";
public MoreLabelFieldLocation() {
// for serialization
}
public MoreLabelFieldLocation(Program p, Address addr, int row, int charOffset) {
super(p, addr, row, 0, charOffset);
}
@Override
public String toString() {
return MORE_LABELS_STRING;
}
}
@@ -21,8 +21,8 @@ import javax.swing.*;
import org.junit.Test; import org.junit.Test;
import docking.widgets.combobox.GhidraComboBox; import ghidra.app.plugin.core.label.LabelHistoryDialog;
import ghidra.app.plugin.core.label.*; import ghidra.app.plugin.core.label.LabelHistoryInputDialog;
import ghidra.app.util.*; import ghidra.app.util.*;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.symbol.LabelHistory; import ghidra.program.model.symbol.LabelHistory;
@@ -85,22 +85,6 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator {
captureDialog(); captureDialog();
} }
@Test
public void testSetLabel() {
LabelMgrPlugin plugin = getPlugin(tool, LabelMgrPlugin.class);
final OperandLabelDialog dialog = new OperandLabelDialog(plugin);
final GhidraComboBox<?> combo = (GhidraComboBox<?>) getInstanceField("myChoice", dialog);
runSwing(new Runnable() {
@Override
public void run() {
dialog.setTitle("Set Label at 004a671");
combo.setSelectedItem("LAB_0040a671");
}
});
showDialogWithoutBlocking(tool, dialog);
captureDialog(350, 116);
}
@Test @Test
public void testShowLabelHistory() { public void testShowLabelHistory() {
AddressSpace space = new GenericAddressSpace("Test", 32, AddressSpace.TYPE_RAM, 0); AddressSpace space = new GenericAddressSpace("Test", 32, AddressSpace.TYPE_RAM, 0);