GP-4007: Add actions to transfer variable and callee names and types to dual decompiler view

This commit is contained in:
ghintern
2025-07-10 13:54:27 +00:00
parent b2e11d5312
commit e72e58117f
17 changed files with 1925 additions and 172 deletions

View File

@@ -0,0 +1,120 @@
/* ###
* 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.features.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import ghidra.app.decompiler.ClangFuncNameToken;
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.Msg;
/**
* Subclass of {@link AbstractMatchedTokensAction} for actions in a
* {@link DecompilerCodeComparisonPanel} that are available only when the matched tokens are
* function calls
*/
public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedTokensAction {
protected static final String MENU_GROUP = "A2_ApplyCallee";
/**
* 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 AbstractMatchedCalleeTokensAction(String actionName, String owner,
DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) {
super(actionName, owner, diffPanel, disableOnReadOnly);
}
@Override
protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
TokenPair tokenPair = context.getTokenPair();
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 dualDecompilerActionPerformed(DualDecompilerActionContext context) {
DecompilerCodeComparisonPanel decompPanel = context.getCodeComparisonPanel();
TokenPair currentPair = context.getTokenPair();
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
if (leftFunction == null || rightFunction == null) {
return;
}
doCalleeActionPerformed(leftFunction, rightFunction);
}
/**
* Once function objects have been recovered from the callee tokens, perform an action
* @param leftFunction the callee function on the left side of the decompiler diff panel
* @param rightFunction the callee function on the right side of the decompiler diff panel
*/
protected abstract void doCalleeActionPerformed(Function leftFunction, Function rightFunction);
private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) {
Address callTarget = funcToken.getPcodeOp().getInput(0).getAddress();
Function func = program.getFunctionManager().getFunctionAt(callTarget);
if (func == null) {
Msg.showWarn(this, null, "Unable to Compare Callees",
"Can't compare callees - null Function for " + funcToken.getText());
return null;
}
if (func.isExternal()) {
Msg.showWarn(this, null, "Unable to Compare Callees",
"Can't compare callees - " + func.getName() + " is external");
return null;
}
if (!func.isThunk()) {
return func;
}
func = func.getThunkedFunction(true);
if (func.isExternal()) {
Msg.showWarn(this, null, "Unable to Compare",
"Can't compare callees - " + func.getName() + " is external");
return null;
}
return func;
}
}

View File

@@ -15,24 +15,15 @@
*/
package ghidra.features.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
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.DecompilerPanel;
import ghidra.features.codecompare.graphanalysis.TokenBin;
import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.Duo.Side;
/**
* This is a base class for actions in a {@link DecompilerCodeComparisonPanel}
*/
public abstract class AbstractMatchedTokensAction extends DockingAction {
protected static final String MENU_PARENT = "Apply From Other Function";
protected static final String HELP_TOPIC = "FunctionComparison";
protected DecompilerCodeComparisonPanel diffPanel;
protected boolean disableOnReadOnly;
@@ -52,83 +43,43 @@ public abstract class AbstractMatchedTokensAction extends DockingAction {
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 void actionPerformed(ActionContext context) {
if (!(context instanceof DualDecompilerActionContext compareContext)) {
return;
}
dualDecompilerActionPerformed(compareContext);
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DualDecompilerActionContext compareContext)) {
return false;
}
DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
if (disableOnReadOnly) {
//get the program corresponding to the panel with focus
Side focusedSide = decompPanel.getActiveSide();
Program program = decompPanel.getProgram(focusedSide);
if (program == null) {
return false; //panel initializing; don't enable action
}
if (!program.canSave()) {
return false; //program is read-only, don't enable action
if (compareContext.isActiveProgramReadOnly()) {
return false; // program is read-only, don't enable action
}
}
TokenPair currentPair = getCurrentTokenPair(decompPanel);
return enabledForTokens(currentPair);
return isEnabledForDualDecompilerContext(compareContext);
}
/**
* 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)
* Subclasses return true if they are enabled for the given context
*
* @param context the context
* @return true if enabled
*/
protected TokenPair getCurrentTokenPair(
DecompilerCodeComparisonPanel decompPanel) {
protected abstract boolean isEnabledForDualDecompilerContext(
DualDecompilerActionContext context);
DecompilerPanel focusedPanel = decompPanel.getActiveDisplay().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.getActiveSide() == LEFT
? new TokenPair(focusedToken, currentMatch)
: new TokenPair(currentMatch, focusedToken);
}
}
return null;
}
/**
* Subclasses will perform their work in this method
* @param context the context
*/
protected abstract void dualDecompilerActionPerformed(DualDecompilerActionContext context);
}

View File

@@ -0,0 +1,72 @@
/* ###
* 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.features.codecompare.decompile;
import docking.action.MenuData;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.FunctionUtility;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
/**
* An action for transferring the 'skeleton' function signature between matched callee tokens.
*/
public class ApplyCalleeEmptySignatureFromMatchedTokensAction
extends AbstractMatchedCalleeTokensAction {
private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Callee Signature";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyCalleeEmptySignatureFromMatchedTokensAction(
DecompilerCodeComparisonPanel diffPanel, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool;
MenuData menuData =
new MenuData(new String[] { MENU_PARENT, "Callee Signature" }, null,
MENU_GROUP);
setPopupMenuData(menuData);
setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
}
@Override
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
Side activeSide = diffPanel.getActiveSide();
Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;
Program activeProgram = activeFunction.getProgram();
try {
activeProgram.withTransaction("Code Comparison Transfer Callee Signature",
() -> FunctionUtility.applySignature(activeFunction, otherFunction, true, null));
}
catch (Exception e) {
Msg.showError(this, tool.getToolFrame(), "Failed to Apply Callee Signature",
e.getMessage());
}
}
}

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.features.codecompare.decompile;
import docking.action.MenuData;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.FunctionUtility;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
/**
* An action for transferring just the function name and namespace between matched callee tokens.
*/
public class ApplyCalleeFunctionNameFromMatchedTokensAction
extends AbstractMatchedCalleeTokensAction {
private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Callee Name";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyCalleeFunctionNameFromMatchedTokensAction(
DecompilerCodeComparisonPanel diffPanel, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool;
MenuData menuData =
new MenuData(new String[] { MENU_PARENT, "Callee Name" }, null, MENU_GROUP);
setPopupMenuData(menuData);
setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
}
@Override
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
Side activeSide = diffPanel.getActiveSide();
Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;
Program activeProgram = activeFunction.getProgram();
try {
activeProgram.withTransaction("Code Comparison Transfer Callee Function Name",
() -> FunctionUtility.applyNameAndNamespace(activeFunction, otherFunction));
}
catch (Exception e) {
Msg.showError(this, tool.getToolFrame(), "Failed to Apply Callee Function Name",
e.getMessage());
}
}
}

View File

@@ -0,0 +1,73 @@
/* ###
* 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.features.codecompare.decompile;
import docking.action.MenuData;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.FunctionUtility;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
/**
* An action for transferring the full function signature between matched callee tokens
*/
public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction
extends AbstractMatchedCalleeTokensAction {
private PluginTool tool;
public static final String ACTION_NAME =
"Function Comparison Apply Callee Signature And Datatypes";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(
DecompilerCodeComparisonPanel diffPanel, PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool;
MenuData menuData =
new MenuData(new String[] { MENU_PARENT, "Callee Signature and Data Types" },
null, MENU_GROUP);
setPopupMenuData(menuData);
setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
}
@Override
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
Side activeSide = diffPanel.getActiveSide();
Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction;
Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction;
Program activeProgram = activeFunction.getProgram();
try {
activeProgram.withTransaction(
"Code Comparison Transfer Callee Signature and Data Types",
() -> FunctionUtility.applySignature(activeFunction, otherFunction, false, null));
}
catch (Exception e) {
Msg.showError(this, tool.getToolFrame(),
"Failed to Apply Callee Signature and Data Types", e.getMessage());
}
}
}

View File

@@ -0,0 +1,121 @@
/* ###
* 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.features.codecompare.decompile;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangVariableToken;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.util.DataTypeCleaner;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.exception.*;
public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatchedTokensAction {
private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Variable Skeleton Type";
private static final String MENU_GROUP = "A1_ApplyVariable";
public ApplyEmptyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool;
MenuData menuData =
new MenuData(new String[] { MENU_PARENT, "Variable Skeleton Type" }, null, MENU_GROUP);
setPopupMenuData(menuData);
setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
}
@Override
protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
TokenPair tokenPair = context.getTokenPair();
if (tokenPair == null) {
return false;
}
if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
return false;
}
if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
!(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
return false;
}
HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
return leftSymbol != null && rightSymbol != null;
}
@Override
protected void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide();
ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
: (ClangVariableToken) currentPair.rightToken();
ClangVariableToken otherToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
: (ClangVariableToken) currentPair.leftToken();
HighSymbol activeHighSymbol =
activeToken.getHighSymbol(context.getHighFunction(activeSide));
HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram();
DataType dt = otherHighSymbol.getDataType();
try {
activeProgram.withTransaction("Code Comparison Transfer Local Skeleton Type", () -> {
DataTypeCleaner dtCleaner =
new DataTypeCleaner(activeProgram.getDataTypeManager(), true);
try {
DataType resolvedDt = dtCleaner.clean(dt);
resolvedDt = activeProgram.getDataTypeManager().resolve(resolvedDt, null);
HighFunctionDBUtil.updateDBVariable(activeHighSymbol, null, resolvedDt,
SourceType.IMPORTED);
}
finally {
dtCleaner.close();
}
});
}
catch (InvalidInputException e) {
Msg.showError(this, tool.getToolFrame(), "Skeleton Type Transfer Failed",
"Could not bring cleaned type " + dt.getName() + " to " + activeProgram.getName());
}
catch (UsrException e) {
throw new AssertException("Unexpected exception", e);
}
}
}

View File

@@ -0,0 +1,127 @@
/* ###
* 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.features.codecompare.decompile;
import docking.action.MenuData;
import ghidra.app.cmd.label.RenameLabelCmd;
import ghidra.app.decompiler.ClangVariableToken;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighCodeSymbol;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.symbol.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
/**
* An action for transferring the name of a global variable between matched tokens.
*/
public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedTokensAction {
private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Global Variable Name";
private static final String MENU_GROUP = "A1_ApplyVariable";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyGlobalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool;
MenuData menuData =
new MenuData(new String[] { MENU_PARENT, "Variable Name" }, null, MENU_GROUP);
setPopupMenuData(menuData);
setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
}
@Override
protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
TokenPair tokenPair = context.getTokenPair();
if (tokenPair == null) {
return false;
}
if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
return false;
}
if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
!(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
return false;
}
HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
if (leftSymbol == null || rightSymbol == null) {
return false;
}
return leftSymbol.isGlobal() && rightSymbol.isGlobal();
}
@Override
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide();
ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
: (ClangVariableToken) currentPair.rightToken();
ClangVariableToken otherToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
: (ClangVariableToken) currentPair.leftToken();
HighSymbol activeHighSymbol =
activeToken.getHighSymbol(context.getHighFunction(activeSide));
HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Program activeProgram = context.getCodeComparisonPanel().getProgram(activeSide);
Symbol activeSymbol = null;
if (activeHighSymbol instanceof HighCodeSymbol activeCodeSymbol) {
activeSymbol = activeCodeSymbol.getCodeSymbol();
if (activeSymbol == null) {
Address addr = activeCodeSymbol.getStorage().getMinAddress();
SymbolTable symbolTable = activeProgram.getSymbolTable();
activeSymbol = symbolTable.getPrimarySymbol(addr);
}
}
if (activeSymbol == null) {
Msg.showError(this, tool.getToolFrame(), "Name transfer failed",
"Failed to find memory storage for target global");
return;
}
RenameLabelCmd cmd = new RenameLabelCmd(activeSymbol, otherHighSymbol.getName(),
otherHighSymbol.getNamespace(), SourceType.IMPORTED);
activeProgram.withTransaction("Code Comparison Apply Global Variable Name",
() -> cmd.applyTo(activeProgram));
if (!cmd.getStatusMsg().isEmpty()) {
Msg.showError(this, tool.getToolFrame(), "Name transfer failed", cmd.getStatusMsg());
}
}
}

View File

@@ -0,0 +1,117 @@
/* ###
* 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.features.codecompare.decompile;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangVariableToken;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.exception.*;
/**
* An action for transferring the name of a local variable between matched tokens.
*/
public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokensAction {
private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Local Variable Name";
private static final String MENU_GROUP = "A1_ApplyVariable";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyLocalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool;
MenuData menuData =
new MenuData(new String[] { MENU_PARENT, "Variable Name" }, null, MENU_GROUP);
setPopupMenuData(menuData);
setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
}
@Override
protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
TokenPair tokenPair = context.getTokenPair();
if (tokenPair == null) {
return false;
}
if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
return false;
}
if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
!(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
return false;
}
HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
if (leftSymbol == null || rightSymbol == null) {
return false;
}
return !leftSymbol.isGlobal() && !rightSymbol.isGlobal();
}
@Override
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide();
ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
: (ClangVariableToken) currentPair.rightToken();
ClangVariableToken otherToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
: (ClangVariableToken) currentPair.leftToken();
HighSymbol activeHighSymbol =
activeToken.getHighSymbol(context.getHighFunction(activeSide));
HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram();
try {
activeProgram.withTransaction("Code Comparison Apply Local Variable Name",
() -> HighFunctionDBUtil.updateDBVariable(activeHighSymbol,
otherHighSymbol.getName(),
null, SourceType.IMPORTED));
}
catch (DuplicateNameException e) {
Msg.showError(this, tool.getToolFrame(), "Duplicate Name",
"Name " + otherHighSymbol.getName() + " already exists in function " +
activeFunction.getName());
}
catch (UsrException e) {
throw new AssertException("Unexpected exception", e);
}
}
}

View File

@@ -0,0 +1,117 @@
/* ###
* 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.features.codecompare.decompile;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangVariableToken;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.Duo.Side;
import ghidra.util.exception.*;
/**
* An action for transferring the type of a local or global variable between matched tokens.
*/
public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTokensAction {
private PluginTool tool;
public static final String ACTION_NAME = "Function Comparison Apply Variable Type";
private static final String MENU_GROUP = "A1_ApplyVariable";
/**
* Construtor
* @param diffPanel diff panel
* @param tool tool
*/
public ApplyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel,
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, true);
this.tool = tool;
MenuData menuData =
new MenuData(new String[] { MENU_PARENT, "Variable Type" }, null, MENU_GROUP);
setPopupMenuData(menuData);
setHelpLocation(new HelpLocation(HELP_TOPIC, getName()));
}
@Override
protected boolean isEnabledForDualDecompilerContext(DualDecompilerActionContext context) {
TokenPair tokenPair = context.getTokenPair();
if (tokenPair == null) {
return false;
}
if (tokenPair.leftToken() == null || tokenPair.rightToken() == null) {
return false;
}
if (!(tokenPair.leftToken() instanceof ClangVariableToken leftVar) ||
!(tokenPair.rightToken() instanceof ClangVariableToken rightVar)) {
return false;
}
HighSymbol leftSymbol = leftVar.getHighSymbol(context.getHighFunction(Side.LEFT));
HighSymbol rightSymbol = rightVar.getHighSymbol(context.getHighFunction(Side.RIGHT));
return leftSymbol != null && rightSymbol != null;
}
@Override
public void dualDecompilerActionPerformed(DualDecompilerActionContext context) {
TokenPair currentPair = context.getTokenPair();
Side activeSide = diffPanel.getActiveSide();
ClangVariableToken activeToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken()
: (ClangVariableToken) currentPair.rightToken();
ClangVariableToken otherToken =
activeSide == Side.LEFT ? (ClangVariableToken) currentPair.rightToken()
: (ClangVariableToken) currentPair.leftToken();
HighSymbol activeHighSymbol =
activeToken.getHighSymbol(context.getHighFunction(activeSide));
HighSymbol otherHighSymbol =
otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide()));
Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide);
Program activeProgram = activeFunction.getProgram();
DataType dt = otherHighSymbol.getDataType();
try {
activeProgram.withTransaction("Code Comparison Transfer Local Type", () -> {
DataType resolvedDt = activeProgram.getDataTypeManager().resolve(dt, null);
HighFunctionDBUtil.updateDBVariable(activeHighSymbol, null, resolvedDt,
SourceType.IMPORTED);
});
}
catch (InvalidInputException e) {
Msg.showError(this, tool.getToolFrame(), "Type Transfer Failed",
"Could not bring type " + dt.getName() + " to " + activeProgram.getName());
}
catch (UsrException e) {
throw new AssertException("Unexpected exception", e);
}
}
}

View File

@@ -4,9 +4,9 @@
* 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.
@@ -15,17 +15,10 @@
*/
package ghidra.features.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import docking.ActionContext;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangFuncNameToken;
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;
@@ -33,11 +26,9 @@ 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 {
public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedCalleeTokensAction {
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
@@ -48,58 +39,14 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
PluginTool tool) {
super(ACTION_NAME, tool.getName(), diffPanel, false);
this.tool = tool;
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
if (service != null) {
MenuData menuData = new MenuData(new String[] { ACTION_NAME }, null, MENU_GROUP);
setPopupMenuData(menuData);
setEnabled(true);
setHelpLocation(new HelpLocation(HELP_TOPIC, "Compare Matching Callees"));
}
MenuData menuData = new MenuData(new String[] { ACTION_NAME }, null, MENU_GROUP);
setPopupMenuData(menuData);
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;
}
DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel();
TokenPair currentPair = getCurrentTokenPair(decompPanel);
if (currentPair == null || currentPair.leftToken() == null ||
currentPair.rightToken() == null) {
return;
}
ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken();
ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken();
Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT));
Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT));
if (leftFunction == null || rightFunction == null) {
return;
}
protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) {
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
if (service == null) {
Msg.error(this, "Function Comparison Service not found!");
@@ -108,30 +55,4 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
service.createComparison(leftFunction, rightFunction);
}
private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) {
Address callTarget = funcToken.getPcodeOp().getInput(0).getAddress();
Function func = program.getFunctionManager().getFunctionAt(callTarget);
if (func == null) {
Msg.showWarn(this, null, "Unable to Compare Callees",
"Can't compare callees - null Function for " + funcToken.getText());
return null;
}
if (func.isExternal()) {
Msg.showWarn(this, null, "Unable to Compare Callees",
"Can't compare callees - " + func.getName() + " is external");
return null;
}
if (!func.isThunk()) {
return func;
}
func = func.getThunkedFunction(true);
if (func.isExternal()) {
Msg.showWarn(this, null, "Unable to Compare",
"Can't compare callees - " + func.getName() + " is external");
return null;
}
return func;
}
}

View File

@@ -182,6 +182,13 @@ public class DecompilerCodeComparisonPanel
actions.add(new DecompilerCodeComparisonOptionsAction());
actions.add(toggleExactConstantMatchingAction);
actions.add(new CompareFuncsFromMatchedTokensAction(this, tool));
actions.add(new ApplyLocalNameFromMatchedTokensAction(this, tool));
actions.add(new ApplyGlobalNameFromMatchedTokensAction(this, tool));
actions.add(new ApplyVariableTypeFromMatchedTokensAction(this, tool));
actions.add(new ApplyEmptyVariableTypeFromMatchedTokensAction(this, tool));
actions.add(new ApplyCalleeFunctionNameFromMatchedTokensAction(this, tool));
actions.add(new ApplyCalleeEmptySignatureFromMatchedTokensAction(this, tool));
actions.add(new ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(this, tool));
}
private void decompileDataSet(Side side, DecompileData dcompileData) {

View File

@@ -4,9 +4,9 @@
* 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.
@@ -15,11 +15,22 @@
*/
package ghidra.features.codecompare.decompile;
import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.Component;
import java.util.Iterator;
import java.util.List;
import docking.ComponentProvider;
import ghidra.app.context.RestrictedAddressSetContext;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.DecompilerLocation;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.features.base.codecompare.panel.CodeComparisonActionContext;
import ghidra.features.codecompare.graphanalysis.TokenBin;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunction;
import ghidra.util.datastruct.Duo.Side;
/**
* Action context for a dual decompiler panel.
@@ -28,6 +39,8 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
implements RestrictedAddressSetContext {
private DecompilerCodeComparisonPanel decompilerComparisonPanel = null;
private TokenPair tokenPair;
private boolean overrideReadOnly = false;
/**
* Creates an action context for a dual decompiler panel.
@@ -38,7 +51,47 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
public DualDecompilerActionContext(ComponentProvider provider,
DecompilerCodeComparisonPanel panel, Component source) {
super(provider, panel, source);
this.decompilerComparisonPanel = panel;
decompilerComparisonPanel = panel;
tokenPair = computeTokenPair();
}
private TokenPair computeTokenPair() {
DecompilerPanel focusedPanel =
decompilerComparisonPanel.getActiveDisplay().getDecompilerPanel();
if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) {
return null;
}
ClangToken focusedToken = focusedLocation.getToken();
if (focusedToken == null) {
return null;
}
List<TokenBin> tokenBin = decompilerComparisonPanel.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 decompilerComparisonPanel.getActiveSide() == LEFT
? new TokenPair(focusedToken, currentMatch)
: new TokenPair(currentMatch, focusedToken);
}
}
return null;
}
/**
@@ -49,4 +102,55 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext
public DecompilerCodeComparisonPanel getCodeComparisonPanel() {
return decompilerComparisonPanel;
}
/**
* Returns the {@link HighFunction} being viewed on the given side by the decompiler panel that
* generated this context
* @param side the side of the comparison to retrieve the high function for
* @return the high function on the given side of the comparison panel that generated this
* context
*/
public HighFunction getHighFunction(Side side) {
return decompilerComparisonPanel.getDecompilerPanel(side).getController().getHighFunction();
}
/**
* Returns the {@link TokenPair} currently selected in the diff view, if any.
* @return the token pair selected when this context was generated
*/
public TokenPair getTokenPair() {
return tokenPair;
}
/**
* Set whether this context will bypass a check to the actual state of the active program
* when resolving {@link #isActiveProgramReadOnly}. Used by tests.
* @param overrideReadOnly true if this context should bypass an
* {@link #isActiveProgramReadOnly} by always returning false
*/
void setOverrideReadOnly(boolean overrideReadOnly) {
this.overrideReadOnly = overrideReadOnly;
}
/**
* Check if the program associated with the focused window in the dual decompiler view is
* read only. Always false if read only override was set to true with a call to
* {@link #setOverrideReadOnly}
* @return true if the active program is read only, always false if override is set to true
*/
public boolean isActiveProgramReadOnly() {
if (overrideReadOnly) {
return false;
}
Program activeProgram =
decompilerComparisonPanel.getProgram(decompilerComparisonPanel.getActiveSide());
if (activeProgram == null) {
return true;
}
return activeProgram.getDomainFile().isReadOnly();
}
}

View File

@@ -0,0 +1,117 @@
/* ###
* 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.features.codecompare.decompile;
import java.awt.Point;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.support.FieldLocation;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.component.ClangTextField;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel;
import ghidra.features.codecompare.plugin.FunctionComparisonPlugin;
import ghidra.features.codecompare.plugin.FunctionComparisonProvider;
import ghidra.program.model.listing.Function;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.datastruct.Duo.Side;
public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedIntegrationTest {
protected TestEnv env;
protected FunctionComparisonPlugin fcPlugin;
protected FunctionPlugin fPlugin;
protected CodeBrowserPlugin cbPlugin;
@Before
public void setUp() throws Exception {
env = new TestEnv();
fcPlugin = env.addPlugin(FunctionComparisonPlugin.class);
fPlugin = env.addPlugin(FunctionPlugin.class);
cbPlugin = env.addPlugin(CodeBrowserPlugin.class);
}
@After
public void tearDown() throws Exception {
env.dispose();
}
protected FunctionComparisonProvider compareFunctions(Set<Function> functions) {
runSwing(() -> fcPlugin.createComparison(functions));
waitForSwing();
return waitForComponentProvider(FunctionComparisonProvider.class);
}
protected DecompilerCodeComparisonPanel findDecompilerPanel(
FunctionComparisonProvider provider) {
for (CodeComparisonPanel panel : provider.getComponent().getComparisonPanels()) {
if (panel instanceof DecompilerCodeComparisonPanel decompPanel) {
return decompPanel;
}
}
return null;
}
protected void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) {
runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName()));
waitForSwing();
}
protected void waitForDecompile(DecompilerCodeComparisonPanel panel) {
waitForSwing();
waitForCondition(() -> !panel.isBusy());
waitForSwing();
}
protected DecompilerPanel getDecompSide(DecompilerCodeComparisonPanel panel, Side side) {
CDisplay sideDisplay = side == Side.LEFT ? panel.getLeftPanel() : panel.getRightPanel();
return sideDisplay.getDecompilerPanel();
}
// 1-indexed lines
protected ClangToken setDecompLocation(DecompilerCodeComparisonPanel comparePanel, Side side,
int line, int charPos) {
DecompilerPanel panel = getDecompSide(comparePanel, side);
FieldPanel fp = panel.getFieldPanel();
FieldLocation loc = new FieldLocation(line - 1, 0, 0, charPos); // 0-indexed lines
fp.scrollTo(loc);
Point p = fp.getPointForLocation(loc);
click(fp, p, 1, true);
waitForSwing();
return getCurrentToken(comparePanel, side);
}
// Get the token under the cursor at the given side
protected ClangToken getCurrentToken(DecompilerCodeComparisonPanel comparePanel, Side side) {
DecompilerPanel panel = getDecompSide(comparePanel, side);
FieldLocation loc = panel.getCursorPosition();
int lineNumber = loc.getIndex().intValue();
ClangTextField field = (ClangTextField) panel.getFields().get(lineNumber);
return field.getToken(loc);
}
}

View File

@@ -0,0 +1,793 @@
/* ###
* 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.features.codecompare.decompile;
import static org.junit.Assert.*;
import java.util.Set;
import org.junit.*;
import docking.ActionContext;
import docking.action.DockingActionIf;
import ghidra.app.decompiler.ClangToken;
import ghidra.features.codecompare.plugin.FunctionComparisonProvider;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.util.datastruct.Duo.Side;
public class DualDecompilerActionTest extends AbstractDualDecompilerTest {
private FunctionComparisonProvider provider = null;
private Program progDemangler24Debug;
private Function funcDemangler24DebugMain;
private Function funcDemangler24DebugCplusDemangle;
private static final long DEMANGLER_24_DEBUG_MAIN_OFFSET = 0x414835;
private static final long DEMANGLER_24_DEBUG_CPLUS_DEMANGLE_OFFSET = 0x40c01b;
private static final long DEMANGLER_24_DEBUG_INTERNAL_CPLUS_DEMANGLE_OFFSET = 0x40c987;
private static final String DEMANGLER_24_DEBUG_PROG_NAME =
"CodeCompare/demangler_gnu_v2_24_fulldebug";
private Program progDemangler24Stripped;
private Function funcDemangler24StrippedMain;
private Function funcDemangler24StrippedCplusDemangle;
private static final long DEMANGLER_24_STRIPPED_MAIN_OFFSET = 0x414835;
private static final long DEMANGLER_24_STRIPPED_CPLUS_DEMANGLE_OFFSET = 0x40c01b;
private static final long DEMANGLER_24_STRIPPED_INTERNAL_CPLUS_DEMANGLE_OFFSET = 0x40c987;
private static final String DEMANGLER_24_STRIPPED_PROG_NAME =
"CodeCompare/demangler_gnu_v2_24_stripped";
@Override
@Before
public void setUp() throws Exception {
super.setUp();
progDemangler24Debug = env.getProgram(DEMANGLER_24_DEBUG_PROG_NAME);
progDemangler24Stripped = env.getProgram(DEMANGLER_24_STRIPPED_PROG_NAME);
assertNotNull(progDemangler24Debug);
assertNotNull(progDemangler24Stripped);
funcDemangler24DebugMain =
getFunctionFromOffset(progDemangler24Debug, DEMANGLER_24_DEBUG_MAIN_OFFSET);
funcDemangler24DebugCplusDemangle =
getFunctionFromOffset(progDemangler24Debug, DEMANGLER_24_DEBUG_CPLUS_DEMANGLE_OFFSET);
funcDemangler24StrippedMain =
getFunctionFromOffset(progDemangler24Stripped, DEMANGLER_24_STRIPPED_MAIN_OFFSET);
funcDemangler24StrippedCplusDemangle =
getFunctionFromOffset(progDemangler24Stripped,
DEMANGLER_24_STRIPPED_CPLUS_DEMANGLE_OFFSET);
assertNotNull(funcDemangler24DebugMain);
assertNotNull(funcDemangler24DebugCplusDemangle);
assertNotNull(funcDemangler24StrippedMain);
assertNotNull(funcDemangler24StrippedCplusDemangle);
showTool(fcPlugin.getTool());
env.open(progDemangler24Debug);
env.open(progDemangler24Stripped);
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
}
/*
* Comparisons of these functions are used in the next few tests
Decomp of 'main' in 'demangler_gnu_v2_24_fulldebug':
1|
2| int main(int argc,char **argv)
3|
4| {
5| char *pcVar1;
6| char **argv_local;
7| int argc_local;
8| char *options;
9| char *demangler;
10| int skip_first;
11| int i;
12| char *valid_symbols;
13| int c;
14|
15| demangler = (char *)0x0;
16| options = (char *)0x0;
17| program_name = *argv;
18| strip_underscore = prepends_underscore;
19| argv_local = argv;
20| argc_local = argc;
21| expandargv(&argc_local,&argv_local);
...
Decomp of 'FUN_00414835' (main) in 'demangler_gnu_v2_41_stripped':
1|
2| undefined8 FUN_00414835(int param_1,undefined8 *param_2)
3|
4| {
5| char *pcVar1;
6| undefined8 *local_48;
7| int local_3c [3];
8| undefined8 local_30;
9| undefined8 local_28;
10| int local_20;
11| int local_1c;
12| char *local_18;
13| uint local_c;
14|
15| local_28 = 0;
16| local_30 = 0;
17| DAT_004252e0 = *param_2;
18| DAT_0041d220 = DAT_00425240;
19| local_48 = param_2;
20| local_3c[0] = param_1;
21| FUN_00401a2d(local_3c,&local_48);
...
Decomp of 'FUN_0040c01b' (cplus_demangle) in 'demangler_gnu_v2_24_stripped':
1|
2| long FUN_0040c01b(undefined8 param_1,uint param_2)
3|
4| {
5| long lVar1;
6| uint local_88 [30];
7| long local_10;
8|
9| if (DAT_0041d150 == 0xffffffff) {
10| local_10 = FUN_0040b419(param_1);
11| }
12| else {
13| memset(local_88,0,0x70);
14| local_88[0] = param_2;
15| if ((param_2 & 0xff04) == 0) {
16| local_88[0] = DAT_0041d150 & 0xff04 | param_2;
17| }
...
*/
@Test
public void testLocalNameTransferAction() throws RuntimeException {
final String actionName = ApplyLocalNameFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf localNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(localNameTransferAction);
line = 15;
col = 0;
currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
// Cursor is now on uncorrelated local variable token 'demangler'
// Ensure the local variable name transfer action is not active
assertEquals("demangler", currentToken.getText());
assertNotEnabled(localNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action
localNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(localNameTransferAction);
line = 15;
col = 11;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated constant token '0'.
// Ensure the local variable name transfer action is not active
assertEquals("0", currentToken.getText());
assertNotEnabled(localNameTransferAction, getProviderContext());
line = 17;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated global variable token 'DAT_004252e0'
// Ensure the local variable name transfer action is not active
assertEquals("DAT_004252e0", currentToken.getText());
assertNotEnabled(localNameTransferAction, getProviderContext());
line = 15;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_28'
// Ensure the local variable name transfer action is active and working
assertEquals("local_28", currentToken.getText());
assertEnabled(localNameTransferAction, getProviderContext());
performAction(localNameTransferAction);
waitForDecompile(correlatedPanel);
currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
assertEquals("demangler", currentToken.getText());
}
@Test
public void testGlobalNameTransferAction() throws RuntimeException {
final String actionName = ApplyGlobalNameFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf globalNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(globalNameTransferAction);
line = 17;
col = 0;
currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
// Cursor is now on uncorrelated global variable token 'program_name'
// Ensure the global variable name transfer action is not active
assertEquals("program_name", currentToken.getText());
assertNotEnabled(globalNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action
globalNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(globalNameTransferAction);
line = 15;
col = 11;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated constant token '0'.
// Ensure the global variable name transfer action is not active
assertEquals("0", currentToken.getText());
assertNotEnabled(globalNameTransferAction, getProviderContext());
line = 15;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_28'
// Ensure the global variable name transfer action is not active
assertEquals("local_28", currentToken.getText());
assertNotEnabled(globalNameTransferAction, getProviderContext());
line = 17;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated global variable token 'DAT_004252e0'
// Ensure the global variable name transfer action is active and working
assertEquals("DAT_004252e0", currentToken.getText());
assertEnabled(globalNameTransferAction, getProviderContext());
performAction(globalNameTransferAction);
waitForDecompile(correlatedPanel);
currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
assertEquals("program_name", currentToken.getText());
}
@Test
public void testVariableTypeTransferAction() throws RuntimeException {
final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction);
line = 15;
col = 0;
currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
// Cursor is now on uncorrelated local variable token 'demangler'
// Ensure the variable type transfer action is not active
assertEquals("demangler", currentToken.getText());
assertNotEnabled(typeTransferAction, getProviderContext());
line = 17;
col = 0;
currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
// Cursor is now on uncorrelated global variable token 'program_name'
// Ensure the variable type transfer action is not active
assertEquals("program_name", currentToken.getText());
assertNotEnabled(typeTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain);
// Recreated provider, need to get new handle on action
typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction);
line = 15;
col = 11;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated constant token '0'.
// Ensure the variable type transfer action is not active
assertEquals("0", currentToken.getText());
assertNotEnabled(typeTransferAction, getProviderContext());
line = 15;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_28'
// Ensure the variable type transfer action is active and works on local variable
assertEquals("local_28", currentToken.getText());
assertEnabled(typeTransferAction, getProviderContext());
performAction(typeTransferAction);
waitForDecompile(correlatedPanel);
// Check that 'local_28 = 0;' has become 'local_28 = (char *)0x0;
col = 12;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
assertEquals("char", currentToken.getText());
col = 17;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
assertEquals("*", currentToken.getText());
line = 17;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated global variable token 'DAT_004252e0'
// Ensure the global variable name transfer action is active and working
assertEquals("DAT_004252e0", currentToken.getText());
assertEnabled(typeTransferAction, getProviderContext());
performAction(typeTransferAction);
waitForDecompile(correlatedPanel);
// Check that 'DAT_004252e0 = *param_2;' has become 'PTR_004252e0 = (char *)*param_2;'
currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
assertEquals("PTR_004252e0", currentToken.getText());
col = 16;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
assertEquals("char", currentToken.getText());
col = 21;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
assertEquals("*", currentToken.getText());
}
/**
* The callee and struct type tests use the decomp of the following functions. We care about
* the work_stuff struct and the call to 'internal_cplus_demangle' / 'FUN_0040c987', which has
* debug signature: char * internal_cplus_demangle(work_stuff *work, char *mangled)
* stripped signature: undefined8 FUN_0040c987(uint *param_1,char *param_2):
Decomp of 'cplus_demangle' in 'demangler_gnu_v2_24_fulldebug':
1|
2| char * cplus_demangle(char *mangled,int options)
3|
4| {
5| char *pcVar1;
6| int options_local;
7| char *mangled_local;
8| work_stuff work [1];
9| char *ret;
10|
11| if (current_demangling_style == no_demangling) {
12| pcVar1 = xstrdup(mangled);
13| }
14| else {
15| memset(work,0,0x70);
16| work[0].options = options;
17| if ((options & 0xff04U) == 0) {
18| work[0].options =
19| current_demangling_style &
20| (gnat_demangling|gnu_v3_demangling|edg_demangling|hp_demangling|arm_demangling|
21| lucid_demangling|gnu_demangling|auto_demangling|java_demangling) | options;
22| }
23| if (((((work[0].options & 0x4000U) == 0) && ((work[0].options & 0x100U) == 0)) ||
24| ((pcVar1 = cplus_demangle_v3(mangled,work[0].options), pcVar1 == (char *)0x0 &&
25| ((work[0].options & 0x4000U) == 0)))) &&
26| (((work[0].options & 4U) == 0 || (pcVar1 = java_demangle_v3(mangled), pcVar1 == (char *)0x0))
27| )) {
28| if ((work[0].options & 0x8000U) == 0) {
29| pcVar1 = internal_cplus_demangle(work,mangled);
30| squangle_mop_up(work);
31| }
32| else {
33| pcVar1 = ada_demangle(mangled,options);
34| }
35| }
36| }
37| return pcVar1;
38| }
Decomp of 'FUN_0040c01b' (cplus_demangle) in 'demangler_gnu_v2_24_stripped':
1|
2| long FUN_0040c01b(undefined8 param_1,uint param_2)
3|
4| {
5| long lVar1;
6| uint local_88 [30];
7| long local_10;
8|
9| if (DAT_0041d150 == 0xffffffff) {
10| local_10 = FUN_0040b419(param_1);
11| }
12| else {
13| memset(local_88,0,0x70);
14| local_88[0] = param_2;
15| if ((param_2 & 0xff04) == 0) {
16| local_88[0] = DAT_0041d150 & 0xff04 | param_2;
17| }
18| if (((((local_88[0] & 0x4000) == 0) && (lVar1 = local_10, (local_88[0] & 0x100) == 0)) ||
19| ((local_10 = FUN_0040a8cc(param_1,local_88[0]), local_10 == 0 &&
20| (lVar1 = 0, (local_88[0] & 0x4000) == 0)))) &&
21| ((local_10 = lVar1, (local_88[0] & 4) == 0 ||
22| (local_10 = FUN_0040a922(param_1), local_10 == 0)))) {
23| if ((local_88[0] & 0x8000) == 0) {
24| local_10 = FUN_0040c987(local_88,param_1);
25| FUN_0040cb6c(local_88);
26| }
27| else {
28| local_10 = FUN_0040c154(param_1,param_2);
29| }
30| }
31| }
32| return local_10;
33| }
*/
@Test
public void testFullStructTypeTransferAction() throws RuntimeException {
final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction);
line = 14;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_88'.
// Ensure the variable type transfer action is active, and
assertEquals("local_88", currentToken.getText());
assertEnabled(typeTransferAction, getProviderContext());
performAction(typeTransferAction);
waitForDecompile(correlatedPanel);
// Sanity check: options field exists
line = 13;
col = 12;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
assertEquals("options", currentToken.getText());
// Check that the work_stuff struct is fully in the data type manager
ProgramBasedDataTypeManager debugDtm = progDemangler24Debug.getDataTypeManager();
Structure debugStructure =
(Structure) debugDtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
assertNotNull(debugStructure);
ProgramBasedDataTypeManager strippedDtm = progDemangler24Stripped.getDataTypeManager();
Structure strippedStructure = (Structure) strippedDtm
.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
assertNotNull(strippedStructure);
assertEquals(debugStructure.getNumComponents(), strippedStructure.getNumComponents());
}
@Test
public void testSkeletonStructTypeTransferAction() throws RuntimeException {
final String actionName = ApplyEmptyVariableTypeFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
DockingActionIf typeTransferAction = getLocalAction(provider, actionName);
assertNotNull(typeTransferAction);
line = 14;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_88'.
// Ensure the variable type transfer action is active, and
assertEquals("local_88", currentToken.getText());
assertEnabled(typeTransferAction, getProviderContext());
performAction(typeTransferAction);
waitForDecompile(correlatedPanel);
// Sanity check: variable declaration is retyped
line = 6;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
assertEquals("work_stuff", currentToken.getText());
// Check that an empty struct is in the data type manager
ProgramBasedDataTypeManager dtm = progDemangler24Stripped.getDataTypeManager();
Structure structure =
(Structure) dtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
assertNotNull(structure);
assertTrue(structure.isNotYetDefined());
assertTrue(structure.isZeroLength());
assertEquals(0, structure.getNumComponents());
}
@Test
public void testCalleeNameTransferAction() throws RuntimeException {
final String actionName = ApplyCalleeFunctionNameFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeNameTransferAction);
line = 12;
col = 9;
currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
// Cursor is now on uncorrelated callee token 'xstrdup'
// Ensure the callee name transfer action is not active
assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeNameTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action
calleeNameTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeNameTransferAction);
line = 9;
col = 4;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated global variable token 'DAT_0041d150'
// Ensure the callee name transfer action is not active
assertEquals("DAT_0041d150", currentToken.getText());
assertNotEnabled(calleeNameTransferAction, getProviderContext());
line = 10;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_10'
// Ensure the callee name transfer action is not active
assertEquals("local_10", currentToken.getText());
assertNotEnabled(calleeNameTransferAction, getProviderContext());
line = 24;
col = 11;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated callee token 'FUN_0040c987'
// Ensure the callee name transfer action is active and working
assertEquals("FUN_0040c987", currentToken.getText());
assertEnabled(calleeNameTransferAction, getProviderContext());
performAction(calleeNameTransferAction);
waitForDecompile(correlatedPanel);
// Check updated function name
currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
assertEquals("internal_cplus_demangle", currentToken.getText());
}
@Test
public void testCalleeFullTypeTransferAction() throws RuntimeException {
final String actionName =
ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction);
line = 12;
col = 9;
currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
// Cursor is now on uncorrelated callee token 'xstrdup'
// Ensure the callee name transfer action is not active
assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action
calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction);
line = 9;
col = 4;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated global variable token 'DAT_0041d150'
// Ensure the callee name transfer action is not active
assertEquals("DAT_0041d150", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
line = 10;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_10'
// Ensure the callee name transfer action is not active
assertEquals("local_10", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
line = 24;
col = 11;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated callee token 'FUN_0040c987'
// Ensure the callee name transfer action is active and working
assertEquals("FUN_0040c987", currentToken.getText());
assertEnabled(calleeFullSignatureTransferAction, getProviderContext());
performAction(calleeFullSignatureTransferAction);
waitForDecompile(correlatedPanel);
// Check updated function name
line = 25;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
assertEquals("internal_cplus_demangle", currentToken.getText());
// Check updated function signature
FunctionSignature debugSig = getFunctionFromOffset(progDemangler24Debug,
DEMANGLER_24_DEBUG_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
FunctionSignature strippedSig = getFunctionFromOffset(progDemangler24Stripped,
DEMANGLER_24_STRIPPED_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
assertEquals(debugSig.getReturnType().getDisplayName(),
strippedSig.getReturnType().getDisplayName());
assertEquals(debugSig.getArguments().length, strippedSig.getArguments().length);
for (int i = 0; i < debugSig.getArguments().length; i++) {
ParameterDefinition p = debugSig.getArguments()[i];
ParameterDefinition p2 = strippedSig.getArguments()[i];
assertEquals(p.getDataType().getDisplayName(), p2.getDataType().getDisplayName());
}
// Check full datatype for work_stuff
ProgramBasedDataTypeManager debugDtm = progDemangler24Debug.getDataTypeManager();
Structure debugStructure =
(Structure) debugDtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
assertNotNull(debugStructure);
ProgramBasedDataTypeManager strippedDtm = progDemangler24Stripped.getDataTypeManager();
Structure strippedStructure = (Structure) strippedDtm
.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
assertNotNull(strippedStructure);
assertEquals(debugStructure.getNumComponents(), strippedStructure.getNumComponents());
}
@Test
public void testCalleeSkeletonTypeTransferAction() throws RuntimeException {
final String actionName = ApplyCalleeEmptySignatureFromMatchedTokensAction.ACTION_NAME;
int line;
int col;
ClangToken currentToken;
DecompilerCodeComparisonPanel uncorrelatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain);
DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction);
line = 12;
col = 9;
currentToken = setDecompLocation(uncorrelatedPanel, Side.LEFT, line, col);
// Cursor is now on uncorrelated callee token 'xstrdup'
// Ensure the callee name transfer action is not active
assertEquals("xstrdup", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
DecompilerCodeComparisonPanel correlatedPanel =
preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle);
// Recreated provider, need to get new handle on action
calleeFullSignatureTransferAction = getLocalAction(provider, actionName);
assertNotNull(calleeFullSignatureTransferAction);
line = 9;
col = 4;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated global variable token 'DAT_0041d150'
// Ensure the callee name transfer action is not active
assertEquals("DAT_0041d150", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
line = 10;
col = 0;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated local variable token 'local_10'
// Ensure the callee name transfer action is not active
assertEquals("local_10", currentToken.getText());
assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext());
line = 24;
col = 11;
currentToken = setDecompLocation(correlatedPanel, Side.RIGHT, line, col);
// Cursor is now on correlated callee token 'FUN_0040c987'
// Ensure the callee name transfer action is active and working
assertEquals("FUN_0040c987", currentToken.getText());
assertEnabled(calleeFullSignatureTransferAction, getProviderContext());
performAction(calleeFullSignatureTransferAction);
waitForDecompile(correlatedPanel);
// Check updated function name
currentToken = getCurrentToken(correlatedPanel, Side.RIGHT);
assertEquals("internal_cplus_demangle", currentToken.getText());
// Check updated function signature
FunctionSignature debugSig = getFunctionFromOffset(progDemangler24Debug,
DEMANGLER_24_DEBUG_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
FunctionSignature strippedSig = getFunctionFromOffset(progDemangler24Stripped,
DEMANGLER_24_STRIPPED_INTERNAL_CPLUS_DEMANGLE_OFFSET).getSignature();
assertEquals(debugSig.getReturnType().getDisplayName(),
strippedSig.getReturnType().getDisplayName());
assertEquals(debugSig.getArguments().length, strippedSig.getArguments().length);
for (int i = 0; i < debugSig.getArguments().length; i++) {
ParameterDefinition p = debugSig.getArguments()[i];
ParameterDefinition p2 = strippedSig.getArguments()[i];
assertEquals(p.getDataType().getDisplayName(), p2.getDataType().getDisplayName());
}
// Check that an empty struct is in the data type manager
ProgramBasedDataTypeManager dtm = progDemangler24Stripped.getDataTypeManager();
Structure structure =
(Structure) dtm.getDataType(new DataTypePath("/DWARF/cplus-dem.c", "work_stuff"));
assertNotNull(structure);
assertTrue(structure.isNotYetDefined());
assertTrue(structure.isZeroLength());
assertEquals(0, structure.getNumComponents());
}
// Setup and focus to a decompiler comparison between the two selected functions. Wait for
// the decompilation to complete so that subsequent calls to navigation, etc. work correctly
private DecompilerCodeComparisonPanel preparePanel(Function leftFunc, Function rightFunc) {
if (provider != null) {
// Always want to clear out existing comparison
closeProvider(provider);
}
provider = compareFunctions(Set.of(leftFunc, rightFunc));
DecompilerCodeComparisonPanel decompPanel = findDecompilerPanel(provider);
waitForDecompile(decompPanel);
decompPanel.setSynchronizedScrolling(true);
setActivePanel(provider, decompPanel);
assertEquals(decompPanel.getFunction(Side.LEFT), leftFunc);
assertEquals(decompPanel.getFunction(Side.RIGHT), rightFunc);
return decompPanel;
}
private Function getFunctionFromOffset(Program prog, long offset) {
return prog.getFunctionManager()
.getFunctionAt(
prog.getAddressFactory().getDefaultAddressSpace().getAddress(offset));
}
private void assertEnabled(DockingActionIf action, ActionContext context) {
assertTrue(runSwing(() -> action.isEnabledForContext(context)));
}
private void assertNotEnabled(DockingActionIf action, ActionContext context) {
assertFalse(runSwing(() -> action.isEnabledForContext(context)));
}
// Test programs are always opened read-only, so set context override
private ActionContext getProviderContext() {
ActionContext context = provider.getActionContext(null);
if (context instanceof DualDecompilerActionContext dualDecompContext) {
dualDecompContext.setOverrideReadOnly(true);
}
return context;
}
}

View File

@@ -4,9 +4,9 @@
* 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.
@@ -35,8 +35,8 @@ import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel;
import ghidra.features.base.codecompare.panel.CodeComparisonPanel;
import ghidra.features.base.codecompare.panel.FunctionComparisonPanel;
import ghidra.features.codecompare.plugin.*;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.ByteDataType;
@@ -130,12 +130,15 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
}
@Test
public void testNextPreviousActionSwitchPanelFocus() {
public void testNextPreviousActionSwitchPanelFocus() throws Exception {
Set<Function> functions = Set.of(foo, bar);
provider = compareFunctions(functions);
DockingActionIf nextAction = getAction(plugin, "Compare Next Function");
DockingActionIf previousAction = getAction(plugin, "Compare Previous Function");
// since we are clicking the listing panel, bring that to the front first
setActivePanel(provider, provider.getComponent().getDualListingPanel());
// left panel has focus, so nextAction should be enabled and previous should be disabled
ActionContext context = provider.getActionContext(null);
assertEnabled(nextAction, context);
@@ -288,6 +291,11 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio
assertFalse(runSwing(() -> action.isEnabledForContext(context)));
}
private void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) {
runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName()));
waitForSwing();
}
private FunctionComparisonProvider compareFunctions(Set<Function> functions) {
runSwing(() -> plugin.createComparison(functions));
waitForSwing();