diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm index 420ff2d108..3f56cd50ff 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm @@ -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. + +
  • Apply Local Variable Name - Available when the selected and matched + decompiler tokens are local variables. Applies just the variable name from the other + function.
  • +
    +
  • Apply Global Variable Name - Avaiable when the selected and matched + decompiler tokens are global variables. Applies just the variable name from the other + function.
  • +
    +
  • Apply Variable Skeleton Type - 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.
  • +
    +
  • Apply Variable Type - 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.
  • +
    +
  • Apply Callee Function Name - Available when the selected and matched + decompiler tokens are function calls. Performs the same action as + Apply Function Name, but acts on the + callee functions.
  • + +
  • Apply Callee Function Signature - Available when the selected and matched + decompiler tokens are function calls. Performs the same action as + Apply Function Signature, but acts on + the callee functions.
  • + +
  • Apply Callee Function Signature and Data Types - Available when the selected + and matched decompiler tokens are function calls. Performs the same action as + Apply Function Signature + and Data Types, but acts on the callee functions.
  • Provided By:  FunctionComparisonPlugin

    diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.java index b7ac2757df..591809872f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/AbstractFunctionComparisonApplyAction.java @@ -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"; /** diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java new file mode 100644 index 0000000000..76f4dd0754 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java @@ -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; + + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java index b15152cc34..0788c7366b 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java @@ -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 = 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 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); } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java new file mode 100644 index 0000000000..cd96648da4 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java @@ -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()); + } + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java new file mode 100644 index 0000000000..77daf33b5c --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java @@ -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()); + } + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java new file mode 100644 index 0000000000..c2903ca6c9 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java @@ -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()); + } + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java new file mode 100644 index 0000000000..f7abeadcee --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java @@ -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); + } + + } + +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java new file mode 100644 index 0000000000..7aa54371b1 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java @@ -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()); + } + } + +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java new file mode 100644 index 0000000000..9397bfc4be --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java @@ -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); + } + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java new file mode 100644 index 0000000000..4adeed1944 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java @@ -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); + } + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java index 127d862c9c..fb2d999a12 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java @@ -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; - - } - } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java index b73ab6baa0..1734223942 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java @@ -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) { diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java index 4c1e5b22c8..675d0b15b1 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java @@ -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 = 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 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(); + } + } diff --git a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java new file mode 100644 index 0000000000..2a8cb73968 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java @@ -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 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); + } +} diff --git a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java new file mode 100644 index 0000000000..93cf92cca3 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java @@ -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; + } + +} diff --git a/Ghidra/Features/CodeCompare/src/main/test/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java similarity index 95% rename from Ghidra/Features/CodeCompare/src/main/test/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java rename to Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java index 691fec0c9f..5b163a32da 100644 --- a/Ghidra/Features/CodeCompare/src/main/test/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java +++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java @@ -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 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 functions) { runSwing(() -> plugin.createComparison(functions)); waitForSwing();