GP-4009 Introduced BSim functionality including support for postgresql,

elasticsearch and h2 databases.  Added BSim correlator to Version
Tracking.
This commit is contained in:
caheckman 2023-11-17 01:13:42 +00:00 committed by ghidra1
parent f0f5b8f2a4
commit 0865a3dfb0
509 changed files with 77125 additions and 934 deletions

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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];
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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) {
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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');
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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";
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}