Merge remote-tracking branch 'origin/GP-4007_ghintern_codecompare_actions--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-07-10 09:58:55 -04:00
commit 1b74b8eb9b
17 changed files with 1925 additions and 172 deletions

View file

@ -627,7 +627,41 @@
namespace and full signature, including any complex data types and referenced types. Use
this action with caution as it can potentially add many inappropriate and conflicting
data types, especially if the source function is from a program with a different
architecture or compiler.
architecture or compiler. </LI>
<A name="Function_Comparison_Apply_Local_Variable_Name">
<LI><B>Apply Local Variable Name</B> - Available when the selected and matched
decompiler tokens are local variables. Applies just the variable name from the other
function. </LI>
<A name="Function_Comparison_Apply_Global_Variable_Name">
<LI><B>Apply Global Variable Name</B> - Avaiable when the selected and matched
decompiler tokens are global variables. Applies just the variable name from the other
function. </LI>
<A name="Function_Comparison_Apply_Variable_Skeleton_Type">
<LI><B>Apply Variable Skeleton Type</B> - Available when the selected and matched
decompiler tokens are variables. Applies a limited form of the variable data type to
the other function where structures and unions are not copied, but instead empty
placeholders are created. </LI>
<A name="Function_Comparison_Apply_Variable_Type">
<LI><B>Apply Variable Type</B> - Available when the selected and matched decompiler
tokens are variables. Applies the full variable type from the other function. Use
this action with caution as it can potentially add an inappropriate and conflicting
data type, especially if the source function is from a program with a different
architecture or compiler. </LI>
<A name="Function_Comparison_Apply_Callee_Name">
<LI><B>Apply Callee Function Name</B> - Available when the selected and matched
decompiler tokens are function calls. Performs the same action as
<A href="#Function_Comparison_Apply_Name">Apply Function Name</A>, but acts on the
callee functions. </LI>
<A name="Function_Comparison_Apply_Callee_Signature">
<LI><B>Apply Callee Function Signature</B> - Available when the selected and matched
decompiler tokens are function calls. Performs the same action as
<A href="#Function_Comparison_Apply_Signature">Apply Function Signature</A>, but acts on
the callee functions. </LI>
<A name="Function_Comparison_Apply_Callee_Signature_And_Datatypes">
<LI><B>Apply Callee Function Signature and Data Types</B> - Available when the selected
and matched decompiler tokens are function calls. Performs the same action as
<A href="#Function_Comparison_Apply_Signature_And_Datatypes">Apply Function Signature
and Data Types</A>, but acts on the callee functions. </LI>
</UL>
<P class="providedbyplugin">Provided By:&nbsp; <I>FunctionComparisonPlugin</I></P>

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.
@ -27,8 +27,8 @@ import ghidra.util.Msg;
* comparison window
*/
public abstract class AbstractFunctionComparisonApplyAction extends DockingAction {
protected static final String MENU_PARENT = "Apply From Other";
protected static final String MENU_GROUP = "A0_Apply";
protected static final String MENU_PARENT = "Apply From Other Function";
protected static final String MENU_GROUP = "A0_ApplyFunction";
protected static final String HELP_TOPIC = "FunctionComparison";
/**

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