diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/cmd/function/DecompilerSwitchAnalysisCmd.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/cmd/function/DecompilerSwitchAnalysisCmd.java index a7b8e702a3..5fd5b4d6b0 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/cmd/function/DecompilerSwitchAnalysisCmd.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/cmd/function/DecompilerSwitchAnalysisCmd.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. @@ -48,8 +48,8 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { protected DecompInterface decompiler; private boolean useArraysForSwitchTables = false; - public DecompilerSwitchAnalysisCmd(DecompileResults decopmileResults) { - this.decompilerResults = decopmileResults; + public DecompilerSwitchAnalysisCmd(DecompileResults decompileResults) { + this.decompilerResults = decompileResults; } @Override @@ -71,20 +71,18 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { } try { - monitor.checkCancelled(); Function f = decompilerResults.getFunction(); HighFunction hfunction = decompilerResults.getHighFunction(); - processBranchIND(f, hfunction, monitor); - - monitor.checkCancelled(); - + String errMsg = getStatusMsg(); - if (decompilerResults.getHighFunction() == null) { + if (hfunction == null) { String msg = (errMsg != null && errMsg.length() != 0) ? (": " + errMsg) : ""; Msg.debug(this, " Failed to decompile function: " + f.getName() + msg); } + + processBranchIND(f, hfunction, monitor); } catch (Exception e) { if (!monitor.isCancelled()) { @@ -114,34 +112,8 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { continue; // skip switch owned by a different defined function } - AddressSetView containingBody = - containingFunction != null ? containingFunction.getBody() : null; - - Reference[] referencesFrom = instr.getReferencesFrom(); - Address[] tableDest = table.getCases(); - - boolean foundNotThere = false; - int tableIndx; - for (tableIndx = 0; tableIndx < tableDest.length; tableIndx++) { - monitor.checkCancelled(); - boolean foundit = false; - if (containingBody != null && !containingBody.contains(tableDest[tableIndx])) { - // switch case missing from owner function's body - foundNotThere = true; - break; - } - for (Reference element : referencesFrom) { - if (element.getToAddress().equals(tableDest[tableIndx])) { - foundit = true; - break; - } - } - if (!foundit) { - foundNotThere = true; - } - } // references already there, ignore this table - if (!foundNotThere) { + if (hasAllReferences(monitor, table, instr, containingFunction)) { continue; } @@ -158,64 +130,158 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { labelSwitch(table, monitor); // disassemble the table - // pull out the current context so we can flow anything that needs to flow - ProgramContext programContext = program.getProgramContext(); - Register baseContextRegister = programContext.getBaseContextRegister(); - RegisterValue switchContext = null; - if (baseContextRegister != null) { - // Use disassembler context based upon context register value at switch address (i.e., computed jump) - // Only use flowing context bits - switchContext = programContext.getRegisterValue(baseContextRegister, switchAddr); - switchContext = programContext.getFlowValue(switchContext); - } - - Listing listing = program.getListing(); - Address[] cases = table.getCases(); - AddressSet disSetList = new AddressSet(); - for (Address caseStart : cases) { - monitor.checkCancelled(); - instr.addMnemonicReference(caseStart, flowType, SourceType.ANALYSIS); - - // if conflict skip case - if (listing.getUndefinedDataAt(caseStart) == null) { - continue; - } - // already done - if (disSetList.contains(caseStart)) { - continue; - } - try { - setSwitchTargetContext(programContext, caseStart, switchContext); - } - catch (ContextChangeException e) { - // This can occur when two or more threads are working on the same function - continue; - } - disSetList.add(caseStart); - } - - // do all cases at one time - if (!disSetList.isEmpty()) { - DisassembleCommand cmd = new DisassembleCommand(disSetList, null, true); - cmd.applyTo(program); - } + disassembleTable(monitor, table, instr, flowType); // fixup the function body - // make sure this case isn't the result of an undefined function, that somehow one of the cases found a real function. - Function fixupFunc = f; - if (fixupFunc instanceof UndefinedFunction) { - Function realFunc = - program.getFunctionManager().getFunctionContaining(instr.getMinAddress()); - if (realFunc != null) { - fixupFunc = realFunc; - } - } - Instruction funcStartInstr = - program.getListing().getInstructionAt(fixupFunc.getEntryPoint()); - CreateFunctionCmd.fixupFunctionBody(program, funcStartInstr, monitor); + fixupFunction(f, monitor, instr); } } + /* + * Fix the functions body with any newly reached code from the switch recovery + */ + private void fixupFunction(Function f, TaskMonitor monitor, Instruction instr) + throws CancelledException { + Function fixupFunc = f; + + // Make sure this case isn't the result of an undefined function, + // that somehow one of the cases found a real function. + if (fixupFunc instanceof UndefinedFunction) { + Function realFunc = + program.getFunctionManager().getFunctionContaining(instr.getMinAddress()); + if (realFunc != null) { + fixupFunc = realFunc; + } + } + Instruction funcStartInstr = + program.getListing().getInstructionAt(fixupFunc.getEntryPoint()); + CreateFunctionCmd.fixupFunctionBody(program, funcStartInstr, monitor); + } + + /* + * Disassemble all code reached from the table. + * Also adds the case flow references to the switching instruction. + */ + private void disassembleTable(TaskMonitor monitor, JumpTable table, + Instruction instr, FlowType flowType) throws CancelledException { + + Address switchAddr = table.getSwitchAddress(); + + // pull out the current context so we can flow anything that needs to flow + ProgramContext programContext = program.getProgramContext(); + Register baseContextRegister = programContext.getBaseContextRegister(); + RegisterValue switchContext = null; + if (baseContextRegister != null) { + // Use disassembler context based upon context register value at switch address (i.e., computed jump) + // Only use flowing context bits + switchContext = programContext.getRegisterValue(baseContextRegister, switchAddr); + switchContext = programContext.getFlowValue(switchContext); + } + + Listing listing = program.getListing(); + Address[] cases = table.getCases(); + Integer[] caseValues = table.getLabelValues(); + AddressSet disSetList = new AddressSet(); + + for (int caseIndex = 0; caseIndex < cases.length; caseIndex++) { + Address caseStart = cases[caseIndex]; + monitor.checkCancelled(); + + if (!isDefaultCase(caseValues, caseIndex)) { + // only non-default cases should be added to the switching instruction + instr.addMnemonicReference(caseStart, flowType, SourceType.ANALYSIS); + } + + // if conflict skip case + if (listing.getUndefinedDataAt(caseStart) == null) { + continue; + } + // already done + if (disSetList.contains(caseStart)) { + continue; + } + try { + setSwitchTargetContext(programContext, caseStart, switchContext); + } + catch (ContextChangeException e) { + // This can occur when two or more threads are working on the same function + continue; + } + disSetList.add(caseStart); + } + + // do all cases at one time + if (!disSetList.isEmpty()) { + DisassembleCommand cmd = new DisassembleCommand(disSetList, null, true); + cmd.applyTo(program); + } + } + + /* + * Check if this case index is a default case. + * + * In general, each case target address should have an associated caseValue. + * A case is default if it is first case to not have a case value, or has a magic case value. + * It is possible that there could be more than one case without a value. The code shouldn't + * blow up if this is the case. + * + * TODO: Should this check if the default case already has a reference to it + * from a conditional jump? + */ + private boolean isDefaultCase(Integer[] caseValues, int caseIndex) { + return (caseIndex == caseValues.length) || + (caseIndex < caseValues.length && caseValues[caseIndex] == DEFAULT_CASE_VALUE); + } + + /* + * Check if the switching instruction has all switch references already. + * Extra check for default case target as part of the table, when it shouldn't be. + */ + public boolean hasAllReferences(TaskMonitor monitor, JumpTable table, Instruction instr, + Function containingFunction) throws CancelledException { + AddressSetView containingBody = + containingFunction != null ? containingFunction.getBody() : null; + + Reference[] referencesFrom = instr.getReferencesFrom(); + Address[] tableDest = table.getCases(); + Integer[] caseValues = table.getLabelValues(); + + // check that all cases are already a reference on the instruction, except default + for (int caseIndex = 0; caseIndex < tableDest.length; caseIndex++) { + monitor.checkCancelled(); + + // a case is default if it is first case to not have a value, or has a magic case value + boolean isDefaultCase = isDefaultCase(caseValues, caseIndex); + + if (containingBody != null && !containingBody.contains(tableDest[caseIndex])) { + // switch case missing from owner function's body + return false; + } + + boolean foundit = false; + for (Reference element : referencesFrom) { + if (element.getToAddress().equals(tableDest[caseIndex])) { + foundit = true; + break; + } + } + if (isDefaultCase) { + // default case should not be on switching instruction + if (foundit) { + return false; + } + } + else if (!foundit) { + return false; + } + } + + return true; + } + + /* + * Set the context that should flow to the target so that target instruction will disassemble correctly + */ private void setSwitchTargetContext(ProgramContext programContext, Address targetStart, RegisterValue switchContext) throws ContextChangeException { if (switchContext == null) { return; @@ -236,6 +302,9 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { program.getProgramContext().setRegisterValue(targetStart, targetStart, switchContext); } + /* + * Label switch table, cases, default with labels in namespace of the switch + */ private void labelSwitch(JumpTable table, TaskMonitor monitor) throws CancelledException { AddLabelCmd tableNameLabel = new AddLabelCmd(table.getSwitchAddress(), "switchD", SourceType.ANALYSIS); @@ -266,18 +335,24 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { tableNameLabel.applyTo(program); Address[] switchCases = table.getCases(); - Integer[] caseLabels = table.getLabelValues(); - Symbol[] caseSymbols = new Symbol[caseLabels.length]; + Integer[] caseValues = table.getLabelValues(); + Symbol[] caseSymbols = new Symbol[caseValues.length]; SymbolTable symTable = program.getSymbolTable(); - for (int i = 0; i < switchCases.length; i++) { + + for (int caseIndex = 0; caseIndex < switchCases.length; caseIndex++) { monitor.checkCancelled(); - int offset = (i >= caseLabels.length ? i : caseLabels[i]); - String caseName = "caseD_" + Integer.toHexString(offset); - if (offset == DEFAULT_CASE_VALUE) { // magic constant to indicate default case + + // if there are more switchCases than switch values, just use the caseIndex + int caseValue = (caseIndex < caseValues.length) ? caseValues[caseIndex] : caseIndex; + + boolean isDefaultCase = isDefaultCase(caseValues, caseIndex); + + String caseName = "caseD_" + Integer.toHexString(caseValue); + if (isDefaultCase) { caseName = "default"; } AddLabelCmd lcmd = - new AddLabelCmd(switchCases[i], caseName, space, SourceType.ANALYSIS); + new AddLabelCmd(switchCases[caseIndex], caseName, space, SourceType.ANALYSIS); Symbol oldSym = symTable.getPrimarySymbol(lcmd.getLabelAddr()); if (oldSym != null && oldSym.getSource() == SourceType.ANALYSIS && @@ -285,8 +360,8 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { // cleanup AddressTableAnalyzer label oldSym.delete(); } - if (lcmd.applyTo(program) && i < caseSymbols.length) { - caseSymbols[i] = symTable.getSymbol(caseName, switchCases[i], space); + if (lcmd.applyTo(program) && caseIndex < caseSymbols.length) { + caseSymbols[caseIndex] = symTable.getSymbol(caseName, switchCases[caseIndex], space); } } @@ -338,6 +413,9 @@ public class DecompilerSwitchAnalysisCmd extends BackgroundCommand { return addresses; } + /* + * put labels on the switch table used to compute the target addresses of the switch. + */ private void labelLoadTable(JumpTable.LoadTable loadtable, Address[] switchCases, Symbol[] caseSymbols, Namespace space, TaskMonitor monitor) throws CancelledException { diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerSwitchAnalyzerTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerSwitchAnalyzerTest.java new file mode 100644 index 0000000000..720b176183 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerSwitchAnalyzerTest.java @@ -0,0 +1,227 @@ +/* ### + * 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.app.plugin.core.decompile; + +import static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.apache.commons.lang3.ArrayUtils; +import org.junit.*; + +import generic.test.AbstractGenericTest; +import ghidra.app.plugin.core.analysis.*; +import ghidra.framework.cmd.Command; +import ghidra.framework.options.Options; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.*; +import ghidra.program.util.SymbolicPropogator.Value; +import ghidra.test.TestEnv; +import ghidra.util.TaskUtilities; +import ghidra.util.exception.NotFoundException; +import ghidra.util.task.TaskMonitor; +import junit.framework.AssertionFailedError; + +/** + * Test of DecompilerSwitchAnalyzer + */ +public class DecompilerSwitchAnalyzerTest extends AbstractGenericTest { + + private ProgramBuilder builder; + private Program program; + + private DecompilerSwitchAnalyzer analyzer; + + public DecompilerSwitchAnalyzerTest() { + super(); + } + + protected void setAnalysisOptions(String optionName) { + int txId = program.startTransaction("Analyze"); + Options analysisOptions = program.getOptions(Program.ANALYSIS_PROPERTIES); + analysisOptions.setBoolean(optionName, false); + program.endTransaction(txId, true); + } + + @Test + public void testDefaultSwitchLabelAndFlow() throws Exception { + + builder = new ProgramBuilder("SwitchDefaultTest", "x86:LE:64:default", "gcc", null); + + // + // void main(undefined4 param_1) + // + // { + // switch(param_1) { + // case 0: + // puts("case 0"); + // break; + // case 1: + // puts("case 1"); + // break; + // case 2: + // puts("case 2"); + // break; + // case 3: + // puts("case 3"); + // break; + // case 4: + // puts("case 4"); + // break; + // case 5: + // puts("case 5"); + // break; + // case 6: + // puts("case 6"); + // break; + // case 7: + // puts("case 7"); + // break; + // case 8: + // puts("case 8"); + // break; + // case 9: + // puts("case 9"); + // break; + // default: + // puts("default"); + // } + // /* WARNING: Subroutine does not return */ + // exit(0); + // } + + builder.setBytes("0x101169", + "f30f1efa554889" + + "e54883ec10897dfc488975f0837dfc09" + + "0f87d70000008b45fc488d1485000000" + + "00488d05bc0e00008b04024898488d15" + + "b00e00004801d03effe0488d05530e00" + + "004889c7e8a7feffffe9ae000000488d" + + "05460e00004889c7e893feffffe99a00" + + "0000488d05390e00004889c7e87ffeff" + + "ffe986000000488d052c0e00004889c7" + + "e86bfeffffeb75488d05220e00004889" + + "c7e85afeffffeb64488d05180e000048" + + "89c7e849feffffeb53488d050e0e0000" + + "4889c7e838feffffeb42488d05040e00" + + "004889c7e827feffffeb31488d05fa0d" + + "00004889c7e816feffffeb20488d05f0" + + "0d00004889c7e805feffffeb0f488d05" + + "e60d00004889c7e8f4fdffffbf000000" + + "00e8fafdffff"); + + // switch table + builder.setBytes("0x102054", + "56f1ffff6af1ffff7ef1ffff" + + "92f1ffffa3f1ffffb4f1ffffc5f1ffff" + + "d6f1ffffe7f1fffff8f1ffff011b033b"); + + builder.disassemble("0x101169", 64); + + builder.createFunction("0x101169"); + + analyzer = new DecompilerSwitchAnalyzer(); + + program = builder.getProgram(); + program.startTransaction("Test"); + + Address codeStart = addr("0x101169"); + Listing listing = program.getListing(); + assertNotNull("Bad instruction disassembly", listing.getInstructionAt(codeStart)); + + AddressSet addressSet = new AddressSet(codeStart, codeStart.add(0x200)); + analyze(addressSet); + + Symbol primarySymbol = program.getSymbolTable().getPrimarySymbol(addr("0x0010125d")); + assertEquals("Default label set", primarySymbol.getName(), "default"); + assertEquals("Switch space set", primarySymbol.getParentNamespace().getName(), "switchD_001011a7"); + + primarySymbol = program.getSymbolTable().getPrimarySymbol(addr("0x0010124c")); + assertEquals("Case label set", primarySymbol.getName(), "caseD_9"); + assertEquals("Switch space set", primarySymbol.getParentNamespace().getName(), "switchD_001011a7"); + + Instruction instr = listing.getInstructionAt(addr("0x001011a7")); + assertNoOperandReference(0, instr); + assertNoOperandReference(1, instr); + assertNumMnemonicReferences(instr, 10); + assertMnemonicReferenceTo(instr, addr("0x001011aa")); + assertMnemonicReferenceTo(instr, addr("0x001011be")); + assertMnemonicReferenceTo(instr, addr("0x001011d2")); + assertMnemonicReferenceTo(instr, addr("0x001011e6")); + assertMnemonicReferenceTo(instr, addr("0x001011f7")); + assertMnemonicReferenceTo(instr, addr("0x00101208")); + assertMnemonicReferenceTo(instr, addr("0x00101219")); + assertMnemonicReferenceTo(instr, addr("0x0010122a")); + assertMnemonicReferenceTo(instr, addr("0x0010123b")); + assertMnemonicReferenceTo(instr, addr("0x0010124c")); + + } + + private void assertNoOperandReference(int opIndex, Instruction instr) { + Reference[] refs = instr.getOperandReferences(opIndex); + assertEquals("No reference on operand " + opIndex, 0, refs.length); + } + + private void assertNumOperandReferences(int opIndex, Instruction instr, int num) { + Reference[] refs = instr.getOperandReferences(opIndex); + assertEquals("Operand " + opIndex + " num refs", num, refs.length); + } + + private void assertNumMnemonicReferences(Instruction instr, int num) { + Reference[] refs = instr.getMnemonicReferences(); + assertEquals("Mnemonic num refs", num, refs.length); + } + + private void assertMnemonicReferenceTo(Instruction instr, Address to) { + Reference[] refs = instr.getMnemonicReferences(); + boolean found = false; + for (Reference reference : refs) { + if (reference.getToAddress().equals(to)) { + found = true; + break; + } + } + assertTrue("Missing Mnemonic Reference " + to + " on " + instr, found); + } + + private void assertOperandReferenceTo(int opIndex, Instruction instr, Address to) { + Reference[] refs = instr.getOperandReferences(opIndex); + boolean found = false; + for (Reference reference : refs) { + if (reference.getToAddress().equals(to)) { + found = true; + break; + } + } + assertTrue("Missing Reference " + to + " on " + instr, found); + } + + private void analyze(AddressSet addrs) throws Exception { + analyzer.added(program, addrs, TaskMonitor.DUMMY, null); + } + + private Address addr(String address) { + return builder.addr(address); + } +}