mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 06:02:09 +00:00
GP-4933 - Fixed function call trees incorrectly dropping thunks
This commit is contained in:
parent
9a04ea643a
commit
1cadb4a26f
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
//==================================================================================================
|
||||
|
Loading…
Reference in New Issue
Block a user