mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-4009 Introduced BSim functionality including support for postgresql,
elasticsearch and h2 databases. Added BSim correlator to Version Tracking.
This commit is contained in:
parent
f0f5b8f2a4
commit
0865a3dfb0
509 changed files with 77125 additions and 934 deletions
|
@ -0,0 +1,136 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.DecompilerLocation;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* This is a base class for actions in a {@link DecompilerDiffCodeComparisonPanel}
|
||||
*/
|
||||
public abstract class AbstractMatchedTokensAction extends DockingAction {
|
||||
|
||||
protected DecompilerDiffCodeComparisonPanel diffPanel;
|
||||
protected boolean disableOnReadOnly;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param actionName name of action
|
||||
* @param owner owner of action
|
||||
* @param diffPanel diff panel containing action
|
||||
* @param disableOnReadOnly if true, action will be disabled for read-only programs
|
||||
*/
|
||||
public AbstractMatchedTokensAction(String actionName, String owner,
|
||||
DecompilerDiffCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
|
||||
super(actionName, owner);
|
||||
this.diffPanel = diffPanel;
|
||||
this.disableOnReadOnly = disableOnReadOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the action should be enable for a pair of
|
||||
* matching tokens.
|
||||
*
|
||||
* @param tokenPair tokens
|
||||
* @return true if action should be enabled
|
||||
*/
|
||||
protected abstract boolean enabledForTokens(TokenPair tokenPair);
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (!(context instanceof DualDecompilerActionContext compareContext)) {
|
||||
return false;
|
||||
}
|
||||
if (!(compareContext
|
||||
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disableOnReadOnly) {
|
||||
//get the program corresponding to the panel with focus
|
||||
Program program = decompPanel.getLeftProgram();
|
||||
if (program == null) {
|
||||
return false; //panel initializing; don't enable action
|
||||
}
|
||||
if (!decompPanel.leftPanelHasFocus()) {
|
||||
program = decompPanel.getRightProgram();
|
||||
}
|
||||
if (!program.canSave()) {
|
||||
return false; //program is read-only, don't enable action
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
TokenPair currentPair = getCurrentTokenPair(decompPanel);
|
||||
return enabledForTokens(currentPair);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TokenPair} consisting of the token under the cursor in the focused
|
||||
* decompiler panel and its counterpart in the other panel.
|
||||
*
|
||||
* @param decompPanel decomp panel
|
||||
* @return matching tokens (or null if no match)
|
||||
*/
|
||||
protected TokenPair getCurrentTokenPair(
|
||||
DecompilerCodeComparisonPanel<? extends DualDecompilerFieldPanelCoordinator> decompPanel) {
|
||||
|
||||
DecompilerPanel focusedPanel = decompPanel.getFocusedDecompilerPanel().getDecompilerPanel();
|
||||
|
||||
if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClangToken focusedToken = focusedLocation.getToken();
|
||||
if (focusedToken == null) {
|
||||
return null;
|
||||
}
|
||||
List<TokenBin> tokenBin = diffPanel.getHighBins();
|
||||
if (tokenBin == null) {
|
||||
return null;
|
||||
}
|
||||
TokenBin containingBin = TokenBin.getBinContainingToken(tokenBin, focusedToken);
|
||||
if (containingBin == null) {
|
||||
return null;
|
||||
}
|
||||
TokenBin matchedBin = containingBin.getMatch();
|
||||
if (matchedBin == null) {
|
||||
return null;
|
||||
}
|
||||
//loop over the tokens in the matching bin and return the first one in the same
|
||||
//class as focusedToken
|
||||
Iterator<ClangToken> tokenIter = matchedBin.iterator();
|
||||
while (tokenIter.hasNext()) {
|
||||
ClangToken currentMatch = tokenIter.next();
|
||||
if (currentMatch.getClass().equals(focusedToken.getClass())) {
|
||||
return decompPanel.leftPanelHasFocus() ? new TokenPair(focusedToken, currentMatch)
|
||||
: new TokenPair(currentMatch, focusedToken);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
|
||||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.app.decompiler.component.DualDecompilerFieldPanelCoordinator;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Class to coordinate the scrolling of two decompiler panels as well as cursor location
|
||||
* highlighting due to cursor location changes.
|
||||
*/
|
||||
public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoordinator {
|
||||
|
||||
private BidiMap<Integer, Integer> leftToRightLineNumberPairing;
|
||||
private List<ClangLine> leftLines = new ArrayList<ClangLine>();
|
||||
private List<ClangLine> rightLines = new ArrayList<ClangLine>();
|
||||
private int lockedLeftLineNumber = 0;
|
||||
private int lockedRightLineNumber = 0;
|
||||
private DecompilerPanel leftDecompilerPanel;
|
||||
private DecompilerPanel rightDecompilerPanel;
|
||||
private boolean matchConstantsExactly;
|
||||
private List<TokenBin> highBins;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param dualDecompilerPanel decomp comparison panel
|
||||
*/
|
||||
public CodeDiffFieldPanelCoordinator(DecompilerDiffCodeComparisonPanel dualDecompilerPanel) {
|
||||
super(dualDecompilerPanel);
|
||||
this.leftDecompilerPanel = dualDecompilerPanel.getLeftDecompilerPanel();
|
||||
this.rightDecompilerPanel = dualDecompilerPanel.getRightDecompilerPanel();
|
||||
leftToRightLineNumberPairing = new DualHashBidiMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the line pairing for two decompiled functions. Any existing line pairing is
|
||||
* cleared.
|
||||
*
|
||||
* @param decompileDataDiff decomp diff
|
||||
* @param monitor monitor
|
||||
* @throws CancelledException if user cancels
|
||||
*/
|
||||
public void computeLinePairing(DecompileDataDiff decompileDataDiff, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
highBins = decompileDataDiff.getTokenMap(matchConstantsExactly, monitor);
|
||||
HighFunction leftHighFunction = decompileDataDiff.getLeftHighFunction();
|
||||
|
||||
clearLineNumberPairing();
|
||||
for (TokenBin bin : highBins) {
|
||||
if (bin.getMatch() != null) {
|
||||
boolean isLeftBin = bin.getHighFunction().equals(leftHighFunction);
|
||||
ClangToken binToken = bin.get(0);
|
||||
ClangToken sidekickToken = bin.getMatch().get(0);
|
||||
ClangToken leftClangToken = isLeftBin ? binToken : sidekickToken;
|
||||
ClangToken rightClangToken = isLeftBin ? sidekickToken : binToken;
|
||||
ClangLine leftLine = leftClangToken.getLineParent();
|
||||
ClangLine rightLine = rightClangToken.getLineParent();
|
||||
leftToRightLineNumberPairing.put(leftLine.getLineNumber(),
|
||||
rightLine.getLineNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leftLocationChanged(ProgramLocation leftLocation) {
|
||||
DecompilerLocation leftDecompilerLocation = (DecompilerLocation) leftLocation;
|
||||
|
||||
// Get the line from the token so we can match it with the line from the other panel.
|
||||
ClangToken leftToken =
|
||||
(leftDecompilerLocation != null) ? leftDecompilerLocation.getToken() : null;
|
||||
ClangLine leftLine = (leftToken != null) ? leftToken.getLineParent() : null;
|
||||
if (leftLine != null) {
|
||||
int leftLineNumber = leftLine.getLineNumber();
|
||||
if (searchLeftForPair(leftLineNumber)) {
|
||||
lockLines(BigInteger.valueOf(lockedLeftLineNumber),
|
||||
BigInteger.valueOf(lockedRightLineNumber));
|
||||
}
|
||||
}
|
||||
panelViewChanged(leftDecompilerPanel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rightLocationChanged(ProgramLocation rightLocation) {
|
||||
DecompilerLocation rightDecompilerLocation = (DecompilerLocation) rightLocation;
|
||||
|
||||
// Get the line from the token so we can try to match it with the line from the other panel.
|
||||
ClangToken rightToken =
|
||||
(rightDecompilerLocation != null) ? rightDecompilerLocation.getToken() : null;
|
||||
ClangLine rightLine = (rightToken != null) ? rightToken.getLineParent() : null;
|
||||
if (rightLine != null) {
|
||||
int rightLineNumber = rightLine.getLineNumber();
|
||||
if (searchRightForPair(rightLineNumber)) {
|
||||
lockLines(BigInteger.valueOf(lockedLeftLineNumber),
|
||||
BigInteger.valueOf(lockedRightLineNumber));
|
||||
}
|
||||
}
|
||||
panelViewChanged(rightDecompilerPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Updates the comparison panel using {@code decompileDataDiff}
|
||||
*
|
||||
* @param decompileDataDiff decomp diff data
|
||||
* @param shouldMatchConstantsExactly if differences in constant values should count
|
||||
* @param monitor monitor
|
||||
* @throws CancelledException if user cancels
|
||||
*/
|
||||
public void replaceDecompileDataDiff(DecompileDataDiff decompileDataDiff,
|
||||
boolean shouldMatchConstantsExactly, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
this.matchConstantsExactly = shouldMatchConstantsExactly;
|
||||
if (leftDecompilerPanel != null) {
|
||||
leftLines = leftDecompilerPanel.getLines();
|
||||
}
|
||||
else {
|
||||
leftLines.clear();
|
||||
}
|
||||
|
||||
if (rightDecompilerPanel != null) {
|
||||
rightLines = rightDecompilerPanel.getLines();
|
||||
}
|
||||
else {
|
||||
rightLines.clear();
|
||||
}
|
||||
lockFunctionSignatureLines();
|
||||
computeLinePairing(decompileDataDiff, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the existing line number pairing
|
||||
*/
|
||||
void clearLineNumberPairing() {
|
||||
leftToRightLineNumberPairing.clear();
|
||||
}
|
||||
|
||||
List<TokenBin> getHighBins() {
|
||||
return highBins;
|
||||
}
|
||||
|
||||
private void panelViewChanged(DecompilerPanel panel) {
|
||||
ViewerPosition viewerPosition = panel.getViewerPosition();
|
||||
BigInteger index = viewerPosition.getIndex();
|
||||
int xOffset = viewerPosition.getXOffset();
|
||||
int yOffset = viewerPosition.getYOffset();
|
||||
viewChanged(panel.getFieldPanel(), index, xOffset, yOffset);
|
||||
}
|
||||
|
||||
//locks the two function signature lines - used when first displaying the comparison
|
||||
private void lockFunctionSignatureLines() {
|
||||
|
||||
ClangLine leftLine = getClangLine(leftLines, 0);
|
||||
ClangLine rightLine = getClangLine(rightLines, 0);
|
||||
|
||||
// If we can find both lines with function signatures then lock lines on them.
|
||||
ClangLine leftFunctionLine = findFunctionSignatureLine(leftLines);
|
||||
ClangLine rightFunctionLine = findFunctionSignatureLine(rightLines);
|
||||
if (leftFunctionLine != null && rightFunctionLine != null) {
|
||||
leftLine = leftFunctionLine;
|
||||
rightLine = rightFunctionLine;
|
||||
}
|
||||
|
||||
if (leftLine != null && rightLine != null) {
|
||||
setLockedLineNumbers(leftLine.getLineNumber(), rightLine.getLineNumber());
|
||||
lockLines(BigInteger.valueOf(lockedLeftLineNumber),
|
||||
BigInteger.valueOf(lockedRightLineNumber));
|
||||
}
|
||||
}
|
||||
|
||||
private ClangLine findFunctionSignatureLine(List<ClangLine> lines) {
|
||||
for (ClangLine clangLine : lines) {
|
||||
int numTokens = clangLine.getNumTokens();
|
||||
for (int i = 0; i < numTokens; i++) {
|
||||
ClangToken token = clangLine.getToken(i);
|
||||
if (token instanceof ClangFuncNameToken) {
|
||||
return clangLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the indicated line number from the list after adjusting the line number when
|
||||
* it falls outside the lower or upper limits of the array list. If there are no items
|
||||
* in the array list then null is returned.
|
||||
* @param lines the ordered array list of ClangLines.
|
||||
* @param lineNumber the decompiler line number (1 based, not 0 based).
|
||||
* @return the ClangLine for the indicated line number. Otherwise, null.
|
||||
*/
|
||||
private ClangLine getClangLine(List<ClangLine> lines, int lineNumber) {
|
||||
if (lines.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int size = lines.size();
|
||||
if (lineNumber < 1) {
|
||||
lineNumber = 1;
|
||||
}
|
||||
if (lineNumber > size) {
|
||||
lineNumber = size;
|
||||
}
|
||||
return lines.get(lineNumber - 1);
|
||||
}
|
||||
|
||||
private void setLockedLineNumbers(int leftLineNumber, int rightLineNumber) {
|
||||
lockedLeftLineNumber = leftLineNumber;
|
||||
lockedRightLineNumber = rightLineNumber;
|
||||
}
|
||||
|
||||
private boolean searchRightForPair(int rightLineNumber) {
|
||||
if (setLeftFromRight(rightLineNumber)) {
|
||||
return true;
|
||||
}
|
||||
int lastLine = rightLines.size();
|
||||
int previous = rightLineNumber - 1;
|
||||
int next = rightLineNumber + 1;
|
||||
while (previous > 0 || next <= lastLine) {
|
||||
if (previous > 0) {
|
||||
if (setLeftFromRight(previous)) {
|
||||
return true;
|
||||
}
|
||||
previous--;
|
||||
}
|
||||
if (next <= lastLine) {
|
||||
if (setLeftFromRight(next)) {
|
||||
return true;
|
||||
}
|
||||
next++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean setLeftFromRight(int rightLineNumber) {
|
||||
Integer leftLineNumber = leftToRightLineNumberPairing.getKey(rightLineNumber);
|
||||
if (leftLineNumber == null) {
|
||||
return false;
|
||||
}
|
||||
setLockedLineNumbers(leftLineNumber, rightLineNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean searchLeftForPair(int leftLineNumber) {
|
||||
if (setRightFromLeft(leftLineNumber)) {
|
||||
return true;
|
||||
}
|
||||
int lastLine = leftLines.size();
|
||||
int previous = leftLineNumber - 1;
|
||||
int next = leftLineNumber + 1;
|
||||
while (previous > 0 || next <= lastLine) {
|
||||
if (previous > 0) {
|
||||
if (setRightFromLeft(previous)) {
|
||||
return true;
|
||||
}
|
||||
previous--;
|
||||
}
|
||||
if (next <= lastLine) {
|
||||
if (setRightFromLeft(next)) {
|
||||
return true;
|
||||
}
|
||||
next++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean setRightFromLeft(int leftLineNumber) {
|
||||
Integer rightLineNumber = leftToRightLineNumberPairing.get(leftLineNumber);
|
||||
if (rightLineNumber == null) {
|
||||
return false;
|
||||
}
|
||||
setLockedLineNumbers(leftLineNumber, rightLineNumber);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.MenuData;
|
||||
import ghidra.app.decompiler.ClangFuncNameToken;
|
||||
import ghidra.app.decompiler.component.DecompilerCodeComparisonPanel;
|
||||
import ghidra.app.decompiler.component.DualDecompilerActionContext;
|
||||
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
|
||||
import ghidra.app.services.FunctionComparisonService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* An action for bringing up a side-by-side function comparison of callees with matching
|
||||
* tokens.
|
||||
*/
|
||||
public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAction {
|
||||
private PluginTool tool;
|
||||
private static final String ACTION_NAME = "Compare Matching Callees";
|
||||
private static final String MENU_GROUP = "A1_Compare";
|
||||
private static final String HELP_TOPIC = "FunctionComparison";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param diffPanel diff Panel
|
||||
* @param tool tool
|
||||
*/
|
||||
public CompareFuncsFromMatchedTokensAction(DecompilerDiffCodeComparisonPanel diffPanel,
|
||||
PluginTool tool) {
|
||||
super(ACTION_NAME, tool.getName(), diffPanel, false);
|
||||
this.tool = tool;
|
||||
MenuData menuData = new MenuData(new String[] { ACTION_NAME }, null, MENU_GROUP);
|
||||
setPopupMenuData(menuData);
|
||||
setEnabled(true);
|
||||
setHelpLocation(new HelpLocation(HELP_TOPIC, "Compare Matching Callees"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean enabledForTokens(TokenPair tokenPair) {
|
||||
if (tokenPair == null) {
|
||||
return false;
|
||||
}
|
||||
if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
|
||||
return false;
|
||||
}
|
||||
PcodeOp leftOp = tokenPair.leftToken().getPcodeOp();
|
||||
PcodeOp rightOp = tokenPair.rightToken().getPcodeOp();
|
||||
if (leftOp == null || rightOp == null) {
|
||||
return false;
|
||||
}
|
||||
if (leftOp.getOpcode() != PcodeOp.CALL || rightOp.getOpcode() != PcodeOp.CALL) {
|
||||
return false;
|
||||
}
|
||||
return (tokenPair.leftToken() instanceof ClangFuncNameToken) &&
|
||||
(tokenPair.rightToken() instanceof ClangFuncNameToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
if (!(context instanceof DualDecompilerActionContext compareContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(compareContext
|
||||
.getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
TokenPair currentPair = getCurrentTokenPair(decompPanel);
|
||||
if (currentPair == null || currentPair.leftToken() == null ||
|
||||
currentPair.rightToken() == null) {
|
||||
return;
|
||||
}
|
||||
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
|
||||
if (service == null) {
|
||||
Msg.error(this, "Function Comparison Service not found!");
|
||||
return;
|
||||
}
|
||||
FunctionComparisonProvider comparisonProvider = service.createFunctionComparisonProvider();
|
||||
comparisonProvider.removeAddFunctionsAction();
|
||||
|
||||
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
|
||||
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
|
||||
|
||||
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getLeftProgram());
|
||||
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getRightProgram());
|
||||
|
||||
if (leftFunction == null || rightFunction == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
comparisonProvider.getModel().compareFunctions(leftFunction, rightFunction);
|
||||
|
||||
}
|
||||
|
||||
private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) {
|
||||
Address callTarget = funcToken.getPcodeOp().getInput(0).getAddress();
|
||||
return program.getFunctionManager().getFunctionAt(callTarget);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.DecompileData;
|
||||
import ghidra.codecompare.graphanalysis.Pinning;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Class that takes decompile data for two functions (referred to as the left and right functions)
|
||||
* and determines the differences between them.
|
||||
*/
|
||||
public class DecompileDataDiff {
|
||||
|
||||
/**
|
||||
* Pairing information for a single configuration of the Pinning algorithm, including:
|
||||
* A list of all TokenBins across both functions that can be paired with each other
|
||||
* (individual TokenBins are marked with their pair, if it exists).
|
||||
* A list of tokens that do not have a match in the left function.
|
||||
* A list of tokens that do not have a match in the right function.
|
||||
*/
|
||||
private static class Configuration {
|
||||
ArrayList<TokenBin> highBins; // List of all token bins
|
||||
HashSet<ClangToken> leftHighlightTokenSet; // Tokens without a match in the left function
|
||||
HashSet<ClangToken> rightHighlightTokenSet; // Tokens without a match in the right function
|
||||
|
||||
public Configuration(ArrayList<TokenBin> highBins, HighFunction leftFunc,
|
||||
HighFunction rightFunc) {
|
||||
this.highBins = highBins;
|
||||
leftHighlightTokenSet = new HashSet<>();
|
||||
rightHighlightTokenSet = new HashSet<>();
|
||||
for (TokenBin bin : highBins) {
|
||||
if (bin.getMatch() == null) {
|
||||
for (ClangToken token : bin) {
|
||||
ClangFunction clangFunction = token.getClangFunction();
|
||||
HighFunction highFunction = clangFunction.getHighFunction();
|
||||
if (leftFunc == highFunction) {
|
||||
leftHighlightTokenSet.add(token);
|
||||
}
|
||||
else if (rightFunc == highFunction) {
|
||||
rightHighlightTokenSet.add(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private DecompileData[] decompileData = new DecompileData[2];
|
||||
private ClangTokenGroup[] markup = new ClangTokenGroup[2];
|
||||
private HighFunction[] hfunc = new HighFunction[2];
|
||||
private boolean sizeCollapse; // True if we are comparing different size architectures
|
||||
|
||||
// Different ways to configure the pinning algorithm
|
||||
private static int NOT_EXACT_MATCH_CONSTANTS = 0;
|
||||
private static int EXACT_MATCH_CONSTANTS = 1;
|
||||
|
||||
private Configuration[] pairing; // Token pairing info from different Pinning configurations
|
||||
|
||||
public DecompileDataDiff(DecompileData decompileData1, DecompileData decompileData2) {
|
||||
this.decompileData[0] = decompileData1;
|
||||
this.decompileData[1] = decompileData2;
|
||||
int size1 = decompileData1.getProgram().getLanguage().getLanguageDescription().getSize();
|
||||
int size2 = decompileData2.getProgram().getLanguage().getLanguageDescription().getSize();
|
||||
sizeCollapse = (size1 != size2);
|
||||
|
||||
markup[0] = decompileData[0].getCCodeMarkup();
|
||||
markup[1] = decompileData[1].getCCodeMarkup();
|
||||
|
||||
hfunc[0] = decompileData[0].getHighFunction();
|
||||
hfunc[1] = decompileData[1].getHighFunction();
|
||||
pairing = new Configuration[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sets of tokens (TokenBins) that have been paired across the two functions.
|
||||
* The pairing can be performed either forcing constants to match, or not.
|
||||
* @param matchConstantsExactly is true if constants should be forced to match
|
||||
* @param monitor is the TaskMonitor
|
||||
* @return the list of TokenBins
|
||||
* @throws CancelledException if the user cancels the task
|
||||
*/
|
||||
public ArrayList<TokenBin> getTokenMap(boolean matchConstantsExactly, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
int index = matchConstantsExactly ? EXACT_MATCH_CONSTANTS : NOT_EXACT_MATCH_CONSTANTS;
|
||||
|
||||
if (pairing[index] == null) {
|
||||
Pinning pin = Pinning.makePinning(hfunc[0], hfunc[1], matchConstantsExactly,
|
||||
sizeCollapse, true, monitor);
|
||||
ArrayList<TokenBin> highBins = pin.buildTokenMap(markup[0], markup[1]);
|
||||
pairing[index] = new Configuration(highBins, hfunc[0], hfunc[1]);
|
||||
}
|
||||
|
||||
return pairing[index].highBins;
|
||||
}
|
||||
|
||||
public HashSet<ClangToken> getLeftHighlightTokenSet(boolean matchConstantsExactly,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
int index = matchConstantsExactly ? EXACT_MATCH_CONSTANTS : NOT_EXACT_MATCH_CONSTANTS;
|
||||
|
||||
if (pairing[index] == null) {
|
||||
getTokenMap(matchConstantsExactly, monitor);
|
||||
}
|
||||
|
||||
return pairing[index].leftHighlightTokenSet;
|
||||
}
|
||||
|
||||
public HashSet<ClangToken> getRightHighlightTokenSet(boolean matchConstantsExactly,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
int matchConstantsIndex =
|
||||
matchConstantsExactly ? EXACT_MATCH_CONSTANTS : NOT_EXACT_MATCH_CONSTANTS;
|
||||
|
||||
if (pairing[matchConstantsIndex] == null) {
|
||||
getTokenMap(matchConstantsExactly, monitor);
|
||||
}
|
||||
|
||||
return pairing[matchConstantsIndex].rightHighlightTokenSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the decompiled high level function for the left function.
|
||||
* @return the left high level function
|
||||
*/
|
||||
public HighFunction getLeftHighFunction() {
|
||||
return hfunc[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the decompiled high level function for the right function.
|
||||
* @return the right high level function
|
||||
*/
|
||||
public HighFunction getRightHighFunction() {
|
||||
return hfunc[1];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import generic.theme.GColor;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
/**
|
||||
* This class holds the options for the decompiler diff view.
|
||||
*/
|
||||
public class DecompilerCodeComparisonOptions {
|
||||
|
||||
private static final String MATCHING_TOKEN_HIGHLIGHT_KEY = "Focused Token Match Highlight";
|
||||
private static final String UNMATCHED_TOKEN_HIGHLIGHT_KEY = "Focused Token Unmatched Highlight";
|
||||
private static final String INELIGIBLE_TOKEN_HIGHLIGHT_KEY =
|
||||
"Focused Token Ineligible Highlight";
|
||||
private static final String DIFF_HIGHLIGHT_KEY = "Difference Highlight";
|
||||
|
||||
private static final Color DEFAULT_MATCHING_TOKEN_HIGHLIGHT_COLOR =
|
||||
new GColor("color.bg.codecompare.highlight.field.diff.matching");
|
||||
private static final Color DEFAULT_UNMATCHED_TOKEN_HIGHLIGHT_COLOR =
|
||||
new GColor("color.bg.codecompare.highlight.field.diff.not.matching");
|
||||
private static final Color DEFAULT_INELIGIBLE_TOKEN_HIGHLIGHT_COLOR =
|
||||
new GColor("color.bg.codecompare.highlight.field.diff.other");
|
||||
private static final Color DEFAULT_DIFF_HIGHLIGHT_COLOR =
|
||||
new GColor("color.bg.codecompare.highlight.diff");
|
||||
|
||||
private static final String MATCHING_TOKEN_HIGHLIGHT_DESCRIPTION =
|
||||
"Highlight Color for Focused Token and Match";
|
||||
private static final String UNMATCHED_TOKEN_HIGHLIGHT_DESCRIPTION =
|
||||
"Highlight Color for a Focused Token with no Match";
|
||||
private static final String INELIGIBLE_TOKEN_HIGHLIGHT_DESCRIPTION =
|
||||
"Highlight Color for a Focused Token which is ineligible for a match (e.g., whitespace)";
|
||||
private static final String DIFF_HIGHLIGHT_DESCRIPTION = "Highlight Color for Differences";
|
||||
|
||||
private Color matchingTokenHighlight;
|
||||
private Color unmatchedTokenHighlight;
|
||||
private Color ineligibleTokenHighlight;
|
||||
private Color diffHighlight;
|
||||
|
||||
public static final String OPTIONS_CATEGORY_NAME = "Decompiler Code Comparison";
|
||||
public static final String HELP_TOPIC = "FunctionComparison";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DecompilerCodeComparisonOptions() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the options
|
||||
* @param options options
|
||||
*/
|
||||
public void registerOptions(ToolOptions options) {
|
||||
HelpLocation help = new HelpLocation(HELP_TOPIC, "Decompiler Code Comparison Options");
|
||||
options.setOptionsHelpLocation(help);
|
||||
|
||||
options.registerThemeColorBinding(MATCHING_TOKEN_HIGHLIGHT_KEY,
|
||||
"color.bg.codecompare.highlight.field.diff.matching", help,
|
||||
MATCHING_TOKEN_HIGHLIGHT_DESCRIPTION);
|
||||
|
||||
options.registerThemeColorBinding(UNMATCHED_TOKEN_HIGHLIGHT_KEY,
|
||||
"color.bg.codecompare.highlight.field.diff.not.matching", help,
|
||||
UNMATCHED_TOKEN_HIGHLIGHT_DESCRIPTION);
|
||||
|
||||
options.registerThemeColorBinding(INELIGIBLE_TOKEN_HIGHLIGHT_KEY,
|
||||
"color.bg.codecompare.highlight.field.diff.other", help,
|
||||
INELIGIBLE_TOKEN_HIGHLIGHT_DESCRIPTION);
|
||||
|
||||
options.registerThemeColorBinding(DIFF_HIGHLIGHT_KEY, "color.bg.codecompare.highlight.diff",
|
||||
help, DIFF_HIGHLIGHT_DESCRIPTION);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the options
|
||||
* @param options options
|
||||
*/
|
||||
public void loadOptions(ToolOptions options) {
|
||||
matchingTokenHighlight =
|
||||
options.getColor(MATCHING_TOKEN_HIGHLIGHT_KEY, DEFAULT_MATCHING_TOKEN_HIGHLIGHT_COLOR);
|
||||
unmatchedTokenHighlight = options.getColor(UNMATCHED_TOKEN_HIGHLIGHT_KEY,
|
||||
DEFAULT_UNMATCHED_TOKEN_HIGHLIGHT_COLOR);
|
||||
ineligibleTokenHighlight = options.getColor(INELIGIBLE_TOKEN_HIGHLIGHT_KEY,
|
||||
DEFAULT_INELIGIBLE_TOKEN_HIGHLIGHT_COLOR);
|
||||
diffHighlight = options.getColor(DIFF_HIGHLIGHT_KEY, DEFAULT_DIFF_HIGHLIGHT_COLOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color used to highlight matches of the focused token
|
||||
* @return match color
|
||||
*/
|
||||
public Color getFocusedTokenMatchHighlightColor() {
|
||||
return matchingTokenHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color used to highlight the focuses token when it does not have a match
|
||||
* @return unmatched color
|
||||
*/
|
||||
public Color getFocusedTokenUnmatchedHighlightColor() {
|
||||
return unmatchedTokenHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color used to highlight the focused token when it is not eligible for a match
|
||||
* (e.g., a whitespace token)
|
||||
* @return ineligible color
|
||||
*/
|
||||
public Color getFocusedTokenIneligibleHighlightColor() {
|
||||
return ineligibleTokenHighlight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color used to highlight differences between the two decompiled functions
|
||||
* @return difference color
|
||||
*/
|
||||
public Color getDiffHighlightColor() {
|
||||
return diffHighlight;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.*;
|
||||
import docking.widgets.fieldpanel.internal.FieldPanelCoordinator;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
import resources.Icons;
|
||||
import resources.MultiIcon;
|
||||
|
||||
/**
|
||||
* This is a CodeComparisonPanel that gets discovered by other providers that display dual
|
||||
* comparison views.<br>
|
||||
* Note: Therefore there may not be any other classes that refer directly to it.
|
||||
*/
|
||||
public class DecompilerDiffCodeComparisonPanel
|
||||
extends DecompilerCodeComparisonPanel<CodeDiffFieldPanelCoordinator>
|
||||
implements DualDecompileResultsListener, OptionsChangeListener {
|
||||
|
||||
public static final String CODE_DIFF_VIEW = "Decompiler Diff View";
|
||||
private DecompileDataDiff decompileDataDiff;
|
||||
private DiffClangHighlightController leftHighlightController;
|
||||
private DiffClangHighlightController rightHighlightController;
|
||||
private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator;
|
||||
private MyToggleExactConstantMatching toggleExactConstantMatchingAction;
|
||||
private boolean isMatchingConstantsExactly = true;
|
||||
private boolean toggleFlagWhenLastVisible = isMatchingConstantsExactly;
|
||||
private CompareFuncsFromMatchedTokensAction compareFuncsAction;
|
||||
private DecompilerCodeComparisonOptions comparisonOptions;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param owner owner
|
||||
* @param tool tool
|
||||
*/
|
||||
public DecompilerDiffCodeComparisonPanel(String owner, PluginTool tool) {
|
||||
super(owner, tool);
|
||||
|
||||
comparisonOptions = new DecompilerCodeComparisonOptions();
|
||||
initializeOptions();
|
||||
leftHighlightController = new DiffClangHighlightController(comparisonOptions);
|
||||
rightHighlightController = new DiffClangHighlightController(comparisonOptions);
|
||||
setHighlightControllers(leftHighlightController, rightHighlightController);
|
||||
|
||||
// Make the left highlight listen to the right.
|
||||
leftHighlightController.addListener(rightHighlightController);
|
||||
// Make the right highlight listen to the left.
|
||||
rightHighlightController.addListener(leftHighlightController);
|
||||
|
||||
addDualDecompileResultsListener(this);
|
||||
decompilerFieldPanelCoordinator = new CodeDiffFieldPanelCoordinator(this);
|
||||
setFieldPanelCoordinator(decompilerFieldPanelCoordinator);
|
||||
|
||||
}
|
||||
|
||||
private void initializeOptions() {
|
||||
ToolOptions options =
|
||||
tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME);
|
||||
options.addOptionsChangeListener(this);
|
||||
comparisonOptions.registerOptions(options);
|
||||
comparisonOptions.loadOptions(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) {
|
||||
comparisonOptions.loadOptions(options);
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean aFlag) {
|
||||
if (aFlag == isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (aFlag) {
|
||||
// Becoming visible.
|
||||
if (toggleFlagWhenLastVisible != isMatchingConstantsExactly) {
|
||||
if (decompileDataDiff != null) {
|
||||
determineDecompilerDifferences();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No longer visible.
|
||||
toggleFlagWhenLastVisible = isMatchingConstantsExactly;
|
||||
}
|
||||
super.setVisible(aFlag);
|
||||
updateActionEnablement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return CODE_DIFF_VIEW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decompileResultsSet(DecompileData leftDecompileResults,
|
||||
DecompileData rightDecompileResults) {
|
||||
|
||||
if ((leftDecompileResults == null) || (rightDecompileResults == null) ||
|
||||
(leftDecompileResults.getFunction() == null) ||
|
||||
(rightDecompileResults.getFunction() == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
decompileDataDiff = new DecompileDataDiff(leftDecompileResults, rightDecompileResults);
|
||||
determineDecompilerDifferences();
|
||||
}
|
||||
|
||||
List<TokenBin> getHighBins() {
|
||||
return decompilerFieldPanelCoordinator.getHighBins();
|
||||
}
|
||||
|
||||
private void determineDecompilerDifferences() {
|
||||
if (decompileDataDiff == null) {
|
||||
return;
|
||||
}
|
||||
DetermineDecompilerDifferencesTask task =
|
||||
new DetermineDecompilerDifferencesTask(decompileDataDiff, isMatchingConstantsExactly,
|
||||
leftHighlightController, rightHighlightController, decompilerFieldPanelCoordinator);
|
||||
|
||||
task.addTaskListener(new TaskListener() {
|
||||
|
||||
@Override
|
||||
public void taskCompleted(Task completedTask) {
|
||||
// Does this need anything here?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void taskCancelled(Task cancelledTask) {
|
||||
// Does this need anything here?
|
||||
}
|
||||
});
|
||||
|
||||
new TaskLauncher(task, getComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createActions() {
|
||||
super.createActions();
|
||||
toggleExactConstantMatchingAction = new MyToggleExactConstantMatching(getClass().getName());
|
||||
compareFuncsAction = new CompareFuncsFromMatchedTokensAction(this, tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DockingAction[] getActions() {
|
||||
DockingAction[] parentActions = super.getActions();
|
||||
DockingAction[] myActions =
|
||||
new DockingAction[] { toggleExactConstantMatchingAction, compareFuncsAction };
|
||||
DockingAction[] allActions = new DockingAction[parentActions.length + myActions.length];
|
||||
System.arraycopy(parentActions, 0, allActions, 0, parentActions.length);
|
||||
System.arraycopy(myActions, 0, allActions, parentActions.length, myActions.length);
|
||||
return allActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateActionEnablement() {
|
||||
// Need to enable/disable toolbar button.
|
||||
toggleExactConstantMatchingAction.setEnabled(isVisible());
|
||||
}
|
||||
|
||||
public class MyToggleExactConstantMatching extends ToggleDockingAction {
|
||||
|
||||
private final Icon EXACT_CONSTANT_MATCHING_ICON = new GIcon("icon.base.source.c");
|
||||
private final Icon NO_EXACT_CONSTANT_MATCHING_ICON =
|
||||
new MultiIcon(EXACT_CONSTANT_MATCHING_ICON, Icons.NOT_ALLOWED_ICON);
|
||||
|
||||
/**
|
||||
* Creates an action for toggling exact constant matching in the code diff's
|
||||
* dual decompiler.
|
||||
* @param owner the owner of this action (typically the provider).
|
||||
*/
|
||||
public MyToggleExactConstantMatching(String owner) {
|
||||
super("Toggle Exact Constant Matching", owner);
|
||||
|
||||
this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles"));
|
||||
|
||||
setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" +
|
||||
"be exactly the same value to be a match\n" + "in the " + CODE_DIFF_VIEW + "."));
|
||||
setSelected(false);
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (context instanceof DualDecompilerActionContext) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
isMatchingConstantsExactly = !isSelected();
|
||||
if (DecompilerDiffCodeComparisonPanel.this.isVisible()) {
|
||||
DecompilerDiffCodeComparisonPanel.this.determineDecompilerDifferences();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(boolean selected) {
|
||||
getToolBarData().setIcon(
|
||||
selected ? NO_EXACT_CONSTANT_MATCHING_ICON : EXACT_CONSTANT_MATCHING_ICON);
|
||||
super.setSelected(selected);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CodeDiffFieldPanelCoordinator createFieldPanelCoordinator() {
|
||||
CodeDiffFieldPanelCoordinator coordinator = new CodeDiffFieldPanelCoordinator(this);
|
||||
if (decompileDataDiff != null) {
|
||||
TaskBuilder.withRunnable(monitor -> {
|
||||
try {
|
||||
coordinator.replaceDecompileDataDiff(decompileDataDiff,
|
||||
isMatchingConstantsExactly, monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
coordinator.clearLineNumberPairing();
|
||||
}
|
||||
}).setTitle("Initializing Code Compare").launchNonModal();
|
||||
}
|
||||
return coordinator;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DetermineDecompilerDifferencesTask extends Task {
|
||||
|
||||
private boolean matchConstantsExactly;
|
||||
|
||||
private DiffClangHighlightController leftHighlightController;
|
||||
private DiffClangHighlightController rightHighlightController;
|
||||
|
||||
private DecompileDataDiff decompileDataDiff;
|
||||
|
||||
private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator;
|
||||
|
||||
public DetermineDecompilerDifferencesTask(DecompileDataDiff decompileDataDiff,
|
||||
boolean matchConstantsExactly, DiffClangHighlightController leftHighlightController,
|
||||
DiffClangHighlightController rightHighlightController,
|
||||
CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator) {
|
||||
|
||||
super("Mapping C Tokens Between Functions", true, true, true);
|
||||
this.decompileDataDiff = decompileDataDiff;
|
||||
this.matchConstantsExactly = matchConstantsExactly;
|
||||
this.leftHighlightController = leftHighlightController;
|
||||
this.rightHighlightController = rightHighlightController;
|
||||
this.decompilerFieldPanelCoordinator = decompilerFieldPanelCoordinator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
monitor.setMessage(
|
||||
(matchConstantsExactly ? "Function Token Mapping By Matching Constants Exactly..."
|
||||
: "Function Token Mapping WITHOUT Matching Constants Exactly..."));
|
||||
try {
|
||||
determineDifferences(monitor);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// User Cancelled.
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void determineDifferences(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
List<TokenBin> highBins = decompileDataDiff.getTokenMap(matchConstantsExactly, monitor);
|
||||
Set<ClangToken> leftHighlightTokenSet =
|
||||
decompileDataDiff.getLeftHighlightTokenSet(matchConstantsExactly, monitor);
|
||||
Set<ClangToken> rightHighlightTokenSet =
|
||||
decompileDataDiff.getRightHighlightTokenSet(matchConstantsExactly, monitor);
|
||||
|
||||
leftHighlightController.setDiffHighlights(highBins, leftHighlightTokenSet);
|
||||
rightHighlightController.setDiffHighlights(highBins, rightHighlightTokenSet);
|
||||
|
||||
decompilerFieldPanelCoordinator.replaceDecompileDataDiff(decompileDataDiff,
|
||||
matchConstantsExactly, monitor);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import generic.theme.GColor;
|
||||
import ghidra.app.decompiler.ClangSyntaxToken;
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.app.decompiler.component.*;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.util.ColorUtils;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Class to handle Function Difference highlights for a decompiled function.
|
||||
*/
|
||||
|
||||
public class DiffClangHighlightController extends LocationClangHighlightController
|
||||
implements DiffClangHighlightListener {
|
||||
|
||||
private Set<ClangToken> diffTokenSet = new HashSet<>();
|
||||
private ClangToken locationToken;
|
||||
private TokenBin locationTokenBin;
|
||||
private List<TokenBin> highlightBins;
|
||||
private List<DiffClangHighlightListener> listenerList = new ArrayList<>();
|
||||
private TokenBin matchingTokenBin;
|
||||
private DecompilerCodeComparisonOptions comparisonOptions;
|
||||
|
||||
public DiffClangHighlightController(DecompilerCodeComparisonOptions comparisonOptions) {
|
||||
this.comparisonOptions = comparisonOptions;
|
||||
}
|
||||
|
||||
public void clearDiffHighlights() {
|
||||
doClearDiffHighlights();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void doClearDiffHighlights() {
|
||||
ClangToken[] array = diffTokenSet.toArray(new ClangToken[diffTokenSet.size()]);
|
||||
for (ClangToken clangToken : array) {
|
||||
clearDiffHighlight(clangToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDiffHighlight(ClangToken clangToken) {
|
||||
Color highlight = clangToken.getHighlight();
|
||||
if (highlight != null && highlight.equals(comparisonOptions.getDiffHighlightColor())) {
|
||||
clangToken.setHighlight(null);
|
||||
}
|
||||
diffTokenSet.remove(clangToken);
|
||||
}
|
||||
|
||||
private void clearNonDiffHighlight(ClangToken clangToken) {
|
||||
if (diffTokenSet.contains(clangToken)) {
|
||||
clangToken.setHighlight(comparisonOptions.getDiffHighlightColor());
|
||||
}
|
||||
else {
|
||||
clangToken.setHighlight(null);
|
||||
}
|
||||
if (clangToken.isMatchingToken()) {
|
||||
clangToken.setMatchingToken(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDiffHighlights(List<TokenBin> highlightBins, Set<ClangToken> tokenSet) {
|
||||
this.highlightBins = highlightBins;
|
||||
doClearDiffHighlights();
|
||||
for (ClangToken clangToken : tokenSet) {
|
||||
clangToken.setHighlight(comparisonOptions.getDiffHighlightColor());
|
||||
diffTokenSet.add(clangToken);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fieldLocationChanged(FieldLocation location, Field field, EventTrigger trigger) {
|
||||
|
||||
if (!(field instanceof ClangTextField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the token for the location so we can highlight its token bin.
|
||||
// Also we will use it when notifying the other panel to highlight.
|
||||
ClangToken tok = ((ClangTextField) field).getToken(location);
|
||||
if (SystemUtilities.isEqual(locationToken, tok)) {
|
||||
return; // Current location's token hasn't changed.
|
||||
}
|
||||
|
||||
// Undo any highlight of the previous matching tokenBin.
|
||||
if (matchingTokenBin != null && matchingTokenBin.getMatch() != null) {
|
||||
clearTokenBinHighlight(matchingTokenBin.getMatch());
|
||||
matchingTokenBin = null;
|
||||
}
|
||||
|
||||
clearCurrentLocationHighlight();
|
||||
|
||||
clearPrimaryHighlights();
|
||||
addPrimaryHighlight(tok, defaultHighlightColor);
|
||||
if (tok instanceof ClangSyntaxToken) {
|
||||
List<ClangToken> tokens = addPrimaryHighlightToTokensForParenthesis(
|
||||
(ClangSyntaxToken) tok, defaultParenColor);
|
||||
reHighlightDiffs(tokens);
|
||||
addBraceHighlight((ClangSyntaxToken) tok, defaultParenColor);
|
||||
}
|
||||
|
||||
TokenBin tokenBin = null;
|
||||
if (tok != null) {
|
||||
Color highlightColor = comparisonOptions.getFocusedTokenIneligibleHighlightColor(); // Don't know
|
||||
if (highlightBins != null) {
|
||||
tokenBin = TokenBin.getBinContainingToken(highlightBins, tok);
|
||||
if (tokenBin != null) {
|
||||
if (tokenBin.getMatch() != null) {
|
||||
highlightColor = comparisonOptions.getFocusedTokenMatchHighlightColor();
|
||||
}
|
||||
else if (tokenBin.getMatch() == null) {
|
||||
highlightColor = comparisonOptions.getFocusedTokenUnmatchedHighlightColor();
|
||||
}
|
||||
else {
|
||||
// All the tokens that didn't fall into the "has a match" or "no match"
|
||||
// categories above are in a single token bin.
|
||||
// We don't want all these highlighted at the same time, so set the
|
||||
// tokenBin to null. By doing this, only the current token gets highlighted.
|
||||
tokenBin = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
locationToken = tok;
|
||||
locationTokenBin = tokenBin;
|
||||
if (tokenBin == null) {
|
||||
addPrimaryHighlight(tok, highlightColor);
|
||||
}
|
||||
else {
|
||||
addTokenBinHighlight(tokenBin, highlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify other decompiler panel highlight controller we have a new location token.
|
||||
for (DiffClangHighlightListener listener : listenerList) {
|
||||
listener.locationTokenChanged(tok, tokenBin);
|
||||
}
|
||||
}
|
||||
|
||||
private void reHighlightDiffs(List<ClangToken> tokenList) {
|
||||
Color averageColor =
|
||||
ColorUtils.blend(defaultParenColor, comparisonOptions.getDiffHighlightColor(), 0.5);
|
||||
for (ClangToken clangToken : tokenList) {
|
||||
if (diffTokenSet.contains(clangToken)) {
|
||||
clangToken.setHighlight(averageColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCurrentLocationHighlight() {
|
||||
if (locationTokenBin != null) {
|
||||
clearTokenBinHighlight(locationTokenBin);
|
||||
locationTokenBin = null;
|
||||
locationToken = null;
|
||||
}
|
||||
if (locationToken != null) {
|
||||
clearNonDiffHighlight(locationToken);
|
||||
locationToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void addTokenBinHighlight(TokenBin tokenBin, Color highlightColor) {
|
||||
for (ClangToken token : tokenBin) {
|
||||
addPrimaryHighlight(token, highlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearTokenBinHighlight(TokenBin tokenBin) {
|
||||
for (ClangToken token : tokenBin) {
|
||||
clearNonDiffHighlight(token);
|
||||
}
|
||||
}
|
||||
|
||||
private void doClearHighlights(TokenHighlights tokens) {
|
||||
List<ClangToken> clangTokens =
|
||||
CollectionUtils.asStream(tokens).map(ht -> ht.getToken()).collect(Collectors.toList());
|
||||
for (ClangToken clangToken : clangTokens) {
|
||||
clearNonDiffHighlight(clangToken);
|
||||
}
|
||||
tokens.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearPrimaryHighlights() {
|
||||
doClearHighlights(getPrimaryHighlights());
|
||||
}
|
||||
|
||||
public boolean addListener(DiffClangHighlightListener listener) {
|
||||
return listenerList.add(listener);
|
||||
}
|
||||
|
||||
public boolean removeListener(DiffClangHighlightListener listener) {
|
||||
return listenerList.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void locationTokenChanged(ClangToken tok, TokenBin tokenBin) {
|
||||
clearCurrentLocationHighlight();
|
||||
|
||||
// The token Changed in our other matching DiffClangHighlightController
|
||||
highlightMatchingToken(tok, tokenBin);
|
||||
}
|
||||
|
||||
private void highlightMatchingToken(ClangToken tok, TokenBin tokenBin) {
|
||||
// Undo any highlight of the previous matching tokenBin.
|
||||
if (matchingTokenBin != null && matchingTokenBin.getMatch() != null) {
|
||||
clearTokenBinHighlight(matchingTokenBin.getMatch());
|
||||
}
|
||||
|
||||
// Highlight the new matching tokenBin.
|
||||
if (tokenBin != null && tokenBin.getMatch() != null) {
|
||||
addTokenBinHighlight(tokenBin.getMatch(),
|
||||
comparisonOptions.getFocusedTokenMatchHighlightColor());
|
||||
}
|
||||
|
||||
matchingTokenBin = tokenBin;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
|
||||
public interface DiffClangHighlightListener {
|
||||
|
||||
/**
|
||||
* Notifier that the current location changed to the specified token.
|
||||
* @param tok the token
|
||||
* @param tokenBin the bin which contains the token. Otherwise, null.
|
||||
*/
|
||||
public void locationTokenChanged(ClangToken tok, TokenBin tokenBin);
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/* ###
|
||||
* 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.codecompare;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
|
||||
record TokenPair(ClangToken leftToken, ClangToken rightToken) {
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.codecompare.graphanalysis.Pinning.Side;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.program.model.pcode.PcodeBlockBasic;
|
||||
|
||||
/**
|
||||
* A control-flow graph of a function for computing n-grams (CtrlNGram) that can be matched
|
||||
* with another function. It mirrors the control-flow graph in HighFunction, but vertices,
|
||||
* CtrlVertex, can have control-flow specific n-grams attached.
|
||||
*/
|
||||
public class CtrlGraph {
|
||||
|
||||
Side side; // Which of the two functions being compared
|
||||
HighFunction hfunc; // The control-flow graph from the decompiler being mirrored
|
||||
Map<PcodeBlockBasic, CtrlVertex> blockToVertex; // Map from PcodeBlockBasic to corresponding CtrlVertex
|
||||
ArrayList<CtrlVertex> nodeList; // The list of nodes (basic blocks) in the graph
|
||||
|
||||
/**
|
||||
* Construct the control-flow graph, given a HighFunction.
|
||||
* @param side is 0 or 1 indicating which of the two functions being compared
|
||||
* @param hfunct is the decompiler produced HighFunction
|
||||
*/
|
||||
public CtrlGraph(Side side, HighFunction hfunct) {
|
||||
this.side = side;
|
||||
this.hfunc = hfunct;
|
||||
this.nodeList = new ArrayList<>();
|
||||
this.blockToVertex = new HashMap<>();
|
||||
ArrayList<PcodeBlockBasic> blockList = this.hfunc.getBasicBlocks();
|
||||
|
||||
// Create the vertices from basic blocks
|
||||
int uidCounter = 0;
|
||||
for (PcodeBlockBasic curBlock : blockList) {
|
||||
CtrlVertex temp = new CtrlVertex(curBlock, uidCounter++, this);
|
||||
this.blockToVertex.put(curBlock, temp);
|
||||
this.nodeList.add(temp);
|
||||
}
|
||||
|
||||
// Make the edges of the graph
|
||||
for (PcodeBlockBasic curBlock : blockList) {
|
||||
CtrlVertex curVert = this.blockToVertex.get(curBlock);
|
||||
for (int i = 0; i < curBlock.getOutSize(); i++) {
|
||||
PcodeBlockBasic neighborBlock = (PcodeBlockBasic) curBlock.getOut(i);
|
||||
CtrlVertex neighborVert = this.blockToVertex.get(neighborBlock);
|
||||
neighborVert.sources.add(curVert);
|
||||
curVert.sinks.add(neighborVert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For every node in the graph, clear calculated n-grams.
|
||||
*/
|
||||
public void clearNGrams() {
|
||||
for (CtrlVertex node : nodeList) {
|
||||
node.clearNGrams();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra distinguishing color to the 0-gram of each control-flow vertex.
|
||||
*/
|
||||
public void addEdgeColor() {
|
||||
for (CtrlVertex vert : nodeList) {
|
||||
vert.addEdgeColor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate n-gram lists for every node. We generate two types of n-grams. One walking
|
||||
* back from the root through sources, and the other walking forward from the root through sinks.
|
||||
* @param numNGrams is the number of n-grams to generate per node
|
||||
*/
|
||||
public void makeNGrams(int numNGrams) {
|
||||
int sourceSize = (numNGrams - 1) / 2 + 1;
|
||||
for (int i = 0; i < sourceSize; ++i) {
|
||||
for (CtrlVertex node : nodeList) {
|
||||
node.nextNGramSource(i); // Construct (n+1)-gram from existing n-gram
|
||||
}
|
||||
}
|
||||
for (CtrlVertex node : nodeList) {
|
||||
node.nextNGramSink(0); // Produces index = (sourceSize + 1)
|
||||
}
|
||||
for (int i = sourceSize + 1; i < numNGrams - 1; ++i) {
|
||||
for (CtrlVertex node : nodeList) {
|
||||
node.nextNGramSink(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
/**
|
||||
* N-gram hash on the control-flow graph rooted at specific node {@link CtrlVertex}.
|
||||
* The n-gram depth is the maximum number of (backward) edge traversals from the root
|
||||
* node to any other node involved in the hash. The n-gram weight is the total number of
|
||||
* nodes involved in the hash. The n-gram sorts with bigger weights first so that
|
||||
* n-grams involving more nodes are paired first.
|
||||
*/
|
||||
public class CtrlNGram {
|
||||
int weight; // The number of nodes involved in this hash
|
||||
int depth; // The maximum distance between nodes in this n-gram set
|
||||
int hash; // The hash
|
||||
CtrlVertex root; // The root node of the n-gram
|
||||
|
||||
/**
|
||||
* Construct a control-flow n-gram.
|
||||
* @param node is the root control-flow node from which the n-gram is computed
|
||||
* @param weight is the number of nodes involved in computing the n-gram
|
||||
* @param depth is the maximum distance between nodes involved in the n-gram
|
||||
* @param hash is the hash value for the n-gram
|
||||
*/
|
||||
public CtrlNGram(CtrlVertex node, int weight, int depth, int hash) {
|
||||
this.depth = depth;
|
||||
this.weight = weight;
|
||||
this.hash = hash;
|
||||
this.root = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the hash of this n-gram with another. The weight and depth of the hashes must also
|
||||
* be equal. The node(s) underlying the n-gram may be different.
|
||||
* @param other is the other n-gram
|
||||
* @return true if the hashes are the same
|
||||
*/
|
||||
public boolean equalHash(CtrlNGram other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
// Compare just the hash data
|
||||
if (weight == other.weight && depth == other.depth && hash == other.hash) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this and another n-gram are rooted in different control-flow graphs
|
||||
* @param other is the other n-gram to compare
|
||||
* @return true if the n-grams are from different graphs
|
||||
*/
|
||||
public boolean graphsDiffer(CtrlNGram other) {
|
||||
return (root.graph.side != other.root.graph.side);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import ghidra.program.model.pcode.PcodeBlockBasic;
|
||||
|
||||
/**
|
||||
* A basic block in the control-flow graph of a function as produced by the Decompiler.
|
||||
* A node stores references to immediate incoming nodes (sources). The node also stores hashes of n-grams
|
||||
* {@link CtrlNGram} involving this node, where an n-gram is a set of adjacent nodes out to a depth of n.
|
||||
*/
|
||||
public class CtrlVertex {
|
||||
private PcodeBlockBasic block; // Underlying basic block from the decompiler
|
||||
int uid; // A unique id
|
||||
ArrayList<CtrlVertex> sources; // List of blocks flowing into this block
|
||||
ArrayList<CtrlVertex> sinks; // List of blocks this block flows into
|
||||
ArrayList<CtrlNGram> ngrams; // A list of n-grams (hashes of nearest neighbors)
|
||||
CtrlGraph graph; // The control-flow graph owning this node
|
||||
|
||||
/**
|
||||
* Construct a control-flow vertex from a basic block.
|
||||
* @param blk is the basic block
|
||||
* @param id is a unique id to assign to the node
|
||||
* @param grph is the graph that the node is part of
|
||||
*/
|
||||
public CtrlVertex(PcodeBlockBasic blk, int id, CtrlGraph grph) {
|
||||
this.block = blk;
|
||||
this.uid = id * 2 + grph.side.getValue();
|
||||
this.sources = new ArrayList<>();
|
||||
this.sinks = new ArrayList<>();
|
||||
this.ngrams = new ArrayList<>();
|
||||
int hash = depthZeroHash(0);
|
||||
ngrams.add(new CtrlNGram(this, 1, 0, hash));
|
||||
this.graph = grph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hash of the meta-data associated with the node, not including edges.
|
||||
* This is effectively a 0-gram of the node. It hashes info about the number of in
|
||||
* and out edges to this node, but nothing else specific about neighbors.
|
||||
* @param flavor is an extra context specific value to be included in the hash
|
||||
* @return the hash
|
||||
*/
|
||||
int depthZeroHash(int flavor) {
|
||||
int encoding = 1; // Initialize
|
||||
encoding = Pinning.hashTwo(encoding, block.getInSize());
|
||||
encoding = Pinning.hashTwo(encoding, block.getOutSize());
|
||||
encoding = Pinning.hashTwo(encoding, flavor);
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and store a new n-gram by combining existing (n-1)-grams from sources.
|
||||
* @param index is the index of the current, already computed, (n-1)-gram to recurse on
|
||||
*/
|
||||
void nextNGramSource(int index) {
|
||||
int nextSize = 1;
|
||||
int masterSourceHash = 0;
|
||||
for (CtrlVertex neighbor : sources) { // Assemble hashes from sources
|
||||
CtrlNGram gram = neighbor.ngrams.get(index);
|
||||
masterSourceHash += gram.hash; // Combine neighbors (n-1)-gram
|
||||
nextSize += gram.weight; // Running tally of the number of nodes in the hash
|
||||
}
|
||||
|
||||
int nextEntry = ngrams.get(0).hash;
|
||||
nextEntry = Pinning.hashTwo(nextEntry, masterSourceHash);
|
||||
ngrams.add(new CtrlNGram(this, nextSize, ngrams.get(index).depth + 1, nextEntry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and store a new n-gram by combining existing (n-1)-grams from sinks.
|
||||
* @param index is the index of the current, already computed, (n-1)-gram to recurse on
|
||||
*/
|
||||
void nextNGramSink(int index) {
|
||||
int nextSize = 1;
|
||||
int masterSourceHash = 0xfabcd; // Starting value to distinguish sinks
|
||||
for (CtrlVertex neighbor : sinks) { // Assemble hashes from sources
|
||||
CtrlNGram gram = neighbor.ngrams.get(index);
|
||||
masterSourceHash += gram.hash; // Combine neighbors (n-1)-gram
|
||||
nextSize += gram.weight; // Running tally of the number of nodes in the hash
|
||||
}
|
||||
|
||||
int nextEntry = ngrams.get(0).hash;
|
||||
nextEntry = Pinning.hashTwo(nextEntry, masterSourceHash);
|
||||
ngrams.add(new CtrlNGram(this, nextSize, ngrams.get(index).depth + 1, nextEntry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some additional color to the 0-gram hash for this node.
|
||||
* If the node has exactly 1 incoming edge, hash in the index of that edge,
|
||||
* i.e. the position of that edge within the last of sink edges of the parent vertex.
|
||||
* This distinguishes the node as either the true or false action after a conditional branch, or
|
||||
* by associating the node with a particular case of a switch branch.
|
||||
*/
|
||||
void addEdgeColor() {
|
||||
if (sources.size() == 1) {
|
||||
CtrlNGram zeroGram = ngrams.get(0);
|
||||
CtrlVertex src = sources.get(0);
|
||||
int edgeColor;
|
||||
for (edgeColor = 0; edgeColor < src.sinks.size(); ++edgeColor) {
|
||||
if (src.sinks.get(edgeColor) == this) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
zeroGram.hash = Pinning.hashTwo(zeroGram.hash, edgeColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove everything except the 0-gram
|
||||
*/
|
||||
public void clearNGrams() {
|
||||
while (ngrams.size() > 1) {
|
||||
ngrams.remove(ngrams.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recompute the 0-gram, adding some additional salt to the hash
|
||||
* @param flavor is the salt value to add
|
||||
*/
|
||||
public void setZeroGram(int flavor) {
|
||||
int hash = depthZeroHash(flavor);
|
||||
ngrams.get(0).hash = hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String result = Integer.toString(uid);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.codecompare.graphanalysis.Pinning.Side;
|
||||
import ghidra.program.model.pcode.*;
|
||||
|
||||
/**
|
||||
* A data-flow graph of a function for computing n-grams {@link DataNGram} that can be matched
|
||||
* with another function. The graph mirrors the HighFunction data-flow graph but unifies
|
||||
* a Varnode and PcodeOp into a single node type (DataVertex) that can have n-grams attached.
|
||||
* This graph can be modified relative to HighFunction to facilitate matching.
|
||||
*/
|
||||
public class DataGraph {
|
||||
|
||||
/**
|
||||
* Helper class for associating a DataVertex with another DataVertex.
|
||||
* To distinguish multiple things associated with one DataVertex, an optional
|
||||
* slot indicates through which input slot the association is made.
|
||||
*/
|
||||
public static class Associate {
|
||||
DataVertex node; // The vertex having associations
|
||||
int slot; // The input slot through which the association is made
|
||||
|
||||
public Associate(DataVertex n, int sl) {
|
||||
node = n;
|
||||
slot = sl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Associate)) {
|
||||
return false;
|
||||
}
|
||||
Associate other = (Associate) obj;
|
||||
if (node.uid != other.node.uid) {
|
||||
return false;
|
||||
}
|
||||
if (slot != other.slot) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int val = node.hashCode();
|
||||
val = val * 31 + slot;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
Side side; // Which of two functions being compared
|
||||
HighFunction hfunc; // The data-flow graph from the decompiler being mirrored
|
||||
ArrayList<DataVertex> nodeList; // Vertices in this graph
|
||||
Map<PcodeOpAST, DataVertex> opToVert; // Map from PcodeOps to corresponding DataVertex
|
||||
Map<VarnodeAST, DataVertex> vnToVert; // Map from Varnodes to corresponding DataVertex
|
||||
Map<Associate, ArrayList<DataVertex>> associates; // Vertices that should get matched together
|
||||
boolean constCaring; // True if constant values are factored into n-gram hashes
|
||||
boolean ramCaring; // True if local/global is factored into n-gram hashes
|
||||
boolean sizeCollapse; // True if big Varnodes are hashed as if they were 4 bytes
|
||||
int pointerSize; // Number of bytes in a (default) pointer
|
||||
long pointerMin; // Minimum offset to consider a pointer
|
||||
|
||||
/**
|
||||
* Construct the data-flow graph, given a HighFunction. Configuration parameters for later
|
||||
* n-gram generation is given.
|
||||
* @param side is 0 or 1 indicating which of the two functions being compared
|
||||
* @param hfunc is the decompiler produced HighFunction
|
||||
* @param constCaring is true if n-grams should take into account exact constant values
|
||||
* @param ramCaring is true if n-grams should distinguish between local and global variables
|
||||
* @param castCollapse is true if CAST operations should not be included in n-gram calculations
|
||||
* @param sizeCollapse is true if variable sizes larger than 4 should be treated as size 4
|
||||
*/
|
||||
public DataGraph(Side side, HighFunction hfunc, boolean constCaring, boolean ramCaring,
|
||||
boolean castCollapse, boolean sizeCollapse) {
|
||||
this.side = side;
|
||||
this.constCaring = constCaring;
|
||||
this.ramCaring = ramCaring;
|
||||
this.sizeCollapse = sizeCollapse;
|
||||
|
||||
pointerSize = hfunc.getFunction().getProgram().getDefaultPointerSize();
|
||||
pointerMin = (pointerSize < 3) ? 0xff : 0xffff;
|
||||
ArrayList<DataVertex> ptrsubs = new ArrayList<>();
|
||||
ArrayList<DataVertex> casts = new ArrayList<>();
|
||||
|
||||
//Initialize the data inside.
|
||||
this.nodeList = new ArrayList<>();
|
||||
this.hfunc = hfunc;
|
||||
this.opToVert = new HashMap<>();
|
||||
this.vnToVert = new HashMap<>();
|
||||
this.associates = new HashMap<>();
|
||||
|
||||
int uidCounter = 0; // Counter for assigning unique ids
|
||||
// Bring in all the Varnodes as vertices
|
||||
Iterator<VarnodeAST> vnIter = this.hfunc.locRange();
|
||||
while (vnIter.hasNext()) {
|
||||
VarnodeAST currentVn = vnIter.next();
|
||||
if (currentVn.getDef() == null) {
|
||||
if (currentVn.hasNoDescend()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
DataVertex temp = new DataVertex(currentVn, this, uidCounter++);
|
||||
this.nodeList.add(temp);
|
||||
this.vnToVert.put(currentVn, temp);
|
||||
}
|
||||
// Bring in all the PcodeOps as vertices
|
||||
Iterator<PcodeOpAST> opIter = this.hfunc.getPcodeOps();
|
||||
while (opIter.hasNext()) {
|
||||
PcodeOpAST currentOp = opIter.next();
|
||||
DataVertex temp = new DataVertex(currentOp, this, uidCounter++);
|
||||
this.nodeList.add(temp);
|
||||
this.opToVert.put(currentOp, temp);
|
||||
if (currentOp.getOpcode() == PcodeOp.PTRSUB) {
|
||||
ptrsubs.add(temp);
|
||||
}
|
||||
if (currentOp.getOpcode() == PcodeOp.CAST) {
|
||||
casts.add(temp);
|
||||
}
|
||||
}
|
||||
|
||||
// Add edges to graph
|
||||
opIter = this.hfunc.getPcodeOps();
|
||||
while (opIter.hasNext()) {
|
||||
PcodeOpAST op = opIter.next();
|
||||
DataVertex node = this.opToVert.get(op);
|
||||
for (int i = 0; i < op.getNumInputs(); i++) {
|
||||
DataVertex sourceNode = this.vnToVert.get(op.getInput(i));
|
||||
if (sourceNode != null) {
|
||||
node.sources.add(sourceNode);
|
||||
sourceNode.sinks.add(node);
|
||||
}
|
||||
}
|
||||
DataVertex sinkNode = this.vnToVert.get(op.getOutput());
|
||||
if (sinkNode != null) {
|
||||
node.sinks.add(sinkNode);
|
||||
sinkNode.sources.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
eliminatePtrsubs(ptrsubs);
|
||||
|
||||
if (castCollapse) {
|
||||
eliminateCasts(casts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the HighFunction this data-flow graph was generated from
|
||||
*/
|
||||
public HighFunction getHighFunction() {
|
||||
return hfunc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given Varnode is a constant and not a pointer.
|
||||
* A constant is only considered a pointer if it has the size of a pointer and
|
||||
* the constant value is not too "small".
|
||||
* @param vn is the Varnode to check
|
||||
* @return true if the constant is constant and not a pointer
|
||||
*/
|
||||
public boolean isConstantNonPointer(Varnode vn) {
|
||||
if (!vn.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
if (vn.getSize() != pointerSize) {
|
||||
return true;
|
||||
}
|
||||
long off = vn.getOffset();
|
||||
return (off >= 0 && off <= pointerMin);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a PTRSUB operation represents a &DAT_#, its input (a constant) is propagated forward
|
||||
* to everything reading the PTRSUB, and the PTRSUB is eliminated.
|
||||
* @param ptrsubs is the list of PTRSUB vertices
|
||||
*/
|
||||
private void eliminatePtrsubs(ArrayList<DataVertex> ptrsubs) {
|
||||
for (DataVertex subop : ptrsubs) {
|
||||
DataVertex in0Node = subop.sources.get(0);
|
||||
if (in0Node.vn.isConstant() && (in0Node.vn.getOffset() == 0)) {
|
||||
DataVertex in1Node = subop.sources.get(1);
|
||||
DataVertex outNode = subop.sinks.get(0);
|
||||
in1Node.sinks.clear();
|
||||
replaceNodeInOutEdges(outNode, in1Node);
|
||||
in0Node.clearEdges();
|
||||
subop.clearEdges();
|
||||
outNode.clearEdges();
|
||||
makeAssociation(subop, outNode, in1Node, 0); // Attach subop and outNode -> in1Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CAST operations are isolated in the graph and either:
|
||||
* - The input replaces reads of the output, and the output is eliminated, OR
|
||||
* - The output is redefined by defining PcodeOp op of the input, and the input is eliminated.
|
||||
* @param casts is the list of CAST vertices
|
||||
*/
|
||||
private void eliminateCasts(ArrayList<DataVertex> casts) {
|
||||
for (DataVertex castNode : casts) {
|
||||
|
||||
DataVertex in = castNode.sources.get(0);
|
||||
DataVertex out = castNode.sinks.get(0);
|
||||
DataVertex assoc = null;
|
||||
int assocSlot = 0;
|
||||
if (out.sinks.size() == 1) {
|
||||
assoc = out.sinks.get(0); // Preferred node to associate with is the reading op
|
||||
// Generate distinguishing slot for associate based on input slot CAST feeds into
|
||||
for (assocSlot = 0; assocSlot < assoc.sources.size(); ++assocSlot) {
|
||||
if (assoc.sources.get(assocSlot) == out) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean outCast = true;
|
||||
if ((out.sinks.size() == 1 && out.vn.isUnique()) || in.sources.size() == 0) {
|
||||
outCast = false;
|
||||
}
|
||||
|
||||
if (outCast) {
|
||||
// PcodeOp defining CAST input, now defines CAST output
|
||||
// input is isolated
|
||||
DataVertex topOp = in.sources.get(0);
|
||||
topOp.sinks.clear();
|
||||
out.sources.clear();
|
||||
topOp.sinks.add(out);
|
||||
out.sources.add(topOp);
|
||||
in.clearEdges();
|
||||
if (assoc == null) {
|
||||
assoc = out;
|
||||
}
|
||||
makeAssociation(castNode, in, assoc, assocSlot);
|
||||
}
|
||||
else {
|
||||
// CAST input becomes input to descendants of CAST output
|
||||
// output is isolated
|
||||
removeInEdge(castNode, 0);
|
||||
replaceNodeInOutEdges(out, in);
|
||||
out.clearEdges();
|
||||
if (assoc == null) {
|
||||
assoc = in;
|
||||
}
|
||||
makeAssociation(castNode, out, assoc, assocSlot);
|
||||
}
|
||||
castNode.clearEdges();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate n-gram lists for every node.
|
||||
* @param numNGrams is the number of n-grams to generate per node
|
||||
*/
|
||||
public void makeNGrams(int numNGrams) {
|
||||
for (int i = 0; i < numNGrams - 1; ++i) {
|
||||
for (DataVertex node : nodeList) {
|
||||
node.nextNGramSource(i); // Construct (n+1)-gram from existing n-gram
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an association between an (op,var) node pair that has been removed from the graph, with
|
||||
* a node that remains in the graph.
|
||||
* @param op is the operation node being removed
|
||||
* @param var is the variable node being removed
|
||||
* @param assoc is the node to associate with
|
||||
* @param assocSlot is other distinguishing info about the association (incoming slot)
|
||||
*/
|
||||
private void makeAssociation(DataVertex op, DataVertex var, DataVertex assoc, int assocSlot) {
|
||||
Associate key = new Associate(assoc, assocSlot);
|
||||
ArrayList<DataVertex> assocList = associates.get(key);
|
||||
if (assocList == null) {
|
||||
assocList = new ArrayList<>();
|
||||
associates.put(key, assocList);
|
||||
}
|
||||
assocList.add(op);
|
||||
assocList.add(var);
|
||||
}
|
||||
|
||||
/**
|
||||
* All out edges of the given node, become out edges of a replacement node.
|
||||
* @param node is the given node
|
||||
* @param replacement is the node receiving the new out edges
|
||||
*/
|
||||
private void replaceNodeInOutEdges(DataVertex node, DataVertex replacement) {
|
||||
for (DataVertex outNode : node.sinks) {
|
||||
for (int i = 0; i < outNode.sources.size(); i++) {
|
||||
if (outNode.sources.get(i) == node) {
|
||||
outNode.sources.set(i, replacement);
|
||||
}
|
||||
}
|
||||
replacement.sinks.add(outNode);
|
||||
}
|
||||
node.sinks = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an edge between the given node and one of its inputs.
|
||||
* @param node is the given node
|
||||
* @param inEdge is the input edge
|
||||
*/
|
||||
private void removeInEdge(DataVertex node, int inEdge) {
|
||||
DataVertex inNode = node.sources.get(inEdge);
|
||||
int outEdge;
|
||||
for (outEdge = 0; outEdge < inNode.sinks.size(); ++outEdge) {
|
||||
if (inNode.sinks.get(outEdge) == node) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
node.sources.remove(inEdge);
|
||||
inNode.sinks.remove(outEdge);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump a string representation of the data-flow graph.
|
||||
* @param writer is the stream to write the string to
|
||||
* @throws IOException for problems with the stream
|
||||
*/
|
||||
public void dump(Writer writer) throws IOException {
|
||||
for (DataVertex vertex : nodeList) {
|
||||
writer.append(vertex.toString());
|
||||
writer.append('\n');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
/**
|
||||
* Sortable n-gram hash on the data-flow graph rooted at specific node {@link DataVertex}.
|
||||
* The n-gram depth is the maximum number of (backward) edge traversals from the root
|
||||
* node to any other node involved in the hash. The n-gram weight is the total number of
|
||||
* nodes involved in the hash. The n-gram sorts with bigger weights first so that
|
||||
* n-grams involving more nodes are paired first.
|
||||
*/
|
||||
public class DataNGram implements Comparable<DataNGram> {
|
||||
int weight; // The number of nodes involved in this hash
|
||||
int depth; // The maximum distance between nodes in this n-gram set
|
||||
int hash; // The hash
|
||||
DataVertex root; // The root node of the n-gram
|
||||
|
||||
/**
|
||||
* Construct a data-flow n-gram.
|
||||
* @param node is the root data-flow node from which the n-gram is computed
|
||||
* @param weight is the number of data-flow nodes involved in the n-gram
|
||||
* @param depth is the maximum distance between nodes involved in the n-gram
|
||||
* @param hash is the hash value for the n-gram
|
||||
*/
|
||||
public DataNGram(DataVertex node, int weight, int depth, int hash) {
|
||||
this.depth = depth;
|
||||
this.weight = weight;
|
||||
this.hash = hash;
|
||||
this.root = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DataNGram other) {
|
||||
if (this.weight > other.weight) { // Sort so that bigger weights come first
|
||||
return -1;
|
||||
}
|
||||
else if (this.weight < other.weight) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (this.depth > other.depth) { // Sort on depth
|
||||
return -1;
|
||||
}
|
||||
else if (this.depth < other.depth) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (this.hash > other.hash) { // Then sort on hash
|
||||
return -1;
|
||||
}
|
||||
else if (this.hash < other.hash) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (this.root.uid > other.root.uid) { // For equivalent hashes, sort based on the node id
|
||||
return -1;
|
||||
}
|
||||
else if (this.root.uid < other.root.uid) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Finally, sort on the graph owning the root node
|
||||
return other.root.graph.side.compareTo(this.root.graph.side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the hash of this n-gram with another. The weight and depth of the hashes must also
|
||||
* be equal. The node(s) underlying the n-gram may be different.
|
||||
* @param other is the other n-gram
|
||||
* @return true if the hashes are the same
|
||||
*/
|
||||
public boolean equalHash(DataNGram other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
// Compare just hash data
|
||||
if (this.weight == other.weight && this.depth == other.depth && this.hash == other.hash) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this and another n-gram are rooted in different data-flow graphs
|
||||
* @param other is the other n-gram to compare
|
||||
* @return true if the n-grams are from different graphs
|
||||
*/
|
||||
public boolean graphsDiffer(DataNGram other) {
|
||||
if (this.root.graph != other.root.graph) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("d=").append(depth);
|
||||
buffer.append(" h=").append(hash);
|
||||
buffer.append(" w=").append(weight);
|
||||
buffer.append(" vert=").append(root.uid);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import ghidra.program.model.pcode.*;
|
||||
|
||||
/**
|
||||
* A node in the data-flow graph of a function as produced by the Decompiler, represented by EITHER
|
||||
* a Varnode or PcodeOp. A node stores references to immediate incoming nodes (sources) and immediate
|
||||
* outgoing nodes (sinks). The node also stores hashes of n-grams involving this node, where
|
||||
* an n-gram is a set of adjacent nodes out to a depth of n.
|
||||
*/
|
||||
public class DataVertex {
|
||||
|
||||
int uid; // Unique identifying integer
|
||||
PcodeOpAST op; // The underlying PcodeOp (or null)
|
||||
VarnodeAST vn; // The underlying Varnode (or null)
|
||||
DataGraph graph; // The containing graph
|
||||
ArrayList<DataVertex> sources; // Nodes with an incoming edge
|
||||
ArrayList<DataVertex> sinks; // Nodes with an outgoing edge
|
||||
ArrayList<DataNGram> ngrams; // A list of n-grams (hashes of nearest neighbors) rooted at this node
|
||||
int passComplete; // Last pass for which this node was evaluated
|
||||
boolean paired; // Found a match for this node
|
||||
|
||||
/**
|
||||
* Construct node from a PcodeOp
|
||||
* @param myOp is the PcodeOp
|
||||
* @param myGraph is the graph owning the node
|
||||
* @param uniqueID is a unique id to assign to the node
|
||||
*/
|
||||
public DataVertex(PcodeOpAST myOp, DataGraph myGraph, int uniqueID) {
|
||||
op = myOp;
|
||||
vn = null;
|
||||
commonConstructor(myGraph, uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct node from a Varnode
|
||||
* @param myVn is the Varnode
|
||||
* @param myGraph is the graph owning the node
|
||||
* @param uniqueID is a unique id to assign to the node
|
||||
*/
|
||||
public DataVertex(VarnodeAST myVn, DataGraph myGraph, int uniqueID) {
|
||||
vn = myVn;
|
||||
op = null;
|
||||
commonConstructor(myGraph, uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize internals of the node. Allocate storage for edges and n-grams.
|
||||
* @param myGraph is the graph owning the node
|
||||
* @param uniqueID is a unique id to assign to the node
|
||||
*/
|
||||
private void commonConstructor(DataGraph myGraph, int uniqueID) {
|
||||
uid = uniqueID * 2 + myGraph.side.getValue();
|
||||
graph = myGraph;
|
||||
sources = new ArrayList<DataVertex>();
|
||||
sinks = new ArrayList<DataVertex>();
|
||||
ngrams = new ArrayList<DataNGram>();
|
||||
int hash = depthZeroHash();
|
||||
ngrams.add(new DataNGram(this, 1, 0, hash));
|
||||
paired = false;
|
||||
passComplete = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("uid=").append(uid).append(' ');
|
||||
if (op != null) {
|
||||
for (int i = 0; i < sinks.size(); ++i) {
|
||||
buffer.append('[').append(sinks.get(i).uid).append("] = ");
|
||||
}
|
||||
buffer.append(op.getMnemonic());
|
||||
for (int i = 0; i < sources.size(); ++i) {
|
||||
buffer.append(" [").append(sources.get(i).uid).append(']');
|
||||
}
|
||||
}
|
||||
else {
|
||||
buffer.append(vn.toString());
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any incoming or outgoing edges to/from this node
|
||||
*/
|
||||
void clearEdges() {
|
||||
sinks.clear();
|
||||
sources.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hash of the meta-data associated with the node, not including edges.
|
||||
* This is effectively a 0-gram of the node, collecting data about the node itself but
|
||||
* nothing about its neighbors. The hash for PcodeOp nodes, is just the opcode. For
|
||||
* Varnodes, the hash collects info about the size and the type of Varnode (local, global, constant).
|
||||
* @return the hash
|
||||
*/
|
||||
private int depthZeroHash() {
|
||||
int encoding = 0; // Initialize
|
||||
if (op != null) {
|
||||
if (op.getOpcode() == PcodeOp.PTRSUB) { // Replace PTRSUBs with INT_ADDs
|
||||
encoding = PcodeOp.INT_ADD;
|
||||
}
|
||||
else {
|
||||
encoding = op.getOpcode();
|
||||
}
|
||||
encoding |= 0xc0000000; // Bits indicating a PcodeOp specifically
|
||||
}
|
||||
else {
|
||||
VarnodeAST node = vn;
|
||||
//For Varnodes, the encoding needs to know whether the node is global or local and what
|
||||
// size is allocated to it.
|
||||
int ramCode = (graph.ramCaring ? 1 : 0) * (node.isPersistent() ? 1 : 0);
|
||||
int constCode = (node.isConstant() ? 1 : 0);
|
||||
int size = node.getSize();
|
||||
if (graph.sizeCollapse && size > 4) { // If sizeCollapse is on, treat sizes larger then 4 bytes
|
||||
size = 4; // the same as a size of 4
|
||||
}
|
||||
int sizeCode = ((size << 4) >> 4); // Make top 4 bits are clear
|
||||
|
||||
encoding |= (ramCode << 29);
|
||||
encoding |= (constCode << 28);
|
||||
encoding |= sizeCode;
|
||||
encoding |= (1 << 31);
|
||||
if (graph.constCaring && graph.isConstantNonPointer(node)) {
|
||||
// Only hash an exact constant value if it doesn't look like a pointer
|
||||
return Pinning.hashTwo(encoding, (int) node.getOffset());
|
||||
}
|
||||
}
|
||||
return Pinning.hashTwo(encoding, 0); // Hash the collected info
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and store a new n-gram by combining existing (n-1)-grams from sources.
|
||||
* @param index is the index of the current, already computed, (n-1)-gram to recurse on
|
||||
*/
|
||||
void nextNGramSource(int index) {
|
||||
int nextSize = 1;
|
||||
DataNGram zeroGram = ngrams.get(0); // 0-gram for this node
|
||||
int finalHash;
|
||||
if (isCommutative()) { // Commutative nodes have indistinguishable sources.
|
||||
finalHash = 0;
|
||||
for (DataVertex neighbor : sources) {
|
||||
DataNGram gram = neighbor.ngrams.get(index); // Immediate neighbor (n-1)-gram
|
||||
finalHash += gram.hash; // Combine hashes using a commutative operation
|
||||
nextSize += gram.weight; // Running tally of number of nodes in hash
|
||||
}
|
||||
finalHash = Pinning.hashTwo(zeroGram.hash, finalHash);
|
||||
}
|
||||
else {
|
||||
finalHash = zeroGram.hash;
|
||||
for (DataVertex neighbor : sources) {
|
||||
DataNGram gram = neighbor.ngrams.get(index); // Immedate neighbor (n-1)-gram
|
||||
finalHash = Pinning.hashTwo(finalHash, gram.hash); // Hash in, in order
|
||||
nextSize += gram.weight; // Running tally of number of nodes in hash
|
||||
}
|
||||
}
|
||||
|
||||
ngrams.add(new DataNGram(this, nextSize, ngrams.size(), finalHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this node represents a PcodeOp (as opposed to a Varnode) in the data-flow
|
||||
*/
|
||||
public boolean isOp() {
|
||||
return (op != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the underlying node a PcodeOp which takes commutative inputs?
|
||||
* @return true if the PcodeOp is commutative
|
||||
*/
|
||||
public boolean isCommutative() {
|
||||
if (op == null) {
|
||||
return false;
|
||||
}
|
||||
int opc = op.getOpcode();
|
||||
if (opc == PcodeOp.MULTIEQUAL) {
|
||||
return true; // For purposes of Pinning algorithm, treat MULTIEQUAL as commutative
|
||||
}
|
||||
return PcodeOp.isCommutative(opc);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,911 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import generic.hash.SimpleCRC32;
|
||||
import generic.stl.Pair;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||
import ghidra.program.model.pcode.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class Pinning {
|
||||
|
||||
private static final int NGRAM_DEPTH = 24; // The (default) n-gram depth
|
||||
|
||||
private DataGraph graphLeft; // Data-flow graph of the LEFT side function
|
||||
private DataGraph graphRight; // Data-flow graph of the RIGHT side function
|
||||
private CtrlGraph cgraphLeft; // Control-flow graph of the LEFT side function
|
||||
private CtrlGraph cgraphRight; // Control-flow graph of the RIGHT side function
|
||||
private Map<DataVertex, DataVertex> pinMap; // Map from LEFT side (data-flow) vertices to RIGHT side vertices
|
||||
private ArrayList<DataNGram> fragments; // n-grams of data-flow vertices, in preparation for matching
|
||||
private int pass; // Current pass being evaluated
|
||||
private Comparator<DataCtrl> compareHashes; // Comparator for CtrlNGram sort
|
||||
private Comparator<DataCtrl> compareWithinBlock; // Comparator for sorting within a basic block
|
||||
|
||||
/**
|
||||
* Labels for the two functions being compared by the Pinning algorithm
|
||||
*/
|
||||
public static enum Side {
|
||||
LEFT(0), RIGHT(1);
|
||||
|
||||
private int value; // Value for uid encodings
|
||||
|
||||
private Side(int val) {
|
||||
value = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the integer encoding of the side
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow vertex linked with an underlying control-flow n-gram.
|
||||
*/
|
||||
public static class DataCtrl {
|
||||
DataVertex dataVertex; // The data-flow vertex
|
||||
CtrlNGram ctrlNGram; // An n-gram of an underlying control-flow vertex
|
||||
|
||||
public DataCtrl(DataVertex data, CtrlNGram ctrl) {
|
||||
dataVertex = data;
|
||||
ctrlNGram = ctrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by control-flow n-gram, then by side.
|
||||
* Within this group, sort by block (multiple blocks can have the same hash),
|
||||
* then by PcodeOp order within the block.
|
||||
*/
|
||||
public static class CompareWithinBlock implements Comparator<DataCtrl> {
|
||||
@Override
|
||||
public int compare(DataCtrl o0, DataCtrl o1) {
|
||||
int hash0 = o0.ctrlNGram.hash;
|
||||
int hash1 = o1.ctrlNGram.hash;
|
||||
if (hash0 < hash1) {
|
||||
return -1;
|
||||
}
|
||||
if (hash0 > hash1) {
|
||||
return 1;
|
||||
}
|
||||
CtrlVertex o0Block = o0.ctrlNGram.root;
|
||||
CtrlVertex o1Block = o1.ctrlNGram.root;
|
||||
int res = o0Block.graph.side.compareTo(o1Block.graph.side);
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
if (o0Block.uid < o1Block.uid) {
|
||||
return -1;
|
||||
}
|
||||
if (o0Block.uid > o1Block.uid) {
|
||||
return 1;
|
||||
}
|
||||
PcodeOp op0 = o0.dataVertex.isOp() ? o0.dataVertex.op : o0.dataVertex.vn.getDef();
|
||||
PcodeOp op1 = o1.dataVertex.isOp() ? o1.dataVertex.op : o1.dataVertex.vn.getDef();
|
||||
int order0 = op0.getSeqnum().getOrder();
|
||||
int order1 = op1.getSeqnum().getOrder();
|
||||
if (order0 < order1) {
|
||||
return -1;
|
||||
}
|
||||
if (order0 > order1) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by control-flow hash, then by control-flow uid.
|
||||
* Higher weight, then higher depth, hashes come first.
|
||||
*/
|
||||
public static class CompareHashes implements Comparator<DataCtrl> {
|
||||
|
||||
@Override
|
||||
public int compare(DataCtrl o1, DataCtrl o2) {
|
||||
CtrlNGram o1gram = o1.ctrlNGram;
|
||||
CtrlNGram o2gram = o2.ctrlNGram;
|
||||
if (o1gram.weight != o2gram.weight) {
|
||||
return (o1gram.weight < o2gram.weight) ? 1 : -1; // Bigger weight first
|
||||
}
|
||||
if (o1gram.depth != o2gram.depth) {
|
||||
return (o1gram.depth < o2gram.depth) ? 1 : -1; // Bigger depth first
|
||||
}
|
||||
if (o1gram.hash != o2gram.hash) {
|
||||
return (o1gram.hash < o2gram.hash) ? -1 : 1;
|
||||
}
|
||||
int res = o1gram.root.graph.side.compareTo(o2gram.root.graph.side);
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
if (o1gram.root.uid != o2gram.root.uid) {
|
||||
return (o1gram.root.uid < o2gram.root.uid) ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a pinning between two HighFunction
|
||||
* @param hfuncLeft is the (LEFT side) HighFunction
|
||||
* @param hfuncRight is the (RIGHT side) HighFunction
|
||||
* @param ngramDepth is the number of n-grams to generate per node
|
||||
* @param constCaring is true if the pinning should take into account exact constant values
|
||||
* @param ramCaring is true if the pinning should distinguish between local and global variables
|
||||
* @param castCollapse is true if CAST operations should be ignored in the pinning
|
||||
* @param sizeCollapse is true if variable sizes larger than 4 should be treated as size 4
|
||||
* @param breakSym is true if symmetries should be paired arbitrarily
|
||||
* @param monitor is the TaskMonitor
|
||||
* @throws CancelledException if the user cancels the task
|
||||
*/
|
||||
public Pinning(HighFunction hfuncLeft, HighFunction hfuncRight, int ngramDepth,
|
||||
boolean constCaring, boolean ramCaring, boolean castCollapse, boolean sizeCollapse,
|
||||
boolean breakSym, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
compareHashes = new DataCtrl.CompareHashes();
|
||||
compareWithinBlock = new DataCtrl.CompareWithinBlock();
|
||||
pinMap = new HashMap<>();
|
||||
|
||||
graphLeft =
|
||||
new DataGraph(Side.LEFT, hfuncLeft, constCaring, ramCaring, castCollapse, sizeCollapse);
|
||||
graphRight = new DataGraph(Side.RIGHT, hfuncRight, constCaring, ramCaring, castCollapse,
|
||||
sizeCollapse);
|
||||
// Control-flow graphs are used to break ties when matching data-flow
|
||||
cgraphLeft = new CtrlGraph(Side.LEFT, hfuncLeft);
|
||||
cgraphRight = new CtrlGraph(Side.RIGHT, hfuncRight);
|
||||
|
||||
// Compute n-gram hashes
|
||||
graphLeft.makeNGrams(ngramDepth);
|
||||
graphRight.makeNGrams(ngramDepth);
|
||||
cgraphLeft.makeNGrams(ngramDepth);
|
||||
cgraphRight.makeNGrams(ngramDepth);
|
||||
|
||||
makeFragments(); // Put data-flow hashes in sorted list for matching
|
||||
doPinning(ngramDepth, breakSym, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update control-flow n-grams with matching info from previous passes, to help
|
||||
* distinguish between similar nodes.
|
||||
* @param ngramDepth is the maximum depth of n-gram being to generate
|
||||
*/
|
||||
private void updateCtrlHashes(int ngramDepth) {
|
||||
|
||||
cgraphLeft.clearNGrams();
|
||||
cgraphRight.clearNGrams();
|
||||
HashSet<CtrlVertex> seen = new HashSet<>();
|
||||
// Recalculate (some) control-flow 0-grams, based on matching data-flow in them
|
||||
for (DataVertex node : pinMap.keySet()) {
|
||||
if (!node.isOp()) {
|
||||
continue;
|
||||
}
|
||||
CtrlVertex cnode = dataToCtrl(node);
|
||||
if (!seen.contains(cnode)) {
|
||||
CtrlVertex csidekick = dataToCtrl(pinMap.get(node));
|
||||
seen.add(cnode);
|
||||
int flavor = cnode.uid; // Flavor to add to the 0-grams
|
||||
cnode.setZeroGram(flavor); // Recompute 0-gram
|
||||
csidekick.setZeroGram(flavor); // with matching flavor
|
||||
}
|
||||
}
|
||||
|
||||
cgraphLeft.makeNGrams(ngramDepth); // Recompute all n-grams, recursively including new 0-grams
|
||||
cgraphRight.makeNGrams(ngramDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Go back through data-flow vertices that were collapsed out of the original graphs.
|
||||
* Each collapsed DataVertex is associated to an uncollapsed vertex. If an uncollapsed vertex
|
||||
* has a match in the other graph also with associated collapsed vertices, we attempt
|
||||
* to pair the two sets of collapsed vertices, just based on PcodeOp opcodes.
|
||||
*/
|
||||
private void pinAssociates() {
|
||||
DataGraph.Associate associate1 = new DataGraph.Associate(null, 0);
|
||||
for (Entry<DataGraph.Associate, ArrayList<DataVertex>> entry : graphLeft.associates
|
||||
.entrySet()) {
|
||||
ArrayList<DataVertex> side0 = entry.getValue();
|
||||
associate1.node = pinMap.get(entry.getKey().node);
|
||||
if (associate1.node == null) {
|
||||
continue;
|
||||
}
|
||||
associate1.slot = entry.getKey().slot;
|
||||
ArrayList<DataVertex> side1 = graphRight.associates.get(associate1);
|
||||
if (side1 == null) {
|
||||
continue;
|
||||
}
|
||||
if (side0.size() != side1.size() || side0.size() > 4) {
|
||||
continue;
|
||||
}
|
||||
boolean matching = true;
|
||||
for (int i = 0; i < side0.size(); i += 2) {
|
||||
DataVertex op0 = side0.get(i);
|
||||
DataVertex op1 = side1.get(i);
|
||||
if (op0.op.getOpcode() != op1.op.getOpcode()) {
|
||||
matching = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matching) {
|
||||
for (int i = 0; i < side0.size(); ++i) {
|
||||
DataVertex v0 = side0.get(i);
|
||||
DataVertex v1 = side1.get(i);
|
||||
if (v0.paired || v1.paired) {
|
||||
continue;
|
||||
}
|
||||
establishMatch(v0, v1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the sorted list of n-gram hashes used to decide which nodes will get pinned.
|
||||
*/
|
||||
private void makeFragments() {
|
||||
fragments = new ArrayList<>();
|
||||
for (int side = 0; side < 2; side++) { // Collect n-grams from both sides
|
||||
DataGraph graph = (side == 0 ? graphLeft : graphRight);
|
||||
for (DataVertex node : graph.nodeList) { // for all data-flow vertices
|
||||
for (int d = 0; d < node.ngrams.size(); d++) { // and for every possible depth
|
||||
fragments.add(node.ngrams.get(d)); // into one list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the list by weight and hash so that possible matches are adjacent
|
||||
Collections.sort(fragments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of more than 2 DataNGrams with matching hash, try to distinguish the underlying
|
||||
* DataVertex objects by comparing CtrlNGrams associated with each DataVertex through its
|
||||
* containing CtrlVertex. If DataVertex pairs can be distinguished, they are added to pinMap.
|
||||
* @param matchList is the list of matching DataNGrams
|
||||
* @param useOrder is true if block order should be used to break ties
|
||||
* @param monitor is the TaskMonitor
|
||||
* @throws CancelledException if the user cancels the task
|
||||
*/
|
||||
private void breakTieWithCtrlFlow(ArrayList<DataNGram> matchList, boolean useOrder,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
DataNGram current = matchList.get(0);
|
||||
if (current.weight <= 1) {
|
||||
return; // Don't try to break ties on low weight n-grams
|
||||
}
|
||||
ArrayList<DataCtrl> cfragsList = new ArrayList<>();
|
||||
|
||||
// Create the list of control-flow n-grams, and set up a way to get back to the original DataVertex
|
||||
for (int j = 0; j < matchList.size(); j++) {
|
||||
monitor.checkCancelled();
|
||||
DataVertex tiedVertex = matchList.get(j).root;
|
||||
|
||||
// Mark the vertex as having been analyzed this pass. This prevents additional
|
||||
// rounds of tie breaking on the same set of nodes for lower depth n-grams. I.e.,
|
||||
// if vertices are indistinguishable at this depth, they will continue to be
|
||||
// indistinguishable at lower depths.
|
||||
tiedVertex.passComplete = pass;
|
||||
|
||||
// The vertex is guaranteed to have at least 1 source, as the weight > 1
|
||||
DataVertex opVertex = (current.root.isOp() ? tiedVertex : tiedVertex.sources.get(0));
|
||||
CtrlVertex ctiedVertex = dataToCtrl(opVertex);
|
||||
for (int d = 0; d < ctiedVertex.ngrams.size(); d++) {
|
||||
CtrlNGram nGram = ctiedVertex.ngrams.get(d);
|
||||
DataCtrl temp = new DataCtrl(tiedVertex, nGram); // Tie CtrlNGram to tiedVertex
|
||||
cfragsList.add(temp);
|
||||
}
|
||||
}
|
||||
|
||||
cfragsList.sort(compareHashes); // Sort list so that identical n-grams are adjacent
|
||||
int j = 0;
|
||||
while (j < cfragsList.size()) {
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
DataCtrl ccurrent = cfragsList.get(j);
|
||||
if (ccurrent.dataVertex.paired) { // If we've already paired DataVertex
|
||||
j++;
|
||||
continue; // Don't look at it again
|
||||
}
|
||||
|
||||
int jbar = j + 1;
|
||||
while (jbar < cfragsList.size()) { // Iterate to the first n-gram whose hash doesn't match
|
||||
DataCtrl cfuture = cfragsList.get(jbar);
|
||||
if (!ccurrent.ctrlNGram.equalHash(cfuture.ctrlNGram)) {
|
||||
break;
|
||||
}
|
||||
jbar++;
|
||||
}
|
||||
|
||||
if (jbar - j == 2) { // Exactly 2 matching n-gram hashes. Possible pair.
|
||||
DataCtrl cnext = cfragsList.get(j + 1);
|
||||
if (ccurrent.ctrlNGram.graphsDiffer(cnext.ctrlNGram)) {
|
||||
DataVertex temp0 = ccurrent.dataVertex;
|
||||
DataVertex temp1 = cnext.dataVertex;
|
||||
ngramPinner(temp0, temp1, current.depth);
|
||||
}
|
||||
}
|
||||
else if (useOrder && jbar - j > 2) {
|
||||
breakTieUsingOrder(cfragsList, j, jbar, current);
|
||||
}
|
||||
j = jbar;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a range of vertices that have a matching data n-gram and a matching control-flow n-gram
|
||||
* occur within a single pair of basic blocks. There must be an equal number of vertices
|
||||
* in one basic block on the LEFT and in one on the RIGHT. Additionally, the vertices must
|
||||
* either have no output edges or be MULTIEQUAL op nodes.
|
||||
* @param frags is the list of vertices associated with control-flow n-grams
|
||||
* @param start is the start of the matching range
|
||||
* @param stop is the end of the matching range
|
||||
* @return true if the vertices occur within a single pair of basic blocks and can be paired
|
||||
*/
|
||||
private static boolean isBlockPair(ArrayList<DataCtrl> frags, int start, int stop) {
|
||||
int leftCount = 0;
|
||||
int rightCount = 0;
|
||||
int leftUid = -1;
|
||||
int rightUid = -1;
|
||||
for (int i = start; i < stop; ++i) {
|
||||
DataCtrl frag = frags.get(i);
|
||||
DataVertex vert = frag.dataVertex;
|
||||
if (!vert.sinks.isEmpty() &&
|
||||
(!vert.isOp() || vert.op.getOpcode() != PcodeOp.MULTIEQUAL)) {
|
||||
// Nodes must be terminal roots of data-flow or MULTIEQUAL ops
|
||||
return false;
|
||||
}
|
||||
CtrlVertex cvert = frag.ctrlNGram.root;
|
||||
if (cvert.graph.side == Side.LEFT) {
|
||||
leftCount += 1;
|
||||
if (leftCount == 1) {
|
||||
leftUid = cvert.uid;
|
||||
}
|
||||
else if (leftUid != cvert.uid) {
|
||||
return false; // More than one block on LEFT side
|
||||
}
|
||||
}
|
||||
else {
|
||||
rightCount += 1;
|
||||
if (rightCount == 1) {
|
||||
rightUid = cvert.uid;
|
||||
}
|
||||
else if (rightUid != cvert.uid) {
|
||||
return false; // More than one block on RIGHT side
|
||||
}
|
||||
}
|
||||
}
|
||||
return (leftCount == rightCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a range of vertices with identical data n-grams and associated control-flow n-grams,
|
||||
* test if they all occur in 1 basic block (pair). If they can, match the vertices
|
||||
* in the order they occur within the basic block. The new paired vertices are added to the pinMap.
|
||||
* @param frags is the list of vertices with associated control-flow n-grams
|
||||
* @param start is the start of the matching range
|
||||
* @param stop is the end of the matching range
|
||||
* @param firstNGram is the matching data n-gram
|
||||
*/
|
||||
private void breakTieUsingOrder(ArrayList<DataCtrl> frags, int start, int stop,
|
||||
DataNGram firstNGram) {
|
||||
|
||||
if (isBlockPair(frags, start, stop)) {
|
||||
List<DataCtrl> subList = frags.subList(start, stop);
|
||||
subList.sort(compareWithinBlock);
|
||||
int size = (stop - start) / 2;
|
||||
for (int i = 0; i < size; ++i) {
|
||||
ngramPinner(subList.get(i).dataVertex, subList.get(i + size).dataVertex,
|
||||
firstNGram.depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting at the given index in fragment, the main n-gram list, collect all n-grams
|
||||
* with the same hash. The matching n-grams are passed back in matchList.
|
||||
* Any n-gram whose weight is less than the minWeight threshold is skipped, as
|
||||
* is any n-gram whose underlying DataVertex is already paired or ruled out.
|
||||
* @param i is the given starting index
|
||||
* @param matchList will contain exactly the list of matching n-grams being passed back
|
||||
* @param minWeight is the minimum weight threshold for n-grams
|
||||
* @return the index advanced to the next unexamined slot
|
||||
*/
|
||||
private int collectEqualHash(int i, ArrayList<DataNGram> matchList, int minWeight) {
|
||||
DataNGram first = null;
|
||||
matchList.clear();
|
||||
for (;;) {
|
||||
if (i >= fragments.size()) {
|
||||
return i;
|
||||
}
|
||||
first = fragments.get(i);
|
||||
i += 1;
|
||||
if (first.weight < minWeight) {
|
||||
if (!first.root.isOp() || first.root.op.getOpcode() != PcodeOp.CALL) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!first.root.paired && first.root.passComplete < pass) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
matchList.add(first);
|
||||
for (;;) {
|
||||
if (i >= fragments.size()) {
|
||||
return i;
|
||||
}
|
||||
DataNGram gram = fragments.get(i);
|
||||
if (!first.equalHash(gram)) {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
if (!gram.root.paired && gram.root.passComplete < pass) {
|
||||
matchList.add(gram);
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pair the two given data-flow vertices.
|
||||
* @param left is the LEFT side vertex
|
||||
* @param right is the RIGHT side vertex
|
||||
*/
|
||||
private void establishMatch(DataVertex left, DataVertex right) {
|
||||
pinMap.put(left, right);
|
||||
left.paired = true;
|
||||
right.paired = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do one pass of the pinning algorithm. The n-gram list is run through once. For each set of
|
||||
* n-grams with matching hashes, if there are exactly 2, the associated data-flow vertices are paired.
|
||||
* If there are more then 2, we attempt to "break the tie" by associating control-flow n-grams to
|
||||
* the subset of data-flow vertices and pairing these.
|
||||
* @param minWeight is the weight threshold for considering an n-gram in the list for matching
|
||||
* @param useOrder is true if block and operand order should be used to break ties
|
||||
* @param monitor is the TaskMonitor
|
||||
* @throws CancelledException if the user cancels the task
|
||||
*/
|
||||
private void pinMain(int minWeight, boolean useOrder, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
int i = 0; // The main index into fragments, the n-gram list
|
||||
monitor.setMessage("Pinning all...");
|
||||
monitor.setIndeterminate(false);
|
||||
monitor.initialize(fragments.size());
|
||||
ArrayList<DataNGram> matchList = new ArrayList<>();
|
||||
while (i < fragments.size()) {
|
||||
if (i % 1000 == 0) {
|
||||
monitor.setProgress(i);
|
||||
}
|
||||
i = collectEqualHash(i, matchList, minWeight);
|
||||
|
||||
if (matchList.size() == 2) { // Exactly 2 matching n-grams. Possible pair.
|
||||
DataNGram gram0 = matchList.get(0);
|
||||
DataNGram gram1 = matchList.get(1);
|
||||
if (gram0.graphsDiffer(gram1)) { // Check that one n-gram comes from each side
|
||||
DataVertex left = gram0.root.graph.side == Side.LEFT ? gram0.root : gram1.root;
|
||||
DataVertex right =
|
||||
gram0.root.graph.side == Side.RIGHT ? gram0.root : gram1.root;
|
||||
ngramPinner(left, right, gram0.depth); // Pin the match and everything above it
|
||||
}
|
||||
}
|
||||
else if (matchList.size() > 2) { // More then 2 matching n-grams
|
||||
breakTieWithCtrlFlow(matchList, useOrder, monitor);
|
||||
if (useOrder) {
|
||||
matchViaOperator(matchList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the full pinning algorithm. Continue doing passes over the n-gram list until no
|
||||
* further pairs are found.
|
||||
* @param nGramDepth is the maximum depth of n-gram to use
|
||||
* @param breakSym is true if a symmetry breaking pass should be performed at the end
|
||||
* @param monitor is the TaskMonitor
|
||||
* @throws CancelledException if the user cancels the task
|
||||
*/
|
||||
private void doPinning(int nGramDepth, boolean breakSym, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
pass = 0;
|
||||
pinMain(2, false, monitor); // First pass, using weight threshold of 2
|
||||
cgraphLeft.addEdgeColor(); // Try to distinguish control-flow nodes further
|
||||
cgraphRight.addEdgeColor();
|
||||
boolean checkForTies = true;
|
||||
while (checkForTies) { // Continue until no further pairs are found
|
||||
pass += 1;
|
||||
int lastPinSize = pinMap.size();
|
||||
updateCtrlHashes(nGramDepth);
|
||||
pinMain(0, false, monitor);
|
||||
checkForTies = (lastPinSize != pinMap.size());
|
||||
}
|
||||
if (breakSym) {
|
||||
checkForTies = true;
|
||||
while (checkForTies) {
|
||||
pass += 1;
|
||||
int lastPinSize = pinMap.size();
|
||||
pinMain(0, true, monitor); // Use block and operand order to break ties
|
||||
checkForTies = (lastPinSize != pinMap.size());
|
||||
}
|
||||
}
|
||||
pinAssociates(); // Try to pair nodes in the original graph that were collapsed out
|
||||
}
|
||||
|
||||
/**
|
||||
* Pin the given pair of data-flow vertices, then recursively try to pin vertices
|
||||
* by following the immediate incoming data-flow edge. The pair is made for a specific n-gram
|
||||
* depth. Recursive pairing stops at any point where n-grams don't match up to this depth.
|
||||
* Edges into commutative PcodeOp nodes are also disambiguated with n-gram hashes up to this depth.
|
||||
* @param left is the LEFT side data-flow vertex to pair
|
||||
* @param right is the RIGHT side data-flow vertex to pair
|
||||
* @param depth is the specific n-gram depth for the pair
|
||||
*/
|
||||
private void ngramPinner(DataVertex left, DataVertex right, int depth) {
|
||||
|
||||
if (depth < 0 || left == null || left.paired || right.paired) {
|
||||
return;
|
||||
}
|
||||
|
||||
establishMatch(left, right); // Formally pair the vertices
|
||||
|
||||
boolean goForIt;
|
||||
|
||||
if (left.isCommutative()) { // Nodes whose incoming edges must be disambiguated
|
||||
if (left.op.getOpcode() != PcodeOp.MULTIEQUAL) {
|
||||
DataVertex left0 = left.sources.get(0);
|
||||
DataVertex left1 = left.sources.get(1);
|
||||
DataVertex right0 = right.sources.get(0);
|
||||
DataVertex right1 = right.sources.get(1);
|
||||
int lasty = left.ngrams.size() - 1;
|
||||
|
||||
if (left0.ngrams.get(lasty).hash == left1.ngrams.get(lasty).hash ||
|
||||
right0.ngrams.get(lasty).hash == right1.ngrams.get(lasty).hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
for (DataVertex srcLeft : left.sources) { // For each incoming edge of the LEFT side vertex
|
||||
if (srcLeft.paired) {
|
||||
continue;
|
||||
}
|
||||
for (DataVertex srcRight : right.sources) { // Search for a matching edge on RIGHT side
|
||||
if (srcRight.paired) {
|
||||
continue;
|
||||
}
|
||||
goForIt = true;
|
||||
for (int i = 0; i < Math.max(1, depth); i++) { // n-grams must match up to given depth
|
||||
if (srcLeft.ngrams.get(i).hash != srcRight.ngrams.get(i).hash) {
|
||||
goForIt = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (goForIt) { // If all hashes match for the two edges, pair them
|
||||
// Try to extend depth of matching hashes before pairing the edges
|
||||
int newDepth = Math.max(depth - 1, 0);
|
||||
while (-1 < newDepth && newDepth < srcLeft.ngrams.size() && srcLeft.ngrams
|
||||
.get(newDepth).hash == srcRight.ngrams.get(newDepth).hash) {
|
||||
newDepth++;
|
||||
}
|
||||
ngramPinner(srcLeft, srcRight, newDepth - 1); // Recursively match these edges
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Edges are paired in order
|
||||
if (left.sources.size() == right.sources.size()) {
|
||||
for (int n = 0; n < left.sources.size(); n++) {
|
||||
|
||||
DataVertex srcLeft = left.sources.get(n);
|
||||
DataVertex srcRight = right.sources.get(n);
|
||||
goForIt = true;
|
||||
for (int i = 0; i < Math.max(1, depth); i++) { // n-grams must match up to given depth
|
||||
if (srcLeft.ngrams.get(i).hash != srcRight.ngrams.get(i).hash) {
|
||||
goForIt = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (goForIt) { // If all hashes match for the two edges, pair them
|
||||
// Try to extend depth of matching hashes before pairing edges
|
||||
int i = Math.max(depth - 1, 0);
|
||||
while (-1 < i && i < srcLeft.ngrams.size() &&
|
||||
srcLeft.ngrams.get(i).hash == srcRight.ngrams.get(i).hash) {
|
||||
i++;
|
||||
}
|
||||
ngramPinner(srcLeft, srcRight, i - 1); // Recursively match these edges
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a data-flow representing a PcodeOp, return the control-flow vertex containing the PcodeOp.
|
||||
* @param node is the data-flow vertex
|
||||
* @return the containing control-flow vertex
|
||||
*/
|
||||
private CtrlVertex dataToCtrl(DataVertex node) {
|
||||
PcodeBlockBasic parent = node.op.getParent();
|
||||
CtrlGraph whichGraph = (node.graph == graphLeft ? cgraphLeft : cgraphRight);
|
||||
return whichGraph.blockToVertex.get(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the RIGHT side Varnode matching the given LEFT side Varnode.
|
||||
* The LEFT and RIGHT sides are determined by the order HighFunctions are given to the constructor.
|
||||
* @param vnLeft is the given Varnode from LEFT side
|
||||
* @return the matching Varnode from RIGHT side or null
|
||||
*/
|
||||
public VarnodeAST findMatch(Varnode vnLeft) {
|
||||
DataVertex vertLeft = graphLeft.vnToVert.get(vnLeft);
|
||||
if (vertLeft == null) {
|
||||
return null;
|
||||
}
|
||||
DataVertex vertRight = pinMap.get(vertLeft);
|
||||
if (vertRight != null) {
|
||||
return vertRight.vn;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the RIGHT side PcodeOp matching the given LEFT side PcodeOp.
|
||||
* The LEFT and RIGHT sides are determined by the order HighFunctions are given to the constructor.
|
||||
* @param opLeft is the given PcodeOp from LEFT side
|
||||
* @return the matching PcodeOp from RIGHT side or null
|
||||
*/
|
||||
public PcodeOpAST findMatch(PcodeOp opLeft) {
|
||||
DataVertex vertLeft = graphLeft.opToVert.get(opLeft);
|
||||
if (vertLeft == null) {
|
||||
return null;
|
||||
}
|
||||
DataVertex vertRight = pinMap.get(vertLeft);
|
||||
if (vertRight != null) {
|
||||
return vertRight.op;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a token should be be filtered from match display.
|
||||
* Some tokens like "," and " " may be attached to matchable operations but can
|
||||
* clutter the display if they are highlighted for a match.
|
||||
* @param token is the specific token to check
|
||||
* @return true if the token should not be highlighted as part of a match
|
||||
*/
|
||||
private boolean filterToken(ClangToken token) {
|
||||
String text = token.getText();
|
||||
if (text.length() == 0) {
|
||||
return true;
|
||||
}
|
||||
if (text.length() == 1) {
|
||||
char c = text.charAt(0);
|
||||
if (c == ' ' || c == ',') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (token instanceof ClangTypeToken) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a TokenBin for every DataVertex on both sides. Pair a TokenBin with its match,
|
||||
* if its underlying DataVertex is paired.
|
||||
* @param leftTokenGp are the tokens for LEFT side
|
||||
* @param rightTokenGp are the tokens for RIGHT side
|
||||
* @return a single list of LEFT side TokenBins then RIGHT side TokenBins
|
||||
*/
|
||||
public ArrayList<TokenBin> buildTokenMap(ClangTokenGroup leftTokenGp,
|
||||
ClangTokenGroup rightTokenGp) {
|
||||
|
||||
HashMap<Pair<DataVertex, DataVertex>, TokenBin> lvertToBin = new HashMap<>();
|
||||
HashMap<Pair<DataVertex, DataVertex>, TokenBin> rvertToBin = new HashMap<>();
|
||||
|
||||
for (int side = 0; side < 2; side++) {
|
||||
// side == 0: set up the left side
|
||||
// side == 1: set up the right side
|
||||
ClangTokenGroup tokGp = (side == 0 ? leftTokenGp : rightTokenGp);
|
||||
DataGraph graph = (side == 0 ? graphLeft : graphRight);
|
||||
HashMap<Pair<DataVertex, DataVertex>, TokenBin> vertToBin =
|
||||
(side == 0 ? lvertToBin : rvertToBin);
|
||||
|
||||
ArrayList<ClangNode> nodes = new ArrayList<>();
|
||||
tokGp.flatten(nodes);
|
||||
for (ClangNode node : nodes) {
|
||||
if (node instanceof ClangToken tok) {
|
||||
if (filterToken(tok)) {
|
||||
continue;
|
||||
}
|
||||
VarnodeAST vn = (VarnodeAST) DecompilerUtils.getVarnodeRef(tok);
|
||||
PcodeOpAST op = (PcodeOpAST) tok.getPcodeOp();
|
||||
|
||||
DataVertex opNode = graph.opToVert.get(op);
|
||||
DataVertex vnNode = graph.vnToVert.get(vn);
|
||||
Pair<DataVertex, DataVertex> nodePair = new Pair<>(opNode, vnNode);
|
||||
|
||||
if (!vertToBin.containsKey(nodePair)) {
|
||||
vertToBin.put(nodePair, new TokenBin(graph.getHighFunction()));
|
||||
}
|
||||
vertToBin.get(nodePair).add(tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match a TokenBin if its underlying DataVertex is paired
|
||||
ArrayList<TokenBin> highBins = new ArrayList<>();
|
||||
for (Pair<DataVertex, DataVertex> lNodePair : lvertToBin.keySet()) {
|
||||
TokenBin lbin = lvertToBin.get(lNodePair);
|
||||
DataVertex lkey = lNodePair.first;
|
||||
DataVertex lval = lNodePair.second;
|
||||
DataVertex rkey = null;
|
||||
DataVertex rval = null;
|
||||
if (lkey != null && lkey.paired) {
|
||||
rkey = pinMap.get(lkey);
|
||||
}
|
||||
if (lval != null && lval.paired) {
|
||||
rval = pinMap.get(lval);
|
||||
}
|
||||
|
||||
if (((lkey == null) != (rkey == null)) || ((lval == null) != (rval == null))) {
|
||||
continue;
|
||||
}
|
||||
if ((rkey == null && rval == null) || (lkey == null && lval == null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair<DataVertex, DataVertex> rNodePair = new Pair<>(rkey, rval);
|
||||
if (rvertToBin.containsKey(rNodePair)) {
|
||||
TokenBin rbin = rvertToBin.get(rNodePair);
|
||||
lbin.sidekick = rbin;
|
||||
rbin.sidekick = lbin;
|
||||
}
|
||||
}
|
||||
|
||||
// Put everything into the final list
|
||||
lvertToBin.remove(new Pair<DataVertex, DataVertex>(null, null));
|
||||
rvertToBin.remove(new Pair<DataVertex, DataVertex>(null, null));
|
||||
highBins.addAll(lvertToBin.values());
|
||||
highBins.addAll(rvertToBin.values());
|
||||
|
||||
return highBins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump a string representation of pinning data structures (for debugging).
|
||||
* @param writer is the stream to write the string to.
|
||||
* @throws IOException for problems writing to the stream
|
||||
*/
|
||||
public void dump(Writer writer) throws IOException {
|
||||
graphLeft.dump(writer);
|
||||
graphRight.dump(writer);
|
||||
// for (DataNGram vertex : fragments) {
|
||||
// writer.append(vertex.toString());
|
||||
// writer.append("\n");
|
||||
// }
|
||||
for (DataVertex vertex : graphLeft.nodeList) {
|
||||
DataVertex match = pinMap.get(vertex);
|
||||
if (match != null) {
|
||||
writer.append("match ");
|
||||
writer.append(Integer.toString(vertex.uid));
|
||||
writer.append(" to ");
|
||||
writer.append(Integer.toString(match.uid));
|
||||
writer.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match data-flow between two HighFunctions. The matching algorithm is performed immediately.
|
||||
* The resulting Pinning object can be queried for matches via
|
||||
* - findMatch(Varnode) or
|
||||
* - findMatch(PcodeOp)
|
||||
*
|
||||
* ClangToken matches can be calculated by calling buildTokenMap().
|
||||
* @param hfuncLeft is the LEFT side function
|
||||
* @param hfuncRight is the RIGHT side function
|
||||
* @param matchConstantsExactly is true if (small) constant values should be forced to match
|
||||
* @param sizeCollapse is true if variable sizes larger than 4 should be treated as size 4
|
||||
* @param breakSym is true if code symmetries should be ordered arbitrarily so they can be paired
|
||||
* @param monitor is the TaskMonitor
|
||||
* @return the populated Pinning object
|
||||
* @throws CancelledException if the user cancels the task
|
||||
*/
|
||||
public static Pinning makePinning(HighFunction hfuncLeft, HighFunction hfuncRight,
|
||||
boolean matchConstantsExactly, boolean sizeCollapse, boolean breakSym,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
boolean matchRamSpace = true;
|
||||
boolean castCollapse = true;
|
||||
// Make the pinning, the map from the data graph of hfuncLeft to that of hfuncRight.
|
||||
Pinning pin = new Pinning(hfuncLeft, hfuncRight, NGRAM_DEPTH, matchConstantsExactly,
|
||||
matchRamSpace, castCollapse, sizeCollapse, breakSym, monitor);
|
||||
|
||||
return pin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pair vertices that are inputs to the same commutative operator.
|
||||
* Reaching here, the vertices could not be distinguished by any other method, so
|
||||
* we order the vertices based on their input slot to the operator.
|
||||
* @param ngrams is the set of matching n-grams
|
||||
*/
|
||||
private void matchViaOperator(ArrayList<DataNGram> ngrams) {
|
||||
DataNGram firstNGram = ngrams.get(0);
|
||||
for (DataNGram ngram : ngrams) {
|
||||
DataVertex vertLeft = ngram.root;
|
||||
if (vertLeft.graph.side != Side.LEFT) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j < vertLeft.sinks.size(); ++j) {
|
||||
DataVertex opVertLeft = vertLeft.sinks.get(j);
|
||||
if (!opVertLeft.isOp() || !opVertLeft.isCommutative() || !opVertLeft.paired) {
|
||||
continue;
|
||||
}
|
||||
DataVertex opVertRight = pinMap.get(opVertLeft);
|
||||
if (opVertLeft.sources.size() != opVertRight.sources.size()) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < opVertLeft.sources.size(); ++i) {
|
||||
DataVertex tVertLeft = opVertLeft.sources.get(i);
|
||||
DataVertex tVertRight = opVertRight.sources.get(i);
|
||||
if (tVertLeft.paired || tVertRight.paired) {
|
||||
continue;
|
||||
}
|
||||
int index = tVertLeft.ngrams.size() - 1;
|
||||
if (!tVertLeft.ngrams.get(index).equalHash(firstNGram)) {
|
||||
continue;
|
||||
}
|
||||
if (!tVertRight.ngrams.get(index).equalHash(firstNGram)) {
|
||||
continue;
|
||||
}
|
||||
ngramPinner(tVertLeft, tVertRight, firstNGram.depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function we use to hash two integers into one. Used in multiple places in the pinning algorithm.
|
||||
* @param first is the first integer to hash
|
||||
* @param second is the second integer
|
||||
* @return the hash of the two integers
|
||||
*/
|
||||
static int hashTwo(int first, int second) {
|
||||
int result = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result = SimpleCRC32.hashOneByte(result, first >> i * 8);
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result = SimpleCRC32.hashOneByte(result, second >> i * 8);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/* ###
|
||||
* 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.codecompare.graphanalysis;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.decompiler.ClangToken;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* An iterable list of Decompiler tokens {@link ClangToken} in one window that is paired with a
|
||||
* list in another window. The matching TokenBin, if it exists, is obtained by calling
|
||||
* getMatch(). This container is constructed and populated only by {@link Pinning}.
|
||||
*/
|
||||
public class TokenBin implements Iterable<ClangToken> {
|
||||
private ArrayList<ClangToken> bin; // The list of tokens in this bin
|
||||
private HighFunction highFunction; // The function owning the tokens
|
||||
TokenBin sidekick; // The bin (from the other window) matching this
|
||||
|
||||
/**
|
||||
* Construct an (initially empty) token bin.
|
||||
* @param highFunction is the function the bin is associated with
|
||||
*/
|
||||
TokenBin(HighFunction highFunction) {
|
||||
this.bin = new ArrayList<>();
|
||||
this.highFunction = highFunction;
|
||||
this.sidekick = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the HighFunction owning the tokens in this bin
|
||||
*/
|
||||
public HighFunction getHighFunction() {
|
||||
return highFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the TokenBin paired with this
|
||||
*/
|
||||
public TokenBin getMatch() {
|
||||
return sidekick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the i-th token in this bin
|
||||
* @param i is the index of the token
|
||||
* @return the selected token
|
||||
*/
|
||||
public ClangToken get(int i) {
|
||||
return bin.get(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of tokens in this bin
|
||||
*/
|
||||
public int size() {
|
||||
return bin.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ClangToken> iterator() {
|
||||
return bin.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new token to this bin.
|
||||
* @param newToken is the token to add
|
||||
*/
|
||||
void add(ClangToken newToken) {
|
||||
// Check to make sure we're in the right function.
|
||||
HighFunction tokenHighFunction = newToken.getClangFunction().getHighFunction();
|
||||
if (!highFunction.equals(tokenHighFunction)) {
|
||||
Msg.warn(this,
|
||||
"ERROR: Trying to ADD token '" + newToken.getText() + "' to incorrect TokenBin.");
|
||||
return;
|
||||
}
|
||||
bin.add(newToken); // Add token to the list
|
||||
}
|
||||
|
||||
/**
|
||||
* From a list of TokenBins, find the (first) one containing a particular ClangToken
|
||||
* @param highBins is the list of bins
|
||||
* @param myToken is the ClangToken to find
|
||||
* @return the first bin containing the token, or null
|
||||
*/
|
||||
public static TokenBin getBinContainingToken(List<TokenBin> highBins, ClangToken myToken) {
|
||||
for (TokenBin bin : highBins) {
|
||||
for (ClangToken token : bin.bin) {
|
||||
if (myToken == token) {
|
||||
return bin;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,455 @@
|
|||
/* ###
|
||||
* 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.feature.vt.api.correlator.address;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.codecompare.graphanalysis.Pinning;
|
||||
import ghidra.codecompare.graphanalysis.TokenBin;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.block.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.pcode.HighFunction;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class CodeCompareAddressCorrelation implements AddressCorrelation {
|
||||
static enum CorrelationKind {
|
||||
CODE_COMPARE, LCS, PARAMETERS;
|
||||
}
|
||||
|
||||
static class CorrelationContainer {
|
||||
public static boolean USE_RANDOM_CC_COLORS = false;
|
||||
public final CorrelationKind kind;
|
||||
public final AddressRange range;
|
||||
|
||||
public CorrelationContainer(CorrelationKind kind, AddressRange range) {
|
||||
this.kind = kind;
|
||||
this.range = range;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int TIMEOUT_SECONDS = 60;
|
||||
|
||||
private final Function sourceFunction;
|
||||
private final Function destinationFunction;
|
||||
private final Program sourceProgram;
|
||||
private final Program destinationProgram;
|
||||
|
||||
private Map<Address, CorrelationContainer> cachedForwardAddressMap;
|
||||
|
||||
public CodeCompareAddressCorrelation(Function sourceFunction, Function destinationFunction) {
|
||||
this.sourceFunction = sourceFunction;
|
||||
this.destinationFunction = destinationFunction;
|
||||
this.sourceProgram = sourceFunction.getProgram();
|
||||
this.destinationProgram = destinationFunction.getProgram();
|
||||
|
||||
DebugUtils.enable(false); // Set to "true" to enable debugging or "false" to disable.
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getCorrelatedDestinationRange(Address sourceAddress, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
initialize(monitor);
|
||||
|
||||
CorrelationContainer container = cachedForwardAddressMap.get(sourceAddress);
|
||||
if (container == null) {
|
||||
return null;
|
||||
}
|
||||
return container.range;
|
||||
}
|
||||
|
||||
private static final Comparator<CodeUnit> CUCOMPARATOR = new Comparator<CodeUnit>() {
|
||||
@Override
|
||||
public int compare(CodeUnit o1, CodeUnit o2) {
|
||||
return o1.getAddress().compareTo(o2.getAddress());
|
||||
}
|
||||
};
|
||||
|
||||
private void initialize(TaskMonitor monitor) throws CancelledException {
|
||||
if (cachedForwardAddressMap == null) {
|
||||
cachedForwardAddressMap = new HashMap<Address, CorrelationContainer>();
|
||||
|
||||
HashMap<Address, Address> sourceToDestinationPairings = new HashMap<Address, Address>();
|
||||
HashMap<Address, Address> destinationToSourcePairings = new HashMap<Address, Address>();
|
||||
|
||||
AddressSet sourceSet = new AddressSet();
|
||||
AddressSet destinationSet = new AddressSet();
|
||||
|
||||
TreeMap<CodeUnit, TreeSet<AddressRange>> sourceMap =
|
||||
new TreeMap<CodeUnit, TreeSet<AddressRange>>(CUCOMPARATOR);
|
||||
TreeMap<CodeUnit, TreeSet<AddressRange>> destinationMap =
|
||||
new TreeMap<CodeUnit, TreeSet<AddressRange>>(CUCOMPARATOR);
|
||||
|
||||
processCodeCompare(monitor, sourceToDestinationPairings, destinationToSourcePairings,
|
||||
sourceSet, destinationSet, sourceMap, destinationMap);
|
||||
|
||||
// now, if we're on the same architecture, try to LCS the
|
||||
// interstices between our golden matches
|
||||
|
||||
if (sourceFunction.getProgram()
|
||||
.getLanguage()
|
||||
.getProcessor()
|
||||
.equals(destinationFunction.getProgram().getLanguage().getProcessor())) {
|
||||
processLCSBlocks(monitor, sourceToDestinationPairings, sourceSet, destinationSet,
|
||||
sourceMap, destinationMap);
|
||||
}
|
||||
|
||||
DebugUtils.processMap(sourceMap, sourceProgram);
|
||||
DebugUtils.processMap(destinationMap, destinationProgram);
|
||||
|
||||
DebugUtils.colorize(cachedForwardAddressMap, sourceProgram, destinationProgram);
|
||||
}
|
||||
}
|
||||
|
||||
private void processCodeCompare(TaskMonitor monitor,
|
||||
HashMap<Address, Address> sourceToDestinationPairings,
|
||||
HashMap<Address, Address> destinationToSourcePairings, AddressSet sourceSet,
|
||||
AddressSet destinationSet, Map<CodeUnit, TreeSet<AddressRange>> sourceMap,
|
||||
Map<CodeUnit, TreeSet<AddressRange>> destinationMap) throws CancelledException {
|
||||
// compute the mapping by means of code compare
|
||||
|
||||
DecompInterface sourceDecompiler = null;
|
||||
DecompInterface destinationDecompiler = null;
|
||||
|
||||
try {
|
||||
sourceDecompiler = new DecompInterface();
|
||||
destinationDecompiler = new DecompInterface();
|
||||
|
||||
sourceDecompiler.openProgram(sourceProgram);
|
||||
destinationDecompiler.openProgram(destinationProgram);
|
||||
|
||||
DecompileResults sourceResults =
|
||||
sourceDecompiler.decompileFunction(sourceFunction, TIMEOUT_SECONDS, monitor);
|
||||
DecompileResults destinationResults = destinationDecompiler
|
||||
.decompileFunction(destinationFunction, TIMEOUT_SECONDS, monitor);
|
||||
|
||||
ClangTokenGroup sourceMarkup = sourceResults.getCCodeMarkup();
|
||||
ClangTokenGroup destinationMarkup = destinationResults.getCCodeMarkup();
|
||||
|
||||
HighFunction sourceHFunc = sourceResults.getHighFunction();
|
||||
HighFunction destinationHFunc = destinationResults.getHighFunction();
|
||||
String errorMessage = "";
|
||||
if (sourceHFunc == null) {
|
||||
errorMessage += (" Source Decompiler failed to get function. " +
|
||||
sourceResults.getErrorMessage());
|
||||
}
|
||||
if (destinationHFunc == null) {
|
||||
errorMessage += (" Destination Decompiler failed to get function. " +
|
||||
destinationResults.getErrorMessage());
|
||||
}
|
||||
if (!errorMessage.isEmpty()) {
|
||||
// For now throw a RuntimeException to see what failed in the decompiler,
|
||||
// otherwise we will just get a NullPointerException.
|
||||
throw new RuntimeException(errorMessage);
|
||||
}
|
||||
|
||||
boolean matchConstantsExactly = false;
|
||||
int srcsize = sourceProgram.getLanguage().getLanguageDescription().getSize();
|
||||
int destsize = destinationProgram.getLanguage().getLanguageDescription().getSize();
|
||||
boolean sizeCollapse = (srcsize != destsize);
|
||||
Pinning pin = Pinning.makePinning(sourceHFunc, destinationHFunc, matchConstantsExactly,
|
||||
sizeCollapse, true, monitor);
|
||||
ArrayList<TokenBin> highBins = pin.buildTokenMap(sourceMarkup, destinationMarkup);
|
||||
|
||||
for (TokenBin tokenBin : highBins) {
|
||||
if (tokenBin.getMatch() == null || tokenBin.getMatch().size() != tokenBin.size()) {
|
||||
continue;
|
||||
}
|
||||
boolean isSourceBin = tokenBin.getHighFunction().equals(sourceHFunc);
|
||||
|
||||
for (int ii = 0; ii < tokenBin.size(); ++ii) {
|
||||
ClangToken binToken = tokenBin.get(ii);
|
||||
ClangToken sidekickToken = tokenBin.getMatch().get(ii);
|
||||
|
||||
ClangToken sourceToken = isSourceBin ? binToken : sidekickToken;
|
||||
ClangToken destinationToken = isSourceBin ? sidekickToken : binToken;
|
||||
|
||||
Address srcStart = sourceToken.getMinAddress();
|
||||
Address srcEnd = sourceToken.getMaxAddress();
|
||||
|
||||
Address destStart = destinationToken.getMinAddress();
|
||||
Address destEnd = destinationToken.getMaxAddress();
|
||||
|
||||
if (destStart == null || destEnd == null || srcStart == null ||
|
||||
srcEnd == null) {
|
||||
continue;
|
||||
}
|
||||
AddressSet sourceIntersection = sourceSet.intersectRange(srcStart, srcEnd);
|
||||
AddressSet destinationIntersection =
|
||||
destinationSet.intersectRange(destStart, destEnd);
|
||||
|
||||
if (!sourceIntersection.isEmpty() || !destinationIntersection.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugUtils.recordEOLComment(sourceMap, sourceProgram, srcStart, srcEnd,
|
||||
destinationProgram, destStart, destEnd);
|
||||
DebugUtils.recordEOLComment(destinationMap, destinationProgram, destStart,
|
||||
destEnd, sourceProgram, srcStart, srcEnd);
|
||||
|
||||
sourceSet.addRange(srcStart, srcEnd);
|
||||
destinationSet.addRange(destStart, destEnd);
|
||||
|
||||
sourceToDestinationPairings.put(srcStart, destStart);
|
||||
destinationToSourcePairings.put(destStart, srcStart);
|
||||
|
||||
AddressRangeImpl range = new AddressRangeImpl(destStart, destEnd);
|
||||
CorrelationContainer container =
|
||||
new CorrelationContainer(CorrelationKind.CODE_COMPARE, range);
|
||||
|
||||
// Assign container to all addresses: srcStart to srcEnd inclusive
|
||||
while (!srcStart.equals(srcEnd)) {
|
||||
cachedForwardAddressMap.put(srcStart, container);
|
||||
srcStart = srcStart.addNoWrap(1);
|
||||
}
|
||||
cachedForwardAddressMap.put(srcStart, container);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
Msg.error(this,
|
||||
"Unexpected address overflow; token's range didn't span continguous region", e);
|
||||
}
|
||||
finally {
|
||||
if (sourceDecompiler != null) {
|
||||
sourceDecompiler.dispose();
|
||||
}
|
||||
if (destinationDecompiler != null) {
|
||||
destinationDecompiler.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processLCSBlocks(TaskMonitor monitor,
|
||||
HashMap<Address, Address> sourceToDestinationPairings, AddressSet sourceSet,
|
||||
AddressSet destinationSet, Map<CodeUnit, TreeSet<AddressRange>> sourceMap,
|
||||
Map<CodeUnit, TreeSet<AddressRange>> destinationMap) throws CancelledException {
|
||||
CodeBlockModel sourceBlockModel = new BasicBlockModel(sourceProgram);
|
||||
CodeBlockModel destinationBlockModel = new BasicBlockModel(destinationProgram);
|
||||
|
||||
Listing sourceListing = sourceProgram.getListing();
|
||||
Listing destinationListing = destinationProgram.getListing();
|
||||
|
||||
Set<Entry<Address, Address>> entrySet = sourceToDestinationPairings.entrySet();
|
||||
for (Entry<Address, Address> entry : entrySet) {
|
||||
Address sourceAddress = entry.getKey();
|
||||
Address destinationAddress = entry.getValue();
|
||||
|
||||
CodeBlock[] sourceBlocks =
|
||||
sourceBlockModel.getCodeBlocksContaining(sourceAddress, monitor);
|
||||
CodeBlock[] destinationBlocks =
|
||||
destinationBlockModel.getCodeBlocksContaining(destinationAddress, monitor);
|
||||
|
||||
if (sourceBlocks != null && destinationBlocks != null) {
|
||||
if (sourceBlocks.length == 1 && destinationBlocks.length == 1) {
|
||||
// work backwards
|
||||
|
||||
CodeUnitIterator sourceCodeUnitIterator =
|
||||
sourceListing.getCodeUnits(sourceAddress, false);
|
||||
CodeUnitIterator destinationCodeUnitIterator =
|
||||
destinationListing.getCodeUnits(destinationAddress, false);
|
||||
|
||||
processLCSCodeUnits(monitor, sourceSet, destinationSet, sourceMap,
|
||||
destinationMap, sourceCodeUnitIterator, destinationCodeUnitIterator);
|
||||
|
||||
// now work forwards
|
||||
sourceCodeUnitIterator = sourceListing.getCodeUnits(sourceAddress, true);
|
||||
destinationCodeUnitIterator =
|
||||
destinationListing.getCodeUnits(destinationAddress, true);
|
||||
|
||||
processLCSCodeUnits(monitor, sourceSet, destinationSet, sourceMap,
|
||||
destinationMap, sourceCodeUnitIterator, destinationCodeUnitIterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processLCSCodeUnits(TaskMonitor monitor, AddressSet sourceSet,
|
||||
AddressSet destinationSet, Map<CodeUnit, TreeSet<AddressRange>> sourceMap,
|
||||
Map<CodeUnit, TreeSet<AddressRange>> destinationMap,
|
||||
CodeUnitIterator sourceCodeUnitIterator, CodeUnitIterator destinationCodeUnitIterator)
|
||||
throws CancelledException {
|
||||
// get rid of the codeUnit we already have
|
||||
if (sourceCodeUnitIterator.hasNext()) {
|
||||
sourceCodeUnitIterator.next();
|
||||
}
|
||||
|
||||
// get rid of the codeUnit we already have
|
||||
if (destinationCodeUnitIterator.hasNext()) {
|
||||
destinationCodeUnitIterator.next();
|
||||
}
|
||||
|
||||
processLCS(sourceMap, destinationMap, sourceSet, destinationSet, sourceCodeUnitIterator,
|
||||
destinationCodeUnitIterator, monitor);
|
||||
}
|
||||
|
||||
private void processLCS(Map<CodeUnit, TreeSet<AddressRange>> sourceMap,
|
||||
Map<CodeUnit, TreeSet<AddressRange>> destinationMap, AddressSet sourceSet,
|
||||
AddressSet destinationSet, CodeUnitIterator sourceCodeUnitIterator,
|
||||
CodeUnitIterator destinationCodeUnitIterator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
List<CodeUnitContainer> source = (sourceFunction != null)
|
||||
? getCodeUnits(sourceFunction, sourceSet, sourceCodeUnitIterator)
|
||||
: new ArrayList<CodeUnitContainer>();
|
||||
List<CodeUnitContainer> destination = (destinationFunction != null)
|
||||
? getCodeUnits(destinationFunction, destinationSet, destinationCodeUnitIterator)
|
||||
: new ArrayList<CodeUnitContainer>();
|
||||
CodeUnitLCS culcs = new CodeUnitLCS(source, destination);
|
||||
List<CodeUnitContainer> lcs = culcs.getLcs(monitor);
|
||||
final int lcsSize = lcs.size();
|
||||
int sourceII = 0;
|
||||
int lcsII = 0;
|
||||
int destinationII = 0;
|
||||
|
||||
monitor.setMessage("Defining address ranges...");
|
||||
monitor.initialize(lcsSize);
|
||||
|
||||
int sourceTransactionID = -1;
|
||||
int destinationTransactionID = -1;
|
||||
|
||||
try {
|
||||
sourceTransactionID =
|
||||
sourceFunction.getProgram().startTransaction("Colorize CodeCompare");
|
||||
destinationTransactionID =
|
||||
destinationFunction.getProgram().startTransaction("Colorize CodeCompare");
|
||||
|
||||
while (lcsII < lcsSize) {
|
||||
monitor.checkCancelled();
|
||||
CodeUnitContainer sourceCodeUnit = source.get(sourceII);
|
||||
CodeUnitContainer lcsCodeUnit = lcs.get(lcsII);
|
||||
CodeUnitContainer destinationCodeUnit = destination.get(destinationII);
|
||||
final boolean sourceCompare = culcs.matches(sourceCodeUnit, lcsCodeUnit);
|
||||
final boolean destinationCompare = culcs.matches(lcsCodeUnit, destinationCodeUnit);
|
||||
if (sourceCompare == destinationCompare) {
|
||||
// either they're both equal to lcs item or they're both different
|
||||
if (sourceCompare) {
|
||||
// they're both equal, define the ranges
|
||||
defineRange(sourceMap, destinationMap, sourceCodeUnit, destinationCodeUnit);
|
||||
// increment the lcs index because everything matched
|
||||
++lcsII;
|
||||
}
|
||||
// in any case, increment both the source and destination indexes
|
||||
// because they were either both the same or both different
|
||||
++sourceII;
|
||||
++destinationII;
|
||||
}
|
||||
else if (sourceCompare) {
|
||||
// destination has extra stuff (new code added)
|
||||
++destinationII;
|
||||
}
|
||||
else if (destinationCompare) {
|
||||
// source has extra stuff (old code deleted)
|
||||
++sourceII;
|
||||
}
|
||||
else {
|
||||
// can't get here!
|
||||
throw new RuntimeException("internal error");
|
||||
}
|
||||
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (sourceTransactionID != -1) {
|
||||
sourceFunction.getProgram().endTransaction(sourceTransactionID, true);
|
||||
}
|
||||
if (destinationTransactionID != -1) {
|
||||
destinationFunction.getProgram().endTransaction(destinationTransactionID, true);
|
||||
}
|
||||
}
|
||||
computeParamCorrelation();
|
||||
}
|
||||
|
||||
protected void computeParamCorrelation() {
|
||||
int sourceCount = sourceFunction.getParameterCount();
|
||||
int destinationCount = destinationFunction.getParameterCount();
|
||||
Parameter[] sourceParameters = sourceFunction.getParameters();
|
||||
Parameter[] destinationParameters = destinationFunction.getParameters();
|
||||
boolean allMatch = false;
|
||||
Map<Address, CorrelationContainer> map = new HashMap<Address, CorrelationContainer>();
|
||||
if (sourceCount == destinationCount) {
|
||||
allMatch = true;
|
||||
for (int i = 0; i < sourceParameters.length; i++) {
|
||||
Parameter sourceParameter = sourceParameters[i];
|
||||
Parameter destinationParameter = destinationParameters[i];
|
||||
if (!sourceParameter.isValid() || !destinationParameter.isValid() ||
|
||||
sourceParameter.getLength() != destinationParameter.getLength()) {
|
||||
// - an invalid parameter does not have defined storage (or address)
|
||||
// - length must also match
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
Address dest = destinationParameter.getVariableStorage().getMinAddress();
|
||||
Address src = sourceParameter.getVariableStorage().getMinAddress();
|
||||
map.put(src, new CorrelationContainer(CorrelationKind.PARAMETERS,
|
||||
new AddressRangeImpl(dest, dest)));
|
||||
}
|
||||
}
|
||||
if (allMatch) {
|
||||
cachedForwardAddressMap.putAll(map);
|
||||
}
|
||||
}
|
||||
|
||||
private void defineRange(Map<CodeUnit, TreeSet<AddressRange>> sourceMap,
|
||||
Map<CodeUnit, TreeSet<AddressRange>> destinationMap, CodeUnitContainer sourceCodeUnit,
|
||||
CodeUnitContainer destinationCodeUnit) {
|
||||
Address minAddress = sourceCodeUnit.getCodeUnit().getMinAddress();
|
||||
Address maxAddress = sourceCodeUnit.getCodeUnit().getMaxAddress();
|
||||
AddressRangeImpl toRange =
|
||||
new AddressRangeImpl(destinationCodeUnit.getCodeUnit().getMinAddress(),
|
||||
destinationCodeUnit.getCodeUnit().getMaxAddress());
|
||||
CorrelationContainer container = new CorrelationContainer(CorrelationKind.LCS, toRange);
|
||||
|
||||
DebugUtils.recordEOLComment(sourceMap, sourceProgram, minAddress, maxAddress,
|
||||
destinationProgram, destinationCodeUnit.getCodeUnit().getMinAddress(),
|
||||
destinationCodeUnit.getCodeUnit().getMaxAddress());
|
||||
DebugUtils.recordEOLComment(destinationMap, destinationProgram,
|
||||
destinationCodeUnit.getCodeUnit().getMinAddress(),
|
||||
destinationCodeUnit.getCodeUnit().getMaxAddress(), sourceProgram, minAddress,
|
||||
maxAddress);
|
||||
|
||||
while (!minAddress.equals(maxAddress)) {
|
||||
cachedForwardAddressMap.put(minAddress, container);
|
||||
minAddress = minAddress.next();
|
||||
}
|
||||
cachedForwardAddressMap.put(maxAddress, container);
|
||||
}
|
||||
|
||||
private static List<CodeUnitContainer> getCodeUnits(Function function,
|
||||
AddressSetView correlations, CodeUnitIterator codeUnits) {
|
||||
AddressSetView body = function.getBody();
|
||||
ArrayList<CodeUnitContainer> result = new ArrayList<CodeUnitContainer>();
|
||||
while (codeUnits.hasNext()) {
|
||||
CodeUnit next = codeUnits.next();
|
||||
Address address = next.getAddress();
|
||||
if (correlations.contains(address) || !body.contains(address)) {
|
||||
break;
|
||||
}
|
||||
result.add(new CodeUnitContainer(next));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "CodeCompareAddressCorrelator";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* ###
|
||||
* 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.feature.vt.api.correlator.address;
|
||||
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.AddressCorrelation;
|
||||
import ghidra.program.util.DiscoverableAddressCorrelator;
|
||||
|
||||
public class CodeCompareAddressCorrelator implements DiscoverableAddressCorrelator {
|
||||
|
||||
private static final String OPTIONS_NAME = "CodeCompareAddressCorrelator";
|
||||
|
||||
private ToolOptions options = new ToolOptions(OPTIONS_NAME);
|
||||
|
||||
public CodeCompareAddressCorrelator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized AddressCorrelation correlate(Function sourceFunction,
|
||||
Function destinationFunction) {
|
||||
|
||||
Program p1 = sourceFunction.getProgram();
|
||||
Program p2 = destinationFunction.getProgram();
|
||||
Language l1 = p1.getLanguage();
|
||||
Language l2 = p2.getLanguage();
|
||||
if (l1.getLanguageID().equals(l2.getLanguageID())) {
|
||||
return null; // this correlator is best used with different architectures
|
||||
}
|
||||
|
||||
return new CodeCompareAddressCorrelation(sourceFunction, destinationFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressCorrelation correlate(Data sourceData, Data destinationData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOptions(ToolOptions options) {
|
||||
options = options.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getDefaultOptions() {
|
||||
return new ToolOptions(OPTIONS_NAME);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/* ###
|
||||
* 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.feature.vt.api.correlator.address;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import ghidra.app.util.viewer.listingpanel.PropertyBasedBackgroundColorModel;
|
||||
import ghidra.feature.vt.api.correlator.address.CodeCompareAddressCorrelation.CorrelationContainer;
|
||||
import ghidra.program.database.IntRangeMap;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
class DebugUtils {
|
||||
|
||||
private static boolean ENABLED = false;
|
||||
|
||||
static void recordEOLComment(Map<CodeUnit, TreeSet<AddressRange>> map, Program fromProgram,
|
||||
Address startAddress, Address endAddress, Program toProgram, Address minAddress,
|
||||
Address maxAddress) {
|
||||
|
||||
if (isEnabled()) {
|
||||
Listing listing = fromProgram.getListing();
|
||||
AddressSet addrSet = new AddressSet(startAddress, endAddress);
|
||||
CodeUnitIterator codeUnits = listing.getCodeUnits(addrSet, true);
|
||||
AddressRange range = new AddressRangeImpl(minAddress, maxAddress);
|
||||
|
||||
while (codeUnits.hasNext()) {
|
||||
CodeUnit codeUnit = codeUnits.next();
|
||||
TreeSet<AddressRange> set = map.get(codeUnit);
|
||||
if (set == null) {
|
||||
set = new TreeSet<AddressRange>();
|
||||
map.put(codeUnit, set);
|
||||
}
|
||||
set.add(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void processMap(Map<CodeUnit, TreeSet<AddressRange>> map, Program program) {
|
||||
if (isEnabled()) {
|
||||
int transactionID = -1;
|
||||
try {
|
||||
transactionID = program.startTransaction("Colorize CodeCompare");
|
||||
Set<Entry<CodeUnit, TreeSet<AddressRange>>> entrySet = map.entrySet();
|
||||
for (Entry<CodeUnit, TreeSet<AddressRange>> entry : entrySet) {
|
||||
entry.getKey().setComment(CodeUnit.EOL_COMMENT, entry.getValue().toString());
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (transactionID != -1) {
|
||||
program.endTransaction(transactionID, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void colorize(Map<Address, CorrelationContainer> map, Program sourceProgram,
|
||||
Program destinationProgram) {
|
||||
if (isEnabled()) {
|
||||
int sourceTransactionID = -1;
|
||||
int destinationTransactionID = -1;
|
||||
try {
|
||||
Listing sourceListing = sourceProgram.getListing();
|
||||
Listing destinationListing = destinationProgram.getListing();
|
||||
|
||||
sourceTransactionID = sourceProgram.startTransaction("Colorize CodeCompare");
|
||||
destinationTransactionID =
|
||||
destinationProgram.startTransaction("Colorize CodeCompare");
|
||||
|
||||
Set<Entry<Address, CorrelationContainer>> entrySet = map.entrySet();
|
||||
for (Entry<Address, CorrelationContainer> entry : entrySet) {
|
||||
Address sourceAddress = entry.getKey();
|
||||
CorrelationContainer container = entry.getValue();
|
||||
Color color = pickColor(container);
|
||||
colorCodeUnits(sourceProgram, color, getCodeUnit(sourceListing, sourceAddress));
|
||||
colorCodeUnits(destinationProgram, color,
|
||||
getCodeUnits(destinationListing, container.range));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (sourceTransactionID != -1) {
|
||||
sourceProgram.endTransaction(sourceTransactionID, true);
|
||||
}
|
||||
if (destinationTransactionID != -1) {
|
||||
destinationProgram.endTransaction(destinationTransactionID, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CodeUnit getCodeUnit(Listing listing, Address address) {
|
||||
return listing.getCodeUnitContaining(address);
|
||||
}
|
||||
|
||||
private static CodeUnit[] getCodeUnits(Listing listing, AddressRange addressRange) {
|
||||
HashSet<CodeUnit> units = new HashSet<CodeUnit>();
|
||||
Address address = addressRange.getMinAddress();
|
||||
Address maxAddress = addressRange.getMaxAddress();
|
||||
while (!address.equals(maxAddress)) {
|
||||
CodeUnit codeUnit = listing.getCodeUnitContaining(address);
|
||||
units.add(codeUnit);
|
||||
try {
|
||||
address = address.addNoWrap(1);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
Msg.debug(DebugUtils.class, "Woah...non-contiguous CodeBlock", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CodeUnit codeUnit = listing.getCodeUnitContaining(maxAddress);
|
||||
units.add(codeUnit);
|
||||
return units.toArray(new CodeUnit[0]);
|
||||
}
|
||||
|
||||
private static void colorCodeUnits(Program program, Color color, CodeUnit... codeUnits) {
|
||||
for (CodeUnit codeUnit : codeUnits) {
|
||||
if (codeUnit != null) {
|
||||
setBackgroundColor(program, codeUnit.getMinAddress(), codeUnit.getMaxAddress(),
|
||||
color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setBackgroundColor(Program program, Address min, Address max, Color c) {
|
||||
IntRangeMap map = getColorRangeMap(program, true);
|
||||
if (map != null) {
|
||||
map.setValue(min, max, c.getRGB());
|
||||
}
|
||||
}
|
||||
|
||||
private static IntRangeMap getColorRangeMap(Program program, boolean create) {
|
||||
if (program == null) {
|
||||
return null;
|
||||
}
|
||||
IntRangeMap map =
|
||||
program.getIntRangeMap(PropertyBasedBackgroundColorModel.COLOR_PROPERTY_NAME);
|
||||
if (map == null && create) {
|
||||
try {
|
||||
map = program.createIntRangeMap(
|
||||
PropertyBasedBackgroundColorModel.COLOR_PROPERTY_NAME);
|
||||
}
|
||||
catch (DuplicateNameException e) {
|
||||
// can't happen since we just checked for it!
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static Random RAND = new Random();
|
||||
|
||||
private static Color pickColor(CorrelationContainer container) {
|
||||
float saturation;
|
||||
float brightness;
|
||||
float hue;
|
||||
switch (container.kind) {
|
||||
case CODE_COMPARE:
|
||||
if (CodeCompareAddressCorrelation.CorrelationContainer.USE_RANDOM_CC_COLORS) {
|
||||
hue = RAND.nextFloat();
|
||||
}
|
||||
else {
|
||||
hue = 0.33f;
|
||||
}
|
||||
saturation = (float) (0.5 - RAND.nextFloat() / 3.0);
|
||||
brightness = (float) (1.0 - RAND.nextFloat() / 5.0);
|
||||
break;
|
||||
case LCS:
|
||||
hue = 0.9f;
|
||||
saturation = (float) (0.5 - RAND.nextFloat() / 3.0);
|
||||
brightness = (float) (1.0 - RAND.nextFloat() / 5.0);
|
||||
break;
|
||||
case PARAMETERS:
|
||||
hue = 0.2f;
|
||||
saturation = (float) (1.0 - RAND.nextFloat() / 3.0);
|
||||
brightness = (float) (1.0 - RAND.nextFloat() / 3.0);
|
||||
break;
|
||||
default:
|
||||
hue = 0.1f;
|
||||
saturation = 1.0f;
|
||||
brightness = 1.0f;
|
||||
break;
|
||||
}
|
||||
return Color.getHSBColor(hue, saturation, brightness);
|
||||
}
|
||||
|
||||
public static void enable(boolean b) {
|
||||
ENABLED = b;
|
||||
}
|
||||
|
||||
private static boolean isEnabled() {
|
||||
return ENABLED;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue