GP-4933 - Fixed function call trees incorrectly dropping thunks

This commit is contained in:
dragonmacher 2024-09-18 12:26:22 -04:00 committed by ghidragon
parent 9a04ea643a
commit 1cadb4a26f
12 changed files with 828 additions and 444 deletions

View File

@ -93,12 +93,6 @@ public abstract class CallNode extends GTreeSlowLoadingNode {
protected void addNode(LazyMap<Function, List<GTreeNode>> nodesByFunction, CallNode node) {
Function function = node.getRemoteFunction();
if (function != null && function.isThunk()) {
if (!callTreeOptions.allowsThunks()) {
return;
}
}
List<GTreeNode> nodes = nodesByFunction.get(function);
if (nodes.contains(node)) {
return; // never add equal() nodes

View File

@ -860,6 +860,7 @@ public class CallTreeProvider extends ComponentProviderAdapter {
}
};
tree.setPaintHandlesForLeafNodes(false);
tree.setDoubleClickExpansionEnabled(false); // reserve double-click for navigation
// tree.setFilterVisible(false);
return tree;
}

View File

@ -65,6 +65,10 @@ public class DeadEndNode extends CallNode {
return reference.getFromAddress();
}
public Address getRemoteAddress() {
return reference.getToAddress();
}
@Override
public Icon getIcon(boolean expanded) {
return ICON;

View File

@ -36,8 +36,8 @@ import resources.icons.TranslateIcon;
public class IncomingCallNode extends CallNode {
private Icon INCOMING_ICON = Icons.ARROW_UP_LEFT_ICON;
private Icon INCOMING_FUNCTION_ICON;
private static final Icon INCOMING_ICON = Icons.ARROW_UP_LEFT_ICON;
private Icon incomingFunctionIcon;
private Icon icon = null;
private final Address functionAddress;
@ -55,10 +55,10 @@ public class IncomingCallNode extends CallNode {
this.sourceAddress = sourceAddress;
this.functionAddress = function.getEntryPoint();
MultiIcon incomingFunctionIcon = new MultiIcon(INCOMING_ICON, false, 32, 16);
MultiIcon multiIcon = new MultiIcon(INCOMING_ICON, false, 32, 16);
TranslateIcon translateIcon = new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0);
incomingFunctionIcon.addIcon(translateIcon);
INCOMING_FUNCTION_ICON = incomingFunctionIcon;
multiIcon.addIcon(translateIcon);
incomingFunctionIcon = multiIcon;
}
@Override
@ -79,8 +79,19 @@ public class IncomingCallNode extends CallNode {
@Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
List<GTreeNode> children = new ArrayList<>();
doGenerateChildren(functionAddress, children, monitor);
Collections.sort(children, new CallNodeComparator());
return children;
}
private void doGenerateChildren(Address address, List<GTreeNode> results, TaskMonitor monitor)
throws CancelledException {
FunctionSignatureFieldLocation location =
new FunctionSignatureFieldLocation(program, functionAddress);
new FunctionSignatureFieldLocation(program, address);
Set<Address> addresses = ReferenceUtils.getReferenceAddresses(location, monitor);
LazyMap<Function, List<GTreeNode>> nodesByFunction =
@ -93,6 +104,13 @@ public class IncomingCallNode extends CallNode {
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();
doGenerateChildren(callerEntry, results, monitor);
continue;
}
IncomingCallNode node =
new IncomingCallNode(program, callerFunction, fromAddress, callTreeOptions);
addNode(nodesByFunction, node);
@ -102,9 +120,7 @@ public class IncomingCallNode extends CallNode {
.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
Collections.sort(children, new CallNodeComparator());
return children;
results.addAll(children);
}
@Override
@ -115,7 +131,7 @@ public class IncomingCallNode extends CallNode {
@Override
public Icon getIcon(boolean expanded) {
if (icon == null) {
icon = INCOMING_FUNCTION_ICON;
icon = incomingFunctionIcon;
if (functionIsInPath()) {
icon = CallTreePlugin.RECURSIVE_ICON;
}

View File

@ -36,31 +36,34 @@ import resources.Icons;
import resources.MultiIcon;
import resources.icons.TranslateIcon;
public abstract class OutgoingCallNode extends CallNode {
public class OutgoingCallNode extends CallNode {
private static final Icon OUTGOING_ICON = Icons.ARROW_DOWN_RIGHT_ICON;
private final Icon OUTGOING_FUNCTION_ICON;
private final Icon outgoingFunctionIcon;
private Icon icon = null;
protected final Program program;
protected final Function function;
protected String name;
private final Address sourceAddress;
private final Icon baseIcon;
OutgoingCallNode(Program program, Function function, Address sourceAddress, Icon baseIcon,
OutgoingCallNode(Program program, Function function, Address sourceAddress,
CallTreeOptions callTreeOptions) {
super(callTreeOptions);
this.program = program;
this.function = function;
this.name = function.getName(callTreeOptions.showNamespace());
this.sourceAddress = sourceAddress;
this.baseIcon = baseIcon;
MultiIcon outgoingFunctionIcon = new MultiIcon(OUTGOING_ICON, false, 32, 16);
TranslateIcon translateIcon = new TranslateIcon(baseIcon, 16, 0);
outgoingFunctionIcon.addIcon(translateIcon);
OUTGOING_FUNCTION_ICON = outgoingFunctionIcon;
MultiIcon multiIcon = new MultiIcon(OUTGOING_ICON, false, 32, 16);
TranslateIcon translateIcon = new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0);
multiIcon.addIcon(translateIcon);
outgoingFunctionIcon = multiIcon;
}
@Override
CallNode recreate() {
return new OutgoingCallNode(program, function, sourceAddress, callTreeOptions);
}
@Override
@ -70,8 +73,23 @@ public abstract class OutgoingCallNode extends CallNode {
@Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
AddressSetView functionBody = function.getBody();
Address entryPoint = function.getEntryPoint();
List<GTreeNode> children = new ArrayList<>();
Address calledEntry = function.getEntryPoint();
doGenerateChildren(calledEntry, children, monitor);
Collections.sort(children, new CallNodeComparator());
return children;
}
private void doGenerateChildren(Address address, List<GTreeNode> results, TaskMonitor monitor)
throws CancelledException {
FunctionManager fm = program.getFunctionManager();
Function currentFunction = fm.getFunctionContaining(address);
AddressSetView functionBody = currentFunction.getBody();
Address entryPoint = currentFunction.getEntryPoint();
Set<Reference> references = getReferencesFrom(program, functionBody, monitor);
LazyMap<Function, List<GTreeNode>> nodesByFunction =
LazyMap.lazyMap(new HashMap<>(), k -> new ArrayList<>());
@ -80,10 +98,22 @@ public abstract class OutgoingCallNode extends CallNode {
monitor.checkCancelled();
Address toAddress = reference.getToAddress();
if (toAddress.equals(entryPoint)) {
continue;
continue; // recursive
}
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);
}
@ -91,9 +121,7 @@ public abstract class OutgoingCallNode extends CallNode {
.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
Collections.sort(children, new CallNodeComparator());
return children;
results.addAll(children);
}
private void createNode(LazyMap<Function, List<GTreeNode>> nodes, Reference reference,
@ -101,21 +129,21 @@ public abstract class OutgoingCallNode extends CallNode {
Address fromAddress = reference.getFromAddress();
if (calledFunction != null) {
if (isExternalCall(calledFunction)) {
CallNode node =
new ExternalCallNode(calledFunction, fromAddress, baseIcon, callTreeOptions);
CallNode node = new ExternalCallNode(calledFunction, fromAddress,
CallTreePlugin.FUNCTION_ICON, callTreeOptions);
addNode(nodes, node);
}
else {
addNode(nodes,
new OutgoingFunctionCallNode(program, calledFunction, fromAddress, callTreeOptions));
new OutgoingCallNode(program, calledFunction, fromAddress, callTreeOptions));
}
}
else if (isCallReference(reference)) {
Function externalFunction = getExternalFunctionTempHackWorkaround(reference);
if (externalFunction != null) {
CallNode node =
new ExternalCallNode(externalFunction, fromAddress, baseIcon, callTreeOptions);
CallNode node = new ExternalCallNode(externalFunction, fromAddress,
CallTreePlugin.FUNCTION_ICON, callTreeOptions);
addNode(nodes, node);
}
else {
@ -201,7 +229,7 @@ public abstract class OutgoingCallNode extends CallNode {
@Override
public Icon getIcon(boolean expanded) {
if (icon == null) {
icon = OUTGOING_FUNCTION_ICON;
icon = outgoingFunctionIcon;
if (functionIsInPath()) {
icon = CallTreePlugin.RECURSIVE_ICON;
}

View File

@ -25,7 +25,7 @@ public class OutgoingCallsRootNode extends OutgoingCallNode {
OutgoingCallsRootNode(Program program, Function function, Address sourceAddress,
CallTreeOptions callTreeOptions) {
super(program, function, sourceAddress, CallTreePlugin.FUNCTION_ICON, callTreeOptions);
super(program, function, sourceAddress, callTreeOptions);
}
@Override

View File

@ -1,33 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.calltree;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
public class OutgoingFunctionCallNode extends OutgoingCallNode {
OutgoingFunctionCallNode(Program program, Function function, Address sourceAddress,
CallTreeOptions callTreeOptions) {
super(program, function, sourceAddress, CallTreePlugin.FUNCTION_ICON, callTreeOptions);
}
@Override
CallNode recreate() {
return new OutgoingFunctionCallNode(program, function, getSourceAddress(), callTreeOptions);
}
}

View File

@ -46,7 +46,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
Program program = getProgram();
addrFactory = program.getAddressFactory();
defaultSpace = addrFactory.getDefaultAddressSpace();
definedInstrAddresses = new ArrayList<Address>();
definedInstrAddresses = new ArrayList<>();
}
/**
@ -86,7 +86,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
Program program = getProgram();
addrFactory = program.getAddressFactory();
defaultSpace = addrFactory.getDefaultAddressSpace();
definedInstrAddresses = new ArrayList<Address>();
definedInstrAddresses = new ArrayList<>();
}
private static String getToyLanguageId(boolean bigEndian, boolean wordAligned) {
@ -149,8 +149,8 @@ public class ToyProgramBuilder extends ProgramBuilder {
}
int relDest = (int) dest.subtract(address);
if (relDest > Byte.MAX_VALUE || relDest < Byte.MIN_VALUE) {
throw new IllegalArgumentException("targetAddr is out of range for instruction: " +
relDest);
throw new IllegalArgumentException(
"targetAddr is out of range for instruction: " + relDest);
}
return (short) (relDest & 0xff);
}
@ -162,8 +162,8 @@ public class ToyProgramBuilder extends ProgramBuilder {
}
int relDest = (int) dest.subtract(address);
if (relDest > Byte.MAX_VALUE || relDest < Byte.MIN_VALUE) {
throw new IllegalArgumentException("targetAddr is out of range for instruction: " +
relDest);
throw new IllegalArgumentException(
"targetAddr is out of range for instruction: " + relDest);
}
return (short) (relDest & 0xffff);
}
@ -391,6 +391,16 @@ public class ToyProgramBuilder extends ProgramBuilder {
addBytesCall(toHex(offset), toHex(dest));
}
/**
* Add call (consumes 2-bytes)
* @param offset instruction address offset
* @param dest call destination offset
* @throws MemoryAccessException
*/
public void addBytesCall(String offset, long dest) throws MemoryAccessException {
addBytesCall(offset, toHex(dest));
}
/**
* Add call (consumes 2-bytes)
* @param addr instruction address
@ -499,8 +509,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
* @param offset instruction address offset
* @throws MemoryAccessException
*/
public void addBytesSkipConditional(long offset)
throws MemoryAccessException {
public void addBytesSkipConditional(long offset) throws MemoryAccessException {
addBytesSkipConditional(toHex(offset));
}
@ -509,8 +518,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
* @param addr instruction address
* @throws MemoryAccessException
*/
public void addBytesSkipConditional(String addr)
throws MemoryAccessException {
public void addBytesSkipConditional(String addr) throws MemoryAccessException {
Address address = addr(addr);
addInstructionWords(address, (short) (0x8000)); // skeq
}

View File

@ -0,0 +1,196 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.calltree;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.*;
import docking.widgets.tree.GTreeNode;
import generic.test.AbstractGenericTest;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.test.ToyProgramBuilder;
import ghidra.util.task.TaskMonitor;
import utility.function.ExceptionalCallback;
public class IncomingCallNodeTest extends AbstractGenericTest {
private ToyProgramBuilder builder;
private Program program;
private IncomingCallNode node1;
private String firstCalledFunctionAddress = "0x0100";
private String firstCalledFromAddress = "0x1100";
@Before
public void setUp() throws Exception {
builder = new ToyProgramBuilder("Call Node Test", true);
builder.createMemory(".text", "0x0", 0x3000);
builder.createFunction(firstCalledFunctionAddress);
program = builder.getProgram();
setUpCallNode(false);
createThunkFunctionCallGraph();
}
@After
public void tearDown() {
builder.dispose();
}
private void setUpCallNode(boolean hideThunks) {
FunctionManager fm = program.getFunctionManager();
Function firstCalledFunction = fm.getFunctionAt(builder.addr(firstCalledFunctionAddress)); // 0x0100
Address calledFromAddress = builder.addr(firstCalledFromAddress); // 0x0010
CallTreeOptions callTreeOptions = new CallTreeOptions();
callTreeOptions = callTreeOptions.withRecurseDepth(5);
callTreeOptions = callTreeOptions.withFilterThunks(hideThunks);
node1 =
new IncomingCallNode(program, firstCalledFunction, calledFromAddress, callTreeOptions);
}
@Test
public void testGenerateChildren_CalledFunctionIsThunk_ShowThunks() throws Exception {
/*
node1 @ 0100
node2 (Function_1100)
node3 (Thunk_Function_1050)
node4
Call: 1200 -> 1050
*/
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
IncomingCallNode node2 = (IncomingCallNode) children.get(0);
Address fromAddress = builder.addr("0x1100");
assertEquals("Function_1100", node2.getName());
assertEquals(fromAddress, node2.getSourceAddress());
children = node2.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
IncomingCallNode node3 = (IncomingCallNode) children.get(0);
assertEquals("Thunk_Function_1050", node3.getName());
fromAddress = builder.addr("0x1050");
assertEquals(fromAddress, node3.getSourceAddress());
children = node3.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size()); // node4; Call: 1200 -> 1050
IncomingCallNode node4 = (IncomingCallNode) children.get(0);
fromAddress = addr("0x1200");
assertEquals("Function_1200", node4.getName());
assertEquals(fromAddress, node4.getSourceAddress());
}
@Test
public void testGenerateChildren_CalledFunctionIsThunk_HideThunks() throws Exception {
/*
node1 @ 0100
node2 (Function_1100)
node3 (Thunk_Function_1050) // <------ This is removed from the tree
node4
Call: 1200 -> 1050
*/
setUpCallNode(true);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
IncomingCallNode node2 = (IncomingCallNode) children.get(0);
Address fromAddress = builder.addr("0x1100");
assertEquals("Function_1100", node2.getName());
assertEquals(fromAddress, node2.getSourceAddress());
children = node2.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
IncomingCallNode node3 = (IncomingCallNode) children.get(0);
assertEquals("Function_1200", node3.getName());
fromAddress = builder.addr("0x1200");
}
private void createThunkFunctionCallGraph() throws Exception {
/*
node1 @ 0100
node2 (Function_1100)
node3 (Thunk_Function_1050)
node4
Call: 1200 -> 1050
*/
String thunkedAddress = "0x1100";
Function thunkedFunction =
builder.createEmptyFunction("Function_1100", thunkedAddress, 10, DataType.DEFAULT);
builder.createMemoryCallReference("0x1100", "0x0100"); // call the root node, node1
String thunkAddress = "0x1050";
Function thunkFunction =
builder.createEmptyFunction("Thunk_Function_1050", thunkAddress, 10, DataType.DEFAULT);
tx(() -> {
thunkFunction.setThunkedFunction(thunkedFunction);
});
// call from thunk to thunked, node3 -> node2
builder.createMemoryCallReference(thunkAddress, thunkedAddress);
// call from somewhere to thunked function, node3
builder.createEmptyFunction("Function_1200", "0x1200", 10, DataType.DEFAULT);
builder.createMemoryCallReference("0x1200", thunkAddress);
}
private <E extends Exception> void tx(ExceptionalCallback<E> c) {
int txId = program.startTransaction("Test - Function in Transaction");
boolean commit = true;
try {
c.call();
program.flushEvents();
}
catch (Exception e) {
commit = false;
failWithException("Exception modifying program '" + program.getName() + "'", e);
}
finally {
program.endTransaction(txId, commit);
}
}
private Address addr(String addrString) {
return builder.addr(addrString);
}
}

View File

@ -0,0 +1,498 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.calltree;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.*;
import docking.widgets.tree.GTreeNode;
import generic.test.AbstractGenericTest;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.test.ToyProgramBuilder;
import ghidra.util.task.TaskMonitor;
import utility.function.ExceptionalCallback;
/**
* This test class is covering the various types of 'calls' a function can make, such as a
* direct call instruction, or through a user-defined reference. External calls are also a bit
* tricky. This test was created by following the code paths in {@link OutgoingCallNode}
* and making sure that each path was followed.
*/
public class OutgoingCallNodeTest extends AbstractGenericTest {
private ToyProgramBuilder builder;
private Program program;
private OutgoingCallNode node1;
private String firstCallSourceAddress = "0x0010";
private String firstCalledFunctionAddress = "0x0100";
@Before
public void setUp() throws Exception {
builder = new ToyProgramBuilder("Call Node Test", true);
builder.createMemory(".text", "0x0", 0x3000);
Function firstCalledFunction = builder.createFunction(firstCalledFunctionAddress);
program = builder.getProgram();
Address calledFromAddress = builder.addr(firstCallSourceAddress); // fake
CallTreeOptions callTreeOptions = new CallTreeOptions();
callTreeOptions = callTreeOptions.withRecurseDepth(5);
node1 =
new OutgoingCallNode(program, firstCalledFunction, calledFromAddress, callTreeOptions);
}
@After
public void tearDown() {
builder.dispose();
}
@Test
public void testGenerateChildren_SelfRecursiveCall() throws Exception {
builder.createMemoryCallReference(firstCalledFunctionAddress, firstCalledFunctionAddress);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_CalledFunctionExists() throws Exception {
String otherAddress = "0x1000";
builder.createEmptyFunction("Function_1000", otherAddress, 10, DataType.DEFAULT);
builder.createMemoryCallReference(firstCalledFunctionAddress, otherAddress);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals("Function_1000", children.get(0).getName());
}
@Test
public void testGenerateChildren_CalledFunctionIsThunk_ShowThunks() throws Exception {
/*
node1
0010 -> Function @ 0100
node2
Call: 0100 -> Thunk_Function_1050 @ 1050
node3
Call: 1050 -> Function_1100 @ 1100
node4
Call: 1100 -> 1200
*/
createThunkFunctionCallGraph();
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
OutgoingCallNode node2 = (OutgoingCallNode) children.get(0);
Address fromAddress = builder.addr("0x0100");
Address toAddress = builder.addr("0x1050"); // thunk address
assertEquals("Thunk_Function_1050", node2.getName());
assertEquals(fromAddress, node2.getSourceAddress());
assertEquals(toAddress, node2.getRemoteFunction().getEntryPoint());
children = node2.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
OutgoingCallNode node3 = (OutgoingCallNode) children.get(0);
assertEquals("Function_1100", node3.getName());
children = node2.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size()); // node4; Call: 1100 -> 1200
}
@Test
public void testGenerateChildren_CalledFunctionIsThunk_HideThunks() throws Exception {
setUpCallNodeWithThunksHidden();
/*
node1
0010 -> Function @ 0100
node2
Call: 0100 -> Thunk_Function_1050 @ 1050 // <------ ignored thunk; omitted from tree
node3
Call: 1050 -> Function_1100 @ 1100
node4
Call: 1100 -> 1200
*/
createThunkFunctionCallGraph();
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
// second node from above should be skipped, since it is at thunk
OutgoingCallNode node3 = (OutgoingCallNode) children.get(0);
Address fromAddress = addr("0x0100");
Address toAddress = addr("0x1100"); // thunked address; no thunk
// verify the function for the child is not the thunk function; the thunk is ignored
assertEquals("Function_1100", node3.getName());
assertEquals(fromAddress, node3.getSourceAddress());
assertEquals(toAddress, node3.getRemoteFunction().getEntryPoint());
children = node3.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size()); // Call: 1100 -> 1200
DeadEndNode node4 = (DeadEndNode) children.get(0);
fromAddress = addr("0x1100");
toAddress = addr("0x1200");
assertEquals(fromAddress, node4.getSourceAddress());
assertEquals(toAddress, node4.getRemoteAddress());
}
@Test
public void testGenerateChildren_CalledFunctionExists_ExternalCall() throws Exception {
String otherAddress = "0x1000";
String externalFunctionName = "External_Function";
ExternalLocation location =
builder.createExternalFunction(otherAddress, "ExternalLibrary", externalFunctionName);
builder.createMemoryCallReference(firstCalledFunctionAddress,
location.getExternalSpaceAddress().toString());
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals(externalFunctionName, children.get(0).getName());
}
@Test
public void testGenerateChildren_CallReference_ExternalFunction_NoFunctionInMemory()
throws Exception {
builder.createMemoryCallReference(firstCalledFunctionAddress, "EXTERNAL:00000001");
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals("EXTERNAL:00000001", children.get(0).getName());
}
@Test
public void testGenerateChildren_CallReference_ToPointer_ToExternalFunction() throws Exception {
//
// Function A
// -> has memory reference to a pointer
// -> this pointer has an external reference to a function
//
Reference ref = builder.createMemoryCallReference(firstCalledFunctionAddress, "0x2000");
Address toAddress = ref.getToAddress();
builder.applyDataType(toAddress.toString(), new Pointer32DataType());
String externalFunctionName = "External_Function";
ExternalLocation location =
builder.createExternalFunction("0x2020", "ExternalLibrary", externalFunctionName);
builder.createMemoryReadReference(toAddress.toString(),
location.getExternalSpaceAddress().toString());
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals(externalFunctionName, children.get(0).getName());
}
@Test
public void testGenerateChildren_CallReference_ToPointer_ToNonExternalFunction()
throws Exception {
//
// Function A
// -> has memory reference to a pointer
// -> this pointer has an non-external reference to a function
//
Reference ref = builder.createMemoryCallReference(firstCalledFunctionAddress, "0x2000");
Address toAddress = ref.getToAddress();
builder.applyDataType(toAddress.toString(), new Pointer32DataType());
String functionAddress = "0x2020";
builder.createEmptyFunction("Function_2020", functionAddress, 1, new VoidDataType());
builder.createMemoryReadReference(toAddress.toString(), functionAddress);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof DeadEndNode);
}
@Test
public void testGenerateChildren_CallReference_ToPointer_Offcut() throws Exception {
//
// Bad code case; expected reference to pointer, but no data there
//
String dataAddress = "0x2000";
String offcutAddress = "0x2001";
builder.applyDataType(dataAddress, new Pointer32DataType());
builder.createMemoryCallReference(firstCalledFunctionAddress, offcutAddress);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof DeadEndNode);
}
@Test
public void testGenerateChildren_WriteReference() throws Exception {
//
// Have a reference in the function to a place that is not another function, and the
// reference is a write reference. No call node is created.
//
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.WRITE,
SourceType.USER_DEFINED);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_NullInstruction() throws Exception {
//
// Have a reference in the function to a place that is not another function, and the
// reference is a read reference.
// Note: since we did not have the builder put an instruction at the 'to' address,
// the instruction there is null.
//
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.READ,
SourceType.USER_DEFINED);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_NotCallInstruction() throws Exception {
//
// Read reference to an instruction with a flow type that is not a call
//
builder.addBytesFallthrough(firstCalledFunctionAddress);
builder.disassemble(firstCalledFunctionAddress, 2);
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.READ,
SourceType.USER_DEFINED);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_InstructionAtToAddress()
throws Exception {
//
// Read reference from an instruction with a flow type of call to a place that is an
// instruction (and thus is not a pointer to a function)
//
createCallInstruction();
// instruction at the other side
builder.addBytesNOP(0x1000, 2);
builder.disassemble("0x1000", 2);
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.READ,
SourceType.USER_DEFINED);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_NoReference()
throws Exception {
//
// Read reference from an instruction with a flow type of call.
//
createCallInstruction();
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.READ,
SourceType.USER_DEFINED);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_NonExternalReference()
throws Exception {
//
// Read reference from an instruction with a flow type that is a call, to a non-external place
//
createCallInstruction();
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.READ,
SourceType.USER_DEFINED);
// put a non-external reference on the data at the 'to' address
builder.createMemoryCallReference("0x1000", "0x1020");
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_ExternalReference_NonFunctionSymbol()
throws Exception {
//
// Read reference from an instruction with a flow type that is a call, to a external place
//
createCallInstruction();
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.READ,
SourceType.USER_DEFINED);
// put a external reference on the data at the 'to' address
builder.createExternalReference("0x1000", "ExternalLib", "ExternalLabel", 0);
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_ExternalReference_FunctionSymbol()
throws Exception {
//
// Read reference from an instruction with a flow type that is a call, to an external
// function symbol
//
createCallInstruction();
builder.createMemoryReference(firstCalledFunctionAddress, "0x1000", RefType.READ,
SourceType.USER_DEFINED);
// put a external reference on the data at the 'to' address that calls the external function
ExternalLocation location =
builder.createExternalFunction("0x1020", "ExternalLib", "ExternalFunction_1");
builder.createMemoryReadReference("0x1000", location.getExternalSpaceAddress().toString());
List<GTreeNode> children = node1.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof DeadEndNode);
}
private Address addr(String addrString) {
return builder.addr(addrString);
}
private void createThunkFunctionCallGraph() throws Exception {
/*
node1
0010 -> Function @ 0100
node2
Call: 0100 -> Thunk_Function_1050 @ 1050
node3
Call: 1050 -> Function_1100 @ 1100
node4
Call: 1100 -> 1200
*/
String thunkedAddress = "0x1100";
Function thunkedFunction =
builder.createEmptyFunction("Function_1100", thunkedAddress, 10, DataType.DEFAULT);
builder.createMemoryCallReference("0x1100", "0x1200"); // ref so a node appears in the tree
String thunkAddress = "0x1050";
Function thunkFunction =
builder.createEmptyFunction("Thunk_Function_1050", thunkAddress, 10, DataType.DEFAULT);
tx(() -> {
thunkFunction.setThunkedFunction(thunkedFunction);
});
builder.createMemoryCallReference(firstCalledFunctionAddress, thunkAddress);
builder.createMemoryCallReference(thunkAddress, thunkedAddress);
}
private void setUpCallNodeWithThunksHidden() {
FunctionManager fm = program.getFunctionManager();
Function firstCalledFunction = fm.getFunctionAt(builder.addr(firstCalledFunctionAddress)); // 0x0100
Address calledFromAddress = builder.addr(firstCallSourceAddress); // 0x0010
CallTreeOptions callTreeOptions = new CallTreeOptions();
callTreeOptions = callTreeOptions.withRecurseDepth(5);
callTreeOptions = callTreeOptions.withFilterThunks(true);
node1 =
new OutgoingCallNode(program, firstCalledFunction, calledFromAddress, callTreeOptions);
}
private <E extends Exception> void tx(ExceptionalCallback<E> c) {
int txId = program.startTransaction("Test - Function in Transaction");
boolean commit = true;
try {
c.call();
program.flushEvents();
}
catch (Exception e) {
commit = false;
failWithException("Exception modifying program '" + program.getName() + "'", e);
}
finally {
program.endTransaction(txId, commit);
}
}
private void createCallInstruction() throws Exception {
Address source = builder.addr(firstCalledFunctionAddress);
Address destination = source.add(1);
builder.addBytesCall(source.getOffset(), destination.getOffset());
builder.disassemble(firstCalledFunctionAddress, 2);
//
// Note: the tests that use this are creating their references to control the code's
// execution path, so get rid of the default reference created for this instruction
//
int txID = program.startTransaction("Remove References");
try {
ReferenceManager rm = program.getReferenceManager();
rm.removeAllReferencesFrom(builder.addr(firstCalledFunctionAddress));
}
finally {
program.endTransaction(txID, true);
}
}
}

View File

@ -1,336 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.calltree;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import docking.widgets.tree.GTreeNode;
import generic.test.AbstractGenericTest;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.test.ToyProgramBuilder;
import ghidra.util.task.TaskMonitor;
/**
* This test class is covering the various types of 'calls' a function can make, such as a
* direct call instruction, or through a user-defined reference. External calls are also a bit
* tricky. This test was created by following the code paths in {@link OutgoingFunctionCallNode}
* and making sure that each path was followed.
*/
public class OutgoingFunctionCallNodeTest extends AbstractGenericTest {
private ToyProgramBuilder builder;
private Program program;
private OutgoingFunctionCallNode node;
private String nodeAddress = "0x0000";
@Before
public void setUp() throws Exception {
builder = new ToyProgramBuilder("Call Node Test", true);
builder.createMemory(".text", "0x0", 0x3000);
Function function = builder.createFunction(nodeAddress);
program = builder.getProgram();
Address source = builder.addr("0x1000"); // fake
CallTreeOptions callTreeOptions = new CallTreeOptions();
callTreeOptions = callTreeOptions.withRecurseDepth(5);
node = new OutgoingFunctionCallNode(program, function, source, callTreeOptions);
}
@Test
public void testGenerateChildren_SelfRecursiveCall() throws Exception {
builder.createMemoryCallReference(nodeAddress, nodeAddress);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_CalledFunctionExists() throws Exception {
String otherAddress = "0x1000";
builder.createEmptyFunction("Function_2", otherAddress, 10, DataType.DEFAULT);
builder.createMemoryCallReference(nodeAddress, otherAddress);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals("Function_2", children.get(0).getName());
}
@Test
public void testGenerateChildren_CalledFunctionExists_ExternalCall() throws Exception {
String otherAddress = "0x1000";
String externalFunctionName = "External_Function";
ExternalLocation location =
builder.createExternalFunction(otherAddress, "ExternalLibrary", externalFunctionName);
builder.createMemoryCallReference(nodeAddress,
location.getExternalSpaceAddress().toString());
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals(externalFunctionName, children.get(0).getName());
}
@Test
public void testGenerateChildren_CallReference_ExternalFunction_NoFunctionInMemory()
throws Exception {
builder.createMemoryCallReference(nodeAddress, "EXTERNAL:00000001");
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals("EXTERNAL:00000001", children.get(0).getName());
}
@Test
public void testGenerateChildren_CallReference_ToPointer_ToExternalFunction() throws Exception {
//
// Function A
// -> has memory reference to a pointer
// -> this pointer has an external reference to a function
//
Reference ref = builder.createMemoryCallReference(nodeAddress, "0x2000");
Address toAddress = ref.getToAddress();
builder.applyDataType(toAddress.toString(), new Pointer32DataType());
String externalFunctionName = "External_Function";
ExternalLocation location =
builder.createExternalFunction("0x2020", "ExternalLibrary", externalFunctionName);
builder.createMemoryReadReference(toAddress.toString(),
location.getExternalSpaceAddress().toString());
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertEquals(externalFunctionName, children.get(0).getName());
}
@Test
public void testGenerateChildren_CallReference_ToPointer_ToNonExternalFunction()
throws Exception {
//
// Function A
// -> has memory reference to a pointer
// -> this pointer has an non-external reference to a function
//
Reference ref = builder.createMemoryCallReference(nodeAddress, "0x2000");
Address toAddress = ref.getToAddress();
builder.applyDataType(toAddress.toString(), new Pointer32DataType());
String functionAddress = "0x2020";
builder.createEmptyFunction("Function_1", functionAddress, 1, new VoidDataType());
builder.createMemoryReadReference(toAddress.toString(), functionAddress);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof DeadEndNode);
}
@Test
public void testGenerateChildren_CallReference_ToPointer_Offcut() throws Exception {
//
// Bad code case; expected reference to pointer, but no data there
//
String dataAddress = "0x2000";
String offcutAddress = "0x2001";
builder.applyDataType(dataAddress, new Pointer32DataType());
builder.createMemoryCallReference(nodeAddress, offcutAddress);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof DeadEndNode);
}
@Test
public void testGenerateChildren_WriteReference() throws Exception {
//
// Have a reference in the function to a place that is not another function, and the
// reference is a write reference. No call node is created.
//
builder.createMemoryReference(nodeAddress, "0x1000", RefType.WRITE,
SourceType.USER_DEFINED);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_NullInstruction() throws Exception {
//
// Have a reference in the function to a place that is not another function, and the
// reference is a read reference.
// Note: since we did not have the builder put an instruction at the 'to' address,
// the instruction there is null.
//
builder.createMemoryReference(nodeAddress, "0x1000", RefType.READ, SourceType.USER_DEFINED);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_NotCallInstruction() throws Exception {
//
// Read reference to an instruction with a flow type that is not a call
//
builder.addBytesFallthrough(nodeAddress);
builder.disassemble(nodeAddress, 2);
builder.createMemoryReference(nodeAddress, "0x1000", RefType.READ, SourceType.USER_DEFINED);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_InstructionAtToAddress()
throws Exception {
//
// Read reference from an instruction with a flow type of call to a place that is an
// instruction (and thus is not a pointer to a function)
//
createCallInstruction();
// instruction at the other side
builder.addBytesNOP(0x1000, 2);
builder.disassemble("0x1000", 2);
builder.createMemoryReference(nodeAddress, "0x1000", RefType.READ, SourceType.USER_DEFINED);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_NoReference()
throws Exception {
//
// Read reference from an instruction with a flow type of call.
//
createCallInstruction();
builder.createMemoryReference(nodeAddress, "0x1000", RefType.READ, SourceType.USER_DEFINED);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_NonExternalReference()
throws Exception {
//
// Read reference from an instruction with a flow type that is a call, to a non-external place
//
createCallInstruction();
builder.createMemoryReference(nodeAddress, "0x1000", RefType.READ, SourceType.USER_DEFINED);
// put a non-external reference on the data at the 'to' address
builder.createMemoryCallReference("0x1000", "0x1020");
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_ExternalReference_NonFunctionSymbol()
throws Exception {
//
// Read reference from an instruction with a flow type that is a call, to a external place
//
createCallInstruction();
builder.createMemoryReference(nodeAddress, "0x1000", RefType.READ, SourceType.USER_DEFINED);
// put a external reference on the data at the 'to' address
builder.createExternalReference("0x1000", "ExternalLib", "ExternalLabel", 0);
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertTrue(children.isEmpty());
}
@Test
public void testGenerateChildren_ReadReference_CallInstruction_ToData_ExternalReference_FunctionSymbol()
throws Exception {
//
// Read reference from an instruction with a flow type that is a call, to an external
// function symbol
//
createCallInstruction();
builder.createMemoryReference(nodeAddress, "0x1000", RefType.READ, SourceType.USER_DEFINED);
// put a external reference on the data at the 'to' address that calls the external function
ExternalLocation location =
builder.createExternalFunction("0x1020", "ExternalLib", "ExternalFunction_1");
builder.createMemoryReadReference("0x1000", location.getExternalSpaceAddress().toString());
List<GTreeNode> children = node.generateChildren(TaskMonitor.DUMMY);
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof DeadEndNode);
}
private void createCallInstruction() throws Exception {
builder.addBytesCall(0x0000, 0x1);
builder.disassemble(nodeAddress, 2);
//
// Note: the tests that use this are creating their references to control the code's
// execution path, so get rid of the default reference created for this instruction
//
int txID = program.startTransaction("Remove References");
try {
ReferenceManager rm = program.getReferenceManager();
rm.removeAllReferencesFrom(builder.addr("0x0000"));
}
finally {
program.endTransaction(txID, true);
}
}
}

View File

@ -1445,6 +1445,14 @@ public class GTree extends JPanel implements BusyListener {
return true;
}
/**
* Enable or disable using double-click to open and close tree nodes. The default is true.
* @param b true to enable
*/
public void setDoubleClickExpansionEnabled(boolean b) {
tree.setToggleClickCount(b ? 2 : 0);
}
//==================================================================================================
// Inner Classes
//==================================================================================================