diff --git a/Ghidra/Features/Base/data/base.icons.theme.properties b/Ghidra/Features/Base/data/base.icons.theme.properties index cad3f267d1..2885347564 100644 --- a/Ghidra/Features/Base/data/base.icons.theme.properties +++ b/Ghidra/Features/Base/data/base.icons.theme.properties @@ -56,6 +56,7 @@ icon.plugin.calltree.filter.duplicates = application_double.png icon.plugin.calltree.filter.select.source = text_align_justify.png icon.plugin.calltree.node.dead.end = stopNode.png icon.plugin.calltree.node.external = package.png +icon.plugin.calltree.data = D.gif icon.plugin.checksum.compute = icon.refresh icon.plugin.checksum.select = NextSelectionBlock16.gif diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CallTreePlugin/Call_Tree_Plugin.html b/Ghidra/Features/Base/src/main/help/help/topics/CallTreePlugin/Call_Tree_Plugin.html index 35f180791b..a362ebff7e 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/CallTreePlugin/Call_Tree_Plugin.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/CallTreePlugin/Call_Tree_Plugin.html @@ -22,13 +22,14 @@
-

The Call Tree Plugin shows all caller functions and all functions called for +

The Call Tree Plugin shows all callers and all callees for the current function (also known as the main function), which is the function that contains the cursor location in the Listing. You can expand the nodes in each - of the trees to show more information about these referenced functions. This plugin can be - used to gain a quick context of a given function by seeing incoming and outgoing function - calls.

+ "help/topics/CodeBrowserPlugin/CodeBrowser.htm">Listing. Functions which reference + or are referenced by the main function are also shown. You can expand the nodes in each of the + trees to show more information about these other functions. This plugin can be used to + gain a quick context of a given function by seeing incoming and outgoing function calls and + references.

You can launch this component by clicking the @@ -63,17 +64,21 @@

-

The Incoming Calls tree shows all caller - functions. Each node in the tree has the incoming function call icon (). Expanding a node in this tree shows all functions that - are callers of the expanded node. Nodes may be expanded recursively to any depth desired.

+

The Incoming tree shows all functions calling + and/or referencing the main function. Each node in the tree has the incoming function + icon () combined with + () for calls and + () for other reference types. Expanding a + node in this tree shows all functions that call or reference the function corresponding + to the expanded node. Nodes may be expanded recursively to any depth desired.

-

The Outgoing Calls tree shows all functions - called. Nodes with the outgoing function call icon - () represent functions - called by the current function that also call other - functions. Expanding a node in this tree shows all functions that are called by the - expanded node. Nodes may be expanded recursively to any depth desired.

+

The Outgoing tree shows all functions + called or referenced by the main function. Each node in the tree has the outgoing function + icon () combined with + () for calls and + () for other reference types. Expanding a node in this + tree shows all functions that are called or referenced by the expanded node. Nodes may be + expanded recursively to any depth desired.

@@ -207,9 +212,9 @@

The button, when toggled on, will cause selections in the tree to navigate to the source of the selected - function call within the main function. To navigate to the destination of a given - function call, you can use the Go To - Call Destination action. No navigation will happen when toggled off.

+ function within the main function. To navigate to the destination of a given + function, you can use the Go To + Destination action. No navigation will happen when toggled off.

@@ -254,36 +259,37 @@ -

The Select Call +

The Select Source action will make a program selection for the node(s) selected in the tree where the context menu was activated. - When activated from the incoming call tree, the caller - function's entry point will be selected. When activated from the outgoing call tree the address of the containing the call - instruction will be selected.

+ When activated from the incoming call tree, the calling + or referencing function's entry point will be selected. When activated from the outgoing call tree the address of the instruction containing the + call or reference will be selected.

The Select - Call Destination will make a program + Destination will make a program selection for the node(s) selected in the outgoing tree. The selection will be the - called function's entry point.

+ called or referenced function's entry point.

-

The Go To Call +

The Go To Source action will navigate the Listing to the address of the selected node in the tree where the context menu was activated. When activated from the incoming call tree, the Listing will be navigated to the caller - function's entry point. When activated from the outgoing - call tree the Listing will be navigated to the address of the containing the call - instruction.

+ "#Incoming_Call_Tree">incoming call tree, the Listing will be navigated to the calling + or referencing function's entry point. When activated from the + outgoing call tree the Listing will be navigated to + the address of the instruction containg the call or reference.

-

The Go To Call - Destination will navigate the Listing to the entry point of the called function.

+

The Go To + Destination will navigate the Listing to the entry point of the appropriate function. +

The - Show Call Tree For Function will load function for the selected node into - the current Call Tree window. + Show Call Tree for (Function Name) will load the function for the selected node + into the current Call Tree window.

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java index c1ecea33d7..3074b5abd7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java @@ -23,15 +23,16 @@ import org.apache.commons.collections4.map.LazyMap; import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeSlowLoadingNode; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.Program; -import ghidra.program.model.symbol.Reference; -import ghidra.program.model.symbol.ReferenceManager; import ghidra.program.util.ProgramLocation; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +/** + * In general, a CallNode represents a function and its relationship (either a call reference or + * a data reference) to the function of its parent node + */ public abstract class CallNode extends GTreeSlowLoadingNode { protected CallTreeOptions callTreeOptions; @@ -40,6 +41,9 @@ public abstract class CallNode extends GTreeSlowLoadingNode { /** Used to signal that this node has been marked for replacement */ protected boolean invalid = false; + /** Indicates whether the associated reference is a call reference **/ + protected boolean isCallRef = false; + public CallNode(CallTreeOptions callTreeOptions) { this.callTreeOptions = Objects.requireNonNull(callTreeOptions); } @@ -72,38 +76,53 @@ public abstract class CallNode extends GTreeSlowLoadingNode { */ abstract CallNode recreate(); - protected Set getReferencesFrom(Program program, AddressSetView addresses, - TaskMonitor monitor) throws CancelledException { - Set set = new HashSet<>(); - ReferenceManager referenceManager = program.getReferenceManager(); - AddressIterator addressIterator = addresses.getAddresses(true); - while (addressIterator.hasNext()) { - monitor.checkCancelled(); - Address address = addressIterator.next(); - Reference[] referencesFrom = referenceManager.getReferencesFrom(address); - if (referencesFrom != null) { - for (Reference reference : referencesFrom) { - set.add(reference); + + @Override + public String getToolTip() { + String refString = isCallRef ? "Called from " : "Referenced from "; + return refString + getSourceAddress(); + } + + protected void addNode(LazyMap> nodesByFunction, CallNode nodeToAdd) { + + Function function = nodeToAdd.getRemoteFunction(); + List nodes = nodesByFunction.get(function); + + GTreeNode nodeToRemove = null; + for (GTreeNode node : nodes) { + if (node.equals(nodeToAdd)) { + return; // never add equal() nodes + } + // don't allow a call reference and a non-call node to the same remote function + // at the same address + CallNode callNode = (CallNode) node; + if (nodeToAdd.isCallRef != callNode.isCallRef) { + if (Objects.equals(nodeToAdd.getSourceAddress(), callNode.getSourceAddress())) { + if (Objects.equals(nodeToAdd.getRemoteFunction(), + callNode.getRemoteFunction())) { + if (nodeToAdd.isCallRef) { + return; // don't replace a call node with a non-call node + } + // add the call node and remove the non-call node + nodeToRemove = callNode; + break; + } } } } - return set; - } - - protected void addNode(LazyMap> nodesByFunction, CallNode node) { - - Function function = node.getRemoteFunction(); - List nodes = nodesByFunction.get(function); - if (nodes.contains(node)) { - return; // never add equal() nodes + if (nodeToRemove != null) { + nodes.remove(nodeToRemove); + nodes.add(nodeToAdd); + return; } if (callTreeOptions.allowsDuplicates()) { - nodes.add(node); // ok to add multiple nodes for this function with different addresses + nodes.add(nodeToAdd); // ok to add multiple nodes for this function with different addresses + return; } if (nodes.isEmpty()) { - nodes.add(node); // no duplicates allowed; only add if this is the only node + nodes.add(nodeToAdd); // no duplicates allowed; only add if this is the only node return; } @@ -112,7 +131,14 @@ public abstract class CallNode extends GTreeSlowLoadingNode { protected class CallNodeComparator implements Comparator { @Override public int compare(GTreeNode o1, GTreeNode o2) { - return ((CallNode) o1).getSourceAddress().compareTo(((CallNode) o2).getSourceAddress()); + CallNode node1 = (CallNode) o1; + CallNode node2 = (CallNode) o2; + int addrCompare = node1.getSourceAddress().compareTo(node2.getSourceAddress()); + if (addrCompare != 0) { + return addrCompare; + } + return Boolean.compare(node1.isCallRef, node2.isCallRef); + } } @@ -163,6 +189,9 @@ public abstract class CallNode extends GTreeSlowLoadingNode { if (!Objects.equals(getSourceAddress(), other.getSourceAddress())) { return false; } + if (other.isCallRef != isCallRef) { + return false; + } return Objects.equals(getRemoteFunction(), other.getRemoteFunction()); } @@ -170,6 +199,7 @@ public abstract class CallNode extends GTreeSlowLoadingNode { public int hashCode() { final int prime = 31; int result = super.hashCode(); + result = prime * result + Boolean.hashCode(isCallRef); Function function = getRemoteFunction(); result = prime * result + ((function == null) ? 0 : function.hashCode()); Address sourceAddress = getSourceAddress(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java index 89e7ae4aff..c4c396c581 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java @@ -42,8 +42,11 @@ import ghidra.util.HelpLocation; import resources.Icons; /** - * Assuming a function foo, this plugin will show all callers of foo and all - * calls to other functions made by foo. + * Assuming a function foo, this plugin will show: + * 1) all callers of foo + * 2) all functions which reference foo + * 3) all callees of foo + * 4) all functions referenced by foo. */ //@formatter:off @PluginInfo( @@ -51,10 +54,10 @@ import resources.Icons; packageName = CorePluginPackage.NAME, category = PluginCategoryNames.GRAPH, shortDescription = "Call Trees Plugin", - description = "This plugin shows incoming and outgoing calls for a given function. " + - "More specifically, one tree of the plugin will show all callers of the " + - "function and the other tree of the plugin will show all calls made " + - "by the function" + description = "This plugin shows incoming and outgoing calls and function references " + + "for a given function foo. More specifically, one tree of the plugin will show all " + + "callers and function referring to foo and the other tree of the plugin will show " + + "all calls and references to functions made by foo." ) //@formatter:on public class CallTreePlugin extends ProgramPlugin { @@ -62,6 +65,7 @@ public class CallTreePlugin extends ProgramPlugin { static final Icon PROVIDER_ICON = Icons.ARROW_DOWN_RIGHT_ICON; static final Icon FUNCTION_ICON = new GIcon("icon.plugin.calltree.function"); static final Icon RECURSIVE_ICON = new GIcon("icon.plugin.calltree.recursive"); + static final Icon DATA_ICON = new GIcon("icon.plugin.calltree.data"); private List providers = new ArrayList<>(); private DockingAction showCallTreeFromMenuAction; @@ -167,14 +171,14 @@ public class CallTreePlugin extends ProgramPlugin { tool.addAction(showCallTreeFromMenuAction); } - private void creatAndShowProvider() { + private void createAndShowProvider(ProgramLocation location) { CallTreeProvider provider = new CallTreeProvider(this, false); CallTreeOptions callTreeOptions = primaryProvider.getCallTreeOptions(); provider.setCallTreeOptions(callTreeOptions); providers.add(provider); - provider.initialize(currentProgram, currentLocation); + provider.initialize(currentProgram, location); tool.showComponentProvider(provider, true); } @@ -219,7 +223,7 @@ public class CallTreePlugin extends ProgramPlugin { return; } - creatAndShowProvider(); + createAndShowProvider(location); } Function getFunction(ProgramLocation location) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java index 8ee6fca8e6..e07785ba5f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java @@ -62,6 +62,7 @@ public class CallTreeProvider extends ComponentProviderAdapter { private static final Icon EMPTY_ICON = Icons.EMPTY_ICON; private static final Icon EXPAND_ICON = Icons.EXPAND_ALL_ICON; private static final Icon COLLAPSE_ICON = Icons.COLLAPSE_ALL_ICON; + private static final String SHOW_CALL_TREE_MENU_PREFIX = "Show Call Tree for "; private static Icon REFRESH_ICON = new GIcon("icon.plugin.calltree.refresh"); private static Icon REFRESH_NOT_NEEDED_ICON = @@ -295,7 +296,7 @@ public class CallTreeProvider extends ComponentProviderAdapter { } }; goToDestinationAction.setPopupMenuData( - new MenuData(new String[] { "Go To Call Destination" }, goToMenu)); + new MenuData(new String[] { "Go To Destination" }, goToMenu)); goToDestinationAction.setHelpLocation( new HelpLocation(plugin.getName(), "Call_Tree_Context_Action_Goto_Destination")); tool.addLocalAction(this, goToDestinationAction); @@ -355,7 +356,7 @@ public class CallTreeProvider extends ComponentProviderAdapter { } }; goToSourceAction - .setPopupMenuData(new MenuData(new String[] { "Go To Call Source" }, goToMenu)); + .setPopupMenuData(new MenuData(new String[] { "Go To Source" }, goToMenu)); goToSourceAction.setHelpLocation( new HelpLocation(plugin.getName(), "Call_Tree_Context_Action_Goto_Source")); tool.addLocalAction(this, goToSourceAction); @@ -512,7 +513,7 @@ public class CallTreeProvider extends ComponentProviderAdapter { }; Icon icon = new GIcon("icon.plugin.calltree.filter.select.source"); selectSourceAction.setPopupMenuData( - new MenuData(new String[] { "Select Call Source" }, icon, selectionMenuGroup)); + new MenuData(new String[] { "Select Source" }, icon, selectionMenuGroup)); selectSourceAction.setHelpLocation( new HelpLocation(plugin.getName(), "Call_Tree_Context_Action_Select_Source")); tool.addLocalAction(this, selectSourceAction); @@ -567,7 +568,7 @@ public class CallTreeProvider extends ComponentProviderAdapter { } }; selectDestinationAction.setPopupMenuData( - new MenuData(new String[] { "Select Call Destination" }, icon, selectionMenuGroup)); + new MenuData(new String[] { "Select Destination" }, icon, selectionMenuGroup)); selectDestinationAction.setHelpLocation( new HelpLocation(plugin.getName(), "Call_Tree_Context_Action_Select_Destination")); tool.addLocalAction(this, selectDestinationAction); @@ -616,22 +617,8 @@ public class CallTreeProvider extends ComponentProviderAdapter { GTree gTree = (GTree) context.getContextObject(); TreePath[] selectionPaths = gTree.getSelectionPaths(); CallNode callNode = (CallNode) selectionPaths[0].getLastPathComponent(); - - ProgramLocation location = null; - if (gTree == incomingTree) { - location = new ProgramLocation(currentProgram, callNode.getSourceAddress()); - - // in-place call tree - // doSetLocation(new ProgramLocation(currentProgram, - // callNode.getSourceAddress())); - } - else { // outgoing - location = callNode.getLocation(); - - // in-place call tree - // doSetLocation(callNode.getLocation()); - } - + ProgramLocation location = new ProgramLocation(currentProgram, + callNode.getRemoteFunction().getEntryPoint()); plugin.showOrCreateNewCallTree(location); } @@ -648,7 +635,19 @@ public class CallTreeProvider extends ComponentProviderAdapter { GTree gTree = (GTree) contextObject; TreePath[] selectionPaths = gTree.getSelectionPaths(); - return selectionPaths.length == 1; + if (selectionPaths.length != 1) { + return false; + } + CallNode node = ((CallNode) selectionPaths[0].getLastPathComponent()); + // remoteFunction can be null for dead-end nodes + Function remoteFunction = node.getRemoteFunction(); + String menuName = + SHOW_CALL_TREE_MENU_PREFIX + + (remoteFunction == null ? "" : remoteFunction.getName()); + MenuData data = getPopupMenuData().cloneData(); + data.setMenuPath(new String[] { menuName }); + setPopupMenuData(data); + return remoteFunction != null; } @Override @@ -675,7 +674,7 @@ public class CallTreeProvider extends ComponentProviderAdapter { }; newCallTree.setHelpLocation(new HelpLocation(plugin.getName(), "Call_Tree_Context_Action_Show_Call_Tree_For_Function")); - newCallTree.setPopupMenuData(new MenuData(new String[] { "Show Call Tree For Function" }, + newCallTree.setPopupMenuData(new MenuData(new String[] { SHOW_CALL_TREE_MENU_PREFIX }, CallTreePlugin.PROVIDER_ICON, newTreeMenu)); newCallTree.setDescription( "Show the Function Call Tree window for the function " + "selected in the call tree"); @@ -844,7 +843,7 @@ public class CallTreeProvider extends ComponentProviderAdapter { private JPanel createTreePanel(boolean isIncoming, GTree tree) { JPanel panel = new JPanel(new BorderLayout()); - panel.add(new GLabel(isIncoming ? "Incoming Calls" : "Outgoing Calls"), BorderLayout.NORTH); + panel.add(new GLabel(isIncoming ? "Incoming" : "Outgoing"), BorderLayout.NORTH); panel.add(tree, BorderLayout.CENTER); return panel; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/DeadEndNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/DeadEndNode.java index 59cec189c7..1a6dac4298 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/DeadEndNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/DeadEndNode.java @@ -29,6 +29,8 @@ import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +import resources.MultiIcon; +import resources.icons.TranslateIcon; public class DeadEndNode extends CallNode { @@ -36,6 +38,7 @@ public class DeadEndNode extends CallNode { private final Reference reference; private String name; + private MultiIcon deadEndIcon; private final Program program; @@ -43,6 +46,12 @@ public class DeadEndNode extends CallNode { super(callTreeOptions); this.program = program; this.reference = reference; + this.isCallRef = reference.getReferenceType().isCall(); + deadEndIcon = new MultiIcon(ICON, false, 32, 16); + TranslateIcon translateIcon = + isCallRef ? new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0) + : new TranslateIcon(CallTreePlugin.DATA_ICON, 16, 0); + deadEndIcon.addIcon(translateIcon); } @Override @@ -71,7 +80,7 @@ public class DeadEndNode extends CallNode { @Override public Icon getIcon(boolean expanded) { - return ICON; + return deadEndIcon; } @Override @@ -90,11 +99,6 @@ public class DeadEndNode extends CallNode { return name; } - @Override - public String getToolTip() { - return "Called from " + reference.getFromAddress(); - } - @Override public boolean isLeaf() { return true; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/ExternalCallNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/ExternalCallNode.java index bf38ab826d..37390e1812 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/ExternalCallNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/ExternalCallNode.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. @@ -34,25 +34,26 @@ import resources.icons.TranslateIcon; public class ExternalCallNode extends CallNode { private static final Icon EXTERNAL_ICON = new GIcon("icon.plugin.calltree.node.external"); - private final Icon EXTERNAL_FUNCTION_ICON; - private final Icon baseIcon; + private MultiIcon externalFunctionIcon; private final Function function; private final Address sourceAddress; private final String name; - ExternalCallNode(Function function, Address sourceAddress, Icon baseIcon, - CallTreeOptions callTreeOptions) { + ExternalCallNode(Function function, Address sourceAddress, + boolean isCallRef, CallTreeOptions callTreeOptions) { super(callTreeOptions); this.function = function; this.sourceAddress = sourceAddress; this.name = function.getName(); - this.baseIcon = baseIcon; + this.isCallRef = isCallRef; + + externalFunctionIcon = new MultiIcon(EXTERNAL_ICON, false, 32, 16); + TranslateIcon translateIcon = + isCallRef ? new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0) + : new TranslateIcon(CallTreePlugin.DATA_ICON, 16, 0); + externalFunctionIcon.addIcon(translateIcon); - MultiIcon outgoingFunctionIcon = new MultiIcon(EXTERNAL_ICON, false, 32, 16); - TranslateIcon translateIcon = new TranslateIcon(baseIcon, 16, 0); - outgoingFunctionIcon.addIcon(translateIcon); - EXTERNAL_FUNCTION_ICON = outgoingFunctionIcon; } @Override @@ -62,7 +63,7 @@ public class ExternalCallNode extends CallNode { @Override CallNode recreate() { - return new ExternalCallNode(function, sourceAddress, baseIcon, callTreeOptions); + return new ExternalCallNode(function, sourceAddress, isCallRef, callTreeOptions); } @Override @@ -87,7 +88,7 @@ public class ExternalCallNode extends CallNode { @Override public Icon getIcon(boolean expanded) { - return EXTERNAL_FUNCTION_ICON; + return externalFunctionIcon; } @Override @@ -97,7 +98,7 @@ public class ExternalCallNode extends CallNode { @Override public String getToolTip() { - return "External Call - called from " + sourceAddress; + return "(External) " + super.getToolTip(); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallNode.java index b3aed50701..b1473d24c7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallNode.java @@ -23,9 +23,10 @@ import javax.swing.Icon; import org.apache.commons.collections4.map.LazyMap; import docking.widgets.tree.GTreeNode; -import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceIterator; import ghidra.program.util.FunctionSignatureFieldLocation; import ghidra.program.util.ProgramLocation; import ghidra.util.exception.CancelledException; @@ -46,7 +47,7 @@ public class IncomingCallNode extends CallNode { protected String name; private final Address sourceAddress; - IncomingCallNode(Program program, Function function, Address sourceAddress, + IncomingCallNode(Program program, Function function, Address sourceAddress, boolean isCallRef, CallTreeOptions callTreeOptions) { super(callTreeOptions); this.program = program; @@ -54,16 +55,19 @@ public class IncomingCallNode extends CallNode { this.name = function.getName(callTreeOptions.showNamespace()); this.sourceAddress = sourceAddress; this.functionAddress = function.getEntryPoint(); + this.isCallRef = isCallRef; MultiIcon multiIcon = new MultiIcon(INCOMING_ICON, false, 32, 16); - TranslateIcon translateIcon = new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0); + TranslateIcon translateIcon = + isCallRef ? new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0) + : new TranslateIcon(CallTreePlugin.DATA_ICON, 16, 0); multiIcon.addIcon(translateIcon); incomingFunctionIcon = multiIcon; } @Override CallNode recreate() { - return new IncomingCallNode(program, function, sourceAddress, callTreeOptions); + return new IncomingCallNode(program, function, sourceAddress, isCallRef, callTreeOptions); } @Override @@ -90,29 +94,29 @@ public class IncomingCallNode extends CallNode { private void doGenerateChildren(Address address, List results, TaskMonitor monitor) throws CancelledException { - FunctionSignatureFieldLocation location = - new FunctionSignatureFieldLocation(program, address); - - Set
addresses = ReferenceUtils.getReferenceAddresses(location, monitor); + ReferenceIterator refIter = program.getReferenceManager().getReferencesTo(address); LazyMap> nodesByFunction = LazyMap.lazyMap(new HashMap<>(), k -> new ArrayList<>()); FunctionManager functionManager = program.getFunctionManager(); - for (Address fromAddress : addresses) { + while (refIter.hasNext()) { monitor.checkCancelled(); - Function callerFunction = functionManager.getFunctionContaining(fromAddress); - if (callerFunction == null) { + Reference ref = refIter.next(); + Address fromAddress = ref.getFromAddress(); + Function caller = functionManager.getFunctionContaining(fromAddress); + if (caller == null) { continue; } // If we are not showing thunks, then replace each thunk with all calls to that thunk - if (callerFunction.isThunk() && !callTreeOptions.allowsThunks()) { - Address callerEntry = callerFunction.getEntryPoint(); + if (caller.isThunk() && !callTreeOptions.allowsThunks()) { + Address callerEntry = caller.getEntryPoint(); doGenerateChildren(callerEntry, results, monitor); continue; } IncomingCallNode node = - new IncomingCallNode(program, callerFunction, fromAddress, callTreeOptions); + new IncomingCallNode(program, caller, fromAddress, ref.getReferenceType().isCall(), + callTreeOptions); addNode(nodesByFunction, node); } @@ -133,7 +137,12 @@ public class IncomingCallNode extends CallNode { if (icon == null) { icon = incomingFunctionIcon; if (functionIsInPath()) { - icon = CallTreePlugin.RECURSIVE_ICON; + MultiIcon multiIcon = new MultiIcon(CallTreePlugin.RECURSIVE_ICON, false, 32, 16); + TranslateIcon translateIcon = + isCallRef ? new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0) + : new TranslateIcon(CallTreePlugin.DATA_ICON, 16, 0); + multiIcon.addIcon(translateIcon); + icon = multiIcon; } } return icon; @@ -144,11 +153,6 @@ public class IncomingCallNode extends CallNode { return name; } - @Override - public String getToolTip() { - return null; - } - @Override public boolean isLeaf() { return false; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallsRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallsRootNode.java index ca3c36da66..a037b78b31 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallsRootNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/IncomingCallsRootNode.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,7 @@ public class IncomingCallsRootNode extends IncomingCallNode { IncomingCallsRootNode(Program program, Function function, Address sourceAddress, CallTreeOptions callTreeOptions) { - super(program, function, sourceAddress, callTreeOptions); + super(program, function, sourceAddress, false, callTreeOptions); name = function.getName(); } @@ -43,4 +43,9 @@ public class IncomingCallsRootNode extends IncomingCallNode { public String getName() { return "Incoming References - " + name; } + + @Override + public String getToolTip() { + return null; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallNode.java index e104b8193d..4a3b88df48 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallNode.java @@ -24,8 +24,7 @@ import javax.swing.tree.TreePath; import org.apache.commons.collections4.map.LazyMap; import docking.widgets.tree.GTreeNode; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.FunctionSignatureFieldLocation; @@ -48,22 +47,24 @@ public class OutgoingCallNode extends CallNode { private final Address sourceAddress; OutgoingCallNode(Program program, Function function, Address sourceAddress, - CallTreeOptions callTreeOptions) { + boolean isCallRef, CallTreeOptions callTreeOptions) { super(callTreeOptions); this.program = program; this.function = function; this.name = function.getName(callTreeOptions.showNamespace()); this.sourceAddress = sourceAddress; + this.isCallRef = isCallRef; MultiIcon multiIcon = new MultiIcon(OUTGOING_ICON, false, 32, 16); - TranslateIcon translateIcon = new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0); + TranslateIcon translateIcon = new TranslateIcon( + isCallRef ? CallTreePlugin.FUNCTION_ICON : CallTreePlugin.DATA_ICON, 16, 0); multiIcon.addIcon(translateIcon); outgoingFunctionIcon = multiIcon; } @Override CallNode recreate() { - return new OutgoingCallNode(program, function, sourceAddress, callTreeOptions); + return new OutgoingCallNode(program, function, sourceAddress, isCallRef, callTreeOptions); } @Override @@ -88,33 +89,35 @@ public class OutgoingCallNode extends CallNode { FunctionManager fm = program.getFunctionManager(); Function currentFunction = fm.getFunctionContaining(address); - AddressSetView functionBody = currentFunction.getBody(); - Address entryPoint = currentFunction.getEntryPoint(); - Set references = getReferencesFrom(program, functionBody, monitor); LazyMap> nodesByFunction = LazyMap.lazyMap(new HashMap<>(), k -> new ArrayList<>()); FunctionManager functionManager = program.getFunctionManager(); - for (Reference reference : references) { - monitor.checkCancelled(); - Address toAddress = reference.getToAddress(); - if (toAddress.equals(entryPoint)) { - continue; // recursive - } + ReferenceManager refManager = program.getReferenceManager(); - Function calledFunction = functionManager.getFunctionAt(toAddress); - if (calledFunction == null) { + AddressRangeIterator rangeIter = currentFunction.getBody().getAddressRanges(); + while (rangeIter.hasNext()) { + AddressRange range = rangeIter.next(); + ReferenceIterator refIter =refManager.getReferenceIterator(range.getMinAddress()); + while (refIter.hasNext()) { + monitor.checkCancelled(); + Reference reference = refIter.next(); + if (!range.contains(reference.getFromAddress())) { + break; // go to next AddressRange + } + Address toAddress = reference.getToAddress(); + Function calledFunction = functionManager.getFunctionAt(toAddress); + if (calledFunction == null) { + createNode(nodesByFunction, reference, calledFunction); + continue; + } + // If we are not showing thunks, then replace the thunk with the thunked function + if (calledFunction.isThunk() && !callTreeOptions.allowsThunks()) { + Function thunkedFunction = calledFunction.getThunkedFunction(true); + createNode(nodesByFunction, reference, thunkedFunction); + continue; + } createNode(nodesByFunction, reference, calledFunction); - continue; } - - // If we are not showing thunks, then replace the thunk with the thunked function - if (calledFunction.isThunk() && !callTreeOptions.allowsThunks()) { - Function thunkedFunction = calledFunction.getThunkedFunction(true); - createNode(nodesByFunction, reference, thunkedFunction); - continue; - } - - createNode(nodesByFunction, reference, calledFunction); } List children = nodesByFunction.values() @@ -130,12 +133,14 @@ public class OutgoingCallNode extends CallNode { if (calledFunction != null) { if (isExternalCall(calledFunction)) { CallNode node = new ExternalCallNode(calledFunction, fromAddress, - CallTreePlugin.FUNCTION_ICON, callTreeOptions); + reference.getReferenceType().isCall(), callTreeOptions); addNode(nodes, node); } else { addNode(nodes, - new OutgoingCallNode(program, calledFunction, fromAddress, callTreeOptions)); + new OutgoingCallNode(program, calledFunction, fromAddress, + reference.getReferenceType().isCall(), + callTreeOptions)); } } else if (isCallReference(reference)) { @@ -143,7 +148,7 @@ public class OutgoingCallNode extends CallNode { Function externalFunction = getExternalFunctionTempHackWorkaround(reference); if (externalFunction != null) { CallNode node = new ExternalCallNode(externalFunction, fromAddress, - CallTreePlugin.FUNCTION_ICON, callTreeOptions); + reference.getReferenceType().isCall(), callTreeOptions); addNode(nodes, node); } else { @@ -231,7 +236,12 @@ public class OutgoingCallNode extends CallNode { if (icon == null) { icon = outgoingFunctionIcon; if (functionIsInPath()) { - icon = CallTreePlugin.RECURSIVE_ICON; + MultiIcon multiIcon = new MultiIcon(CallTreePlugin.RECURSIVE_ICON, false, 32, 16); + TranslateIcon translateIcon = + isCallRef ? new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0) + : new TranslateIcon(CallTreePlugin.DATA_ICON, 16, 0); + multiIcon.addIcon(translateIcon); + icon = multiIcon; } } return icon; @@ -255,11 +265,6 @@ public class OutgoingCallNode extends CallNode { return name; } - @Override - public String getToolTip() { - return "Called from " + sourceAddress; - } - @Override public boolean isLeaf() { return false; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallsRootNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallsRootNode.java index df9aeb9e7f..565c4f4ba4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallsRootNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/OutgoingCallsRootNode.java @@ -25,7 +25,7 @@ public class OutgoingCallsRootNode extends OutgoingCallNode { OutgoingCallsRootNode(Program program, Function function, Address sourceAddress, CallTreeOptions callTreeOptions) { - super(program, function, sourceAddress, callTreeOptions); + super(program, function, sourceAddress, false, callTreeOptions); } @Override @@ -52,4 +52,5 @@ public class OutgoingCallsRootNode extends OutgoingCallNode { public String getToolTip() { return null; } + } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/IncomingCallNodeTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/IncomingCallNodeTest.java index ef697ce6f0..40d3467f68 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/IncomingCallNodeTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/IncomingCallNodeTest.java @@ -67,7 +67,8 @@ public class IncomingCallNodeTest extends AbstractGenericTest { callTreeOptions = callTreeOptions.withRecurseDepth(5); callTreeOptions = callTreeOptions.withFilterThunks(hideThunks); node1 = - new IncomingCallNode(program, firstCalledFunction, calledFromAddress, callTreeOptions); + new IncomingCallNode(program, firstCalledFunction, calledFromAddress, true, + callTreeOptions); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/OutgoingCallNodeTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/OutgoingCallNodeTest.java index 355eacd751..cbd65bfbbe 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/OutgoingCallNodeTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/OutgoingCallNodeTest.java @@ -59,7 +59,8 @@ public class OutgoingCallNodeTest extends AbstractGenericTest { CallTreeOptions callTreeOptions = new CallTreeOptions(); callTreeOptions = callTreeOptions.withRecurseDepth(5); node1 = - new OutgoingCallNode(program, firstCalledFunction, calledFromAddress, callTreeOptions); + new OutgoingCallNode(program, firstCalledFunction, calledFromAddress, true, + callTreeOptions); } @After @@ -73,7 +74,18 @@ public class OutgoingCallNodeTest extends AbstractGenericTest { builder.createMemoryCallReference(firstCalledFunctionAddress, firstCalledFunctionAddress); List children = node1.generateChildren(TaskMonitor.DUMMY); - assertTrue(children.isEmpty()); + assertEquals(1, children.size()); + + } + + @Test + public void testGenerateChildren_SelfRecursiveReference() throws Exception { + + builder.createMemoryReadReference(firstCalledFunctionAddress, firstCalledFunctionAddress); + + List children = node1.generateChildren(TaskMonitor.DUMMY); + assertEquals(1, children.size()); + } @Test @@ -456,7 +468,8 @@ public class OutgoingCallNodeTest extends AbstractGenericTest { callTreeOptions = callTreeOptions.withRecurseDepth(5); callTreeOptions = callTreeOptions.withFilterThunks(true); node1 = - new OutgoingCallNode(program, firstCalledFunction, calledFromAddress, callTreeOptions); + new OutgoingCallNode(program, firstCalledFunction, calledFromAddress, true, + callTreeOptions); }