From f9f71fe57640285d0601234eeaaeb6f051f3ff08 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:17:58 -0400 Subject: [PATCH] GP-6692 - Added an option to limit the number of symbols displayed at an address --- .../topics/CodeBrowserPlugin/CodeBrowser.htm | 53 ++- .../CodeBrowserPlugin/CodeBrowserOptions.htm | 4 + .../help/topics/LabelMgrPlugin/Labels.htm | 10 +- .../symbol_table_transient.htm | 12 + .../app/plugin/core/label/LabelMgrPlugin.java | 15 +- .../plugin/core/label/OperandLabelDialog.java | 178 ------- .../core/label/SetOperandLabelAction.java | 19 +- .../core/label/SymbolChooserDialog.java | 192 ++++++++ .../core/symboltree/SymbolTreeProvider.java | 4 +- .../actions/CreateSymbolTableAction.java | 75 +-- .../actions/SetSymbolPrimaryAction.java | 86 ++++ .../symtable/AbstractSymbolTableModel.java | 164 +++++-- .../app/plugin/core/symtable/SymbolPanel.java | 14 +- .../core/symtable/SymbolTablePlugin.java | 190 +++++++- .../core/symtable/SymbolTableService.java | 33 ++ .../symtable/TransientSymbolTableModel.java | 12 +- .../viewer/field/LabelCodeUnitFormat.java | 11 +- .../util/viewer/field/LabelFieldFactory.java | 433 ++++++++---------- .../viewer/field/LabelFieldMouseHandler.java | 95 ++++ .../viewer/field/LabelFieldSymbolLoader.java | 223 +++++++++ .../viewer/field/XRefFieldMouseHandler.java | 2 +- .../program/model/listing/CodeUnitFormat.java | 20 +- .../core/label/OperandLabelDialogTest.java | 19 +- .../viewer/field/LabelFieldFactoryTest.java | 3 +- .../fieldpanel/support/FieldLocation.java | 7 + .../table/DynamicColumnTableModel.java | 21 +- .../java/docking/widgets/table/GTable.java | 33 +- .../ghidra/framework/cmd/CompoundCmd.java | 16 +- .../program/model/symbol/SymbolTable.java | 4 +- .../ghidra/program/util/CodeUnitLocation.java | 16 +- .../program/util/MoreLabelFieldLocation.java | 40 ++ .../screenshot/LabelMgrPluginScreenShots.java | 20 +- 32 files changed, 1424 insertions(+), 600 deletions(-) delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/OperandLabelDialog.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SymbolChooserDialog.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SetSymbolPrimaryAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableService.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldMouseHandler.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/MoreLabelFieldLocation.java diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm index 6e98541b5d..9bbf7281f4 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm @@ -112,14 +112,18 @@ certain Navigation behaviors.

+ +

Xrefs

+
+

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.

Double-clicking on the "XREF[n]: or [more]" text will cause a - dialog containing all the Xrefs to appear.

+ "green">XREF[n]: or [more]" + text will cause a dialog containing all the Xrefs to appear.

This differs from the @@ -178,6 +182,51 @@

+ + + +

Labels

+
+ + +

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.

+ +
+

Double-clicking on the + "[more]" text will cause a dialog containing all the + labels to appear.

+
+ +

Refresh +

+ +
+

+ 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. +

+
+ + +

Delete Label +

+ +
+

+ This action will delete all selected labels from the database. This differs + from the + Remove Items action, which will simply remove items from the table. +

+
+ +
+ + + + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm index 4e0cecd1bd..c51a55f9e8 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowserOptions.htm @@ -848,6 +848,10 @@ 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.

+

Maximum Number of Labels to Display - 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.

+

Display Non-local Namespace - Select this option to prepend the namespace to all labels that are not in the current Function's namespace.  Currently, this would only affect a label that is not global, but is in a namespace other than the function that diff --git a/Ghidra/Features/Base/src/main/help/help/topics/LabelMgrPlugin/Labels.htm b/Ghidra/Features/Base/src/main/help/help/topics/LabelMgrPlugin/Labels.htm index afb0bfa9bd..e6dfaa6dd2 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/LabelMgrPlugin/Labels.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/LabelMgrPlugin/Labels.htm @@ -254,20 +254,12 @@ the address using the Set Label dialog.

- - - - - - -

-

Label

-

The list in the combo box will show all symbols associated with the address shown in the +

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 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.

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm index bd5a6b7f8f..349be4903f 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTablePlugin/symbol_table_transient.htm @@ -45,6 +45,15 @@

+

Set Primary

+ +
+

+ 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. +

+
+
@@ -60,6 +69,9 @@
  • Symbol Tree +
  • +
  • + Show All Labels
  • diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/LabelMgrPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/LabelMgrPlugin.java index 396031052b..19f1f3e0c7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/LabelMgrPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/LabelMgrPlugin.java @@ -102,10 +102,6 @@ public class LabelMgrPlugin extends Plugin { 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 * 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) { - getOperandLabelDialog().setOperandLabel(context); + + SymbolChooserDialog dialog = new SymbolChooserDialog(this, context); + dialog.show(); } Symbol getSymbol(ListingActionContext context) { ProgramLocation location = context.getLocation(); - if (location instanceof LabelFieldLocation) { - LabelFieldLocation lfl = (LabelFieldLocation) location; + if (location instanceof LabelFieldLocation lfl) { return lfl.getSymbol(); } - else if (location instanceof OperandFieldLocation) { - VariableOffset variableOffset = ((OperandFieldLocation) location).getVariableOffset(); + else if (location instanceof OperandFieldLocation ofl) { + VariableOffset variableOffset = ofl.getVariableOffset(); if (variableOffset != null) { Variable var = variableOffset.getVariable(); if (var != null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/OperandLabelDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/OperandLabelDialog.java deleted file mode 100644 index 37d9b0cf57..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/OperandLabelDialog.java +++ /dev/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 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 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; - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SetOperandLabelAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SetOperandLabelAction.java index 460e689f54..5655a32021 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SetOperandLabelAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SetOperandLabelAction.java @@ -4,9 +4,9 @@ * 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. @@ -26,20 +26,12 @@ import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; import ghidra.program.util.OperandFieldLocation; -/** - * AddLabelAction allows the user to add a label. - */ class SetOperandLabelAction extends ListingContextAction { private LabelMgrPlugin plugin; private static final String[] POPUP_PATH = { "Set Associated Label..." }; 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) { super("Set Operand Label", plugin.getName()); @@ -59,13 +51,8 @@ class SetOperandLabelAction extends ListingContextAction { plugin.isOnSymbol(context); } - /** - * Method called when the action is invoked. - * @param ActionEvent details regarding the invocation of this action - */ @Override public void actionPerformed(ListingActionContext context) { plugin.setOperandLabelCallback(context); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SymbolChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SymbolChooserDialog.java new file mode 100644 index 0000000000..18b2eb603d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/label/SymbolChooserDialog.java @@ -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 names; + private DropDownSelectionTextField 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 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 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); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index d0376311fa..40be83a626 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -291,7 +291,8 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { goToExternalAction.setEnabled(false); 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, setExternalProgramAction); @@ -311,6 +312,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { tool.addLocalAction(this, goToExternalAction); tool.addLocalAction(this, cloneAction); tool.addLocalAction(this, tableAction); + tool.addLocalAction(this, primaryAction); } //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java index d3f8b3fd68..e084ecd281 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateSymbolTableAction.java @@ -22,6 +22,7 @@ import javax.swing.table.TableColumnModel; import docking.action.KeyBindingType; import docking.action.MenuData; +import docking.tool.ToolConstants; import docking.widgets.table.GTable; import docking.widgets.table.threaded.GThreadedTablePanel; import ghidra.app.context.ProgramSymbolActionContext; @@ -32,8 +33,8 @@ import ghidra.app.plugin.core.table.TableComponentProvider; import ghidra.app.services.GoToService; import ghidra.app.util.SymbolInspector; import ghidra.app.util.query.TableService; -import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; import ghidra.util.HelpLocation; @@ -43,11 +44,11 @@ import ghidra.util.table.GhidraThreadedTablePanel; public class CreateSymbolTableAction extends ProgramSymbolContextAction { - private Plugin plugin; + private ServiceProvider services; - public CreateSymbolTableAction(Plugin plugin) { - super("Create Table", plugin.getName(), KeyBindingType.SHARED); - this.plugin = plugin; + public CreateSymbolTableAction(ServiceProvider services) { + super("Create Table", ToolConstants.SHARED_OWNER, KeyBindingType.SHARED); + this.services = services; setPopupMenuData(new MenuData(new String[] { "Create Table" }, SymbolTreeContextAction.MIDDLE_MENU_GROUP)); @@ -72,25 +73,39 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction { rowObjects.add(new SymbolRowObject(symbol)); } - PluginTool tool = plugin.getTool(); 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 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; - GoToService goToService = tool.getService(GoToService.class); + GoToService goToService = services.getService(GoToService.class); if (goToService != null) { 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 provider = - service.showTable("Symbols", "Symbols", model, "Symbols", navigatable); + service.showTable(title, "Symbols", model, "Symbols", navigatable); provider.setActionContextProvider(mouseEvent -> { @@ -101,32 +116,38 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction { return new ProgramSymbolActionContext(provider, program, selectedSymbols, table); }); - // replace the generic provider help with this action's help - provider.setHelpLocation(getHelpLocation()); + // replace the generic provider help + provider.setHelpLocation(new HelpLocation("SymbolTablePlugin", "Temporary_Symbol_Table")); - addActions(provider, model); + addActions(services, provider, model); GhidraThreadedTablePanel tablePanel = provider.getThreadedTablePanel(); GhidraTable table = tablePanel.getTable(); - configureSymbolTable(tool, table, model, program); + configureSymbolTable(services, table, model, program); + + return provider; } - private void addActions(TableComponentProvider provider, - TransientSymbolTableModel model) { + private static void addActions(ServiceProvider services, + TableComponentProvider provider, TransientSymbolTableModel model) { provider.installRemoveItemsAction(); - CreateSymbolTableAction tableAction = new CreateSymbolTableAction(plugin); - provider.getTool().addLocalAction(provider, tableAction); + CreateSymbolTableAction tableAction = new CreateSymbolTableAction(services); + 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) { new TransientSymbolTableDnDAdapter(table, model); - SymbolInspector symbolInspector = new SymbolInspector(tool, table); + SymbolInspector symbolInspector = new SymbolInspector(services, table); SymbolRenderer renderer = model.getSymbolRenderer(); renderer.setSymbolInspector(symbolInspector); @@ -141,7 +162,7 @@ public class CreateSymbolTableAction extends ProgramSymbolContextAction { } } - private List getSelectedSymbols(GTable table, TransientSymbolTableModel model) { + private static List getSelectedSymbols(GTable table, TransientSymbolTableModel model) { List list = new ArrayList<>(); int[] rows = table.getSelectedRows(); for (int row : rows) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SetSymbolPrimaryAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SetSymbolPrimaryAction.java new file mode 100644 index 0000000000..d9b763f823 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SetSymbolPrimaryAction.java @@ -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); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java index 4a1d2cceee..ee69d99ba6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java @@ -23,8 +23,8 @@ import ghidra.app.cmd.label.DeleteLabelCmd; import ghidra.app.cmd.label.RenameLabelCmd; import ghidra.app.util.template.TemplateSimplifier; import ghidra.docking.settings.Settings; +import ghidra.framework.cmd.Command; import ghidra.framework.cmd.CompoundCmd; -import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.address.*; import ghidra.program.model.data.DataType; @@ -33,9 +33,11 @@ import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; import ghidra.util.table.AddressBasedTableModel; import ghidra.util.table.column.*; import ghidra.util.table.field.*; +import ghidra.util.task.*; public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { @@ -51,7 +53,6 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel rowObjects) { - if (rowObjects == null || rowObjects.isEmpty()) { + protected void delete(List symbols) { + if (symbols == null || symbols.isEmpty()) { return; } - tool.setStatusInfo(""); List deleteList = new LinkedList<>(); CompoundCmd cmd = new CompoundCmd<>("Delete symbol(s)"); - for (Symbol symbol : rowObjects) { + for (Symbol symbol : symbols) { if (symbol.isDynamic()) { continue; // can't delete dynamic symbols... } @@ -262,16 +263,19 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel compoundCmd; + + private boolean success; + private String status; + + DeleteTask(CompoundCmd 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> commands = compoundCmd.getCommands(); + for (Command 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 //================================================================================================== - private class NameTableColumn + protected class NameTableColumn extends AbstractProgramBasedDynamicTableColumn { + public static final String NAME = "Name"; + @Override public String getColumnName() { - return "Name"; + return NAME; } @Override @@ -347,7 +437,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { private PinnedRenderer renderer = new PinnedRenderer(); @@ -378,7 +468,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { @Override @@ -404,12 +494,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { + public static final String NAME = "Type"; + @Override public String getColumnName() { - return "Type"; + return NAME; } @Override @@ -426,14 +518,14 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { @Override @@ -470,7 +562,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { @Override @@ -489,9 +581,11 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { + public static final String NAME = "Source"; + private GColumnRenderer renderer = new AbstractGColumnRenderer<>() { @Override protected String getText(Object value) { @@ -509,7 +603,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { + public static final String NAME = "Ref Count"; + private ReferenceCountRenderer renderer = new ReferenceCountRenderer(); @Override public String getColumnName() { - return "Reference Count"; + return NAME; } @Override @@ -562,7 +658,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { private OffcutReferenceCountRenderer renderer = new OffcutReferenceCountRenderer(); @@ -613,7 +709,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { @Override @@ -650,7 +746,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { @Override @@ -688,7 +784,7 @@ public abstract class AbstractSymbolTableModel extends AddressBasedTableModel { private TemplateSimplifier simplifier = new TemplateSimplifier(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java index a966d475d6..b8e399f595 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolPanel.java @@ -36,6 +36,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolTable; +import ghidra.program.util.LabelFieldLocation; import ghidra.program.util.ProgramLocation; import ghidra.util.table.*; @@ -126,12 +127,17 @@ class SymbolPanel extends JPanel { Program program = location.getProgram(); SymbolTable symbolTable = program.getSymbolTable(); Address address = location.getAddress(); - Symbol primarySymbol = symbolTable.getPrimarySymbol(address); - if (primarySymbol == null) { - return; + + Symbol symbol = null; + 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); if (index >= 0) { gTable.selectRow(index); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java index a720ac7b9f..d09aa41262 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java @@ -21,16 +21,19 @@ import static ghidra.program.util.ProgramEvent.*; import java.awt.Component; import java.awt.Cursor; import java.awt.event.KeyEvent; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import javax.swing.Icon; import docking.ActionContext; import docking.action.*; import docking.action.builder.ActionBuilder; +import docking.tool.ToolConstants; import docking.widgets.OptionDialog; import docking.widgets.OptionDialogBuilder; +import docking.widgets.table.DynamicTableColumn; +import docking.widgets.table.TableColumnDescriptor; import generic.theme.GIcon; import ghidra.app.CorePluginPackage; import ghidra.app.cmd.refs.RemoveReferenceCmd; @@ -39,17 +42,20 @@ import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.symboltree.actions.*; +import ghidra.app.plugin.core.table.TableComponentProvider; import ghidra.app.services.BlockModelService; import ghidra.app.services.GoToService; +import ghidra.app.util.HelpTopics; 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.DomainObjectListenerBuilder; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.Data; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramLocation; @@ -79,12 +85,13 @@ import resources.Icons; "allows symbols to be renamed and deleted. This plugin also " + "shows references to a symbol. Filters can be set " + "to show subsets of the symbols.", + servicesProvided = { SymbolTableService.class }, servicesRequired = { GoToService.class, BlockModelService.class }, eventsProduced = { ProgramLocationPluginEvent.class }, eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class } ) //@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_OUTGOING_EVENT_KEY = "NAVIGATE_ON_OUTGOING_EVENT"; @@ -106,6 +113,10 @@ public class SymbolTablePlugin extends Plugin { private BlockModelService blockModelService; private SwingUpdateManager swingMgr; + // providers shown by the service interface + private Map> transientTableProviders = + new HashMap<>(); + private DomainObjectListener domainObjectListener = createDomainObjectListener(); /** @@ -126,6 +137,7 @@ public class SymbolTablePlugin extends Plugin { @Override protected void init() { + gotoService = tool.getService(GoToService.class); blockModelService = tool.getService(BlockModelService.class); @@ -138,11 +150,6 @@ public class SymbolTablePlugin extends Plugin { 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 public void dispose() { super.dispose(); @@ -456,10 +463,14 @@ public class SymbolTablePlugin extends Plugin { DockingAction clearPinnedAction = new ClearPinSymbolAction(getName(), pinnedPopupGroup); tool.addAction(clearPinnedAction); - CreateSymbolTableAction tableAction = new CreateSymbolTableAction(this); + CreateSymbolTableAction tableAction = new CreateSymbolTableAction(getTool()); tableAction.getPopupMenuData().setMenuGroup(popupGroup); tool.addLocalAction(symProvider, tableAction); + SetSymbolPrimaryAction primaryAction = new SetSymbolPrimaryAction(); + primaryAction.getPopupMenuData().setMenuGroup(popupGroup); + tool.addLocalAction(symProvider, primaryAction); + //@formatter:off String bottomGroup = "ShowReferencesTo" + 1; new ActionBuilder("Delete All References", getName()) @@ -641,6 +652,163 @@ public class SymbolTablePlugin extends Plugin { action.setSelected(true); } +//================================================================================================= +// Service Methods +//================================================================================================= + + @Override + public TableComponentProvider showSymbols(CodeUnit codeUnit) { + + Objects.requireNonNull(codeUnit); + + Program program = codeUnit.getProgram(); + Address addr = codeUnit.getMinAddress(); + String title = "Labels at " + addr; + + TableComponentProvider 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 list = symbols.getAllSymbols(); + + HashSet 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 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 provider, + LabelFieldSymbolModel model) { + + List 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 list = symbols.getAllSymbols(); + + HashSet 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 rowObjects) { + super(sp, program, rowObjects); + } + + public void setData(HashSet rowObjects) { + this.rowObjects = rowObjects; + reload(); + } + + @Override + protected void delete(List symbols) { + super.delete(symbols); + } + + @Override + protected TableColumnDescriptor createTableColumnDescriptor() { + + TableColumnDescriptor descriptor = super.createTableColumnDescriptor(); + + //@formatter:off + Set visibleNames = new HashSet<>(Set.of( + NameTableColumn.NAME, + SymbolTypeTableColumn.NAME, + SourceTableColumn.NAME, + ReferenceCountTableColumn.NAME)); + //@formatter:on + + List> allColumns = descriptor.getAllColumns(); + + for (DynamicTableColumn column : allColumns) { + String columnName = column.getColumnName(); + boolean visible = visibleNames.contains(columnName); + descriptor.setVisible(columnName, visible); + } + + return descriptor; + } + } + //================================================================================================== // Table Update Jobs //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableService.java new file mode 100644 index 0000000000..43cf2ef935 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableService.java @@ -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 showSymbols(CodeUnit codeUnit); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java index 60e1a55f37..e02a4b8bb1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/TransientSymbolTableModel.java @@ -4,9 +4,9 @@ * 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. @@ -22,7 +22,7 @@ import java.util.HashSet; import java.util.List; import ghidra.framework.model.DomainObjectListenerBuilder; -import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramChangeRecord; import ghidra.util.datastruct.Accumulator; @@ -36,13 +36,13 @@ import ghidra.util.task.TaskMonitor; */ public class TransientSymbolTableModel extends AbstractSymbolTableModel { - private HashSet rowObjects; + protected HashSet rowObjects; private SwingUpdateManager updater = new SwingUpdateManager(this::fireTableDataChanged); - public TransientSymbolTableModel(PluginTool tool, Program program, + public TransientSymbolTableModel(ServiceProvider sp, Program program, HashSet rowObjects) { - super(tool); + super(sp); this.rowObjects = rowObjects; setProgram(program); symbolTable = program.getSymbolTable(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelCodeUnitFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelCodeUnitFormat.java index 5c2197ffc9..72a13e143c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelCodeUnitFormat.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelCodeUnitFormat.java @@ -34,17 +34,20 @@ public class LabelCodeUnitFormat extends BrowserCodeUnitFormat { @Override protected String getOffcutLabelStringForInstruction(Address offcutAddress, - Instruction instruction, Address markupAddress) { + Instruction instruction, Address markupAddress, Symbol symbol) { if (markupAddress != null) { throw new UnsupportedOperationException(); } Program program = instruction.getProgram(); - Symbol offsym = program.getSymbolTable().getPrimarySymbol(offcutAddress); + + if (symbol == null) { + symbol = program.getSymbolTable().getPrimarySymbol(offcutAddress); + } Address instructionAddress = instruction.getMinAddress(); long diff = offcutAddress.subtract(instructionAddress); - boolean decorate = !offsym.isDynamic(); + boolean decorate = !symbol.isDynamic(); boolean simplify = true; - return getDefaultOffcutString(offsym, instruction, diff, decorate, simplify); + return getDefaultOffcutString(symbol, instruction, diff, decorate, simplify); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldFactory.java index 59e99b70a6..28c02ead3b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldFactory.java @@ -15,8 +15,11 @@ */ package ghidra.app.util.viewer.field; +import java.awt.Color; +import java.awt.FontMetrics; import java.math.BigInteger; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import javax.swing.Icon; import javax.swing.event.ChangeListener; @@ -25,13 +28,14 @@ import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.support.FieldLocation; import generic.theme.GIcon; import ghidra.app.util.*; +import ghidra.app.util.viewer.field.LabelFieldSymbolLoader.Symbols; import ghidra.app.util.viewer.format.FieldFormatModel; import ghidra.app.util.viewer.options.OptionsGui; import ghidra.app.util.viewer.proxy.ProxyObj; import ghidra.framework.options.*; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressIterator; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.*; import ghidra.program.util.*; import ghidra.util.HelpLocation; @@ -44,8 +48,6 @@ import resources.icons.EmptyIcon; */ public class LabelFieldFactory extends FieldFactory { - private final static int MAX_OFFCUT_DISPLAY = 30; // arbitrary - public final static String FIELD_NAME = "Label"; public final static String OFFCUT_STYLE = "XRef Offcut Style"; public final static String GROUP_TITLE = "Labels Field"; @@ -54,6 +56,12 @@ public class LabelFieldFactory extends FieldFactory { private final static String NAMESPACE_OPTIONS = GROUP_TITLE + Options.DELIMITER + "Display Namespace"; + private static final String MAX_LABELS_LABEL = + GROUP_TITLE + Options.DELIMITER + "Maximum Number of Labels to Display"; + + private static final int MAX_LABELS = 10; + private int maxLabels = MAX_LABELS; + // These icons would normally be static, but can't be because the class searcher loads this // class and it triggers swing access which is not allowed in headless. private Icon EMPTY_ICON = new EmptyIcon(12, 16); @@ -92,10 +100,15 @@ public class LabelFieldFactory extends FieldFactory { fieldOptions.registerOption(DISPLAY_FUNCTION_LABEL, true, hl, "Shows function names in a label field below the function header"); + fieldOptions.registerOption(MAX_LABELS_LABEL, MAX_LABELS, hl, + "Sets the maximum number of labels to display."); + displayFunctionLabel = fieldOptions.getBoolean(DISPLAY_FUNCTION_LABEL, true); setupNamespaceOptions(fieldOptions); + maxLabels = fieldOptions.getInt(MAX_LABELS_LABEL, MAX_LABELS); + // Create code unit format and associated options - listen for changes codeUnitFormat = new LabelCodeUnitFormat(fieldOptions); codeUnitFormat.addChangeListener(codeUnitFormatListener); @@ -132,116 +145,148 @@ public class LabelFieldFactory extends FieldFactory { setupNamespaceOptions(options); model.update(); } + else if (optionName.equals(MAX_LABELS_LABEL)) { + setMaxSize(((Integer) newValue).intValue(), options); + model.update(); + } + } + + private void setMaxSize(int n, Options options) { + if (n < 1) { + n = 1; + options.setInt(MAX_LABELS_LABEL, 1); + } + maxLabels = n; } - /** - * @see ghidra.app.util.viewer.field.FieldFactory#getField(ProxyObj, int) - */ @Override public ListingField getField(ProxyObj proxy, int varWidth) { Object obj = proxy.getObject(); if (!enabled || !(obj instanceof CodeUnit)) { return null; } - int x = startX + varWidth; + CodeUnit cu = (CodeUnit) obj; + LabelFieldSymbolLoader loader = + new LabelFieldSymbolLoader(cu, maxLabels, displayFunctionLabel); + Symbols symbols = loader.getSymbols(); - Address currAddr = cu.getMinAddress(); - - Program prog = cu.getProgram(); - - Listing list = prog.getListing(); - Function func = list.getFunctionAt(currAddr); - - Symbol[] symbols = cu.getSymbols(); - - // check to see if there is an offcut reference to this code unit - // if there is, then create a "OFF" label - List
    offcuts = getOffcutReferenceAddress(cu); - boolean hasOffcuts = offcuts.size() > 0; - - // if there is only a function symbol and we are not showing function symbols, get out. - if (!hasOffcuts && symbols.length == 1 && func != null && !displayFunctionLabel) { + int total = symbols.size(); + if (total == 0) { return null; } - makePrimaryLastItem(symbols); + Address addr = cu.getMinAddress(); + Program program = cu.getProgram(); - int length = symbols.length; - if (!displayFunctionLabel && func != null) { - length = symbols.length - 1; - } - - if (hasOffcuts) { - length += offcuts.size(); - } - - if (length == 0) { - return null; - } - - List elements = new ArrayList<>(length); - - if (hasOffcuts) { - for (Address offcut : offcuts) { - AttributedString as = getAttributedOffsetText(obj, cu, currAddr, offcut); - if (as == null) { - as = new AttributedString(EMPTY_ICON, - SymbolUtilities.getDynamicOffcutName(currAddr), - inspector.getOffcutSymbolColor(), - getMetrics(inspector.getOffcutSymbolStyle()), false, null); - } - elements.add(new TextFieldElement(as, elements.size(), 0)); + int x = startX + varWidth; + if (total == 1 && addr.isExternalAddress()) { + Symbol s = symbols.get(0); + TextFieldElement externalField = createExternalField(program, s); + if (externalField != null) { + return ListingTextField.createMultilineTextField(this, proxy, + List.of(externalField), x, width, hlProvider); } } - if (currAddr.isExternalAddress() && length == 1) { - // Show extenal address and original imported name (not supported by field location) - ExternalLocation extLoc = prog.getExternalManager().getExternalLocation(symbols[0]); - if (extLoc != null) { - StringBuilder externalLocationDetails = new StringBuilder(); - Address addr = extLoc.getAddress(); - if (addr != null) { - externalLocationDetails.append(addr.toString()); - } - String origImportedName = extLoc.getOriginalImportedName(); - if (origImportedName != null) { - if (!externalLocationDetails.isEmpty()) { - externalLocationDetails.append(": "); - } - externalLocationDetails.append(origImportedName); - } - if (!externalLocationDetails.isEmpty()) { - AttributedString as = - new AttributedString(EMPTY_ICON, externalLocationDetails.toString(), - OptionsGui.LABELS_NON_PRIMARY.getColor(), - getMetrics(OptionsGui.LABELS_NON_PRIMARY.getStyle()), false, null); - elements.add(new TextFieldElement(as, elements.size(), 0)); - } - } - } + List elements = new ArrayList<>(); - for (Symbol symbol : symbols) { - if (func != null && symbol.isPrimary() && !displayFunctionLabel) { - continue; - } + List offcuts = symbols.getOffcuts(); + createOffcutElements(obj, cu, addr, offcuts, elements); - Icon icon = symbol.isPinned() ? ANCHOR_ICON : EMPTY_ICON; - ColorAndStyle c = inspector.getColorAndStyle(symbol); - AttributedString as = new AttributedString(icon, checkLabelString(symbol, prog), - c.getColor(), getMetrics(c.getStyle()), false, null); + // grab the remaining non-offcut symbols + for (int i = offcuts.size(); i < symbols.size(); i++) { + Symbol s = symbols.get(i); + AttributedString as = createSymbolString(s); elements.add(new TextFieldElement(as, elements.size(), 0)); } + if (loader.hasMore()) { + Symbol nonPrimarySymbol = symbols.get(0); + int lastRow = elements.size(); + AttributedString as = createMoreSymbolsString(nonPrimarySymbol); + + // place this above primary symbol, as that seems to look the best + int primaryIndex = elements.size() - 1; + int index = primaryIndex; + elements.add(index, new TextFieldElement(as, lastRow, 0)); + } + return ListingTextField.createMultilineTextField(this, proxy, elements, x, width, hlProvider); } - private String getOffsetText(CodeUnit cu, Address currAddr, Address offcutAddress) { + private AttributedString createSymbolString(Symbol s) { + Icon icon = s.isPinned() ? ANCHOR_ICON : EMPTY_ICON; + ColorAndStyle c = inspector.getColorAndStyle(s); + FontMetrics fm = getMetrics(c.getStyle()); + Color color = c.getColor(); + String text = getLabelString(s); + return new AttributedString(icon, text, color, fm, false, null); + } - SymbolTable symbolTable = cu.getProgram().getSymbolTable(); - Symbol offcutSymbol = symbolTable.getPrimarySymbol(offcutAddress); - if (offcutSymbol == null) { + private AttributedString createMoreSymbolsString(Symbol prototype) { + ColorAndStyle c = inspector.getColorAndStyle(prototype); + FontMetrics fm = getMetrics(c.getStyle()); + Color color = c.getColor(); + String text = MoreLabelFieldLocation.MORE_LABELS_STRING; + return new AttributedString(EMPTY_ICON, text, color, fm, false, null); + } + + private void createOffcutElements(Object obj, CodeUnit cu, Address addr, List offcuts, + List elements) { + + FontMetrics fm = getMetrics(inspector.getOffcutSymbolStyle()); + Color color = inspector.getOffcutSymbolColor(); + for (Symbol s : offcuts) { + + Address offcut = s.getAddress(); + String text = getOffcutText(cu, addr, offcut, s); + if (text == null) { + text = SymbolUtilities.getDynamicOffcutName(addr); + } + + AttributedString as = new AttributedString(EMPTY_ICON, text, color, fm, false, null); + elements.add(new TextFieldElement(as, elements.size(), 0)); + } + } + + private TextFieldElement createExternalField(Program p, Symbol symbol) { + + // Show external address and original imported name (not supported by field location) + ExternalLocation extLoc = p.getExternalManager().getExternalLocation(symbol); + if (extLoc == null) { + return null; + } + + StringBuilder externalLocationDetails = new StringBuilder(); + Address addr = extLoc.getAddress(); + if (addr != null) { + externalLocationDetails.append(addr.toString()); + } + String origImportedName = extLoc.getOriginalImportedName(); + if (origImportedName != null) { + if (!externalLocationDetails.isEmpty()) { + externalLocationDetails.append(": "); + } + externalLocationDetails.append(origImportedName); + } + + if (externalLocationDetails.isEmpty()) { + return null; + } + + FontMetrics fm = getMetrics(OptionsGui.LABELS_NON_PRIMARY.getStyle()); + String text = externalLocationDetails.toString(); + Color color = OptionsGui.LABELS_NON_PRIMARY.getColor(); + AttributedString as = new AttributedString(EMPTY_ICON, text, color, fm, false, null); + return new TextFieldElement(as, 0, 0); + } + + private String getOffcutText(CodeUnit cu, Address currAddr, Address offcutAddress, + Symbol symbol) { + + if (symbol == null) { // While we should always have a primary symbol to a referenced // address - invalid data could cause this rule to be violated, // so lets play nice and return something @@ -249,31 +294,24 @@ public class LabelFieldFactory extends FieldFactory { } String offcutSymbolText = null; - if (!offcutSymbol.isDynamic()) { + if (!symbol.isDynamic()) { // the formatter doesn't change dynamic labels - offcutSymbolText = codeUnitFormat.getOffcutLabelString(offcutAddress, cu, null); + offcutSymbolText = codeUnitFormat.getOffcutLabelString(offcutAddress, cu, null, symbol); } else { - offcutSymbolText = offcutSymbol.getName(); + offcutSymbolText = symbol.getName(); } return offcutSymbolText; } - private AttributedString getAttributedOffsetText(Object obj, CodeUnit cu, Address currAddr, - Address offcutAddress) { - - return new AttributedString(EMPTY_ICON, getOffsetText(cu, currAddr, offcutAddress), - inspector.getOffcutSymbolColor(), getMetrics(inspector.getOffcutSymbolStyle()), false, - null); - } - - private String checkLabelString(Symbol symbol, Program program) { + private String getLabelString(Symbol symbol) { if (!displayLocalNamespace && !displayNonLocalNamespace) { return simplifyTemplates(symbol.getName()); // no namespaces being shown } + Program program = symbol.getProgram(); Namespace addressNamespace = program.getSymbolTable().getNamespace(symbol.getAddress()); Namespace symbolNamespace = symbol.getParentNamespace(); boolean isLocal = symbolNamespace.equals(addressNamespace); @@ -293,176 +331,94 @@ public class LabelFieldFactory extends FieldFactory { return simplifyTemplates(symbol.getName(true)); } - private List
    getOffcutReferenceAddress(CodeUnit cu) { - - Address startAddr = cu.getMinAddress(); - if (!startAddr.isMemoryAddress()) { - return Collections.emptyList(); - } - - Program prog = cu.getProgram(); - if (cu.getLength() == 1) { - return Collections.emptyList(); - } - Address nextAddr = startAddr.next(); - if (nextAddr == null) { - return Collections.emptyList(); - } - - Address endAddress = cu.getMaxAddress(); - - List
    list = new ArrayList<>(); - AddressIterator it = - prog.getReferenceManager().getReferenceDestinationIterator(nextAddr, true); - while (it.hasNext()) { - Address addr = it.next(); - if (addr.compareTo(endAddress) > 0) { - break; - } - -// TODO: check for wrapping - temporary work-around - if (addr.compareTo(cu.getMinAddress()) > 0) { - list.remove(addr); - list.add(addr); - if (list.size() > MAX_OFFCUT_DISPLAY) { - return list; // short-circuit - } - } - } - - SymbolIterator symIter = prog.getSymbolTable().getSymbolIterator(nextAddr, true); - while (symIter.hasNext()) { - Symbol s = symIter.next(); - Address addr = s.getAddress(); - if (addr.compareTo(endAddress) > 0) { - break; - } - -// TODO: check for wrapping - temporary work-around - if (addr.compareTo(cu.getMinAddress()) > 0) { - list.remove(addr); - list.add(addr); - if (list.size() > MAX_OFFCUT_DISPLAY) { - return list; // short-circuit - } - } - } - - return list; - } - - /** - * Move primary symbol to last element in array ... - */ - private void makePrimaryLastItem(Symbol[] symbols) { - for (int i = 0; i < symbols.length - 1; ++i) { - if (symbols[i].isPrimary()) { - Symbol primary = symbols[i]; - System.arraycopy(symbols, i + 1, symbols, i, symbols.length - i - 1); - symbols[symbols.length - 1] = primary; - - break; - } - } - } - @Override public ProgramLocation getProgramLocation(int row, int col, ListingField bf) { Object obj = bf.getProxy().getObject(); - if (!(obj instanceof CodeUnit)) { + if (!(obj instanceof CodeUnit cu)) { return null; } - CodeUnit cu = (CodeUnit) obj; - int[] cpath = null; - if (cu instanceof Data) { - cpath = ((Data) cu).getComponentPath(); + LabelFieldSymbolLoader loader = + new LabelFieldSymbolLoader(cu, maxLabels, displayFunctionLabel); + + MoreLabelFieldLocation moreLocation = createMoreLocation(cu, loader, row, col); + if (moreLocation != null) { + return moreLocation; } - List
    offcuts = getOffcutReferenceAddress(cu); - - // fictitious offcut labels are listed first, check if row is a fictitious offcut label - if (row < offcuts.size()) { - return getLocationForOffcuttLabel(row, col, cu, cpath, offcuts); + // If we have more, then the [more] row is added to the display, which pushes the last + // row down 1. + int symbolRow = row; + if (row >= maxLabels) { + symbolRow = row - 1; } - int symbolIndex = row - offcuts.size(); - - Symbol s = getCodeOrFunctionSymbol(cu, symbolIndex); - if (s == null) { - return new CodeUnitLocation(cu.getProgram(), cu.getMinAddress(), cpath, 0, 0, 0); - } - return new LabelFieldLocation(s, row, col); + Symbols symbols = loader.getSymbols(); + Symbol s = symbols.get(symbolRow); + return new LabelFieldLocation(s, symbolRow, col); } - private Symbol getCodeOrFunctionSymbol(CodeUnit cu, int symbolIndex) { - Symbol[] symbols = cu.getSymbols(); - if (symbols.length == 0) { + private MoreLabelFieldLocation createMoreLocation(CodeUnit cu, LabelFieldSymbolLoader loader, + int row, int col) { + + if (!loader.hasMore()) { return null; } - makePrimaryLastItem(symbols); - - if (symbolIndex >= symbols.length) { - symbolIndex = symbols.length - 1; - } - Symbol symbol = symbols[symbolIndex]; - SymbolType symbolType = symbol.getSymbolType(); - if (symbolType != SymbolType.LABEL && symbolType != SymbolType.FUNCTION) { + int moreRow = maxLabels - 1; // the [more] text is just above the last, primary symbol + if (row != moreRow) { return null; } - return symbol; - } - private ProgramLocation getLocationForOffcuttLabel(int row, int col, CodeUnit cu, int[] cpath, - List
    offcuts) { + Program p = cu.getProgram(); Address addr = cu.getMinAddress(); - String text = getOffsetText(cu, addr, offcuts.get(row)); - if (text == null) { - text = SymbolUtilities.getDynamicOffcutName(addr); - } - // since these labels are fictitious, they don't have a namespace. - return new LabelFieldLocation(cu.getProgram(), addr, cpath, text, null, row, col); + return new MoreLabelFieldLocation(p, addr, moreRow, col); } @Override public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum, - ProgramLocation programLoc) { + ProgramLocation location) { Object obj = bf.getProxy().getObject(); - if (!(programLoc instanceof LabelFieldLocation)) { - return null; - } - LabelFieldLocation loc = (LabelFieldLocation) programLoc; - - if (!(obj instanceof CodeUnit)) { + if (!(obj instanceof CodeUnit cu)) { return null; } - String lableName = loc.getName(); - - CodeUnit cu = (CodeUnit) obj; - - List
    offcuts = getOffcutReferenceAddress(cu); - for (int i = 0; i < offcuts.size(); i++) { - String text = getOffsetText(cu, cu.getMinAddress(), offcuts.get(i)); - if (text != null && text.equals(lableName)) { - return new FieldLocation(index, fieldNum, i, loc.getCharOffset()); - } - } - - Symbol[] symbols = cu.getSymbols(); - makePrimaryLastItem(symbols); - if (symbols.length == 0) { + if (!(location instanceof LabelFieldLocation) && + !(location instanceof MoreLabelFieldLocation)) { return null; } - for (int i = 0; i < symbols.length; i++) { - if (symbols[i].getName().equals(lableName)) { - return new FieldLocation(index, fieldNum, i + offcuts.size(), loc.getCharOffset()); - } + /* + Handle the case where we have 2 tools with a different number of rows showing. + 1) if the given location is a [more] location, then use the [more] field. + 2) If the given row is the primary symbol's row, then use that. + 3) What's left is all rows above the [more]. + */ + + LabelFieldSymbolLoader loader = + new LabelFieldSymbolLoader(cu, maxLabels, displayFunctionLabel); + if (loader.hasMore() && location instanceof MoreLabelFieldLocation moreLoc) { + // we have [more] showing and the location is a [more] location + int row = maxLabels - 1; // my more row + int col = moreLoc.getCharOffset(); + return new FieldLocation(index, fieldNum, row, col); } - return null; + + LabelFieldLocation labelLocation = (LabelFieldLocation) location; + Symbol symbol = labelLocation.getSymbol(); + Symbols symbols = loader.getSymbols(); + if (symbol != null && symbol.isPrimary()) { + int row = loader.hasMore() ? symbols.size() : symbols.size() - 1; + return new FieldLocation(index, fieldNum, row, labelLocation.getCharOffset()); + } + + int symbolRow = labelLocation.getRow(); + symbolRow = Math.min(symbolRow, maxLabels - 1); + + // We already handle the primary case and the case when we have a more location. For all + // other locations, just use the given row, capping at our max display. + return new FieldLocation(index, fieldNum, symbolRow, labelLocation.getCharOffset()); } @Override @@ -479,4 +435,5 @@ public class LabelFieldFactory extends FieldFactory { ToolOptions pDisplayOptions, ToolOptions fieldOptions) { return new LabelFieldFactory(formatModel, provider, pDisplayOptions, fieldOptions); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldMouseHandler.java new file mode 100644 index 0000000000..98d9c2f9f8 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldMouseHandler.java @@ -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; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java new file mode 100644 index 0000000000..062c413468 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/LabelFieldSymbolLoader.java @@ -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 realSymbols = new ArrayList<>(); + private List 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 getAllSymbols() { + List list = new ArrayList<>(); + + list.addAll(realSymbols); + list.addAll(offcutSymbols); + + return list; + } + + List getOffcuts() { + return Collections.unmodifiableList(offcutSymbols); + } + + void reverseSymbols() { + realSymbols = realSymbols.reversed(); + } + + @Override + public String toString() { + return Json.toString(this); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java index 4083a75037..287428920b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/XRefFieldMouseHandler.java @@ -30,7 +30,7 @@ import ghidra.program.model.symbol.Reference; import ghidra.program.util.*; /** - * A handler to process {@link XRefFieldMouseHandler} clicks + * A handler to process XRef field clicks */ public class XRefFieldMouseHandler implements FieldMouseHandlerExtension { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java index ec48231400..5976b4d848 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java @@ -1367,7 +1367,7 @@ public class CodeUnitFormat { if (symbolAddress.isMemoryAddress()) { CodeUnit cu = program.getListing().getCodeUnitContaining(symbolAddress); if (isOffcut(symbolAddress, cu)) { - return getOffcutLabelString(symbolAddress, cu, markupAddress); + return getOffcutLabelString(symbolAddress, cu, markupAddress, symbol); } else if (isStringData(cu)) { return getLabelStringForStringData((Data) cu, symbol); @@ -1408,10 +1408,11 @@ public class CodeUnitFormat { 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) { return getOffcutLabelStringForInstruction(offcutAddress, (Instruction) cu, - markupAddress); + markupAddress, symbol); } return getOffcutDataString(offcutAddress, (Data) cu); } @@ -1460,17 +1461,22 @@ public class CodeUnitFormat { * @param offcutAddress address for which generated label represents * @param instruction instruction containing offcut address * @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 */ protected String getOffcutLabelStringForInstruction(Address offcutAddress, - Instruction instruction, Address markupAddress) { + Instruction instruction, Address markupAddress, Symbol symbol) { Program program = instruction.getProgram(); - Symbol offsym = program.getSymbolTable().getPrimarySymbol(offcutAddress); + + if (symbol == null) { + symbol = program.getSymbolTable().getPrimarySymbol(offcutAddress); + } + Address instructionAddress = instruction.getMinAddress(); long diff = offcutAddress.subtract(instructionAddress); boolean decorate = false; // we never decorate in the operand field boolean simplify = true; // we always simplify names of instruction labels - if (offsym.isDynamic()) { + if (symbol.isDynamic()) { Symbol containingSymbol = program.getSymbolTable().getPrimarySymbol(instructionAddress); if (containingSymbol != null) { String displayName = containingSymbol.getName(); @@ -1481,7 +1487,7 @@ public class CodeUnitFormat { 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, diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/OperandLabelDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/OperandLabelDialogTest.java index 9eb78113b8..6030a16665 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/OperandLabelDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/label/OperandLabelDialogTest.java @@ -4,9 +4,9 @@ * 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. @@ -20,7 +20,6 @@ import static org.junit.Assert.*; import org.junit.*; import docking.ActionContext; -import docking.widgets.combobox.GhidraComboBox; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.framework.plugintool.PluginTool; @@ -74,10 +73,9 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest performAction(setLabelAction, context, false); waitForSwing(); - OperandLabelDialog dialog = waitForDialogComponent(OperandLabelDialog.class); - GhidraComboBox combo = (GhidraComboBox) findComponentByName(dialog, "MYCHOICE"); + SymbolChooserDialog dialog = waitForDialogComponent(SymbolChooserDialog.class); - setSelectedItem(combo, "bob"); + setSelectedItem(dialog, "bob"); pressButtonByText(dialog, "OK"); waitForSwing(); @@ -91,10 +89,9 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest performAction(setLabelAction, context, false); waitForSwing(); - dialog = waitForDialogComponent(OperandLabelDialog.class); - combo = (GhidraComboBox) findComponentByName(dialog, "MYCHOICE"); + dialog = waitForDialogComponent(SymbolChooserDialog.class); - setSelectedItem(combo, "b"); + setSelectedItem(dialog, "b"); pressButtonByText(dialog, "OK"); program.flushEvents(); @@ -108,7 +105,7 @@ public class OperandLabelDialogTest extends AbstractGhidraHeadedIntegrationTest assertEquals("dword ptr [b]", cb.getCurrentFieldText()); } - private void setSelectedItem(GhidraComboBox combo, String s) { - runSwing(() -> combo.setSelectedItem(s)); + private void setSelectedItem(SymbolChooserDialog dialog, String item) { + runSwing(() -> dialog.setSelectedItem(item)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/LabelFieldFactoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/LabelFieldFactoryTest.java index 33fee42dad..060fc0e4c9 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/LabelFieldFactoryTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/viewer/field/LabelFieldFactoryTest.java @@ -169,8 +169,9 @@ public class LabelFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest { ListingTextField tf = (ListingTextField) cb.getCurrentField(); // 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", - tf.getText()); // bad offcut put on instruction + actual); // bad offcut put on instruction assertEquals(5, tf.getNumRows()); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.java index 488804655d..5f3046aaf8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.java @@ -93,6 +93,13 @@ public class FieldLocation implements Comparable { 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) { this.index = index; this.fieldNum = fieldNum; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java index 5e5c49400f..74cef04688 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/DynamicColumnTableModel.java @@ -4,9 +4,9 @@ * 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. @@ -38,4 +38,21 @@ public interface DynamicColumnTableModel * @return the model index */ public int getColumnIndex(DynamicTableColumn column); + + /** + * Allows table models to create their own preference key. + *

    + * 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; + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java index 79d850ce8f..6b8ba7e24b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java @@ -15,10 +15,10 @@ */ package docking.widgets.table; -import static docking.DockingUtils.CONTROL_KEY_MODIFIER_MASK; -import static docking.action.MenuData.NO_MNEMONIC; -import static java.awt.event.InputEvent.SHIFT_DOWN_MASK; -import static javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION; +import static docking.DockingUtils.*; +import static docking.action.MenuData.*; +import static java.awt.event.InputEvent.*; +import static javax.swing.ListSelectionModel.*; import java.awt.*; 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() { TableModel model = getUnwrappedTableModel(); @@ -646,6 +646,17 @@ public class GTable extends JTable { 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 * model. @@ -912,7 +923,17 @@ public class GTable extends JTable { * @see #setPreferenceKey(String) */ public String getPreferenceKey() { - return preferenceKey; + if (preferenceKey != null) { + // prefer the key that has been set programmatically + return preferenceKey; + } + + DynamicColumnTableModel dynamicModel = getDynamicTableModel(); + if (dynamicModel != null) { + return dynamicModel.getPreferenceKey(); + } + + return null; } /** diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/cmd/CompoundCmd.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/cmd/CompoundCmd.java index 75eab940d9..61dbc61740 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/cmd/CompoundCmd.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/cmd/CompoundCmd.java @@ -4,9 +4,9 @@ * 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. @@ -15,9 +15,9 @@ */ 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. @@ -28,7 +28,7 @@ import java.util.ArrayList; * @param {@link DomainObject} implementation interface */ public class CompoundCmd implements Command { - private ArrayList> cmds; + private List> cmds; private String statusMsg; private String name; @@ -81,4 +81,10 @@ public class CompoundCmd implements Command { return cmds.size(); } + /** + * {@return the commands in this compound command} + */ + public List> getCommands() { + return Collections.unmodifiableList(cmds); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java index 9bacb8e6f7..f4dda9b75a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/SymbolTable.java @@ -65,7 +65,7 @@ public interface SymbolTable { * @param name the name of the symbol * @param source the source of this symbol. In general, a source of {@link SourceType#DEFAULT} * 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 * non-default source * @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. *

    - * NOTE: All external symbols will be omiitted unless the full + * NOTE: All external symbols will be omitted unless the full * {@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. * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/CodeUnitLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/CodeUnitLocation.java index 3ae9ca4cae..242975d0ce 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/CodeUnitLocation.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/CodeUnitLocation.java @@ -4,9 +4,9 @@ * 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. @@ -36,8 +36,8 @@ public class CodeUnitLocation extends ProgramLocation { * @param componentPath if this is not null it is the path to a data * component inside of another data component * @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 charOffset - the character offset within the display item. + * @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. * */ 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 * component inside of another data component * @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 charOffset - the character offset within the display item. + * @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. * */ 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 addr address of the location; should not be null * @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 charOffset - the character offset within the display item. + * @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. * */ public CodeUnitLocation(Program program, Address addr, int row, int col, int charOffset) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/MoreLabelFieldLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/MoreLabelFieldLocation.java new file mode 100644 index 0000000000..11a849456a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/MoreLabelFieldLocation.java @@ -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; + } +} diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java index 69f5dc025e..d5fc118b32 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java @@ -21,8 +21,8 @@ import javax.swing.*; import org.junit.Test; -import docking.widgets.combobox.GhidraComboBox; -import ghidra.app.plugin.core.label.*; +import ghidra.app.plugin.core.label.LabelHistoryDialog; +import ghidra.app.plugin.core.label.LabelHistoryInputDialog; import ghidra.app.util.*; import ghidra.program.model.address.*; import ghidra.program.model.symbol.LabelHistory; @@ -85,22 +85,6 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator { 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 public void testShowLabelHistory() { AddressSpace space = new GenericAddressSpace("Test", 32, AddressSpace.TYPE_RAM, 0);