GP-6643 Added ability to order Libraries via an ordinal assignment. This ordered list is now used by external symbol resolver analyzer.

This commit is contained in:
ghidra1
2026-04-20 09:27:11 -04:00
parent 7be1d8c943
commit 5e82e2869c
32 changed files with 1473 additions and 553 deletions
@@ -14,14 +14,36 @@
<BODY lang="EN-US">
<H1><A name="View_External_Program_Names"></A>External Program Names</H1>
<P>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.
<A name="Resolve"></A>If the association has been defined, then the external reference is said
to have been <B><I>resolved</I></B>. The <I>External Programs</I> 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 <I>External Programs</I> view to add
external names, delete external names, set associations, and clear associations.</P>
<P>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. </P>
<P>There is a reserved
Library named <B>&lt;EXTERNAL&gt;</B> which is a holding area for external locations whose
associated Library is unknown (commonly used by ELF Imports).
The <B>External Symbol Resolver</B> 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.</P>
<P>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.
<A name="Resolve"></A>If a Library's Program association has been specified, then any related
external reference is said to have been <B><I>resolved</I></B>. </P>
<P>The <I>External Programs</I> view manages the associations
between external Library names and Program files as well as the ordered Library sequence.
Use the <I>External Programs</I> 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
<A href="../SymbolTreePlugin/SymbolTree.htm#Externals">Symbol Tree</A> Imports node provides
similar actions plus the ability to navigate.</P>
<P><I><IMG src="help/shared/note.png" border="0"> 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.</P>
<H2><A name="ExternalNamesDialog"></A>External Programs View</H2>
@@ -29,26 +51,33 @@
</P>
<BLOCKQUOTE>
<P>The <I>External Programs</I> view consists of a main scrollable list of external program
<P>The <I>External Programs</I> view consists of a main scrollable list of external Library
names and their associated Ghidra program files.</P>
<BLOCKQUOTE>
<H3>Name Column</H3>
<BLOCKQUOTE>
<P>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 &lt;Enter&gt; key.</P>
<P>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.</P>
<P>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 &lt;Enter&gt; to commit the change.</P>
</BLOCKQUOTE>
<H3>Ghidra Program Column<BR>
</H3>
<BLOCKQUOTE>
<P>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.</P>
<P>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.</P>
<P>See <A href="#ChooseExternalProgram">Set External Path Association</A> for changing the
path shown.</P>
</BLOCKQUOTE>
<H3><A name="Add_External_Program_Name"></A>Add Button</H3>
@@ -58,14 +87,48 @@
for entering a new external program name.<BR>
</P>
</BLOCKQUOTE>
<H3><A name="Delete_External_Program_Name"></A>Delete External Name Button</H3>
<BLOCKQUOTE>
<P>The <SPAN style="font-weight: bold;">Delete</SPAN> <IMG alt="" src=
"images/edit-delete.png"> button deletes the selected external Library names from the
program.&nbsp; If a selected external Library name has associated external locations, it can
not be deleted. The <SPAN style="font-weight: bold;">Delete</SPAN> button is enabled
whenever one or more rows are selected.</P>
</BLOCKQUOTE>
<H3><A name="Move_Library_Up"></A>Move Library Up Button</H3>
<BLOCKQUOTE>
<P>The <SPAN style="font-weight: bold;">Up</SPAN> <IMG alt="" src=
"images/up.png"> 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 <SPAN style="font-weight: bold;">Up</SPAN>
button is enabled whenever one external program name is selected and can be moved
up within the Library list.</P>
<P>
</BLOCKQUOTE>
<H3><A name="Move_Library_Down"></A>Move Library Down Button</H3>
<BLOCKQUOTE>
<P>The <SPAN style="font-weight: bold;">Down</SPAN> <IMG alt="" src=
"images/down.png"> 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 <SPAN style="font-weight: bold;">Down</SPAN>
button is enabled whenever one external program name is selected and can be moved
down within the Library list.</P>
<P>
</BLOCKQUOTE>
<H3><A name="ChooseExternalProgram"></A><A name="Set_External_Name_Association"></A>Set
External Name Association Button</H3>
External Path Association Button</H3>
<BLOCKQUOTE>
<P>The <SPAN style="font-weight: bold;">Set</SPAN> <IMG alt="" src=
"images/editbytes.gif"> button brings up a <I>Ghidra program chooser</I> 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.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
@@ -77,26 +140,13 @@
<BLOCKQUOTE>
<BLOCKQUOTE>
<H3><A name="Clear_External_Name_Association"></A>Clear External Name Association
<H3><A name="Clear_External_Name_Association"></A>Clear External Path Association
Button</H3>
<BLOCKQUOTE>
<P>This <SPAN style="font-weight: bold;">Clear <IMG alt="" src=
"images/edit-delete.png">&nbsp;</SPAN> button clears the assocated program for all the
selected external program names.<BR>
</P>
</BLOCKQUOTE>
<H3><A name="Delete_External_Program_Name"></A>Delete External Name Button</H3>
<BLOCKQUOTE>
<P>This <SPAN style="font-weight: bold;">Delete</SPAN> <IMG alt="" src=
"images/edit-delete.png"> button deletes the selected external program names from the
program.&nbsp; If a selected external program name contains external locations, it can
not be deleted. The <SPAN style="font-weight: bold;">Delete</SPAN> button is enabled
whenever one or more external program names are selected.</P>
<P><BR>
<P>The <SPAN style="font-weight: bold;">Clear <IMG alt="" src=
"Icons.CLEAR_ICON">&nbsp;</SPAN> button clears the associated program path for all the
selected rows.<BR>
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
@@ -115,17 +165,12 @@
<LI>Enter the new external program name into the pop-up dialog.</LI>
<LI style="list-style: none">
&lt;&gt;
<P><I><IMG src="help/shared/note.png" border="0">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.
</I></P>
<P><I><IMG src="help/shared/note.png" border="0"> 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 <IMG src="Icons.SORT_ASCENDING_ICON" border="0"> or <IMG
src="Icons.SORT_DESCENDING_ICON" border="0"> 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.</I></P>
&lt;/&gt;
</LI>
</OL>
<H3>Resolving an External Name to an existing Ghidra program<BR>
@@ -157,7 +202,7 @@
<LI>Click on the external program name that has an association to be cleared.</LI>
<LI>Press the <B>Clear</B> <IMG alt="" src="images/erase16.png"> button</LI>
<LI>Press the <B>Clear</B> <IMG alt="" src="Icons.CLEAR_ICON"> button</LI>
</OL>
<H3>Removing an External Program Name</H3>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

@@ -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<Program> {
public class ClearExternalPathCmd implements Command<Program> {
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<Program> {
@Override
public String getName() {
return "Remove External Program Name";
return "Clear External Library Path";
}
}
@@ -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<Program> {
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<Program> {
@Override
public String getName() {
return "Set External Program Name";
return "Set External Library Name and Path";
}
}
@@ -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) {
@@ -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);
@@ -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<String> 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<String> 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<Program> 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<ExternalNamesRow> {
class ExternalNamesTableModel extends AbstractGTableModel<ExternalNamesRow> {
final static int NAME_COL = 0;
final static int PATH_COL = 1;
@@ -336,18 +396,18 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
private List<ExternalNamesRow> 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<ExternalNamesRow> 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<ExternalNamesRow> 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);
}
}
}
@@ -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)
@@ -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}.
*
@@ -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);
}
}
@@ -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);
}
}
@@ -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.
* <p>
* 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<String> getOrderedRequiredLibraryNames(Program program) {
TreeMap<Integer, String> 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<String> 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<ExtLibInfo> getLibsToSearch() throws CancelledException {
List<ExtLibInfo> 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<String> 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();
}
}
}
/**
@@ -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();
@@ -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);
@@ -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<String, Object> result) throws IOException {