diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/DecompilerStackProblemsFinderScript.java b/Ghidra/Features/Decompiler/ghidra_scripts/DecompilerStackProblemsFinderScript.java new file mode 100644 index 0000000000..02bbef3455 --- /dev/null +++ b/Ghidra/Features/Decompiler/ghidra_scripts/DecompilerStackProblemsFinderScript.java @@ -0,0 +1,222 @@ +/* ### + * 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. + */ +// Displays a table showing locations where the decompiled code writes a value within the containing +// function's body to the stack. This is a good indication that the decompiler's +// stack analysis is missing information. For example, the function or a callee might need +// to have its signature, calling convention, or "No Return" status adjusted. + +// @category Analysis + +import java.util.*; + +import ghidra.app.decompiler.*; +import ghidra.app.decompiler.parallel.*; +import ghidra.app.script.GhidraScript; +import ghidra.app.tablechooser.*; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class DecompilerStackProblemsFinderScript extends GhidraScript { + + @Override + protected void run() throws Exception { + + if (isRunningHeadless()) { + println("This script cannot be run headlessly"); + return; + } + + AddressSetView selection = currentSelection; + if (selection == null) { + selection = currentProgram.getMemory().getExecuteSet(); + } + + DecompilerCallback> callback = + new DecompilerCallback<>(currentProgram, new StackErrorConfigurer(currentProgram)) { + + @Override + public List process(DecompileResults results, TaskMonitor tMonitor) + throws Exception { + tMonitor.checkCanceled(); + return findStackErrors(results, tMonitor); + } + }; + + List> results = Collections.emptyList(); + try { + results = + ParallelDecompiler.decompileFunctions(callback, currentProgram, selection, monitor); + } + finally { + callback.dispose(); + monitor.checkCanceled(); + } + + TableChooserDialog tableDialog = + createTableChooserDialog(currentProgram.getName() + " problematic stack writes", null); + configureTableColumns(tableDialog); + + boolean foundSomething = false; + for (List list : results) { + for (StackErrorRow row : list) { + tableDialog.add(row); + foundSomething = true; + } + } + if (!foundSomething) { + popup("No problematic writes found"); + return; + } + tableDialog.show(); + } + + private List findStackErrors(DecompileResults results, TaskMonitor tMonitor) + throws CancelledException { + + List rows = new ArrayList<>(); + HighFunction highFunction = results.getHighFunction(); + if (highFunction == null) { + return rows; + } + AddressSetView body = results.getFunction().getBody(); + AddressSpace addrSpace = body.getMinAddress().getAddressSpace(); + Iterator ops = highFunction.getPcodeOps(); + while (ops.hasNext()) { + tMonitor.checkCanceled(); + PcodeOp op = ops.next(); + if (op.getOpcode() != PcodeOp.COPY) { + continue; + } + if (!op.getOutput().getAddress().isStackAddress()) { + continue; + } + Varnode input = op.getInput(0); + if (!input.isConstant()) { + continue; + } + try { + Address addr = addrSpace.getAddress(input.getOffset()); + if (body.contains(addr)) { + rows.add(new StackErrorRow(results.getFunction(), op.getSeqnum().getTarget(), + input.getOffset())); + } + } + catch (AddressOutOfBoundsException e) { + //this is can happen when the constant is an encoding of a floating + //point value. + continue; + } + } + return rows; + } + + class StackErrorConfigurer implements DecompileConfigurer { + private Program p; + + public StackErrorConfigurer(Program prog) { + p = prog; + } + + @Override + public void configure(DecompInterface decompiler) { + decompiler.toggleCCode(false); + decompiler.toggleSyntaxTree(true); + decompiler.setSimplificationStyle("normalize"); + DecompileOptions opts = new DecompileOptions(); + opts.grabFromProgram(p); + decompiler.setOptions(opts); + } + } + + /** + * Table stuff + */ + + static class StackErrorRow implements AddressableRowObject { + private Function func; + private Address errorAddress; + private long value; + + public StackErrorRow(Function func, Address errorAddress, long value) { + this.func = func; + this.errorAddress = errorAddress; + this.value = value; + } + + public Function getFunction() { + return func; + } + + public long getValue() { + return value; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(func.getName()); + sb.append(" error address: "); + sb.append(errorAddress.toString()); + sb.append(", error value: "); + sb.append(Long.toUnsignedString(value, 16)); + return sb.toString(); + } + + @Override + public Address getAddress() { + return errorAddress; + } + + } + + private void configureTableColumns(TableChooserDialog dialog) { + + StringColumnDisplay functionNameColumn = new StringColumnDisplay() { + @Override + public String getColumnName() { + return "Function Name"; + } + + @Override + public String getColumnValue(AddressableRowObject rowObject) { + return ((StackErrorRow) rowObject).getFunction().getName(); + } + }; + + ColumnDisplay errorValueColumn = new AbstractComparableColumnDisplay<>() { + + @Override + public String getColumnValue(AddressableRowObject rowObject) { + long errorVal = ((StackErrorRow) rowObject).getValue(); + int size = rowObject.getAddress().getAddressSpace().getSize() / 4; + return String.format("0x%0" + size + "x", errorVal); + } + + @Override + public String getColumnName() { + return "Value"; + } + }; + + dialog.addCustomColumn(functionNameColumn); + dialog.addCustomColumn(errorValueColumn); + } + +}