Merge remote-tracking branch 'origin/GP-5583_emteere_DefaultCaseFlowAndLabels' into patch

This commit is contained in:
ghidra1
2025-04-14 19:35:42 -04:00
2 changed files with 404 additions and 99 deletions
@@ -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<Program> {
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<Program> {
}
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<Program> {
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<Program> {
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> {
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<Program> {
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<Program> {
// 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<Program> {
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 {
@@ -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);
}
}