Merge remote-tracking branch 'origin/Ghidra_11.2'

This commit is contained in:
ghidra1 2024-09-18 14:49:53 -04:00
commit a85d15e11b
16 changed files with 888 additions and 495 deletions

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -45,8 +45,8 @@ public abstract class CallNode extends GTreeSlowLoadingNode {
} }
/** /**
* Returns this node's remote function, where remote is the source function for * Returns this node's remote function, where remote is the source function for
* an incoming call or a destination function for an outgoing call. May return * an incoming call or a destination function for an outgoing call. May return
* null for nodes that do not have functions. * null for nodes that do not have functions.
* @return the function or null * @return the function or null
*/ */
@ -65,8 +65,8 @@ public abstract class CallNode extends GTreeSlowLoadingNode {
public abstract Address getSourceAddress(); public abstract Address getSourceAddress();
/** /**
* Called when this node needs to be reconstructed due to external changes, such as when * Called when this node needs to be reconstructed due to external changes, such as when
* functions are renamed. * functions are renamed.
* *
* @return a new node that is the same type as 'this' node. * @return a new node that is the same type as 'this' node.
*/ */
@ -93,12 +93,6 @@ public abstract class CallNode extends GTreeSlowLoadingNode {
protected void addNode(LazyMap<Function, List<GTreeNode>> nodesByFunction, CallNode node) { protected void addNode(LazyMap<Function, List<GTreeNode>> nodesByFunction, CallNode node) {
Function function = node.getRemoteFunction(); Function function = node.getRemoteFunction();
if (function != null && function.isThunk()) {
if (!callTreeOptions.allowsThunks()) {
return;
}
}
List<GTreeNode> nodes = nodesByFunction.get(function); List<GTreeNode> nodes = nodesByFunction.get(function);
if (nodes.contains(node)) { if (nodes.contains(node)) {
return; // never add equal() nodes return; // never add equal() nodes

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -681,18 +681,18 @@ public class CallTreeProvider extends ComponentProviderAdapter {
"Show the Function Call Tree window for the function " + "selected in the call tree"); "Show the Function Call Tree window for the function " + "selected in the call tree");
tool.addLocalAction(this, newCallTree); tool.addLocalAction(this, newCallTree);
// //
// Provider menu actions // Provider menu actions
// //
//@formatter:off //@formatter:off
filterThunksAction = new ToggleActionBuilder("Filter Thunks", plugin.getName()) filterThunksAction = new ToggleActionBuilder("Filter Thunks", plugin.getName())
.selected(false) .selected(false)
.description("Thunk functions will not be shown in the tree when selected") .description("Thunk functions will not be shown in the tree when selected")
.helpLocation(new HelpLocation(plugin.getName(), "Call_Tree_Action_Filter_Thunks")) .helpLocation(new HelpLocation(plugin.getName(), "Call_Tree_Action_Filter_Thunks"))
.menuPath("Filter Thunks") .menuPath("Filter Thunks")
.onAction(c -> { .onAction(c -> {
callTreeOptions = callTreeOptions.withFilterThunks(filterThunksAction.isSelected()); callTreeOptions = callTreeOptions.withFilterThunks(filterThunksAction.isSelected());
doUpdate(); doUpdate();
}) })
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
//@formatter:on //@formatter:on
@ -705,7 +705,7 @@ public class CallTreeProvider extends ComponentProviderAdapter {
.menuPath("Show Namespace") .menuPath("Show Namespace")
.onAction(c -> { .onAction(c -> {
callTreeOptions = callTreeOptions.withShowNamespace(showNamespaceAction.isSelected()); callTreeOptions = callTreeOptions.withShowNamespace(showNamespaceAction.isSelected());
doUpdate(); doUpdate();
}) })
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
//@formatter:on //@formatter:on
@ -860,6 +860,7 @@ public class CallTreeProvider extends ComponentProviderAdapter {
} }
}; };
tree.setPaintHandlesForLeafNodes(false); tree.setPaintHandlesForLeafNodes(false);
tree.setDoubleClickExpansionEnabled(false); // reserve double-click for navigation
// tree.setFilterVisible(false); // tree.setFilterVisible(false);
return tree; return tree;
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -65,6 +65,10 @@ public class DeadEndNode extends CallNode {
return reference.getFromAddress(); return reference.getFromAddress();
} }
public Address getRemoteAddress() {
return reference.getToAddress();
}
@Override @Override
public Icon getIcon(boolean expanded) { public Icon getIcon(boolean expanded) {
return ICON; return ICON;

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -36,8 +36,8 @@ import resources.icons.TranslateIcon;
public class IncomingCallNode extends CallNode { public class IncomingCallNode extends CallNode {
private Icon INCOMING_ICON = Icons.ARROW_UP_LEFT_ICON; private static final Icon INCOMING_ICON = Icons.ARROW_UP_LEFT_ICON;
private Icon INCOMING_FUNCTION_ICON; private Icon incomingFunctionIcon;
private Icon icon = null; private Icon icon = null;
private final Address functionAddress; private final Address functionAddress;
@ -55,10 +55,10 @@ public class IncomingCallNode extends CallNode {
this.sourceAddress = sourceAddress; this.sourceAddress = sourceAddress;
this.functionAddress = function.getEntryPoint(); 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); TranslateIcon translateIcon = new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0);
incomingFunctionIcon.addIcon(translateIcon); multiIcon.addIcon(translateIcon);
INCOMING_FUNCTION_ICON = incomingFunctionIcon; incomingFunctionIcon = multiIcon;
} }
@Override @Override
@ -79,8 +79,19 @@ public class IncomingCallNode extends CallNode {
@Override @Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException { 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 = FunctionSignatureFieldLocation location =
new FunctionSignatureFieldLocation(program, functionAddress); new FunctionSignatureFieldLocation(program, address);
Set<Address> addresses = ReferenceUtils.getReferenceAddresses(location, monitor); Set<Address> addresses = ReferenceUtils.getReferenceAddresses(location, monitor);
LazyMap<Function, List<GTreeNode>> nodesByFunction = LazyMap<Function, List<GTreeNode>> nodesByFunction =
@ -93,6 +104,13 @@ public class IncomingCallNode extends CallNode {
continue; 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 = IncomingCallNode node =
new IncomingCallNode(program, callerFunction, fromAddress, callTreeOptions); new IncomingCallNode(program, callerFunction, fromAddress, callTreeOptions);
addNode(nodesByFunction, node); addNode(nodesByFunction, node);
@ -102,9 +120,7 @@ public class IncomingCallNode extends CallNode {
.stream() .stream()
.flatMap(list -> list.stream()) .flatMap(list -> list.stream())
.collect(Collectors.toList()); .collect(Collectors.toList());
Collections.sort(children, new CallNodeComparator()); results.addAll(children);
return children;
} }
@Override @Override
@ -115,7 +131,7 @@ public class IncomingCallNode extends CallNode {
@Override @Override
public Icon getIcon(boolean expanded) { public Icon getIcon(boolean expanded) {
if (icon == null) { if (icon == null) {
icon = INCOMING_FUNCTION_ICON; icon = incomingFunctionIcon;
if (functionIsInPath()) { if (functionIsInPath()) {
icon = CallTreePlugin.RECURSIVE_ICON; icon = CallTreePlugin.RECURSIVE_ICON;
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -36,31 +36,34 @@ import resources.Icons;
import resources.MultiIcon; import resources.MultiIcon;
import resources.icons.TranslateIcon; 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 static final Icon OUTGOING_ICON = Icons.ARROW_DOWN_RIGHT_ICON;
private final Icon OUTGOING_FUNCTION_ICON; private final Icon outgoingFunctionIcon;
private Icon icon = null; private Icon icon = null;
protected final Program program; protected final Program program;
protected final Function function; protected final Function function;
protected String name; protected String name;
private final Address sourceAddress; 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) { CallTreeOptions callTreeOptions) {
super(callTreeOptions); super(callTreeOptions);
this.program = program; this.program = program;
this.function = function; this.function = function;
this.name = function.getName(callTreeOptions.showNamespace()); this.name = function.getName(callTreeOptions.showNamespace());
this.sourceAddress = sourceAddress; this.sourceAddress = sourceAddress;
this.baseIcon = baseIcon;
MultiIcon outgoingFunctionIcon = new MultiIcon(OUTGOING_ICON, false, 32, 16); MultiIcon multiIcon = new MultiIcon(OUTGOING_ICON, false, 32, 16);
TranslateIcon translateIcon = new TranslateIcon(baseIcon, 16, 0); TranslateIcon translateIcon = new TranslateIcon(CallTreePlugin.FUNCTION_ICON, 16, 0);
outgoingFunctionIcon.addIcon(translateIcon); multiIcon.addIcon(translateIcon);
OUTGOING_FUNCTION_ICON = outgoingFunctionIcon; outgoingFunctionIcon = multiIcon;
}
@Override
CallNode recreate() {
return new OutgoingCallNode(program, function, sourceAddress, callTreeOptions);
} }
@Override @Override
@ -70,8 +73,23 @@ public abstract class OutgoingCallNode extends CallNode {
@Override @Override
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException { 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); Set<Reference> references = getReferencesFrom(program, functionBody, monitor);
LazyMap<Function, List<GTreeNode>> nodesByFunction = LazyMap<Function, List<GTreeNode>> nodesByFunction =
LazyMap.lazyMap(new HashMap<>(), k -> new ArrayList<>()); LazyMap.lazyMap(new HashMap<>(), k -> new ArrayList<>());
@ -80,10 +98,22 @@ public abstract class OutgoingCallNode extends CallNode {
monitor.checkCancelled(); monitor.checkCancelled();
Address toAddress = reference.getToAddress(); Address toAddress = reference.getToAddress();
if (toAddress.equals(entryPoint)) { if (toAddress.equals(entryPoint)) {
continue; continue; // recursive
} }
Function calledFunction = functionManager.getFunctionAt(toAddress); 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); createNode(nodesByFunction, reference, calledFunction);
} }
@ -91,9 +121,7 @@ public abstract class OutgoingCallNode extends CallNode {
.stream() .stream()
.flatMap(list -> list.stream()) .flatMap(list -> list.stream())
.collect(Collectors.toList()); .collect(Collectors.toList());
Collections.sort(children, new CallNodeComparator()); results.addAll(children);
return children;
} }
private void createNode(LazyMap<Function, List<GTreeNode>> nodes, Reference reference, private void createNode(LazyMap<Function, List<GTreeNode>> nodes, Reference reference,
@ -101,21 +129,21 @@ public abstract class OutgoingCallNode extends CallNode {
Address fromAddress = reference.getFromAddress(); Address fromAddress = reference.getFromAddress();
if (calledFunction != null) { if (calledFunction != null) {
if (isExternalCall(calledFunction)) { if (isExternalCall(calledFunction)) {
CallNode node = CallNode node = new ExternalCallNode(calledFunction, fromAddress,
new ExternalCallNode(calledFunction, fromAddress, baseIcon, callTreeOptions); CallTreePlugin.FUNCTION_ICON, callTreeOptions);
addNode(nodes, node); addNode(nodes, node);
} }
else { else {
addNode(nodes, addNode(nodes,
new OutgoingFunctionCallNode(program, calledFunction, fromAddress, callTreeOptions)); new OutgoingCallNode(program, calledFunction, fromAddress, callTreeOptions));
} }
} }
else if (isCallReference(reference)) { else if (isCallReference(reference)) {
Function externalFunction = getExternalFunctionTempHackWorkaround(reference); Function externalFunction = getExternalFunctionTempHackWorkaround(reference);
if (externalFunction != null) { if (externalFunction != null) {
CallNode node = CallNode node = new ExternalCallNode(externalFunction, fromAddress,
new ExternalCallNode(externalFunction, fromAddress, baseIcon, callTreeOptions); CallTreePlugin.FUNCTION_ICON, callTreeOptions);
addNode(nodes, node); addNode(nodes, node);
} }
else { else {
@ -201,7 +229,7 @@ public abstract class OutgoingCallNode extends CallNode {
@Override @Override
public Icon getIcon(boolean expanded) { public Icon getIcon(boolean expanded) {
if (icon == null) { if (icon == null) {
icon = OUTGOING_FUNCTION_ICON; icon = outgoingFunctionIcon;
if (functionIsInPath()) { if (functionIsInPath()) {
icon = CallTreePlugin.RECURSIVE_ICON; icon = CallTreePlugin.RECURSIVE_ICON;
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -25,7 +25,7 @@ public class OutgoingCallsRootNode extends OutgoingCallNode {
OutgoingCallsRootNode(Program program, Function function, Address sourceAddress, OutgoingCallsRootNode(Program program, Function function, Address sourceAddress,
CallTreeOptions callTreeOptions) { CallTreeOptions callTreeOptions) {
super(program, function, sourceAddress, CallTreePlugin.FUNCTION_ICON, callTreeOptions); super(program, function, sourceAddress, callTreeOptions);
} }
@Override @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

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -279,7 +279,7 @@ public class MyProgramChangesDisplayPlugin extends ProgramPlugin implements Doma
} }
if (isTrackingServerChanges()) { if (isTrackingServerChanges()) {
if (programSaved) { if (programSaved || programChangedRemotely) {
currentChangesSinceCheckoutMarks currentChangesSinceCheckoutMarks
.setAddressSetCollection(changeSet.getAddressSetCollectionSinceCheckout()); .setAddressSetCollection(changeSet.getAddressSetCollectionSinceCheckout());
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -45,12 +45,12 @@ public class CodeUnitFormat {
/** /**
* Supported memory address shift cases (bits) * Supported memory address shift cases (bits)
*/ */
private static final int[] SHIFT_CASES = new int[] { 1, 2, 8, 16, 32 }; private static final int[] SHIFT_CASES = new int[] { 1, 2, 8, 16 };
/** /**
* Supported memory address mask cases (mask value) * Supported memory address mask cases (mask value)
*/ */
private static final long[] MASK_CASES = new long[] { 0x0ff, 0x0ffff, 0x0ffffffff }; private static final long[] MASK_CASES = new long[] { 0x0ffff, 0x0ffffffff };
/** /**
* Default code unit format * Default code unit format
@ -834,29 +834,31 @@ public class CodeUnitFormat {
if (addr.isMemoryAddress()) { if (addr.isMemoryAddress()) {
// Include "offset" prefix since addrOffset does not match originalValue // Include "offset" prefix since addrOffset does not match originalValue
list.add("offset "); if (options.includeScalarReferenceAdjustment) {
list.add("offset ");
// Check for shift cases
for (int element : SHIFT_CASES) { // Check for shift cases
if ((addrOffset >>> element) == originalValue && originalValue != 0x0) { for (int element : SHIFT_CASES) {
list.add(opObj); if ((addrOffset >>> element) == originalValue && originalValue != 0x0) {
if (options.includeScalarReferenceAdjustment) { list.add(opObj);
list.add(" >>"); if (options.includeScalarReferenceAdjustment) {
list.add(Integer.toString(element)); list.add(" >>");
list.add(Integer.toString(element));
}
return list;
} }
return list;
} }
}
// Check for mask cases
// Check for mask cases for (long element : MASK_CASES) {
for (long element : MASK_CASES) { if ((addrOffset & element) == originalValue) {
if ((addrOffset & element) == originalValue) { list.add(opObj);
list.add(opObj); if (options.includeScalarReferenceAdjustment) {
if (options.includeScalarReferenceAdjustment) { list.add(" &");
list.add(" &"); list.add("0x" + Long.toHexString(element));
list.add("0x" + Long.toHexString(element)); }
return list;
} }
return list;
} }
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -46,7 +46,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
Program program = getProgram(); Program program = getProgram();
addrFactory = program.getAddressFactory(); addrFactory = program.getAddressFactory();
defaultSpace = addrFactory.getDefaultAddressSpace(); defaultSpace = addrFactory.getDefaultAddressSpace();
definedInstrAddresses = new ArrayList<Address>(); definedInstrAddresses = new ArrayList<>();
} }
/** /**
@ -86,7 +86,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
Program program = getProgram(); Program program = getProgram();
addrFactory = program.getAddressFactory(); addrFactory = program.getAddressFactory();
defaultSpace = addrFactory.getDefaultAddressSpace(); defaultSpace = addrFactory.getDefaultAddressSpace();
definedInstrAddresses = new ArrayList<Address>(); definedInstrAddresses = new ArrayList<>();
} }
private static String getToyLanguageId(boolean bigEndian, boolean wordAligned) { private static String getToyLanguageId(boolean bigEndian, boolean wordAligned) {
@ -149,8 +149,8 @@ public class ToyProgramBuilder extends ProgramBuilder {
} }
int relDest = (int) dest.subtract(address); int relDest = (int) dest.subtract(address);
if (relDest > Byte.MAX_VALUE || relDest < Byte.MIN_VALUE) { if (relDest > Byte.MAX_VALUE || relDest < Byte.MIN_VALUE) {
throw new IllegalArgumentException("targetAddr is out of range for instruction: " + throw new IllegalArgumentException(
relDest); "targetAddr is out of range for instruction: " + relDest);
} }
return (short) (relDest & 0xff); return (short) (relDest & 0xff);
} }
@ -162,8 +162,8 @@ public class ToyProgramBuilder extends ProgramBuilder {
} }
int relDest = (int) dest.subtract(address); int relDest = (int) dest.subtract(address);
if (relDest > Byte.MAX_VALUE || relDest < Byte.MIN_VALUE) { if (relDest > Byte.MAX_VALUE || relDest < Byte.MIN_VALUE) {
throw new IllegalArgumentException("targetAddr is out of range for instruction: " + throw new IllegalArgumentException(
relDest); "targetAddr is out of range for instruction: " + relDest);
} }
return (short) (relDest & 0xffff); return (short) (relDest & 0xffff);
} }
@ -391,6 +391,16 @@ public class ToyProgramBuilder extends ProgramBuilder {
addBytesCall(toHex(offset), toHex(dest)); 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) * Add call (consumes 2-bytes)
* @param addr instruction address * @param addr instruction address
@ -499,8 +509,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
* @param offset instruction address offset * @param offset instruction address offset
* @throws MemoryAccessException * @throws MemoryAccessException
*/ */
public void addBytesSkipConditional(long offset) public void addBytesSkipConditional(long offset) throws MemoryAccessException {
throws MemoryAccessException {
addBytesSkipConditional(toHex(offset)); addBytesSkipConditional(toHex(offset));
} }
@ -509,8 +518,7 @@ public class ToyProgramBuilder extends ProgramBuilder {
* @param addr instruction address * @param addr instruction address
* @throws MemoryAccessException * @throws MemoryAccessException
*/ */
public void addBytesSkipConditional(String addr) public void addBytesSkipConditional(String addr) throws MemoryAccessException {
throws MemoryAccessException {
Address address = addr(addr); Address address = addr(addr);
addInstructionWords(address, (short) (0x8000)); // skeq 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

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -250,9 +250,9 @@ public class GTree extends JPanel implements BusyListener {
/** /**
* Sets an accessible name on the GTree. This prefix will be used to assign * Sets an accessible name on the GTree. This prefix will be used to assign
* meaningful accessible names to the tree, filter text field and the filter options button such * meaningful accessible names to the tree, filter text field and the filter options button such
* that screen readers will properly describe them. * that screen readers will properly describe them.
* <P> * <P>
* This prefix should be the base name that describes the type of items in the tree. * This prefix should be the base name that describes the type of items in the tree.
* This method will then append the necessary information to name the text field and the button. * This method will then append the necessary information to name the text field and the button.
* *
* @param namePrefix the accessible name prefix to assign to the filter component. For * @param namePrefix the accessible name prefix to assign to the filter component. For
@ -385,7 +385,7 @@ public class GTree extends JPanel implements BusyListener {
} }
/** /**
* Sets the filter restore state. This method is a way to override the tree's filtering * Sets the filter restore state. This method is a way to override the tree's filtering
* behavior, which is usually set by a call to {@link #saveFilterRestoreState()}. Most clients * behavior, which is usually set by a call to {@link #saveFilterRestoreState()}. Most clients
* will never need to call this method. * will never need to call this method.
* *
@ -1445,6 +1445,14 @@ public class GTree extends JPanel implements BusyListener {
return true; 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 // Inner Classes
//================================================================================================== //==================================================================================================

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -78,7 +78,7 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
bf = dbItem.open(version, minChangeVersion); bf = dbItem.open(version, minChangeVersion);
dbh = new DBHandle(bf); dbh = new DBHandle(bf);
program = new ProgramDB(dbh, OpenMode.IMMUTABLE, monitor, consumer); program = new ProgramDB(dbh, OpenMode.IMMUTABLE, monitor, consumer);
getProgramChangeSet(program, bf); loadProgramChangeSet(program, bf);
success = true; success = true;
return program; return program;
} }
@ -130,7 +130,7 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
dbh = new DBHandle(bf); dbh = new DBHandle(bf);
OpenMode openMode = okToUpgrade ? OpenMode.UPGRADE : OpenMode.UPDATE; OpenMode openMode = okToUpgrade ? OpenMode.UPGRADE : OpenMode.UPDATE;
program = new ProgramDB(dbh, openMode, monitor, consumer); program = new ProgramDB(dbh, openMode, monitor, consumer);
getProgramChangeSet(program, bf); loadProgramChangeSet(program, bf);
program.setProgramUserData(new ProgramUserDataDB(program)); program.setProgramUserData(new ProgramUserDataDB(program));
success = true; success = true;
return program; return program;
@ -184,7 +184,7 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
OpenMode openMode = okToUpgrade ? OpenMode.UPGRADE : OpenMode.UPDATE; OpenMode openMode = okToUpgrade ? OpenMode.UPGRADE : OpenMode.UPDATE;
program = new ProgramDB(dbh, openMode, monitor, consumer); program = new ProgramDB(dbh, openMode, monitor, consumer);
if (checkoutId == FolderItem.DEFAULT_CHECKOUT_ID) { if (checkoutId == FolderItem.DEFAULT_CHECKOUT_ID) {
getProgramChangeSet(program, bf); loadProgramChangeSet(program, bf);
} }
if (recover) { if (recover) {
recoverChangeSet(program, dbh); recoverChangeSet(program, dbh);
@ -255,9 +255,11 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
} }
} }
private ProgramDBChangeSet getProgramChangeSet(ProgramDB program, ManagedBufferFile bf) private ProgramDBChangeSet loadProgramChangeSet(ProgramDB program, ManagedBufferFile bf)
throws IOException { throws IOException {
ProgramDBChangeSet changeSet = (ProgramDBChangeSet) program.getChangeSet(); ProgramDBChangeSet changeSet = (ProgramDBChangeSet) program.getChangeSet();
changeSet.clearAll();
BufferFile cf = bf.getNextChangeDataFile(true); BufferFile cf = bf.getNextChangeDataFile(true);
DBHandle cfh = null; DBHandle cfh = null;
while (cf != null) { while (cf != null) {
@ -292,7 +294,7 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
bf = dbItem.open(toVer, fromVer); bf = dbItem.open(toVer, fromVer);
dbh = new DBHandle(bf); dbh = new DBHandle(bf);
program = new ProgramDB(dbh, OpenMode.IMMUTABLE, null, this); program = new ProgramDB(dbh, OpenMode.IMMUTABLE, null, this);
return getProgramChangeSet(program, bf); return loadProgramChangeSet(program, bf);
} }
catch (VersionException | IOException e) { catch (VersionException | IOException e) {
throw e; throw e;
@ -374,7 +376,7 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
} }
LocalManagedBufferFile bf = dbItem.openForUpdate(FolderItem.DEFAULT_CHECKOUT_ID); LocalManagedBufferFile bf = dbItem.openForUpdate(FolderItem.DEFAULT_CHECKOUT_ID);
program.getDBHandle().setDBVersionedSourceFile(bf); program.getDBHandle().setDBVersionedSourceFile(bf);
getProgramChangeSet(program, bf); loadProgramChangeSet(program, bf);
} }
} }

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -361,25 +361,29 @@ class ProgramDBChangeSet implements ProgramChangeSet, DomainObjectDBChangeSet {
} }
if (!isCheckedOut) { // if not versioned, wipe out change sets if (!isCheckedOut) { // if not versioned, wipe out change sets
changedAddrsSinceCheckout.clear(); clearAll();
changedRegAddrsSinceCheckout.clear();
changedAddrsSinceSave.clear();
changedRegAddrsSinceSave.clear();
changedCategoryIds.clear();
changedDataTypeIds.clear();
changedProgramTreeIds.clear();
changedSymbolIds.clear();
changedSourceArchiveIds.clear();
addedCategoryIds.clear();
addedDataTypeIds.clear();
addedProgramTreeIds.clear();
addedSymbolIds.clear();
addedSourceArchiveIds.clear();
} }
clearUndo(); clearUndo();
} }
void clearAll() {
changedAddrsSinceCheckout.clear();
changedRegAddrsSinceCheckout.clear();
changedAddrsSinceSave.clear();
changedRegAddrsSinceSave.clear();
changedCategoryIds.clear();
changedDataTypeIds.clear();
changedProgramTreeIds.clear();
changedSymbolIds.clear();
changedSourceArchiveIds.clear();
addedCategoryIds.clear();
addedDataTypeIds.clear();
addedProgramTreeIds.clear();
addedSymbolIds.clear();
addedSourceArchiveIds.clear();
}
@Override @Override
public synchronized void startTransaction() { public synchronized void startTransaction() {
inTransaction = true; inTransaction = true;
@ -659,6 +663,7 @@ class ProgramDBChangeSet implements ProgramChangeSet, DomainObjectDBChangeSet {
} }
return true; return true;
} }
} }
class ChangeDiff { class ChangeDiff {