diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/external_program_names.htm b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/external_program_names.htm index fc5647cfe3..d8f3a7ef6e 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/external_program_names.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/external_program_names.htm @@ -14,14 +14,36 @@

External Program Names

-

An external reference is a reference to a location in another program. The reference - destination includes the name of some program. To use an external reference to navigate, the - external program name must be associated with an existing program file in the Ghidra project. - If the association has been defined, then the external reference is said - to have been resolved. The External Programs view manages the associations - between external program names and program files. The table shows all external program names - and their associated links to program files. Use the External Programs view to add - external names, delete external names, set associations, and clear associations.

+

An external location identifies an external function or data dependency + associated with a named Library (i.e., External Program). One or more external references may refer to + a single external location. Thunk functions within a Program may also refer to an external + location if it corresponds to a function. Each Library defined within Ghidra can optionally + be associated with another Program file within the Ghidra project.

+ +

There is a reserved + Library named <EXTERNAL> which is a holding area for external locations whose + associated Library is unknown (commonly used by ELF Imports). + The External Symbol Resolver analyzer can be used to search + for these external locations among the ordered list of Libraries which have a Program file association. + The ordered Library sequence dictates the order that this analyzer searches through them.

+ +

To navigate on an external reference/location, or resolve external locations, the + external Library name must be associated with an existing Program file in the same project. + If a Library's Program association has been specified, then any related + external reference is said to have been resolved.

+ +

The External Programs view manages the associations + between external Library names and Program files as well as the ordered Library sequence. + Use the External Programs view to add/delete external Library names, + set/clear Program associations and adjust search order using up/down placement actions. + Other than the up/down ordering functionality, the + Symbol Tree Imports node provides + similar actions plus the ability to navigate.

+ +

The term "External Program" within Ghidra + is used interchangeably with the term "Library" and represents a dependency. Each defined + Library has a corresponding Library Symbol within a program's symbol table and may have related + external locations.

External Programs View

@@ -29,26 +51,33 @@

-

The External Programs view consists of a main scrollable list of external program +

The External Programs view consists of a main scrollable list of external Library names and their associated Ghidra program files.

Name Column

-

The name of the external program. Many external programs will share the same external - program name. Setting or changing the associated Ghidra file will affect all the external - references to that name. Double-click on this field to edit the name. After you change - the name, hit the <Enter> key.

+

The name of the external Library. Many external function and/or data locations will + correspond to the same external Library within a Ghidra program which are considered to + be Imports.

+ +

The name used within the current program to identify the Library may be + changed. To do this, double-click on the Name to enter edit mode. After you change + the name, press <Enter> to commit the change.

Ghidra Program Column

-

The Ghidra file associated with the external program name. This field is blank if - external reference has not been resolved. Ghidra will not be able to "follow" an external - reference if its external program name does not have an associated Ghidra file.

+

The Ghidra program file associated with the external Library name. This field is blank if + the external Library has not been resolved. Ghidra will not be able to "follow" an external + location reference into a Library, or search for external symbols within a Library, + if a Library does not have a Ghidra program file association.

+ +

See Set External Path Association for changing the + path shown.

Add Button

@@ -58,14 +87,48 @@ for entering a new external program name.

+ +

Delete External Name Button

+
+

The Delete button deletes the selected external Library names from the + program.  If a selected external Library name has associated external locations, it can + not be deleted. The Delete button is enabled + whenever one or more rows are selected.

+
+ +

Move Library Up Button

+ +
+

The Up button will shift a selected external Library up + within the list of Libraries thus increasing its priority when used to + search for external symbols. The Up + button is enabled whenever one external program name is selected and can be moved + up within the Library list.

+

+

+ +

Move Library Down Button

+ +
+

The Down button will shift a selected external Library down + within the list of Libraries thus reducing its priority when used to + search for external symbols. The Down + button is enabled whenever one external program name is selected and can be moved + down within the Library list.

+

+

+

Set - External Name Association Button

+ External Path Association Button

The Set button brings up a Ghidra program chooser dialog. Choose a - Ghidra program file to associate it with the selected external program name. This button + Ghidra program file to associate it with the selected external Library name. This button is only enabled when a single external program name is selected.

@@ -77,26 +140,13 @@
-

Clear External Name Association +

Clear External Path Association Button

-

This Clear   button clears the assocated program for all the - selected external program names.
-

-
- -

Delete External Name Button

- -
-

This Delete button deletes the selected external program names from the - program.  If a selected external program name contains external locations, it can - not be deleted. The Delete button is enabled - whenever one or more external program names are selected.

- -


+

The Clear   button clears the associated program path for all the + selected rows.

@@ -115,17 +165,12 @@
  • Enter the new external program name into the pop-up dialog.
  • -
  • - <> +

    Any new External Program added + will be placed at the bottom of the list. If relying on external symbol resolution + analysis and the library search order is important, the new entry may be moved up + in the list using the Up button. +

    -

    If the table is sorted by Name, then - the name you enter will be placed at the correct position in the table to maintain the - sort order. The sort icon or indicates the order and what column is - being sorted. You can also sort by Ghidra Pathname by clicking on the header for this - column. Click on the sort icon to change the order.

    - </> -
  • Resolving an External Name to an existing Ghidra program
    @@ -157,7 +202,7 @@
  • Click on the external program name that has an association to be cleared.
  • -
  • Press the Clear button
  • +
  • Press the Clear button
  • Removing an External Program Name

    diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/images/External_names_dialog.png b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/images/External_names_dialog.png index 6e05e50386..ce6f1adac8 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/images/External_names_dialog.png and b/Ghidra/Features/Base/src/main/help/help/topics/ReferencesPlugin/images/External_names_dialog.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/ClearExternalNameCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/ClearExternalPathCmd.java similarity index 55% rename from Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/ClearExternalNameCmd.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/ClearExternalPathCmd.java index 7b4df72a04..64f9837e84 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/ClearExternalNameCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/ClearExternalPathCmd.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. @@ -16,37 +16,46 @@ package ghidra.app.cmd.refs; import ghidra.framework.cmd.Command; +import ghidra.program.model.listing.Library; import ghidra.program.model.listing.Program; -import ghidra.util.exception.AssertException; +import ghidra.program.model.symbol.ExternalManager; import ghidra.util.exception.InvalidInputException; /** - * Command to remove an external program name from the reference manager. + * Command to clear the external program path associated with an external Library. * */ -public class ClearExternalNameCmd implements Command { +public class ClearExternalPathCmd implements Command { private String externalName; private String status; private boolean userDefined = true; /** - * Constructs a new command removing an external program name. - * @param externalName the name of the external program name to be removed. + * Constructs a new command for clearing the external program path associated with a + * specified external Library. + * @param externalName external Library name */ - public ClearExternalNameCmd(String externalName) { + public ClearExternalPathCmd(String externalName) { this.externalName = externalName; } @Override public boolean applyTo(Program program) { try { - program.getExternalManager().setExternalPath(externalName, null, userDefined); + // Avoid creating the Library if it does not already exist + ExternalManager externalManager = program.getExternalManager(); + Library lib = externalManager.getExternalLibrary(externalName); + if (lib != null) { + externalManager.setExternalPath(externalName, null, userDefined); + return true; + } + status = "Library not found: " + externalName; } catch (InvalidInputException e) { - throw new AssertException(e); + status = e.getMessage(); } - return true; + return false; } @Override @@ -56,7 +65,7 @@ public class ClearExternalNameCmd implements Command { @Override public String getName() { - return "Remove External Program Name"; + return "Clear External Library Path"; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java index 43055175c3..e58ab86536 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/refs/SetExternalNameCmd.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. @@ -16,7 +16,11 @@ package ghidra.app.cmd.refs; import ghidra.framework.cmd.Command; +import ghidra.program.model.listing.Library; import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.ExternalManager; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.InvalidInputException; /** @@ -26,29 +30,48 @@ public class SetExternalNameCmd implements Command { private String externalName; private String externalPath; + private SourceType source; + private String status; - private boolean userDefined = true; /** - * Constructs a new command for setting the external program name and path. - * @param externalName the name of the link. - * @param externalPath the path of the file to associate with this link. + * Constructs a new command for creating a Library, if it does not exist, and optionally + * setting the associated external program path. If created, a {@link SourceType#USER_DEFINED} + * source will be specified. + * @param externalName the Library name. + * @param externalPath the project file path of the program file to associate with the Library. */ public SetExternalNameCmd(String externalName, String externalPath) { + this(externalName, externalPath, SourceType.USER_DEFINED); + } + + /** + * Constructs a new command for creating a Library, if it does not exist, and optionally + * setting the associated external program path. + * @param externalName the Library name. + * @param externalPath the project file path of the program file to associate with the Library. + * @param source the symbol source type to be applied if the library must be created. + */ + public SetExternalNameCmd(String externalName, String externalPath, SourceType source) { this.externalName = externalName; this.externalPath = externalPath; + this.source = source; } @Override public boolean applyTo(Program program) { try { - program.getExternalManager().setExternalPath(externalName, externalPath, userDefined); + ExternalManager externalManager = program.getExternalManager(); + Library library = externalManager.getExternalLibrary(externalName); + if (library == null) { + externalManager.addExternalLibraryName(externalName, source); + } + library.setAssociatedProgramPath(externalPath); } - catch (InvalidInputException e) { - status = "Invalid name specified"; - return false; + catch (DuplicateNameException | InvalidInputException e) { + status = e.getMessage(); } - return true; + return false; } @Override @@ -58,7 +81,7 @@ public class SetExternalNameCmd implements Command { @Override public String getName() { - return "Set External Program Name"; + return "Set External Library Name and Path"; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalProgramMerger.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalProgramMerger.java index e4f0ed1cad..914743a362 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalProgramMerger.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalProgramMerger.java @@ -26,8 +26,7 @@ import ghidra.app.merge.*; import ghidra.app.merge.util.ConflictUtility; import ghidra.app.util.HelpTopics; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.Program; -import ghidra.program.model.listing.ProgramChangeSet; +import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramMerge; import ghidra.program.util.SimpleDiffUtility; @@ -448,20 +447,36 @@ public class ExternalProgramMerger implements MergeResolver, ListingMergeConstan mergeManager.updateProgress(progress, "Merging external program information for " + name + "..."); - String originalPath = - (originalName != null) ? originalExtMgr.getExternalLibraryPath(originalName) : null; - String latestPath = - (latestName != null) ? latestExtMgr.getExternalLibraryPath(latestName) : null; - String myPath = (myName != null) ? myExtMgr.getExternalLibraryPath(myName) : null; - if (same(latestName, myName) && same(latestPath, myPath)) { + Library originalLib = + (originalName != null) ? originalExtMgr.getExternalLibrary(originalName) : null; + Library latestLib = + (latestName != null) ? latestExtMgr.getExternalLibrary(latestName) : null; + Library myLib = (myName != null) ? myExtMgr.getExternalLibrary(myName) : null; + + String originalPath = (originalLib != null) ? originalLib.getAssociatedProgramPath() : null; + String latestPath = (latestLib != null) ? latestLib.getAssociatedProgramPath() : null; + String myPath = (myLib != null) ? myLib.getAssociatedProgramPath() : null; + + int originalOrdinal = + originalLib != null ? originalExtMgr.getLibraryOrdinal(originalName) : -1; + int latestOrdinal = latestLib != null ? latestExtMgr.getLibraryOrdinal(latestName) : -1; + int myOrdinal = myLib != null ? myExtMgr.getLibraryOrdinal(myName) : -1; + + if (same(latestName, myName) && same(latestPath, myPath) && latestOrdinal == myOrdinal) { return; } boolean changedLatestName = !same(originalName, latestName); boolean changedMyName = !same(originalName, myName); boolean changedLatestPath = !same(originalPath, latestPath); boolean changedMyPath = !same(originalPath, myPath); - boolean changedLatest = changedLatestName || changedLatestPath; - boolean changedMy = changedMyName || changedMyPath; + + // Temper ordinal changes - don't allow them to drive a conflict but try to preserve my change. + // Ordinal changes may be lost during any library change conflict resolution. + boolean changedMyOrdinal = myLib != null && myOrdinal != originalOrdinal; + + boolean changedLatest = changedLatestName || changedLatestPath; // ignore ordinal change + boolean changedMy = changedMyName || changedMyPath || changedMyOrdinal; + if (changedLatest) { if (changedMy) { // conflict: Ask to keep latest or my @@ -473,7 +488,7 @@ public class ExternalProgramMerger implements MergeResolver, ListingMergeConstan if (resultID != -1 && resultName == null) { resultName = latestName; // Need to create Library symbol in Result program. } - autoMergeWhenOnlyLatestChanged(resultName, latestName, latestPath); + autoMergeWhenOnlyLatestChanged(resultName, latestName, latestPath, latestOrdinal); } } else { @@ -491,13 +506,13 @@ public class ExternalProgramMerger implements MergeResolver, ListingMergeConstan symbol.getSymbolType()); } } - autoMergeWhenOnlyMyChanged(resultName, myName, myPath); + autoMergeWhenOnlyMyChanged(resultName, myName, myPath, myOrdinal); } } } private void autoMergeWhenOnlyLatestChanged(String resultName, String latestName, - String latestPath) { + String latestPath, int latestOrdinal) { if (resultName == null) { // latestName appears to have been discarded during SymbolMerge. return; @@ -509,6 +524,9 @@ public class ExternalProgramMerger implements MergeResolver, ListingMergeConstan else { resultExtMgr.setExternalPath(resultName, latestPath, isExternalUserDefined(latestPgm, latestName)); + if (latestOrdinal >= 0) { + resultExtMgr.setLibraryOrdinal(resultName, latestOrdinal); + } } } catch (InvalidInputException e) { @@ -516,7 +534,8 @@ public class ExternalProgramMerger implements MergeResolver, ListingMergeConstan } } - private void autoMergeWhenOnlyMyChanged(String resultName, String myName, String myPath) { + private void autoMergeWhenOnlyMyChanged(String resultName, String myName, String myPath, + int myOrdinal) { if (resultName == null) { // myName appears to have been discarded during SymbolMerge. return; @@ -528,6 +547,9 @@ public class ExternalProgramMerger implements MergeResolver, ListingMergeConstan else { resultExtMgr.setExternalPath(resultName, myPath, isExternalUserDefined(myPgm, myName)); + if (myOrdinal >= 0) { + resultExtMgr.setLibraryOrdinal(resultName, myOrdinal); + } } } catch (InvalidInputException e) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/SymbolMerger.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/SymbolMerger.java index 1a94a410d3..4507247013 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/SymbolMerger.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/SymbolMerger.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. @@ -2463,6 +2463,8 @@ class SymbolMerger extends AbstractListingMerger { ExternalManager srcExtMgr = srcPgm.getExternalManager(); String path = srcExtMgr.getExternalLibraryPath(name); + // NOTE: No attempt is made to preserve library ordinal + ExternalManagerDB extMgr = (ExternalManagerDB) resultPgm.getExternalManager(); extMgr.setExternalPath(name, path, (source == SourceType.USER_DEFINED)); symbol = resultSymTab.getLibrarySymbol(name); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/ExternalReferencesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/ExternalReferencesProvider.java index 0def2056ae..92d5af585c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/ExternalReferencesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/ExternalReferencesProvider.java @@ -27,7 +27,7 @@ import docking.ActionContext; import docking.DefaultActionContext; import docking.action.builder.ActionBuilder; import docking.widgets.dialogs.InputDialog; -import docking.widgets.table.AbstractSortedTableModel; +import docking.widgets.table.AbstractGTableModel; import generic.theme.GIcon; import ghidra.app.cmd.refs.*; import ghidra.framework.cmd.Command; @@ -51,6 +51,8 @@ import resources.Icons; public class ExternalReferencesProvider extends ComponentProviderAdapter { private static Icon ADD_ICON = Icons.ADD_ICON; private static Icon DELETE_ICON = Icons.DELETE_ICON; + private static Icon UP_ICON = Icons.UP_ICON; + private static Icon DOWN_ICON = Icons.DOWN_ICON; private static Icon EDIT_ICON = new GIcon("icon.base.edit.bytes"); private static Icon CLEAR_ICON = Icons.CLEAR_ICON; @@ -98,6 +100,22 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter { .onAction(ac -> deleteExternalProgram()) .buildAndInstallLocal(this); + new ActionBuilder("Move Library Up", getOwner()) + .popupMenuPath("Move Library Up") + .popupMenuIcon(UP_ICON) + .toolBarIcon(UP_ICON) + .enabledWhen(ac -> canDecrementSelectedLibraryOrdinal()) + .onAction(ac -> adjustLibraryOrdinal(true)) + .buildAndInstallLocal(this); + + new ActionBuilder("Move Library Down", getOwner()) + .popupMenuPath("Move Library Down") + .popupMenuIcon(DOWN_ICON) + .toolBarIcon(DOWN_ICON) + .enabledWhen(ac -> canIncrementSelectedLibraryOrdinal()) + .onAction(ac -> adjustLibraryOrdinal(false)) + .buildAndInstallLocal(this); + new ActionBuilder("Set External Name Association", getOwner()) .popupMenuPath("Set External Name Association") .popupMenuIcon(EDIT_ICON) @@ -238,6 +256,48 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter { } } + private void adjustLibraryOrdinal(boolean moveUp) { + if ((moveUp && !canDecrementSelectedLibraryOrdinal()) || + (!moveUp && !canIncrementSelectedLibraryOrdinal())) { + return; + } + int ordinalAdjustment = moveUp ? -1 : 1; + List selectedExternalNames = getSelectedExternalNames(); + String externalName = selectedExternalNames.get(0); // must be exactly one for us to be enabled. + ExternalManager externalManager = program.getExternalManager(); + Library externalLibrary = externalManager.getExternalLibrary(externalName); + if (externalLibrary != null) { + program.withTransaction("Adjust Library Order", () -> { + int ordinal = externalManager.getLibraryOrdinal(externalName); + if (ordinal < 0) { + Msg.showError(this, mainPanel, "Library Ordering Change Error", + "Failed to update Library ordinal for: " + externalName); + return; + } + externalManager.setLibraryOrdinal(externalName, ordinal + ordinalAdjustment); + }); + } + } + + private boolean canDecrementSelectedLibraryOrdinal() { + int[] selectedRows = table.getSelectedRows(); + if (selectedRows.length == 1 && selectedRows[0] != 0) { + ExternalNamesRow rowBefore = tableModel.getRowObject(selectedRows[0] - 1); + return !Library.UNKNOWN.equals(rowBefore.getName()); // cannot displace UNKNOWN Library + } + return false; + } + + private boolean canIncrementSelectedLibraryOrdinal() { + int lastRow = table.getRowCount() - 1; + int[] selectedRows = table.getSelectedRows(); + if (selectedRows.length == 1 && selectedRows[0] != lastRow) { + ExternalNamesRow row = tableModel.getRowObject(selectedRows[0]); + return !Library.UNKNOWN.equals(row.getName()); // cannot alter UNKNOWN Library ordinal + } + return false; + } + private void setExternalProgramAssociation() { List selectedExternalNames = getSelectedExternalNames(); String externalName = selectedExternalNames.get(0); // must be exactly one for us to be enabled. @@ -268,7 +328,7 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter { private void clearExternalAssociation() { CompoundCmd cmd = new CompoundCmd<>("Clear External Program Associations"); for (String externalName : getSelectedExternalNames()) { - cmd.add(new ClearExternalNameCmd(externalName)); + cmd.add(new ClearExternalPathCmd(externalName)); } getTool().execute(cmd, program); } @@ -325,7 +385,7 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter { } - class ExternalNamesTableModel extends AbstractSortedTableModel { + class ExternalNamesTableModel extends AbstractGTableModel { final static int NAME_COL = 0; final static int PATH_COL = 1; @@ -336,18 +396,18 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter { private List paths = new ArrayList<>(); void updateTableData() { + paths.clear(); if (program != null && isVisible()) { ExternalManager extMgr = program.getExternalManager(); + // NOTE: Keep table in ordinal sequence as provided by external manager String[] programNames = extMgr.getExternalLibraryNames(); - Arrays.sort(programNames); for (String programName : programNames) { if (Library.UNKNOWN.equals(programName)) { continue; } - ExternalNamesRow path = new ExternalNamesRow(programName, extMgr.getExternalLibraryPath(programName)); paths.add(path); @@ -391,11 +451,6 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter { return "External Programs Model"; } - @Override - public boolean isSortable(int columnIndex) { - return true; - } - @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == NAME_COL) { @@ -448,21 +503,6 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter { } } - @Override - protected Comparator createSortComparator(int columnIndex) { - if (columnIndex == PATH_COL) { - // force the path column to have a secondary compare using the name column - // to ensure a 'stable' sort. Without this during analysis - // the constant updates cause the table to sort randomly when - // there are lots of empty path values. - Comparator c1 = - (r1, r2) -> Objects.requireNonNullElse(r1.getPath(), "") - .compareTo(Objects.requireNonNullElse(r2.getPath(), "")); - return c1.thenComparing((r1, r2) -> r1.getName().compareTo(r2.getName())); - } - return super.createSortComparator(columnIndex); - } - } } 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..e3aaa85cd9 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 @@ -645,8 +645,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { .each(SYMBOL_REMOVED).call(this::processSymbolRemoved) .each(EXTERNAL_ENTRY_ADDED, EXTERNAL_ENTRY_REMOVED) .call(this::processExternalEntryChanged) - .any(EXTERNAL_PATH_CHANGED, EXTERNAL_NAME_ADDED, - EXTERNAL_NAME_REMOVED, EXTERNAL_NAME_CHANGED) + .any(EXTERNAL_NAME_ADDED, EXTERNAL_NAME_REMOVED, EXTERNAL_NAME_CHANGED) // Rather than try to find each affected symbol, it is easier to just reload // the tree. This is infrequent enough that it should not be disruptive. .call(this::reloadTree) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java index bb3da3d3f0..21824da9fc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java @@ -39,6 +39,7 @@ import ghidra.program.model.listing.Library; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.ExternalManager; import ghidra.util.Msg; +import ghidra.util.StringUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.exception.InvalidInputException; import ghidra.util.task.TaskMonitor; @@ -101,6 +102,17 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader public static final String LOAD_ONLY_LIBRARIES_OPTION_NAME = "Only Load Libraries"; // hidden static final boolean LOAD_ONLY_LIBRARIES_OPTION_DEFAULT = false; + /** + * Gets a program property name to represent the ordered required library of the given index + * + * @param libraryIndex The index of the required library + * @return A program property name to represent the ordered required library of the given index + */ + public static String getRequiredLibraryProperty(int libraryIndex) { + return String.format("%s %s]", "Required Library [", + StringUtilities.pad("" + libraryIndex, ' ', 4)); + } + /** * Loads bytes in a particular format into the given {@link Program}. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java index 54d0888fb4..9d5f07d62b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java @@ -54,7 +54,6 @@ import ghidra.program.model.scalar.Scalar; import ghidra.program.model.symbol.*; import ghidra.program.model.util.AddressSetPropertyMap; import ghidra.program.model.util.CodeUnitInsertionException; -import ghidra.program.util.ExternalSymbolResolver; import ghidra.util.*; import ghidra.util.datastruct.*; import ghidra.util.exception.*; @@ -458,7 +457,8 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper { String[] neededLibs = elf.getDynamicLibraryNames(); for (String neededLib : neededLibs) { monitor.checkCancelled(); - props.setString(ExternalSymbolResolver.getRequiredLibraryProperty(libraryIndex++), + props.setString( + AbstractLibrarySupportLoader.getRequiredLibraryProperty(libraryIndex++), neededLib); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java index 2916eaeda8..20680e2840 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java @@ -57,7 +57,6 @@ import ghidra.program.model.reloc.RelocationResult; import ghidra.program.model.reloc.RelocationTable; import ghidra.program.model.symbol.*; import ghidra.program.model.util.CodeUnitInsertionException; -import ghidra.program.util.ExternalSymbolResolver; import ghidra.util.*; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; @@ -1298,7 +1297,7 @@ public class MachoProgramBuilder { libraryPaths.add(libraryPath); addLibrary(libraryPath); props.setString( - ExternalSymbolResolver.getRequiredLibraryProperty(libraryIndex++), + AbstractLibrarySupportLoader.getRequiredLibraryProperty(libraryIndex++), libraryPath); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java index becba23162..c511deda76 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java @@ -23,75 +23,18 @@ import java.util.function.Consumer; import db.Transaction; import ghidra.app.util.opinion.Loaded; import ghidra.framework.model.*; -import ghidra.framework.options.Options; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.util.Msg; -import ghidra.util.StringUtilities; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; /** * Moves dangling external function symbols found in the {@link Library#UNKNOWN EXTERNAL/UNKNOWN} * namespace into the namespace of the external library that publishes a matching symbol. - *

    - * This uses an ordered list of external library names that was attached to the program during - * import by the Elf or Macho loader (see {@link #REQUIRED_LIBRARY_PROPERTY_PREFIX}). * */ public class ExternalSymbolResolver implements Closeable { - private final static String REQUIRED_LIBRARY_PROPERTY_PREFIX = "Required Library ["; - - /** - * Gets a program property name to represent the ordered required library of the given index - * - * @param libraryIndex The index of the required library - * @return A program property name to represent the ordered required library of the given index - */ - public static String getRequiredLibraryProperty(int libraryIndex) { - return String.format("%s %s]", REQUIRED_LIBRARY_PROPERTY_PREFIX, - StringUtilities.pad("" + libraryIndex, ' ', 4)); - } - - /** - * Returns an ordered list of library names, as specified by the logic/rules of the original - * operating system's loader (eg. Elf / MachO dynamic library loading / symbol resolving - * rules) - * - * @param program The {@link Program} - * @return list of library names, in original order - */ - public static List getOrderedRequiredLibraryNames(Program program) { - TreeMap orderLibraryMap = new TreeMap<>(); - Options options = program.getOptions(Program.PROGRAM_INFO); - for (String optionName : options.getOptionNames()) { - - // Legacy programs may have the old "ELF Required Library [" program property, so - // we should not assume that the option name starts exactly with - // REQUIRED_LIBRARY_PROPERTY_PREFIX. We must deal with a potential substring at the - // start of the option name. - int prefixIndex = optionName.indexOf(REQUIRED_LIBRARY_PROPERTY_PREFIX); - if (prefixIndex == -1 || !optionName.endsWith("]")) { - continue; - } - String libName = options.getString(optionName, null); - if (libName == null) { - continue; - } - String indexStr = optionName - .substring(prefixIndex + REQUIRED_LIBRARY_PROPERTY_PREFIX.length(), - optionName.length() - 1) - .trim(); - try { - orderLibraryMap.put(Integer.parseInt(indexStr), libName.trim()); - } - catch (NumberFormatException e) { - Msg.error(ExternalSymbolResolver.class, - "Program contains invalid property: " + optionName); - } - } - return new ArrayList<>(orderLibraryMap.values()); - } private final ProjectData projectData; private final TaskMonitor monitor; @@ -236,33 +179,6 @@ public class ExternalSymbolResolver implements Closeable { * Represents a program that needs its external symbols to be fixed. */ private class ProgramSymbolResolver { - record ExtLibInfo(String name, Library lib, String programPath, Program program, - List resolvedSymbols, Throwable problem) { - String getProblemMessage() { - if (problem instanceof VersionException ve) { - return getVersionError(ve); - } - return problem != null ? problem.getMessage() : ""; - } - - String getLibPath() { - return programPath != null ? programPath : "missing"; - } - - String getVersionError(VersionException ve) { - String versionType = switch (ve.getVersionIndicator()) { - case VersionException.NEWER_VERSION -> " newer"; - case VersionException.OLDER_VERSION -> "n older"; - default -> "n unknown"; - }; - - String upgradeMsg = ve.isUpgradable() ? " (upgrade is possible)" : ""; - - return "skipped: file was created with a%s version of Ghidra%s" - .formatted(versionType, upgradeMsg); - } - - } Program program; String programPath; @@ -296,16 +212,19 @@ public class ExternalSymbolResolver implements Closeable { logger.accept("\t%d external symbols resolved, %d remain unresolved" .formatted(getResolvedSymbolCount(), unresolvedExternalFunctionIds.size())); for (ExtLibInfo extLib : extLibs) { + String libPath = extLib.getAssociatedProgramPath(); + String loggedLibPath = libPath != null ? libPath : "missing"; if (extLib.problem != null) { - logger.accept("\t[%s] -> %s, %s".formatted(extLib.name, extLib.getLibPath(), + logger.accept("\t[%s] -> %s, %s".formatted(extLib.getName(), loggedLibPath, extLib.getProblemMessage())); } - else if (extLib.programPath != null) { - logger.accept("\t[%s] -> %s, %d new symbols resolved".formatted(extLib.name, - extLib.getLibPath(), extLib.resolvedSymbols.size())); + else if (libPath != null) { + logger.accept( + "\t[%s] -> %s, %d new symbols resolved".formatted(extLib.getName(), + loggedLibPath, extLib.resolvedSymbols.size())); } else { - logger.accept("\t[%s] -> %s".formatted(extLib.name, extLib.getLibPath())); + logger.accept("\t[%s] -> %s".formatted(extLib.getName(), loggedLibPath)); } if (!shortSummary) { for (String symbolName : extLib.resolvedSymbols) { @@ -328,8 +247,8 @@ public class ExternalSymbolResolver implements Closeable { private boolean hasSomeLibrariesConfigured() { for (ExtLibInfo extLib : extLibs) { - if (extLib.program != null || extLib.problem != null || - extLib.programPath != null) { + if (extLib.problem != null || + extLib.getAssociatedProgramPath() != null) { return true; } } @@ -367,19 +286,17 @@ public class ExternalSymbolResolver implements Closeable { private List getLibsToSearch() throws CancelledException { List result = new ArrayList<>(); ExternalManager externalManager = program.getExternalManager(); - for (String libName : getOrderedRequiredLibraryNames(program)) { - Library lib = externalManager.getExternalLibrary(libName); - String libPath = lib != null ? lib.getAssociatedProgramPath() : null; + // External manager supplies external Libraries in appropriate search order + for (Library lib : externalManager.getLibraries()) { + String libPath = lib.getAssociatedProgramPath(); Program libProg = libPath != null ? getLibraryProgram(libPath) : null; Throwable problem = libProg == null && libPath != null ? problemLibraries.get(libPath) : null; - - result.add( - new ExtLibInfo(libName, lib, libPath, libProg, new ArrayList<>(), problem)); + result.add(new ExtLibInfo(lib, problem)); } return result; } - + /** * Moves unresolved functions from the EXTERNAL/UNKNOWN namespace to the namespace of the * external library if the extLib publishes a symbol with a matching name. @@ -388,10 +305,6 @@ public class ExternalSymbolResolver implements Closeable { * @throws CancelledException if cancelled */ private void resolveSymbolsToLibrary(ExtLibInfo extLib) throws CancelledException { - if (extLib.program == null) { - // can't do anything if the external library doesn't have a valid program associated - return; - } ExternalManager externalManager = program.getExternalManager(); SymbolTable symbolTable = program.getSymbolTable(); @@ -409,7 +322,7 @@ public class ExternalSymbolResolver implements Closeable { ExternalLocation extLoc = externalManager.getExternalLocation(s); String extLocName = Objects.requireNonNullElse(extLoc.getOriginalImportedName(), extLoc.getLabel()); - if (isExportedSymbol(extLib.program, extLocName)) { + if (isExportedSymbol(program, extLocName)) { try { s.setNamespace(extLib.lib); idIterator.remove(); @@ -443,6 +356,56 @@ public class ExternalSymbolResolver implements Closeable { } return symbolIds; } + + private class ExtLibInfo { + + final Library lib; + final List resolvedSymbols = new ArrayList<>(); + final Throwable problem; + + /** + * Define external Library dependency associated with {@link ProgramSymbolResolver} + * instance. + * @param lib external library dependency + * @param problem exception which occured while accessing Library + */ + ExtLibInfo(Library lib, Throwable problem) { + if (program != lib.getSymbol().getProgram()) { + throw new AssertionError("Program mismatch"); + } + this.lib = lib; + this.problem = problem; + } + + String getName() { + return lib.getName(); + } + + String getProblemMessage() { + if (problem instanceof VersionException ve) { + return getVersionError(ve); + } + return problem != null ? problem.getMessage() : ""; + } + + String getVersionError(VersionException ve) { + String versionType = switch (ve.getVersionIndicator()) { + case VersionException.NEWER_VERSION -> " newer"; + case VersionException.OLDER_VERSION -> "n older"; + default -> "n unknown"; + }; + + String upgradeMsg = ve.isUpgradable() ? " (upgrade is possible)" : ""; + + return "skipped: file was created with a%s version of Ghidra%s" + .formatted(versionType, upgradeMsg); + } + + String getAssociatedProgramPath() { + return lib.getAssociatedProgramPath(); + } + + } } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolMerge.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolMerge.java index 6259f96911..61eca424c2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolMerge.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolMerge.java @@ -230,11 +230,20 @@ class SymbolMerge { } else if (type == SymbolType.LIBRARY) { ExternalManager fromExtMgr = fromProgram.getExternalManager(); - String path = fromExtMgr.getExternalLibraryPath(name); + Library fromLib = fromExtMgr.getExternalLibrary(name); + + String path = fromLib != null ? fromLib.getAssociatedProgramPath() : null; + int ordinal = fromLib != null ? fromExtMgr.getLibraryOrdinal(name) : -1; ExternalManagerDB extMgr = (ExternalManagerDB) toProgram.getExternalManager(); - extMgr.setExternalPath(name, path, source == SourceType.USER_DEFINED); - symbol = toSymbolTable.getLibrarySymbol(name); + Library newLib = extMgr.addExternalLibraryName(name, source); + symbol = newLib.getSymbol(); + if (ordinal >= 0) { + extMgr.setLibraryOrdinal(name, ordinal); + } + if (path != null) { + extMgr.setExternalPath(name, path, source == SourceType.USER_DEFINED); + } } else if (type == SymbolType.FUNCTION) { FunctionManager fromFunctionMgr = fromProgram.getFunctionManager(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/external/ExternalManagerDBTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/external/ExternalManagerDBTest.java index 3532177a77..c9e720080a 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/external/ExternalManagerDBTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/external/ExternalManagerDBTest.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. @@ -25,7 +25,6 @@ import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.listing.CircularDependencyException; import ghidra.program.model.listing.Library; import ghidra.program.model.symbol.*; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -47,7 +46,7 @@ public class ExternalManagerDBTest extends AbstractGhidraHeadedIntegrationTest { public void setUp() throws Exception { program = createDefaultProgram(testName.getMethodName(), ProgramBuilder._TOY, this); space = program.getAddressFactory().getDefaultAddressSpace(); - extMgr = (ExternalManagerDB) program.getExternalManager(); + extMgr = program.getExternalManager(); transactionID = program.startTransaction("Test"); } @@ -336,7 +335,7 @@ public class ExternalManagerDBTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testOriginalImportName() - throws InvalidInputException, DuplicateNameException, CircularDependencyException { + throws InvalidInputException, DuplicateNameException { ExternalLocation extLoc = extMgr.addExtLocation("ext1", "foo", addr(1000), SourceType.IMPORTED); diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/managers/ExternalLibSarifMgr.java b/Ghidra/Features/Sarif/src/main/java/sarif/managers/ExternalLibSarifMgr.java index 0b3f776017..224684326d 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/managers/ExternalLibSarifMgr.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/managers/ExternalLibSarifMgr.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. @@ -16,28 +16,15 @@ package sarif.managers; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import com.google.gson.JsonArray; import ghidra.app.util.importer.MessageLog; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFormatException; -import ghidra.program.model.address.AddressOverflowException; -import ghidra.program.model.listing.GhidraClass; -import ghidra.program.model.listing.Library; -import ghidra.program.model.listing.Program; -import ghidra.program.model.symbol.ExternalLocation; -import ghidra.program.model.symbol.ExternalManager; -import ghidra.program.model.symbol.Namespace; -import ghidra.program.model.symbol.SourceType; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.DuplicateNameException; -import ghidra.util.exception.InvalidInputException; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.*; import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskMonitor; import sarif.SarifProgramOptions; @@ -127,14 +114,21 @@ public class ExternalLibSarifMgr extends SarifMgr { return; // already has a value--don't override it } - // NB: Can't use "DEFAULT" here or result.get("sourceType") which may be - // "DEFAULT" + // NB: Can't use "DEFAULT" here or result.get("sourceType") which may be "DEFAULT" String source = (String) result.get("sourceType"); SourceType sourceType = getSourceType(source); if (sourceType.equals(SourceType.DEFAULT)) { sourceType = SourceType.IMPORTED; } library = extManager.addExternalLibraryName(progName, sourceType); + + // NOTE: It is assumed that a full export/import will maintain the ordered sequence of Libraries + + // NOTE: Skip setting library location within project which is unlikely to match-up properly + // String progPath = (String) result.get("location"); + // if (progPath != null) { + // extManager.setExternalPath(progName, progPath, sourceType == SourceType.USER_DEFINED); + // } } private void processExternalLocation(Map result) throws IOException { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java index 931b495ac5..e54be07605 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java @@ -120,8 +120,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM * 15-Sep-2025 - version 31 Code Mananger dropped Composites property map use * 19-Sep-2025 - version 32 Expanded number of SourceType values and record storage affecting * SymbolDB, FunctionDB and RefListFlagsV0 + * 14-Apr-2026 - version 33 Introduced Library symbol ordinal assignment. */ - static final int DB_VERSION = 32; + static final int DB_VERSION = 33; /** * UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION anytime the @@ -142,6 +143,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM public static final int COMPOUND_VARIABLE_STORAGE_ADDED_VERSION = 18; public static final int AUTO_PARAMETERS_ADDED_VERSION = 19; public static final int RELOCATION_STATUS_ADDED_VERSION = 26; + public static final int LIBRARY_ORDINAL_ASSIGNMENT_ADDED_VERSION = 33; private static final String DATA_MAP_TABLE_NAME = "Program"; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalLocationDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalLocationDB.java index 8123443106..aea5b01f93 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalLocationDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalLocationDB.java @@ -55,6 +55,12 @@ public class ExternalLocationDB implements ExternalLocation { return library != null ? library.getName() : ""; } + @Override + public String getExternalLibraryPath() { + Library library = getLibrary(); + return library != null ? library.getAssociatedProgramPath() : null; + } + private Library getLibrary() { Namespace parent = symbol.getParentNamespace(); while (parent != null && !(parent instanceof Library)) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java index 5de0ecdfe8..5bec8b448f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/external/ExternalManagerDB.java @@ -22,7 +22,6 @@ import org.apache.commons.lang3.StringUtils; import db.*; import ghidra.framework.data.OpenMode; -import ghidra.framework.store.FileSystem; import ghidra.program.database.ManagerDB; import ghidra.program.database.ProgramDB; import ghidra.program.database.function.FunctionDB; @@ -126,7 +125,7 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { String name = rec.getString(OldExtNameAdapter.EXT_NAME_COL); try { - addExternalName(name, rec.getString(OldExtNameAdapter.EXT_PATHNAME_COL), + doAddExternalName(name, rec.getString(OldExtNameAdapter.EXT_PATHNAME_COL), SourceType.USER_DEFINED); nameMap.put(rec.getKey(), name); } @@ -200,11 +199,8 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { SourceType sourceType) throws InvalidInputException, DuplicateNameException { SourceType locSourceType = checkExternalLabel(extLabel, extAddr, sourceType); try (Closeable c = lock.write()) { - Library libraryScope = getLibraryScope(extLibraryName); - if (libraryScope == null) { - libraryScope = addExternalName(extLibraryName, null, sourceType); - } - return addExtLocation(libraryScope, extLabel, extAddr, false, locSourceType, true); + Library library = addExternalLibraryName(extLibraryName, sourceType); + return addExtLocation(library, extLabel, extAddr, false, locSourceType, true); } } @@ -231,12 +227,9 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { SourceType sourceType) throws InvalidInputException, DuplicateNameException { SourceType locSourceType = checkExternalLabel(extLabel, extAddr, sourceType); try (Closeable c = lock.write()) { - Library libraryScope = getLibraryScope(extLibraryName); - if (libraryScope == null) { - libraryScope = addExternalName(extLibraryName, null, + Library library = addExternalLibraryName(extLibraryName, sourceType != SourceType.DEFAULT ? sourceType : SourceType.ANALYSIS); - } - return addExtLocation(libraryScope, extLabel, extAddr, true, locSourceType, true); + return addExtLocation(library, extLabel, extAddr, true, locSourceType, true); } } @@ -272,16 +265,11 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { Address extAddr, boolean isFunction, SourceType sourceType, boolean reuseExisting) throws InvalidInputException { if (extNamespace == null) { - extNamespace = getLibraryScope(Library.UNKNOWN); - if (extNamespace == null) { - try { - extNamespace = addExternalLibraryName(Library.UNKNOWN, SourceType.ANALYSIS); - } - catch (DuplicateNameException e) { - // TODO: really need to reserve the unknown namespace name - throw new InvalidInputException( - "Failed to establish " + Library.UNKNOWN + " library"); - } + try { + extNamespace = addExternalLibraryName(Library.UNKNOWN, SourceType.ANALYSIS); + } + catch (InvalidInputException | DuplicateNameException e) { + throw new AssertionError(e); // reserved name should be OK } } else if (!extNamespace.isExternal()) { @@ -471,7 +459,7 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { @Override public Set getExternalLocations(String libraryName, String label) { - Library library = getLibraryScope(libraryName); + Library library = getExternalLibrary(libraryName); if (library == null && !StringUtils.isBlank(libraryName)) { return Set.of(); } @@ -489,7 +477,7 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { @Override public ExternalLocation getUniqueExternalLocation(String libraryName, String label) { - Library library = getLibraryScope(libraryName); + Library library = getExternalLibrary(libraryName); if (library == null && !StringUtils.isBlank(libraryName)) { return null; } @@ -598,12 +586,16 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { } @Override - public void updateExternalLibraryName(String oldName, String newName, SourceType source) + public boolean updateExternalLibraryName(String oldName, String newName, SourceType source) throws DuplicateNameException, InvalidInputException { - Symbol s = symbolMgr.getLibrarySymbol(oldName); - if (s != null) { - s.setName(newName, source); + try (Closeable c = lock.write()) { + Symbol s = symbolMgr.getLibrarySymbol(oldName); + if (s != null) { + s.setName(newName, source); + return true; + } } + return false; } @Override @@ -614,34 +606,37 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { if (librarySymbol != null) { return (Library) librarySymbol.getObject(); } - return addExternalName(name, null, source); + return doAddExternalName(name, null, source); } } - private Library addExternalName(String name, String pathname, SourceType source) + private Library doAddExternalName(String name, String pathname, SourceType source) throws DuplicateNameException, InvalidInputException { SymbolDB s = symbolMgr.createLibrarySymbol(name, pathname, source); return (Library) s.getObject(); } - private Library getLibraryScope(String name) { - LibrarySymbol s = symbolMgr.getLibrarySymbol(name); - return s == null ? null : s.getObject(); - } - @Override public boolean contains(String libraryName) { return symbolMgr.getLibrarySymbol(libraryName) != null; } + @Override + public List getLibraries() { + try (Closeable c = lock.read()) { + List orderedLibraries = new ArrayList<>(); + for (LibrarySymbol libSym : symbolMgr.getLibrarySymbolList()) { + orderedLibraries.add(libSym.getObject()); + } + return orderedLibraries; + } + } + @Override public String[] getExternalLibraryNames() { ArrayList list = new ArrayList<>(); - Symbol[] syms = symbolMgr.getSymbols(Address.NO_ADDRESS); - for (Symbol s : syms) { - if (s.getSymbolType() == SymbolType.LIBRARY) { - list.add(s.getName()); - } + for (LibrarySymbol libSym : symbolMgr.getLibrarySymbolList()) { + list.add(libSym.getName()); } String[] names = new String[list.size()]; list.toArray(names); @@ -650,8 +645,10 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { @Override public Library getExternalLibrary(String name) { - Symbol s = symbolMgr.getLibrarySymbol(name); - return s != null ? (Library) s.getObject() : null; + try (Closeable c = lock.read()) { + Symbol s = symbolMgr.getLibrarySymbol(name); + return s != null ? (Library) s.getObject() : null; + } } @Override @@ -672,34 +669,38 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { return; } - validateExternalPath(externalPath); + LibrarySymbol.validateExternalPath(externalPath); try (Closeable c = lock.write()) { - LibrarySymbol s = symbolMgr.getLibrarySymbol(externalName); - if (s == null) { - try { - addExternalName(externalName, externalPath, - userDefined ? SourceType.USER_DEFINED : SourceType.IMPORTED); - } - catch (DuplicateNameException e) { - throw new AssertException(e); - } - } - else { - s.setExternalLibraryPath(externalPath); - } + Library library = addExternalLibraryName(externalName, + userDefined ? SourceType.USER_DEFINED : SourceType.IMPORTED); + LibrarySymbol libSym = (LibrarySymbol) library.getSymbol(); + libSym.setExternalLibraryPath(externalPath); + } + catch (DuplicateNameException e) { + // ignore - new externalName conflicts with another namespace } } - private void validateExternalPath(String path) throws InvalidInputException { - if (path == null) { - return; // null is an allowed value (used to clear) - } + @Override + public int getLibraryOrdinal(String libraryName) { + LibrarySymbol libSym = symbolMgr.getLibrarySymbol(libraryName); + return libSym != null ? libSym.getOrdinal() : -1; + } - int len = path.length(); - if (len == 0 || path.charAt(0) != FileSystem.SEPARATOR_CHAR) { - throw new InvalidInputException( - "Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'"); + @Override + public int setLibraryOrdinal(String libraryName, int ordinal) { + if (Library.UNKNOWN.equals(libraryName)) { + Msg.warn(this, "Ignoring external library ordinal assignment for " + libraryName); + return -1; + } + try (Closeable c = lock.write()) { + LibrarySymbol libSym = symbolMgr.getLibrarySymbol(libraryName); + if (libSym == null) { + return -1; + } + libSym.setOrdinal(ordinal); + return libSym.getOrdinal(); } } @@ -740,9 +741,9 @@ public class ExternalManagerDB implements ManagerDB, ExternalManager { @Override public ExternalLocationIterator getExternalLocations(String externalName) { - Library scope = getLibraryScope(externalName); - if (scope != null) { - return new ExternalLocationDBIterator(symbolMgr.getSymbols(scope)); + Library library = getExternalLibrary(externalName); + if (library != null) { + return new ExternalLocationDBIterator(symbolMgr.getSymbols(library)); } return new ExternalLocationDBIterator(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibraryDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibraryDB.java index 1dd5ef0965..8cd76ad8c4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibraryDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibraryDB.java @@ -99,6 +99,11 @@ class LibraryDB implements Library { return symbol.getExternalLibraryPath(); } + @Override + public void setAssociatedProgramPath(String programPath) throws InvalidInputException { + symbol.setExternalLibraryPath(programPath); + } + @Override public boolean isExternal() { return true; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibrarySymbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibrarySymbol.java index decb22c33c..55aaf6107e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibrarySymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/LibrarySymbol.java @@ -15,7 +15,11 @@ */ package ghidra.program.database.symbol; +import java.util.*; + import db.DBRecord; +import db.Field; +import ghidra.framework.store.FileSystem; import ghidra.program.model.address.Address; import ghidra.program.model.listing.CircularDependencyException; import ghidra.program.model.listing.Library; @@ -32,7 +36,7 @@ import ghidra.util.exception.InvalidInputException; * Symbol data usage: * String stringData - associated program project file path */ -public class LibrarySymbol extends SymbolDB { +public class LibrarySymbol extends SymbolDB implements Comparable { private LibraryDB library; @@ -46,35 +50,69 @@ public class LibrarySymbol extends SymbolDB { } @Override - public void setName(String newName, SourceType source) - throws DuplicateNameException, InvalidInputException { + public void setNameAndNamespace(String newName, Namespace newNamespace, SourceType source) + throws DuplicateNameException, InvalidInputException, CircularDependencyException { + String oldName = getName(); if (Library.UNKNOWN.equals(oldName)) { Msg.warn(this, "Unable to change name of " + Library.UNKNOWN + " Library"); return; } + if (newNamespace.getID() != Namespace.GLOBAL_NAMESPACE_ID) { + throw new InvalidInputException("Namespace \"" + newNamespace.getName(true) + + "\" is not valid for library " + getName()); + } - super.setName(newName, source); + try (Closeable c = lock.write()) { - if (!oldName.equals(getName())) { - symbolMgr.getProgram() - .setObjChanged(ProgramEvent.EXTERNAL_NAME_CHANGED, (Address) null, null, - oldName, newName); + super.setNameAndNamespace(newName, newNamespace, source); + + if (!oldName.equals(getName())) { + symbolMgr.getProgram() + .setObjChanged(ProgramEvent.EXTERNAL_NAME_CHANGED, (Address) null, this, + oldName, newName); + } + + if (Library.UNKNOWN.equals(newName)) { + symbolMgr.adjustLibraryOrdinals(this, 0); + setExternalLibraryPath(null); // clear file path for UNKNOWN lib + } } } @Override - public void setNameAndNamespace(String newName, Namespace newNamespace, SourceType source) - throws DuplicateNameException, InvalidInputException, CircularDependencyException { - String oldName = getName(); + public boolean delete() { + try (Closeable c = lock.write()) { - super.setNameAndNamespace(newName, newNamespace, source); + // Pre-fetch library symbol list to facilitate ordinal reassignments after removal + int ordinal = getOrdinal(); + List libSymList = new ArrayList<>(symbolMgr.getLibrarySymbolList()); - if (!oldName.equals(getName())) { - symbolMgr.getProgram() - .setObjChanged(ProgramEvent.EXTERNAL_NAME_CHANGED, (Address) null, null, - oldName, newName); + if (super.delete()) { + // Perform ordinal reassignments for remaining library symbols if needed. + // It is expected that all ordinals are accounted for in cached library list + // but to be safe we will perform brute-force search if mismatch and ignore + // if not found in list; + LibrarySymbol s = libSymList.get(ordinal); + if (s != this) { + String libName = record.getString(SymbolDatabaseAdapter.SYMBOL_NAME_COL); + Msg.error(this, + "Library symbol list did not contain removed symbol: " + libName); + for (ordinal = 0; ordinal < libSymList.size(); ordinal++) { + s = libSymList.get(ordinal); + if (s == this) { + break; + } + } + } + if (s == this) { + libSymList.remove(ordinal); + symbolMgr.assignLibraryOrdinals(libSymList, false); + } + return true; + } } + return false; } @Override @@ -111,6 +149,76 @@ public class LibrarySymbol extends SymbolDB { SymbolType.LIBRARY.isValidParent(symbolMgr.getProgram(), parent, address, isExternal()); } + /** + * {@return Library's ordinal placement within ordered library list.} + */ + public int getOrdinal() { + // NOTE: This method must not be used by compareTo since it may invoke it + validate(lock); + int ordinal = doGetOrdinalFromRecord(); + if (ordinal < 0) { + // NOTE: this method invocation relies on the use of the compareTo method + ordinal = symbolMgr.computeLibraryOrdinal(this); + } + return ordinal; + } + + /** + * Get this Library's ordinal as stored in the database. A value of -1 is returned if one has + * not yet been established in which case the symbol ID should be used for sort while ensuring + * {@link Library#UNKNOWN} always sorts as first. + * + * @return Library symbol stored ordinal or -1 if not yet stored. + */ + int doGetOrdinalFromRecord() { + Field fieldValue = record.getFieldValue(SymbolDatabaseAdapter.SYMBOL_LIB_ORDINAL_COL); + return fieldValue != null ? fieldValue.getIntValue() : -1; + } + + /** + * Set the Library ordinal. + * Other Library ordinals will be adjusted if displaced by ordinal change. + * No change is made if this Library symbol corresponds to {@link Library#UNKNOWN} Library. + * + * @param ordinal positive greater or equal to 0. + * @throws IllegalArgumentException if a negative ordinal is specified + */ + public void setOrdinal(int ordinal) { + if (ordinal < 0) { + throw new IllegalArgumentException("Non-negative ordinal is required"); + } + if (Library.UNKNOWN.equals(getName())) { + return; // ordinal change ignored for UNKNOWN Library + } + + try (Closeable c = lock.write()) { + checkDeleted(); + if (ordinal == 0) { + // Cannot displace UNKNOWN Library which may reside at ordinal 0 + LibrarySymbol displacedLibSym = symbolMgr.getLibrarySymbolByOrdinal(0); + if (displacedLibSym != null && Library.UNKNOWN.equals(displacedLibSym.getName())) { + return; + } + } + symbolMgr.adjustLibraryOrdinals(this, ordinal); + } + } + + void doSetOrdinal(int newOrdinal, boolean notify) { + if (newOrdinal < 0) { + throw new IllegalArgumentException("Unsupported ordinal assignment: " + newOrdinal); + } + int oldOrdinal = doGetOrdinalFromRecord(); + if (oldOrdinal == newOrdinal) { + return; + } + record.setIntValue(SymbolDatabaseAdapter.SYMBOL_LIB_ORDINAL_COL, newOrdinal); + updateRecord(); + if (notify) { + symbolMgr.symbolDataChanged(this); + } + } + /** * {@return the library program path within the project (may be null)} */ @@ -121,22 +229,73 @@ public class LibrarySymbol extends SymbolDB { /** * Set the library program path within the project. + * The {@link Library#UNKNOWN} Library path may only be cleared. + * * @param libraryPath library program path or null to clear + * @throws InvalidInputException if an invalid project file path is specified */ - public void setExternalLibraryPath(String libraryPath) { - - String oldPath = getExternalLibraryPath(); - + public void setExternalLibraryPath(String libraryPath) throws InvalidInputException { try (Closeable c = lock.write()) { checkDeleted(); - setRecordFields(record, libraryPath); + if (Library.UNKNOWN.equals(getName())) { + libraryPath = null; + } + validateExternalPath(libraryPath); + String oldPath = record.getString(SymbolDatabaseAdapter.SYMBOL_LIBPATH_COL); + if (Objects.equals(oldPath, libraryPath)) { + return; + } + record.setString(SymbolDatabaseAdapter.SYMBOL_LIBPATH_COL, libraryPath); updateRecord(); } - symbolMgr.getProgram() - .setObjChanged(ProgramEvent.EXTERNAL_PATH_CHANGED, getName(), oldPath, libraryPath); + symbolMgr.symbolDataChanged(this); } - static void setRecordFields(DBRecord record, String libraryPath) { + /** + * Perform path validation for an external library path within the project + * @param path external library path within the project (null is allowed for clearing path) + * @throws InvalidInputException if path is invalid + */ + public static void validateExternalPath(String path) throws InvalidInputException { + if (path == null) { + return; // null is an allowed value (used to clear) + } + + int len = path.length(); + if (len == 0 || path.charAt(0) != FileSystem.SEPARATOR_CHAR) { + throw new InvalidInputException( + "Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'"); + } + } + + /** + * Set library symbol record fields during symbol creation + * @param record new symbol record + * @param ordinal library symbol ordinal + * @param libraryPath library path or null + */ + static void setRecordFields(DBRecord record, int ordinal, String libraryPath) { + // NOTE: method use must be limited since ordinal re-assignments of affected Libraries + // is not handled here. + record.setIntValue(SymbolDatabaseAdapter.SYMBOL_LIB_ORDINAL_COL, ordinal); record.setString(SymbolDatabaseAdapter.SYMBOL_LIBPATH_COL, libraryPath); } + + @Override + public int compareTo(LibrarySymbol o) { + validate(lock); + o.validate(lock); + // NOTE: this method is not intended to be used between symbols from different programs + // where one may have stored ordinals and the other may not in which case this comparison + // would be invalid. For a single program it is required that all library symbols either + // have an assigned ordinal or do not in which cases symbol ID comparison is used + int c = Long.compare(doGetOrdinalFromRecord(), o.doGetOrdinalFromRecord()); + if (c == 0) { + // Handles case where all library symbols report a -1 ordinal (not yet assigned) + // UNKNOWN Library placement is arbitrary in this case. + c = Long.compare(getID(), o.getID()); + } + return c; + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapter.java index 4f14e7c5a7..9c3cdf4338 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapter.java @@ -16,7 +16,8 @@ package ghidra.program.database.symbol; import java.io.IOException; -import java.util.*; +import java.util.Objects; +import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -52,8 +53,9 @@ abstract class SymbolDatabaseAdapter { static final int SYMBOL_EXTERNAL_PROG_ADDR_COL = 10; static final int SYMBOL_COMMENT_COL = 11; static final int SYMBOL_LIBPATH_COL = 12; + static final int SYMBOL_LIB_ORDINAL_COL = 13; - static final Schema SYMBOL_SCHEMA = SymbolDatabaseAdapterV4.V4_SYMBOL_SCHEMA; + static final Schema SYMBOL_SCHEMA = SymbolDatabaseAdapterV5.V5_SYMBOL_SCHEMA; // Bits 0, 1 and 3 are used for the source of the symbol. // NOTE: On the next V5 adapter revision the source type bits should be made contiguous @@ -96,11 +98,11 @@ abstract class SymbolDatabaseAdapter { throws VersionException, CancelledException, IOException { if (openMode == OpenMode.CREATE) { - return new SymbolDatabaseAdapterV4(dbHandle, addrMap, true); + return new SymbolDatabaseAdapterV5(dbHandle, addrMap, true); } try { - SymbolDatabaseAdapter adapter = new SymbolDatabaseAdapterV4(dbHandle, addrMap, false); + SymbolDatabaseAdapter adapter = new SymbolDatabaseAdapterV5(dbHandle, addrMap, false); return adapter; } catch (VersionException e) { @@ -122,6 +124,13 @@ abstract class SymbolDatabaseAdapter { private static SymbolDatabaseAdapter findReadOnlyAdapter(DBHandle handle, AddressMap addrMap) throws VersionException { + try { + return new SymbolDatabaseAdapterV4(handle, addrMap.getOldAddressMap()); + } + catch (VersionException e) { + // failed try older version + } + try { return new SymbolDatabaseAdapterV3(handle, addrMap.getOldAddressMap()); } @@ -168,7 +177,7 @@ abstract class SymbolDatabaseAdapter { dbHandle.deleteTable(SYMBOL_TABLE_NAME); - SymbolDatabaseAdapter newAdapter = new SymbolDatabaseAdapterV4(dbHandle, addrMap, true); + SymbolDatabaseAdapter newAdapter = new SymbolDatabaseAdapterV5(dbHandle, addrMap, true); copyTempToNewAdapter(tmpAdapter, newAdapter, monitor); return newAdapter; @@ -194,7 +203,7 @@ abstract class SymbolDatabaseAdapter { ((SymbolDatabaseAdapterV0) oldAdapter).extractLocalSymbols(tmpHandle, monitor); } - SymbolDatabaseAdapterV4 tmpAdapter = new SymbolDatabaseAdapterV4(tmpHandle, addrMap, true); + SymbolDatabaseAdapterV5 tmpAdapter = new SymbolDatabaseAdapterV5(tmpHandle, addrMap, true); RecordIterator iter = oldAdapter.getSymbols(); while (iter.hasNext()) { monitor.checkCancelled(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV2.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV2.java index 9a32f284ea..eb4da55350 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV2.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV2.java @@ -97,7 +97,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { if (key == AddressMap.INVALID_ADDRESS_KEY && !addr.equals(Address.NO_ADDRESS)) { return Field.EMPTY_ARRAY; } - return symbolTable.findRecords(new LongField(key), SYMBOL_ADDR_COL); + return symbolTable.findRecords(new LongField(key), V2_SYMBOL_ADDR_COL); } @Override @@ -113,7 +113,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { @Override RecordIterator getSymbolsByAddress(boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, - new AddressIndexPrimaryKeyIterator(symbolTable, SYMBOL_ADDR_COL, addrMap, forward)); + new AddressIndexPrimaryKeyIterator(symbolTable, V2_SYMBOL_ADDR_COL, addrMap, forward)); return new V2ConvertedRecordIterator(it); } @@ -121,7 +121,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { RecordIterator getSymbolsByAddress(Address startAddr, boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, startAddr, forward)); + V2_SYMBOL_ADDR_COL, addrMap, startAddr, forward)); return new V2ConvertedRecordIterator(it); } @@ -139,7 +139,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { RecordIterator getSymbols(Address start, Address end, boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, start, end, forward)); + V2_SYMBOL_ADDR_COL, addrMap, start, end, forward)); return new V2ConvertedRecordIterator(it); } @@ -147,7 +147,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { RecordIterator getSymbols(AddressSetView set, boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, set, forward)); + V2_SYMBOL_ADDR_COL, addrMap, set, forward)); return new V2ConvertedRecordIterator(it); } @@ -155,7 +155,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { RecordIterator getPrimarySymbols(AddressSetView set, boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, set, forward)); + V2_SYMBOL_ADDR_COL, addrMap, set, forward)); return getPrimaryFilterRecordIterator(new V2ConvertedRecordIterator(it)); } @@ -222,21 +222,21 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { @Override RecordIterator getSymbolsByNamespace(long id) throws IOException { LongField field = new LongField(id); - RecordIterator it = symbolTable.indexIterator(SYMBOL_PARENT_ID_COL, field, field, true); + RecordIterator it = symbolTable.indexIterator(V2_SYMBOL_PARENT_ID_COL, field, field, true); return new V2ConvertedRecordIterator(it); } @Override RecordIterator getSymbolsByName(String name) throws IOException { StringField field = new StringField(name); - RecordIterator it = symbolTable.indexIterator(SYMBOL_NAME_COL, field, field, true); + RecordIterator it = symbolTable.indexIterator(V2_SYMBOL_NAME_COL, field, field, true); return new V2ConvertedRecordIterator(it); } @Override RecordIterator scanSymbolsByName(String startName) throws IOException { StringField field = new StringField(startName); - RecordIterator it = symbolTable.indexIterator(SYMBOL_NAME_COL, field, null, true); + RecordIterator it = symbolTable.indexIterator(V2_SYMBOL_NAME_COL, field, null, true); return new V2ConvertedRecordIterator(it); } @@ -262,7 +262,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { Address getMaxSymbolAddress(AddressSpace space) throws IOException { if (space.isMemorySpace()) { AddressIndexKeyIterator addressKeyIterator = new AddressIndexKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, space.getMinAddress(), space.getMaxAddress(), false); + V2_SYMBOL_ADDR_COL, addrMap, space.getMinAddress(), space.getMaxAddress(), false); if (addressKeyIterator.hasNext()) { return addrMap.decodeAddress(addressKeyIterator.next()); } @@ -270,7 +270,7 @@ class SymbolDatabaseAdapterV2 extends SymbolDatabaseAdapter { else { LongField max = new LongField(addrMap.getKey(space.getMaxAddress(), false)); DBFieldIterator iterator = - symbolTable.indexFieldIterator(null, max, false, SYMBOL_ADDR_COL); + symbolTable.indexFieldIterator(null, max, false, V2_SYMBOL_ADDR_COL); if (iterator.hasPrevious()) { LongField val = (LongField) iterator.previous(); Address addr = addrMap.decodeAddress(val.getLongValue()); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV3.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV3.java index 28205a83cd..8f2d5ffccc 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV3.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV3.java @@ -110,7 +110,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { if (key == AddressMap.INVALID_ADDRESS_KEY && !addr.equals(Address.NO_ADDRESS)) { return false; } - return symbolTable.hasRecord(new LongField(key), SYMBOL_ADDR_COL); + return symbolTable.hasRecord(new LongField(key), V3_SYMBOL_ADDR_COL); } @Override @@ -119,7 +119,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { if (key == AddressMap.INVALID_ADDRESS_KEY && !addr.equals(Address.NO_ADDRESS)) { return Field.EMPTY_ARRAY; } - return symbolTable.findRecords(new LongField(key), SYMBOL_ADDR_COL); + return symbolTable.findRecords(new LongField(key), V3_SYMBOL_ADDR_COL); } @Override @@ -135,7 +135,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { @Override RecordIterator getSymbolsByAddress(boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, - new AddressIndexPrimaryKeyIterator(symbolTable, SYMBOL_ADDR_COL, addrMap, forward)); + new AddressIndexPrimaryKeyIterator(symbolTable, V3_SYMBOL_ADDR_COL, addrMap, forward)); return new V3ConvertedRecordIterator(it); } @@ -143,7 +143,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { RecordIterator getSymbolsByAddress(Address startAddr, boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, startAddr, forward)); + V3_SYMBOL_ADDR_COL, addrMap, startAddr, forward)); return new V3ConvertedRecordIterator(it); } @@ -161,7 +161,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { RecordIterator getSymbols(Address start, Address end, boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, start, end, forward)); + V3_SYMBOL_ADDR_COL, addrMap, start, end, forward)); return new V3ConvertedRecordIterator(it); } @@ -169,7 +169,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { RecordIterator getSymbols(AddressSetView set, boolean forward) throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, set, forward)); + V3_SYMBOL_ADDR_COL, addrMap, set, forward)); return new V3ConvertedRecordIterator(it); } @@ -178,14 +178,14 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { throws IOException { KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_PRIMARY_COL, addrMap, set, forward)); + V3_SYMBOL_PRIMARY_COL, addrMap, set, forward)); return new V3ConvertedRecordIterator(it); } @Override protected DBRecord getPrimarySymbol(Address address) throws IOException { AddressIndexPrimaryKeyIterator it = new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_PRIMARY_COL, addrMap, address, address, true); + V3_SYMBOL_PRIMARY_COL, addrMap, address, address, true); if (it.hasNext()) { return convertV3Record(symbolTable.getRecord(it.next())); } @@ -257,21 +257,21 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { @Override RecordIterator getSymbolsByNamespace(long id) throws IOException { LongField field = new LongField(id); - RecordIterator it = symbolTable.indexIterator(SYMBOL_PARENT_ID_COL, field, field, true); + RecordIterator it = symbolTable.indexIterator(V3_SYMBOL_PARENT_ID_COL, field, field, true); return new V3ConvertedRecordIterator(it); } @Override RecordIterator getSymbolsByName(String name) throws IOException { StringField field = new StringField(name); - RecordIterator it = symbolTable.indexIterator(SYMBOL_NAME_COL, field, field, true); + RecordIterator it = symbolTable.indexIterator(V3_SYMBOL_NAME_COL, field, field, true); return new V3ConvertedRecordIterator(it); } @Override RecordIterator scanSymbolsByName(String startName) throws IOException { StringField field = new StringField(startName); - RecordIterator it = symbolTable.indexIterator(SYMBOL_NAME_COL, field, null, true); + RecordIterator it = symbolTable.indexIterator(V3_SYMBOL_NAME_COL, field, null, true); return new V3ConvertedRecordIterator(it); } @@ -286,7 +286,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { Field end = computeLocatorHash(name, id, MAX_ADDRESS_OFFSET); - RecordIterator it = symbolTable.indexIterator(SYMBOL_HASH_COL, start, end, true); + RecordIterator it = symbolTable.indexIterator(V3_SYMBOL_HASH_COL, start, end, true); it = new V3ConvertedRecordIterator(it); RecordIterator filtered = getNameAndNamespaceFilterIterator(name, id, it); @@ -300,7 +300,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { if (search == null) { return null; } - RecordIterator it = symbolTable.indexIterator(SYMBOL_HASH_COL, search, search, true); + RecordIterator it = symbolTable.indexIterator(V3_SYMBOL_HASH_COL, search, search, true); it = new V3ConvertedRecordIterator(it); RecordIterator filtered = @@ -315,7 +315,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { Address getMaxSymbolAddress(AddressSpace space) throws IOException { if (space.isMemorySpace()) { AddressIndexKeyIterator addressKeyIterator = new AddressIndexKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, space.getMinAddress(), space.getMaxAddress(), false); + V3_SYMBOL_ADDR_COL, addrMap, space.getMinAddress(), space.getMaxAddress(), false); if (addressKeyIterator.hasNext()) { return addrMap.decodeAddress(addressKeyIterator.next()); } @@ -323,7 +323,7 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { else { LongField max = new LongField(addrMap.getKey(space.getMaxAddress(), false)); DBFieldIterator iterator = - symbolTable.indexFieldIterator(null, max, false, SYMBOL_ADDR_COL); + symbolTable.indexFieldIterator(null, max, false, V3_SYMBOL_ADDR_COL); if (iterator.hasPrevious()) { LongField val = (LongField) iterator.previous(); Address addr = addrMap.decodeAddress(val.getLongValue()); @@ -341,8 +341,8 @@ class SymbolDatabaseAdapterV3 extends SymbolDatabaseAdapter { } /** - * Returns a record matching the current database schema from the version 2 record. - * @param record the record matching the version 2 schema. + * Returns a record matching the current database schema from the version 3 record. + * @param record the record matching the version 3 schema. * @return a current symbol record. */ private DBRecord convertV3Record(DBRecord record) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV4.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV4.java index 82276b74f1..42d49601b2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV4.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV4.java @@ -16,13 +16,11 @@ package ghidra.program.database.symbol; import java.io.IOException; -import java.util.HashSet; import java.util.Set; import db.*; import ghidra.program.database.map.*; import ghidra.program.database.util.EmptyRecordIterator; -import ghidra.program.database.util.RecordFilter; import ghidra.program.model.address.*; import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SymbolType; @@ -31,7 +29,7 @@ import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; /** - * SymbolDatabaseAdapter for version 3 + * SymbolDatabaseAdapter for version 4 * * This version added additional sparse columns to store optional data specific to certain * symbol types. The adhoc string data column was eliminated. @@ -48,94 +46,64 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { // allows us to index this field and quickly find the primary symbols. The field is sparse // so that non-primary symbols don't consume any space for this field. - static final Schema V4_SYMBOL_SCHEMA = new Schema(SYMBOL_VERSION, "Key", - new Field[] { StringField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE, - ByteField.INSTANCE, ByteField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE, - LongField.INSTANCE, IntField.INSTANCE, StringField.INSTANCE, StringField.INSTANCE, - StringField.INSTANCE, StringField.INSTANCE }, - new String[] { "Name", "Address", "Namespace", "Symbol Type", "Flags", "Locator Hash", - "Primary", "Datatype", "Variable Offset", "ExtOrigImportName", "ExtProgAddr", "Comment", - "LibPath" }, - new int[] { SYMBOL_HASH_COL, SYMBOL_PRIMARY_COL, SYMBOL_DATATYPE_COL, SYMBOL_VAROFFSET_COL, - SYMBOL_ORIGINAL_IMPORTED_NAME_COL, SYMBOL_EXTERNAL_PROG_ADDR_COL, SYMBOL_COMMENT_COL, - SYMBOL_LIBPATH_COL }); +/* Do not remove the following commented out schema! It shows the version 4 symbol table schema. */ +// static final Schema V4_SYMBOL_SCHEMA = new Schema(SYMBOL_VERSION, "Key", +// new Field[] { StringField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE, +// ByteField.INSTANCE, ByteField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE, +// LongField.INSTANCE, IntField.INSTANCE, StringField.INSTANCE, StringField.INSTANCE, +// StringField.INSTANCE, StringField.INSTANCE }, +// new String[] { "Name", "Address", "Namespace", "Symbol Type", "Flags", "Locator Hash", +// "Primary", "Datatype", "Variable Offset", "ExtOrigImportName", "ExtProgAddr", "Comment", +// "LibPath" }, +// new int[] { SYMBOL_HASH_COL, SYMBOL_PRIMARY_COL, SYMBOL_DATATYPE_COL, SYMBOL_VAROFFSET_COL, +// SYMBOL_ORIGINAL_IMPORTED_NAME_COL, SYMBOL_EXTERNAL_PROG_ADDR_COL, SYMBOL_COMMENT_COL, +// SYMBOL_LIBPATH_COL }); + + static final int V4_SYMBOL_NAME_COL = 0; + static final int V4_SYMBOL_ADDR_COL = 1; + static final int V4_SYMBOL_PARENT_ID_COL = 2; + static final int V4_SYMBOL_TYPE_COL = 3; + static final int V4_SYMBOL_FLAGS_COL = 4; + + // Sparse fields - the following fields are not always applicable so they are optional and + // don't consume space in the database if they aren't used. + static final int V4_SYMBOL_HASH_COL = 5; + static final int V4_SYMBOL_PRIMARY_COL = 6; + static final int V4_SYMBOL_DATATYPE_COL = 7; + static final int V4_SYMBOL_VAROFFSET_COL = 8; + static final int V4_SYMBOL_ORIGINAL_IMPORTED_NAME_COL = 9; + static final int V4_SYMBOL_EXTERNAL_PROG_ADDR_COL = 10; + static final int V4_SYMBOL_COMMENT_COL = 11; + static final int V4_SYMBOL_LIBPATH_COL = 12; private Table symbolTable; private AddressMap addrMap; - SymbolDatabaseAdapterV4(DBHandle handle, AddressMap addrMap, boolean create) - throws VersionException, IOException { + SymbolDatabaseAdapterV4(DBHandle handle, AddressMap addrMap) throws VersionException { this.addrMap = addrMap; - if (create) { - symbolTable = handle.createTable(SYMBOL_TABLE_NAME, SYMBOL_SCHEMA, - new int[] { SYMBOL_ADDR_COL, SYMBOL_NAME_COL, SYMBOL_PARENT_ID_COL, SYMBOL_HASH_COL, - SYMBOL_PRIMARY_COL, SYMBOL_ORIGINAL_IMPORTED_NAME_COL, - SYMBOL_EXTERNAL_PROG_ADDR_COL }); + symbolTable = handle.getTable(SYMBOL_TABLE_NAME); + if (symbolTable == null) { + throw new VersionException("Missing Table: " + SYMBOL_TABLE_NAME); } - else { - symbolTable = handle.getTable(SYMBOL_TABLE_NAME); - if (symbolTable == null) { - throw new VersionException("Missing Table: " + SYMBOL_TABLE_NAME); - } - if (symbolTable.getSchema().getVersion() != SYMBOL_VERSION) { - int version = symbolTable.getSchema().getVersion(); - if (version < SYMBOL_VERSION) { - throw new VersionException(true); - } - throw new VersionException(VersionException.NEWER_VERSION, false); + if (symbolTable.getSchema().getVersion() != SYMBOL_VERSION) { + int version = symbolTable.getSchema().getVersion(); + if (version < SYMBOL_VERSION) { + throw new VersionException(true); } + throw new VersionException(VersionException.NEWER_VERSION, false); } } -// @Override -// DBRecord createSymbol(String name, Address address, long namespaceID, SymbolType symbolType, -// SourceType source, boolean isPrimary) throws IOException { -// long nextID = symbolTable.getKey(); -// -// // avoiding key 0, as it is reserved for the global namespace -// if (nextID == 0) { -// nextID++; -// } -// return createSymbol(nextID, name, address, namespaceID, symbolType, (byte) source.getStorageId(), -// isPrimary); -// } - @Override DBRecord createSymbolRecord(String name, long namespaceID, Address address, SymbolType symbolType, boolean isPrimary, SourceType source) { - - long nextID = symbolTable.getKey(); - - // Avoid key 0, as it is reserved for the global namespace - if (nextID == 0) { - nextID++; - } - - long addressKey = addrMap.getKey(address, true); - - DBRecord rec = symbolTable.getSchema().createRecord(nextID); - rec.setString(SYMBOL_NAME_COL, name); - rec.setLongValue(SYMBOL_ADDR_COL, addressKey); - rec.setLongValue(SYMBOL_PARENT_ID_COL, namespaceID); - rec.setByteValue(SYMBOL_TYPE_COL, symbolType.getID()); - rec.setByteValue(SYMBOL_FLAGS_COL, getSourceTypeFlagsBits(source)); // assume non-pinned - - // Sparse columns - these columns don't apply to all symbols. - // they default to null unless specifically set. Null values don't consume space. - - rec.setField(SYMBOL_HASH_COL, computeLocatorHash(name, namespaceID, addressKey)); - - if (isPrimary) { - rec.setLongValue(SYMBOL_PRIMARY_COL, addressKey); - } - - return rec; + throw new UnsupportedOperationException(); } @Override void removeSymbol(long symbolID) throws IOException { - symbolTable.deleteRecord(symbolID); + throw new UnsupportedOperationException(); } @Override @@ -144,7 +112,7 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { if (key == AddressMap.INVALID_ADDRESS_KEY && !addr.equals(Address.NO_ADDRESS)) { return false; } - return symbolTable.hasRecord(new LongField(key), SYMBOL_ADDR_COL); + return symbolTable.hasRecord(new LongField(key), V4_SYMBOL_ADDR_COL); } @Override @@ -153,12 +121,12 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { if (key == AddressMap.INVALID_ADDRESS_KEY && !addr.equals(Address.NO_ADDRESS)) { return Field.EMPTY_ARRAY; } - return symbolTable.findRecords(new LongField(key), SYMBOL_ADDR_COL); + return symbolTable.findRecords(new LongField(key), V4_SYMBOL_ADDR_COL); } @Override DBRecord getSymbolRecord(long symbolID) throws IOException { - return symbolTable.getRecord(symbolID); + return convertV4Record(symbolTable.getRecord(symbolID)); } @Override @@ -168,116 +136,117 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { @Override RecordIterator getSymbolsByAddress(boolean forward) throws IOException { - return new KeyToRecordIterator(symbolTable, - new AddressIndexPrimaryKeyIterator(symbolTable, SYMBOL_ADDR_COL, addrMap, forward)); + KeyToRecordIterator it = new KeyToRecordIterator(symbolTable, + new AddressIndexPrimaryKeyIterator(symbolTable, V4_SYMBOL_ADDR_COL, addrMap, forward)); + return new V4ConvertedRecordIterator(it); } @Override RecordIterator getSymbolsByAddress(Address startAddr, boolean forward) throws IOException { - return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, startAddr, forward)); + KeyToRecordIterator it = + new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + V4_SYMBOL_ADDR_COL, addrMap, startAddr, forward)); + return new V4ConvertedRecordIterator(it); } @Override void updateSymbolRecord(DBRecord record) throws IOException { - // make sure hash is updated to current name and name space - String name = record.getString(SYMBOL_NAME_COL); - long namespaceId = record.getLongValue(SYMBOL_PARENT_ID_COL); - long addressKey = record.getLongValue(SYMBOL_ADDR_COL); - record.setField(SYMBOL_HASH_COL, computeLocatorHash(name, namespaceId, addressKey)); - symbolTable.putRecord(record); + throw new UnsupportedOperationException(); } @Override RecordIterator getSymbols() throws IOException { - return symbolTable.iterator(); + return new V4ConvertedRecordIterator(symbolTable.iterator()); } @Override RecordIterator getSymbols(Address start, Address end, boolean forward) throws IOException { - return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, start, end, forward)); + KeyToRecordIterator it = + new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + V4_SYMBOL_ADDR_COL, addrMap, start, end, forward)); + return new V4ConvertedRecordIterator(it); } @Override RecordIterator getSymbols(AddressSetView set, boolean forward) throws IOException { - return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, set, forward)); + KeyToRecordIterator it = + new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + V4_SYMBOL_ADDR_COL, addrMap, set, forward)); + return new V4ConvertedRecordIterator(it); } @Override protected RecordIterator getPrimarySymbols(AddressSetView set, boolean forward) throws IOException { - return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_PRIMARY_COL, addrMap, set, forward)); + KeyToRecordIterator it = + new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + V4_SYMBOL_PRIMARY_COL, addrMap, set, forward)); + return new V4ConvertedRecordIterator(it); } @Override protected DBRecord getPrimarySymbol(Address address) throws IOException { AddressIndexPrimaryKeyIterator it = new AddressIndexPrimaryKeyIterator(symbolTable, - SYMBOL_PRIMARY_COL, addrMap, address, address, true); + V4_SYMBOL_PRIMARY_COL, addrMap, address, address, true); if (it.hasNext()) { - return symbolTable.getRecord(it.next()); + return convertV4Record(symbolTable.getRecord(it.next())); } return null; } void deleteExternalEntries(Address start, Address end) throws IOException { - AddressRecordDeleter.deleteRecords(symbolTable, SYMBOL_ADDR_COL, addrMap, start, end, null); + throw new UnsupportedOperationException(); } @Override void moveAddress(Address oldAddr, Address newAddr) throws IOException { - LongField oldKey = new LongField(addrMap.getKey(oldAddr, false)); - long newKey = addrMap.getKey(newAddr, true); - Field[] keys = symbolTable.findRecords(oldKey, SYMBOL_ADDR_COL); - for (Field key : keys) { - DBRecord rec = symbolTable.getRecord(key); - rec.setLongValue(SYMBOL_ADDR_COL, newKey); - symbolTable.putRecord(rec); - } + throw new UnsupportedOperationException(); } @Override Set

    deleteAddressRange(Address startAddr, Address endAddr, TaskMonitor monitor) throws CancelledException, IOException { - - AnchoredSymbolRecordFilter filter = new AnchoredSymbolRecordFilter(); - AddressRecordDeleter.deleteRecords(symbolTable, SYMBOL_ADDR_COL, addrMap, startAddr, - endAddr, filter); - - return filter.getAddressesForSkippedRecords(); + throw new UnsupportedOperationException(); } @Override RecordIterator getSymbolsByNamespace(long id) throws IOException { LongField field = new LongField(id); - return symbolTable.indexIterator(SYMBOL_PARENT_ID_COL, field, field, true); + RecordIterator it = + symbolTable.indexIterator(V4_SYMBOL_PARENT_ID_COL, field, field, true); + return new V4ConvertedRecordIterator(it); } @Override RecordIterator getSymbolsByName(String name) throws IOException { StringField field = new StringField(name); - return symbolTable.indexIterator(SYMBOL_NAME_COL, field, field, true); + RecordIterator it = symbolTable.indexIterator(V4_SYMBOL_NAME_COL, field, field, true); + return new V4ConvertedRecordIterator(it); } @Override RecordIterator scanSymbolsByName(String startName) throws IOException { StringField field = new StringField(startName); - return symbolTable.indexIterator(SYMBOL_NAME_COL, field, null, true); + RecordIterator it = symbolTable.indexIterator(V4_SYMBOL_NAME_COL, field, null, true); + return new V4ConvertedRecordIterator(it); } @Override RecordIterator getExternalSymbolsByOriginalImportName(String extLabel) throws IOException { StringField extLabelField = new StringField(extLabel); - return symbolTable.indexIterator(SYMBOL_ORIGINAL_IMPORTED_NAME_COL, extLabelField, + RecordIterator it = + symbolTable.indexIterator(V4_SYMBOL_ORIGINAL_IMPORTED_NAME_COL, extLabelField, extLabelField, true); + return new V4ConvertedRecordIterator(it); } @Override RecordIterator getExternalSymbolsByMemoryAddress(Address extProgAddr) throws IOException { StringField addrField = new StringField(extProgAddr.toString()); - return symbolTable.indexIterator(SYMBOL_EXTERNAL_PROG_ADDR_COL, addrField, addrField, true); + RecordIterator it = + symbolTable.indexIterator(V4_SYMBOL_EXTERNAL_PROG_ADDR_COL, addrField, addrField, + true); + return new V4ConvertedRecordIterator(it); } @Override @@ -291,8 +260,9 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { Field end = computeLocatorHash(name, id, MAX_ADDRESS_OFFSET); - RecordIterator it = symbolTable.indexIterator(SYMBOL_HASH_COL, start, end, true); - return getNameAndNamespaceFilterIterator(name, id, it); + RecordIterator it = symbolTable.indexIterator(V4_SYMBOL_HASH_COL, start, end, true); + it = getNameAndNamespaceFilterIterator(name, id, it); + return new V4ConvertedRecordIterator(it); } @Override @@ -302,11 +272,11 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { if (search == null) { return null; } - RecordIterator it = symbolTable.indexIterator(SYMBOL_HASH_COL, search, search, true); + RecordIterator it = symbolTable.indexIterator(V4_SYMBOL_HASH_COL, search, search, true); RecordIterator filtered = getNameNamespaceAddressFilterIterator(name, namespaceId, addressKey, it); if (filtered.hasNext()) { - return filtered.next(); + return convertV4Record(filtered.next()); } return null; } @@ -315,7 +285,7 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { Address getMaxSymbolAddress(AddressSpace space) throws IOException { if (space.isMemorySpace()) { AddressIndexKeyIterator addressKeyIterator = new AddressIndexKeyIterator(symbolTable, - SYMBOL_ADDR_COL, addrMap, space.getMinAddress(), space.getMaxAddress(), false); + V4_SYMBOL_ADDR_COL, addrMap, space.getMinAddress(), space.getMaxAddress(), false); if (addressKeyIterator.hasNext()) { return addrMap.decodeAddress(addressKeyIterator.next()); } @@ -323,7 +293,7 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { else { LongField max = new LongField(addrMap.getKey(space.getMaxAddress(), false)); DBFieldIterator iterator = - symbolTable.indexFieldIterator(null, max, false, SYMBOL_ADDR_COL); + symbolTable.indexFieldIterator(null, max, false, V4_SYMBOL_ADDR_COL); if (iterator.hasPrevious()) { LongField val = (LongField) iterator.previous(); Address addr = addrMap.decodeAddress(val.getLongValue()); @@ -340,24 +310,82 @@ class SymbolDatabaseAdapterV4 extends SymbolDatabaseAdapter { return symbolTable; } - private class AnchoredSymbolRecordFilter implements RecordFilter { - private Set
    set = new HashSet
    (); + /** + * Returns a record matching the current database schema from the version 4 record. + * @param record the record matching the version 4 schema. + * @return a current symbol record. + */ + private DBRecord convertV4Record(DBRecord record) { + if (record == null) { + return null; + } + DBRecord rec = SymbolDatabaseAdapter.SYMBOL_SCHEMA.createRecord(record.getKey()); - @Override - public boolean matches(DBRecord record) { - // only move symbols whose anchor flag is not on - Address addr = addrMap.decodeAddress(record.getLongValue(SYMBOL_ADDR_COL)); - byte flags = record.getByteValue(SymbolDatabaseAdapter.SYMBOL_FLAGS_COL); - boolean pinned = (flags & SymbolDatabaseAdapter.SYMBOL_PINNED_FLAG) != 0; - if (!pinned) { - return true; - } - set.add(addr); - return false; + String symbolName = record.getString(V4_SYMBOL_NAME_COL); + rec.setString(SymbolDatabaseAdapter.SYMBOL_NAME_COL, symbolName); + + long symbolAddrKey = record.getLongValue(V4_SYMBOL_ADDR_COL); + rec.setLongValue(SymbolDatabaseAdapter.SYMBOL_ADDR_COL, symbolAddrKey); + + long namespaceId = record.getLongValue(V4_SYMBOL_PARENT_ID_COL); + rec.setLongValue(SymbolDatabaseAdapter.SYMBOL_PARENT_ID_COL, namespaceId); + + byte symbolTypeId = record.getByteValue(V4_SYMBOL_TYPE_COL); + rec.setByteValue(SymbolDatabaseAdapter.SYMBOL_TYPE_COL, symbolTypeId); + + rec.setByteValue(SymbolDatabaseAdapter.SYMBOL_FLAGS_COL, + record.getByteValue(V4_SYMBOL_FLAGS_COL)); + + // + // Convert sparse columns + // + + if (symbolTypeId == SYMBOL_TYPE_LABEL || symbolTypeId == SYMBOL_TYPE_FUNCTION) { + record.setString(SYMBOL_EXTERNAL_PROG_ADDR_COL, + record.getString(V4_SYMBOL_EXTERNAL_PROG_ADDR_COL)); + record.setString(SYMBOL_ORIGINAL_IMPORTED_NAME_COL, + record.getString(V4_SYMBOL_ORIGINAL_IMPORTED_NAME_COL)); + } + else if (symbolTypeId == SYMBOL_TYPE_LOCAL_VAR || symbolTypeId == SYMBOL_TYPE_PARAMETER) { + record.setString(SYMBOL_COMMENT_COL, record.getString(V4_SYMBOL_COMMENT_COL)); + } + else if (symbolTypeId == SYMBOL_TYPE_LIBRARY) { + // NOTE: don't set new sparse ordinal column + record.setString(SYMBOL_LIBPATH_COL, record.getString(V4_SYMBOL_LIBPATH_COL)); } - Set
    getAddressesForSkippedRecords() { - return set; + Field hash = record.getFieldValue(V4_SYMBOL_HASH_COL); + if (hash != null) { + rec.setField(SymbolDatabaseAdapter.SYMBOL_HASH_COL, hash); + } + + Field primaryAddr = record.getFieldValue(V4_SYMBOL_PRIMARY_COL); + if (primaryAddr != null) { + rec.setField(SymbolDatabaseAdapter.SYMBOL_PRIMARY_COL, primaryAddr); + } + + Field dataTypeId = record.getFieldValue(V4_SYMBOL_DATATYPE_COL); + if (dataTypeId != null) { + rec.setField(SymbolDatabaseAdapter.SYMBOL_DATATYPE_COL, dataTypeId); + } + + Field varOffset = record.getFieldValue(V4_SYMBOL_VAROFFSET_COL); + if (varOffset != null) { + rec.setField(SymbolDatabaseAdapter.SYMBOL_VAROFFSET_COL, varOffset); + } + + return rec; + } + + private class V4ConvertedRecordIterator extends ConvertedRecordIterator { + + V4ConvertedRecordIterator(RecordIterator originalIterator) { + super(originalIterator, false); + } + + @Override + protected DBRecord convertRecord(DBRecord record) { + return convertV4Record(record); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV5.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV5.java new file mode 100644 index 0000000000..f50812dc0f --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDatabaseAdapterV5.java @@ -0,0 +1,351 @@ +/* ### + * 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.database.symbol; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import db.*; +import ghidra.program.database.map.*; +import ghidra.program.database.util.EmptyRecordIterator; +import ghidra.program.database.util.RecordFilter; +import ghidra.program.model.address.*; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.symbol.SymbolType; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; + +/** + * SymbolDatabaseAdapter for version 5 + * + * This version added additional sparse columns to store optional data specific to certain + * symbol types. The adhoc string data column was eliminated. + */ +class SymbolDatabaseAdapterV5 extends SymbolDatabaseAdapter { + + static final int SYMBOL_VERSION = 5; + + // Used to create a range when searching symbols by name/namespace but don't care about address + private static final long MIN_ADDRESS_OFFSET = 0; + private static final long MAX_ADDRESS_OFFSET = -1; + + // NOTE: the primary field duplicates the symbol's address when the symbol is primary. This + // allows us to index this field and quickly find the primary symbols. The field is sparse + // so that non-primary symbols don't consume any space for this field. + + static final Schema V5_SYMBOL_SCHEMA = new Schema(SYMBOL_VERSION, "Key", + new Field[] { StringField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE, + ByteField.INSTANCE, ByteField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE, + LongField.INSTANCE, IntField.INSTANCE, StringField.INSTANCE, StringField.INSTANCE, + StringField.INSTANCE, StringField.INSTANCE, IntField.INSTANCE }, + new String[] { "Name", "Address", "Namespace", "Symbol Type", "Flags", "Locator Hash", + "Primary", "Datatype", "Variable Offset", "ExtOrigImportName", "ExtProgAddr", "Comment", + "LibPath", "LibOrdinal" }, + new int[] { SYMBOL_HASH_COL, SYMBOL_PRIMARY_COL, SYMBOL_DATATYPE_COL, SYMBOL_VAROFFSET_COL, + SYMBOL_ORIGINAL_IMPORTED_NAME_COL, SYMBOL_EXTERNAL_PROG_ADDR_COL, SYMBOL_COMMENT_COL, + SYMBOL_LIBPATH_COL, SYMBOL_LIB_ORDINAL_COL }); + + private Table symbolTable; + private AddressMap addrMap; + + SymbolDatabaseAdapterV5(DBHandle handle, AddressMap addrMap, boolean create) + throws VersionException, IOException { + + this.addrMap = addrMap; + if (create) { + symbolTable = handle.createTable(SYMBOL_TABLE_NAME, SYMBOL_SCHEMA, + new int[] { SYMBOL_ADDR_COL, SYMBOL_NAME_COL, SYMBOL_PARENT_ID_COL, SYMBOL_HASH_COL, + SYMBOL_PRIMARY_COL, SYMBOL_ORIGINAL_IMPORTED_NAME_COL, + SYMBOL_EXTERNAL_PROG_ADDR_COL }); + } + else { + symbolTable = handle.getTable(SYMBOL_TABLE_NAME); + if (symbolTable == null) { + throw new VersionException("Missing Table: " + SYMBOL_TABLE_NAME); + } + if (symbolTable.getSchema().getVersion() != SYMBOL_VERSION) { + int version = symbolTable.getSchema().getVersion(); + if (version < SYMBOL_VERSION) { + throw new VersionException(true); + } + throw new VersionException(VersionException.NEWER_VERSION, false); + } + } + } + + @Override + DBRecord createSymbolRecord(String name, long namespaceID, Address address, + SymbolType symbolType, boolean isPrimary, SourceType source) { + + long nextID = symbolTable.getKey(); + + // Avoid key 0, as it is reserved for the global namespace + if (nextID == 0) { + nextID++; + } + + long addressKey = addrMap.getKey(address, true); + + DBRecord rec = symbolTable.getSchema().createRecord(nextID); + rec.setString(SYMBOL_NAME_COL, name); + rec.setLongValue(SYMBOL_ADDR_COL, addressKey); + rec.setLongValue(SYMBOL_PARENT_ID_COL, namespaceID); + rec.setByteValue(SYMBOL_TYPE_COL, symbolType.getID()); + rec.setByteValue(SYMBOL_FLAGS_COL, getSourceTypeFlagsBits(source)); // assume non-pinned + + // Sparse columns - these columns don't apply to all symbols. + // they default to null unless specifically set. Null values don't consume space. + + rec.setField(SYMBOL_HASH_COL, computeLocatorHash(name, namespaceID, addressKey)); + + if (isPrimary) { + rec.setLongValue(SYMBOL_PRIMARY_COL, addressKey); + } + + return rec; + } + + @Override + void removeSymbol(long symbolID) throws IOException { + symbolTable.deleteRecord(symbolID); + } + + @Override + boolean hasSymbol(Address addr) throws IOException { + long key = addrMap.getKey(addr, false); + if (key == AddressMap.INVALID_ADDRESS_KEY && !addr.equals(Address.NO_ADDRESS)) { + return false; + } + return symbolTable.hasRecord(new LongField(key), SYMBOL_ADDR_COL); + } + + @Override + Field[] getSymbolIDs(Address addr) throws IOException { + long key = addrMap.getKey(addr, false); + if (key == AddressMap.INVALID_ADDRESS_KEY && !addr.equals(Address.NO_ADDRESS)) { + return Field.EMPTY_ARRAY; + } + return symbolTable.findRecords(new LongField(key), SYMBOL_ADDR_COL); + } + + @Override + DBRecord getSymbolRecord(long symbolID) throws IOException { + return symbolTable.getRecord(symbolID); + } + + @Override + int getSymbolCount() { + return symbolTable.getRecordCount(); + } + + @Override + RecordIterator getSymbolsByAddress(boolean forward) throws IOException { + return new KeyToRecordIterator(symbolTable, + new AddressIndexPrimaryKeyIterator(symbolTable, SYMBOL_ADDR_COL, addrMap, forward)); + } + + @Override + RecordIterator getSymbolsByAddress(Address startAddr, boolean forward) throws IOException { + return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + SYMBOL_ADDR_COL, addrMap, startAddr, forward)); + } + + @Override + void updateSymbolRecord(DBRecord record) throws IOException { + // make sure hash is updated to current name and name space + String name = record.getString(SYMBOL_NAME_COL); + long namespaceId = record.getLongValue(SYMBOL_PARENT_ID_COL); + long addressKey = record.getLongValue(SYMBOL_ADDR_COL); + record.setField(SYMBOL_HASH_COL, computeLocatorHash(name, namespaceId, addressKey)); + symbolTable.putRecord(record); + } + + @Override + RecordIterator getSymbols() throws IOException { + return symbolTable.iterator(); + } + + @Override + RecordIterator getSymbols(Address start, Address end, boolean forward) throws IOException { + return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + SYMBOL_ADDR_COL, addrMap, start, end, forward)); + } + + @Override + RecordIterator getSymbols(AddressSetView set, boolean forward) throws IOException { + return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + SYMBOL_ADDR_COL, addrMap, set, forward)); + } + + @Override + protected RecordIterator getPrimarySymbols(AddressSetView set, boolean forward) + throws IOException { + return new KeyToRecordIterator(symbolTable, new AddressIndexPrimaryKeyIterator(symbolTable, + SYMBOL_PRIMARY_COL, addrMap, set, forward)); + } + + @Override + protected DBRecord getPrimarySymbol(Address address) throws IOException { + AddressIndexPrimaryKeyIterator it = new AddressIndexPrimaryKeyIterator(symbolTable, + SYMBOL_PRIMARY_COL, addrMap, address, address, true); + if (it.hasNext()) { + return symbolTable.getRecord(it.next()); + } + return null; + } + + void deleteExternalEntries(Address start, Address end) throws IOException { + AddressRecordDeleter.deleteRecords(symbolTable, SYMBOL_ADDR_COL, addrMap, start, end, null); + } + + @Override + void moveAddress(Address oldAddr, Address newAddr) throws IOException { + LongField oldKey = new LongField(addrMap.getKey(oldAddr, false)); + long newKey = addrMap.getKey(newAddr, true); + Field[] keys = symbolTable.findRecords(oldKey, SYMBOL_ADDR_COL); + for (Field key : keys) { + DBRecord rec = symbolTable.getRecord(key); + rec.setLongValue(SYMBOL_ADDR_COL, newKey); + symbolTable.putRecord(rec); + } + } + + @Override + Set
    deleteAddressRange(Address startAddr, Address endAddr, TaskMonitor monitor) + throws CancelledException, IOException { + + AnchoredSymbolRecordFilter filter = new AnchoredSymbolRecordFilter(); + AddressRecordDeleter.deleteRecords(symbolTable, SYMBOL_ADDR_COL, addrMap, startAddr, + endAddr, filter); + + return filter.getAddressesForSkippedRecords(); + } + + @Override + RecordIterator getSymbolsByNamespace(long id) throws IOException { + LongField field = new LongField(id); + return symbolTable.indexIterator(SYMBOL_PARENT_ID_COL, field, field, true); + } + + @Override + RecordIterator getSymbolsByName(String name) throws IOException { + StringField field = new StringField(name); + return symbolTable.indexIterator(SYMBOL_NAME_COL, field, field, true); + } + + @Override + RecordIterator scanSymbolsByName(String startName) throws IOException { + StringField field = new StringField(startName); + return symbolTable.indexIterator(SYMBOL_NAME_COL, field, null, true); + } + + @Override + RecordIterator getExternalSymbolsByOriginalImportName(String extLabel) throws IOException { + StringField extLabelField = new StringField(extLabel); + return symbolTable.indexIterator(SYMBOL_ORIGINAL_IMPORTED_NAME_COL, extLabelField, + extLabelField, true); + } + + @Override + RecordIterator getExternalSymbolsByMemoryAddress(Address extProgAddr) throws IOException { + StringField addrField = new StringField(extProgAddr.toString()); + return symbolTable.indexIterator(SYMBOL_EXTERNAL_PROG_ADDR_COL, addrField, addrField, true); + } + + @Override + RecordIterator getSymbolsByNameAndNamespace(String name, long id) throws IOException { + // create a range of hash fields for all symbols with this name and namespace id over all + // possible addresses + Field start = computeLocatorHash(name, id, MIN_ADDRESS_OFFSET); + if (start == null) { + return EmptyRecordIterator.INSTANCE; + } + + Field end = computeLocatorHash(name, id, MAX_ADDRESS_OFFSET); + + RecordIterator it = symbolTable.indexIterator(SYMBOL_HASH_COL, start, end, true); + return getNameAndNamespaceFilterIterator(name, id, it); + } + + @Override + DBRecord getSymbolRecord(Address address, String name, long namespaceId) throws IOException { + long addressKey = addrMap.getKey(address, false); + Field search = computeLocatorHash(name, namespaceId, addressKey); + if (search == null) { + return null; + } + RecordIterator it = symbolTable.indexIterator(SYMBOL_HASH_COL, search, search, true); + RecordIterator filtered = + getNameNamespaceAddressFilterIterator(name, namespaceId, addressKey, it); + if (filtered.hasNext()) { + return filtered.next(); + } + return null; + } + + @Override + Address getMaxSymbolAddress(AddressSpace space) throws IOException { + if (space.isMemorySpace()) { + AddressIndexKeyIterator addressKeyIterator = new AddressIndexKeyIterator(symbolTable, + SYMBOL_ADDR_COL, addrMap, space.getMinAddress(), space.getMaxAddress(), false); + if (addressKeyIterator.hasNext()) { + return addrMap.decodeAddress(addressKeyIterator.next()); + } + } + else { + LongField max = new LongField(addrMap.getKey(space.getMaxAddress(), false)); + DBFieldIterator iterator = + symbolTable.indexFieldIterator(null, max, false, SYMBOL_ADDR_COL); + if (iterator.hasPrevious()) { + LongField val = (LongField) iterator.previous(); + Address addr = addrMap.decodeAddress(val.getLongValue()); + if (space.equals(addr.getAddressSpace())) { + return addr; + } + } + } + return null; + } + + @Override + Table getTable() { + return symbolTable; + } + + private class AnchoredSymbolRecordFilter implements RecordFilter { + private Set
    set = new HashSet
    (); + + @Override + public boolean matches(DBRecord record) { + // only move symbols whose anchor flag is not on + Address addr = addrMap.decodeAddress(record.getLongValue(SYMBOL_ADDR_COL)); + byte flags = record.getByteValue(SymbolDatabaseAdapter.SYMBOL_FLAGS_COL); + boolean pinned = (flags & SymbolDatabaseAdapter.SYMBOL_PINNED_FLAG) != 0; + if (!pinned) { + return true; + } + set.add(addr); + return false; + } + + Set
    getAddressesForSkippedRecords() { + return set; + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java index 446c1440bf..df71b74623 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java @@ -72,6 +72,8 @@ public class SymbolManager implements SymbolTable, ManagerDB { private NamespaceManager namespaceMgr; private VariableStorageManagerDB variableStorageMgr; + private List libSymbolList; // unmodifiable; use getLibrarySymbolList() to obtain instance + private OldVariableStorageManagerDB oldVariableStorageMgr; // required for upgrade private AddressMapImpl dynamicSymbolAddressMap; @@ -182,6 +184,10 @@ public class SymbolManager implements SymbolTable, ManagerDB { // older than program version 10 processOldVariableAddresses(monitor); } + + if (currentRevision < ProgramDB.LIBRARY_ORDINAL_ASSIGNMENT_ADDED_VERSION) { + assignLibraryOrdinals(); + } } } @@ -663,9 +669,6 @@ public class SymbolManager implements SymbolTable, ManagerDB { SymbolType symType = sym.getSymbolType(); try { Address address = sym.getAddress(); -// if (address.isVariableAddress()) { -// variableStorageMgr.deleteVariableStorage(address); -// } String name = sym.getName(); boolean primary = sym.isPrimary(); @@ -674,7 +677,6 @@ public class SymbolManager implements SymbolTable, ManagerDB { adapter.removeSymbol(id); cache.delete(id); - //sym.setInvalid(); // already invalidated by removeObj // if any symbols still exist here, then // make one of these remaining symbols 'primary' @@ -734,11 +736,10 @@ public class SymbolManager implements SymbolTable, ManagerDB { private SymbolDB getDynamicSymbol(Address memoryAddress) { long symbolID = getDynamicSymbolID(memoryAddress); - // retrieve the symbol from the cache without validating or refreshing it. We know - // if it exists is should + // Retrieve the symbol from the cache without validating or refreshing it. CodeSymbol symbol = (CodeSymbol) cache.getRaw(symbolID); if (symbol != null) { - // we know if we got here, the symbol is valid regardless of what it thinks + // We know if we got here the symbol is valid, so force it to be valid symbol.setIsValid(); return symbol; } @@ -1378,8 +1379,8 @@ public class SymbolManager implements SymbolTable, ManagerDB { @Override public LabelHistory[] getLabelHistory(Address addr) { - ArrayList list = new ArrayList<>(); - try { + try (Closeable c = lock.read()) { + ArrayList list = new ArrayList<>(); RecordIterator iter = historyAdapter.getRecordsByAddress(addrMap.getKey(addr, false)); while (iter.hasNext()) { DBRecord rec = iter.next(); @@ -1401,8 +1402,9 @@ public class SymbolManager implements SymbolTable, ManagerDB { @Override public void invalidateCache(boolean all) { - variableStorageMgr.invalidateCache(all); try (Closeable c = lock.write()) { + variableStorageMgr.invalidateCache(all); + libSymbolList = null; cache.invalidate(); dynamicSymbolAddressMap.reconcile(); } @@ -2739,7 +2741,11 @@ public class SymbolManager implements SymbolTable, ManagerDB { } /** - * Create a Library symbol with the specified name and optional pathname + * Create a Library symbol with the specified name and optional pathname. + * The new Library symbol will be assigned the next available ordinal at the end of the + * ordered Library symbol sequence. Use {@link LibrarySymbol#setOrdinal(int)} to adjust + * ordinal. + * * * @param name library name * @param pathname project file path (may be null) @@ -2757,14 +2763,24 @@ public class SymbolManager implements SymbolTable, ManagerDB { try (Closeable c = lock.write()) { DBRecord record = doCreateBasicSymbolRecord(name, null, Address.NO_ADDRESS, SymbolType.LIBRARY, false, source, true); - LibrarySymbol.setRecordFields(record, pathname); + + List librarySymbolList = new ArrayList<>(getLibrarySymbolList()); + int ordinal = librarySymbolList.size(); + if (Library.UNKNOWN.equals(name)) { + // UNKNOWN Library always assigned ordinal of 0 + ordinal = 0; + } + + LibrarySymbol newLibSymbol = new LibrarySymbol(this, record); + LibrarySymbol.setRecordFields(record, ordinal, pathname); adapter.updateSymbolRecord(record); + cache.add(newLibSymbol); - LibrarySymbol newSymbol = new LibrarySymbol(this, record); - cache.add(newSymbol); - - symbolAdded(newSymbol); - return newSymbol; + librarySymbolList.add(ordinal, newLibSymbol); + assignLibraryOrdinals(librarySymbolList, true); + + symbolAdded(newLibSymbol); + return newLibSymbol; } catch (IOException e) { program.dbError(e); // will not return @@ -2772,6 +2788,139 @@ public class SymbolManager implements SymbolTable, ManagerDB { } } + /** + * {@return computed ordinal of the specified library symbol} + * @param libSym library symbol + */ + int computeLibraryOrdinal(LibrarySymbol libSym) { + List list = getLibrarySymbolList(); + for (int i = 0; i < list.size(); i++) { + LibrarySymbol s = list.get(i); + if (s == libSym) { + return i; + } + } + throw new AssertionError("Invalid symbol instance"); + } + + /** + * @return an ordered unmodifiable list of Library symbols + */ + public List getLibrarySymbolList() { + List list = libSymbolList; + if (list == null) { + try (Closeable c = lock.read()) { + list = buildLibrarySymbolList(); + libSymbolList = list; + } + catch (IOException e) { + dbError(e); + } + } + return list; + } + + private List buildLibrarySymbolList() throws IOException { + List list = new ArrayList<>(); + byte libraryTypeId = SymbolType.LIBRARY.getID(); + RecordIterator iter = new QueryRecordIterator(adapter.getSymbols(), + rec -> libraryTypeId == rec + .getByteValue(SymbolDatabaseAdapter.SYMBOL_TYPE_COL)); + + // Assume records will be returned in order of symbol ID + long lastId = -1; + while (iter.hasNext()) { + LibrarySymbol libSym = (LibrarySymbol) getSymbol(iter.next()); + long id = libSym.getID(); + if (id <= lastId) { + throw new AssertionError("Unexpected symbol order"); + } + list.add(libSym); + } + Collections.sort(list); + return Collections.unmodifiableList(list); + } + + /** + * Assign and store ordinals for all library symbols during upgrade + */ + private void assignLibraryOrdinals() { + List libSymList = new ArrayList<>(getLibrarySymbolList()); + for (int i = 0; i < libSymList.size(); i++) { + LibrarySymbol libSym = libSymList.get(i); + if (Library.UNKNOWN.equals(libSym.getName())) { + // UNKNOWN Library always assigned ordinal of 0 but may have arbitrary + // placement in list prior to upgrade + if (i != 0) { + libSymList.remove(i); + libSymList.add(0, libSym); + } + break; + } + } + assignLibraryOrdinals(libSymList, false); + } + + /** + * Update library symbol ordinal to reflect current placement within ordered list. + * The cached {@code libSymbolList} will be updated to refer to the list instance provided. + * @param list new library symbol ordered list + * @param notify if true any library symbol ordinal change will generate change event for + * that symbol. + */ + void assignLibraryOrdinals(List list, boolean notify) { + for (int i = 0; i < list.size(); i++) { + LibrarySymbol libSym = list.get(i); + libSym.doSetOrdinal(i, notify); + } + libSymbolList = list; // update cached list + } + + /** + * Adjust ordinals based upon the movement of an existing Library symbol. + *

    + * NOTE: The caller is responsible for not displacing UNKNOWN Library if already exists + * at ordinal 0. + * + * @param libSym existing Library symbol + * @param newOrdinal non-negative ordinal (max value will be limited) + */ + void adjustLibraryOrdinals(LibrarySymbol libSym, int newOrdinal) { + if (newOrdinal < 0) { + throw new IllegalArgumentException("Positive ordinal required"); + } + List libSymList = new ArrayList<>(getLibrarySymbolList()); + int listSize = libSymList.size(); + if (newOrdinal >= listSize) { + newOrdinal = listSize - 1; // do not go beyond end of list + } + + int oldOrdinal = libSym.getOrdinal(); + if (oldOrdinal == newOrdinal) { + return; + } + + LibrarySymbol s = libSymList.remove(oldOrdinal); + if (s != libSym) { + throw new AssertionError("Library symbol ordinal mismatch with ordered list"); + } + libSymList.add(newOrdinal, libSym); + assignLibraryOrdinals(libSymList, true); + } + + /** + * {@return library symbol at the specified ordinal or null if ordinal is beyond end of list} + * @param ordinal library symbol ordinal + * @throws IndexOutOfBoundsException if a negative ordinal is specified + */ + LibrarySymbol getLibrarySymbolByOrdinal(int ordinal) throws IndexOutOfBoundsException { + List librarySymbolList = getLibrarySymbolList(); + if (ordinal >= librarySymbolList.size()) { + return null; + } + return librarySymbolList.get(ordinal); + } + /** * Create a Class symbol with the specified name and parent * @@ -2875,6 +3024,11 @@ public class SymbolManager implements SymbolTable, ManagerDB { source = validateSource(source, name, address, symbolType); name = validateName(name, source); + if ((symbolType == SymbolType.CLASS || symbolType == SymbolType.NAMESPACE) && + Library.UNKNOWN.equals(name)) { + throw new InvalidInputException(Library.UNKNOWN + " is a reserved Library name"); + } + if (checkForDuplicates) { checkDuplicateSymbolName(address, name, parent, symbolType); } @@ -2905,6 +3059,9 @@ public class SymbolManager implements SymbolTable, ManagerDB { if (!addr.isExternalAddress()) { throw new IllegalArgumentException("External address required"); } + if (!namespace.isExternal()) { + throw new IllegalArgumentException("External namespace required"); + } if (externalProgramAddress != null && !externalProgramAddress.isLoadedMemoryAddress()) { throw new IllegalArgumentException("Memory address required for external program"); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Library.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Library.java index 7cc27b9b3b..f55ecc2076 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Library.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Library.java @@ -16,9 +16,10 @@ package ghidra.program.model.listing; import ghidra.program.model.symbol.*; +import ghidra.util.exception.InvalidInputException; /** - * Interface for a Library namespace. + * Interface for a Library dependency and namespace. */ public interface Library extends Namespace { @@ -30,10 +31,20 @@ public interface Library extends Namespace { } /** - * @return the associated program within the project which corresponds to this library + * @return the associated program file pathname within the project which corresponds to this library. */ public String getAssociatedProgramPath(); + /** + * Sets the program file pathname within the project which corresponds to this library. + *

    + * NOTE: Assigning a path to {@link Library#UNKNOWN} Library will be ignored. + * + * @param programPath a program file pathname or null to clear the stored path. + * @throws InvalidInputException on invalid programPath is specified + */ + public void setAssociatedProgramPath(String programPath) throws InvalidInputException; + /** * Get the Library which contains the specified external symbol. * @param symbol external symbol diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalLocation.java index c937e3b684..0f71e77c3b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalLocation.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalLocation.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. @@ -42,6 +42,15 @@ public interface ExternalLocation { */ public String getLibraryName(); + /** + * NOTE: If this external location corresponds to a back-reference this + * may correspond to an application and not a library. + * + * @return the external Program path which contains the referenced symbol or + * null if unknown. + */ + public String getExternalLibraryPath(); + /** * Returns the parent namespace containing this location. * @return the parent namespace containing this location. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java index c5aa1d72d2..4990ddeda8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/ExternalManager.java @@ -15,6 +15,7 @@ */ package ghidra.program.model.symbol; +import java.util.List; import java.util.Set; import ghidra.program.model.address.Address; @@ -31,13 +32,32 @@ import ghidra.util.exception.InvalidInputException; public interface ExternalManager { /** - * Returns an array of all external names for which locations have been defined. - * @return array of external names + * Returns an array of all external Library names sorted by preferred search order. + * This order reflects the preferred search order when looking for external symbols + * which were not linked to a specific Library. + *

    + * NOTE: The {@link Library#UNKNOWN} library will always be returned as first in the list + * but will not have an associated program path and cannot be searched. + * + * @return array of all external Library names sorted by preferred search order. */ public String[] getExternalLibraryNames(); + /** + * Get a list of all external Libraries sorted by preferred search order. + * This order reflects the preferred search order when looking for external symbols + * which were not linked to a specific Library. + *

    + * NOTE: The {@link Library#UNKNOWN} library will always be returned as first in the list + * but will not have an associated program path and cannot be searched. + * + * @return list of all external Libraries sorted by preferred search order. + */ + public List getLibraries(); + /** * Get the Library which corresponds to the specified name + * * @param libraryName name of library * @return library or null if not found */ @@ -45,6 +65,7 @@ public interface ExternalManager { /** * Removes external name if no associated ExternalLocation's exist + * * @param libraryName external library name * @return true if removed, false if unable to due to associated locations/references */ @@ -54,35 +75,69 @@ public interface ExternalManager { * Returns the file pathname associated with an external name. * Null is returned if either the external name does not exist or * a pathname has not been set. + * * @param libraryName external name * @return project file pathname or null */ public String getExternalLibraryPath(String libraryName); /** - * Sets the file pathname associated with an existing external name. + * Sets the file pathname associated with an external name. + * If the Library namespace/symbol does not already exist it will be created provided + * the libraryName does not conflict with another namespace whose parent is the global + * namespace. + *

    + * NOTE: Assigning path for {@link Library#UNKNOWN} Library will be ignored. + *

    + * NOTE: Assigning path to a non-Library namespace will fail silently. + * * @param libraryName the name of the library to associate with a file. * @param pathname the path to the program to be associated with the library name. * @param userDefined true if the external path is being specified by the user - * @throws InvalidInputException on invalid input + * @throws InvalidInputException on invalid input specified */ public void setExternalPath(String libraryName, String pathname, boolean userDefined) throws InvalidInputException; + + /** + * {@return the ordinal associated with an external library which represents its + * sequence within the order list of libraries or -1 if library name not found} + * + * @param libraryName the library name + */ + public int getLibraryOrdinal(String libraryName); + + /** + * Sets the Library search ordinal associated with an external name. + *

    + * Assigning ordinal for {@link Library#UNKNOWN} Library will fail and return -1. + * Assigning ordinal to a non-existing Library will fail and return -1. + *

    + * NOTE: The actual ordinal applied my be limited based on placement restrictions. + * + * @param libraryName the name of the library to position within Library search sequence + * @param ordinal library ordinal greater or equal to 1 + * @return the actual ordinal applied or -1 if change failed. + */ + public int setLibraryOrdinal(String libraryName, int ordinal); /** * Change the name of an existing external name. + * * @param oldName the old name of the external library name. * @param newName the new name of the external library name. * @param source the source of this external library + * @return true if symbol was found and renamed, false if symbol not found * @throws DuplicateNameException if name conflicts with another symbol. * @throws InvalidInputException if an invalid or null name specified (see * {@link SymbolUtilities#validateName}). */ - public void updateExternalLibraryName(String oldName, String newName, SourceType source) + public boolean updateExternalLibraryName(String oldName, String newName, SourceType source) throws DuplicateNameException, InvalidInputException; /** * Get an iterator over all external locations associated with the specified Library. + * * @param libraryName the name of the library to get locations for * @return external location iterator */ @@ -91,6 +146,7 @@ public interface ExternalManager { /** * Get an iterator over all external locations which have been associated to * the specified memory address + * * @param memoryAddress memory address * @return external location iterator */ @@ -151,6 +207,7 @@ public interface ExternalManager { /** * Determines if the indicated external library name is being managed (exists). + * * @param libraryName the external library name * @return true if the name is defined (whether it has a path or not). */ @@ -158,6 +215,7 @@ public interface ExternalManager { /** * Adds a new external library name + * * @param libraryName the new external library name to add. * @param source the source of this external library * @return library external {@link Library namespace} @@ -173,6 +231,7 @@ public interface ExternalManager { * Get or create an external location associated with a library/file named {@code libraryName} * and the location within that file identified by {@code extLabel} and/or its memory address * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. + * * @param libraryName the external library name * @param extLabel the external label or null * @param extAddr the external memory address or null @@ -191,6 +250,7 @@ public interface ExternalManager { * Create an external location in the indicated external parent namespace * and identified by {@code extLabel} and/or its memory address * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. + * * @param extNamespace the external namespace * @param extLabel the external label or null * @param extAddr the external memory address or null @@ -208,6 +268,7 @@ public interface ExternalManager { * Get or create an external location in the indicated external parent namespace * and identified by {@code extLabel} and/or its memory address * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. + * * @param extNamespace the external namespace * @param extLabel the external label or null * @param extAddr the external memory address or null @@ -227,6 +288,7 @@ public interface ExternalManager { * Create an external {@link Function} in the external {@link Library} namespace * {@code libararyName} and identified by {@code extLabel} and/or its memory address * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. + * * @param libraryName the external library name * @param extLabel label within the external program, may be null if extAddr is not null * @param extAddr memory address within the external program, may be null @@ -245,6 +307,7 @@ public interface ExternalManager { * Create an external {@link Function} in the indicated external parent namespace * and identified by {@code extLabel} and/or its memory address * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. + * * @param extNamespace the external namespace * @param extLabel the external label or null * @param extAddr the external memory address or null @@ -262,6 +325,7 @@ public interface ExternalManager { * Get or create an external {@link Function} in the indicated external parent namespace * and identified by {@code extLabel} and/or its memory address * {@code extAddr}. Either or both {@code extLabel} or {@code extAddr} must be specified. + * * @param extNamespace the external namespace * @param extLabel the external label or null * @param extAddr the external memory address or null diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java index e98dd70595..2e135cf59a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ChangeManager.java @@ -357,13 +357,6 @@ public interface ChangeManager { @Deprecated public static final ProgramEvent DOCR_MEM_REF_PRIMARY_REMOVED = REFERENCE_PRIMARY_REMOVED; - /** - * The external path name changed for an external program name. - * @deprecated Event type numeric constants have been changed to enums. Use the enum directly. - */ - @Deprecated - public static final ProgramEvent DOCR_EXTERNAL_PATH_CHANGED = EXTERNAL_PATH_CHANGED; - /** * An external program name was added. * @deprecated Event type numeric constants have been changed to enums. Use the enum directly. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java index 528a0f66d1..978643ca21 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramEvent.java @@ -53,9 +53,12 @@ public enum ProgramEvent implements EventType { SYMBOL_DATA_CHANGED, // some symbol property was changed SYMBOL_ADDRESS_CHANGED, // the symbol's address changed (only applies to param and variables) - EXTERNAL_ENTRY_ADDED, // an external entry point was added - EXTERNAL_ENTRY_REMOVED, // an external entry point was removed - EXTERNAL_PATH_CHANGED, // the external path name changed for an external program + EXTERNAL_ENTRY_ADDED, // an external entry point was added (i.e., Export) + EXTERNAL_ENTRY_REMOVED, // an external entry point was removed (i.e., Export) + + // + // Events related to Library symbols and associated External Location + // EXTERNAL_NAME_ADDED, // an external program name was added EXTERNAL_NAME_REMOVED, // an external program name was removed EXTERNAL_NAME_CHANGED, // the name of an external program was changed diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java index 025c3e264b..aa6e66cb5f 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java @@ -38,9 +38,9 @@ import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.symbol.ExternalManager; import ghidra.util.InvalidNameException; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.VersionException; +import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator { @@ -122,9 +122,15 @@ public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator { } @Test - public void testExternal_names_dialog() { - showProvider(ExternalReferencesProvider.class); - captureProvider(ExternalReferencesProvider.class); + public void testExternal_names_dialog() throws InvalidInputException { + ExternalManager externalManager = program.getExternalManager(); + program.withTransaction("Set Program Path", () -> { + externalManager.setExternalPath("USER32.DLL", "/libs/user32.dll", true); + }); + ExternalReferencesProvider provider = showProvider(ExternalReferencesProvider.class); + JTable table = findComponent(provider.getComponent(), JTable.class); + selectRow(table, 0); + captureIsolatedProvider(ExternalReferencesProvider.class, 500, 500); } @Test