mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-10 06:02:09 +00:00
Merge remote-tracking branch 'origin/Ghidra_11.2'
This commit is contained in:
commit
a85d15e11b
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user