diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java new file mode 100644 index 0000000000..2487297a66 --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/EmuDeskCheckScript.java @@ -0,0 +1,427 @@ +/* ### + * 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. + */ +import java.util.*; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; +import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.script.GhidraScript; +import ghidra.app.tablechooser.*; +import ghidra.debug.flatapi.FlatDebuggerAPI; +import ghidra.docking.settings.*; +import ghidra.pcode.emu.BytesPcodeThread; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.struct.StructuredSleigh; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.ByteMemBufferImpl; +import ghidra.program.model.symbol.Symbol; +import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; + +/** + * NOTE: Testing with bash: set_shellopts + */ +public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI { + + public class Injects extends StructuredSleigh { + Var RAX = lang("RAX", type("void *")); + Var RSP = lang("RSP", type("void *")); + + protected Injects() { + super(currentProgram); + } + + public Var POP() { + Var tgt = local("tgt", RSP.cast(type("void **")).deref()); + RSP.set(RSP.addi(8)); + return tgt; + } + + public void RET() { + _goto(POP()); + } + + public void RET(RVal val) { + RAX.set(val); + RET(); + } + + /** + * TODO: A framework for stubbing the functions. This is close, and the system calls stuff + * can get us closer in its handling of calling conventions. We need either to generate + * Sleigh that gets the parameters in place, or if we're going to use the aliasing idea that + * the syscall stuff does, then we need to allow injection of the already-compiled Sleigh + * program. For now, we'll have to declare the parameter-holding register as a language + * variable. + * + * @param s + */ + @StructuredUserop + public void strlen(/*@Param(name = "RDI", type = "char *") Var s*/) { + Var s = lang("RDI", type("char *")); + Var t = temp(type("char *")); + _for(t.set(s), t.deref().neq(0), t.inc(), () -> { + }); + RET(t.subi(s)); + } + } + + public final List watches = List.of( + watch("RAX", type("int")), + watch("RCX", type("int"), + set(FormatSettingsDefinition.DEF, FormatSettingsDefinition.DECIMAL)), + watch("RSP", type("void *"))); + // TODO: Snarf from Watches window? + + @Override + protected void run() throws Exception { + Trace trace = emulateLaunch(currentProgram, currentAddress); + TracePlatform platform = trace.getPlatformManager().getHostPlatform(); + long snap = 0; + + TableChooserDialog tableDialog = + createTableChooserDialog("Desk Check", new CheckRowChooser()); + + tableDialog.show(); + + tableDialog.addCustomColumn(new CheckRowScheduleDisplay()); + tableDialog.addCustomColumn(new CheckRowCounterDisplay()); + + List compiled = new ArrayList<>(); + TypeLoader loader = new TypeLoader(currentProgram); + for (Watch w : watches) { + PcodeExpression ce = SleighProgramCompiler + .compileExpression((SleighLanguage) platform.getLanguage(), w.expression); + tableDialog.addCustomColumn(new CheckRowWatchDisplay(loader, w, compiled.size())); + compiled.add(ce); + } + + TraceSchedule schedule; + while (true) { + try { + schedule = TraceSchedule + .parse(askString("Schedule", "Enter the steping schedule", "0:t0-1000")); + break; + } + catch (CancelledException e) { + throw e; + } + catch (Exception e) { + Msg.showError(this, null, "Schedule", "Error: " + e); + } + } + + BytesDebuggerPcodeEmulator emu = new BytesDebuggerPcodeEmulator( + new DefaultPcodeDebuggerAccess(state.getTool(), null, platform, snap)) { + TraceSchedule position = TraceSchedule.snap(snap); + + @Override + protected BytesPcodeThread createThread(String name) { + return new BytesPcodeThread(name, this) { + TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, name); + PcodeExecutor> inspector = + new PcodeExecutor<>(language, + new PairedPcodeArithmetic<>(arithmetic, + AddressOfPcodeArithmetic.INSTANCE), + state.paired(new AddressOfPcodeExecutorStatePiece(language)), + Reason.INSPECT); + + { + tableDialog.add(createRow()); + } + + @Override + public void stepInstruction() { + super.stepInstruction(); + position = position.steppedForward(thread, 1); + tableDialog.add(createRow()); + } + + @Override + public void stepPcodeOp() { + super.stepPcodeOp(); + position = position.steppedPcodeForward(thread, 1); + tableDialog.add(createRow()); + } + + public CheckRow createRow() { + List> values = new ArrayList<>(); + for (PcodeExpression exp : compiled) { + values.add(exp.evaluate(inspector)); + } + return new CheckRow(position, getCounter(), values); + } + }; + } + }; + + for (SleighPcodeUseropDefinition inject : new Injects().generate().values()) { + String source = inject.getBody(); + println("Injecting " + inject.getName() + ":\n" + source); + for (Symbol sym : currentProgram.getSymbolTable() + .getExternalSymbols(inject.getName())) { + if (sym.getObject() instanceof Function fun) { + Set
addresses = + new HashSet<>(List.of(fun.getFunctionThunkAddresses(true))); + addresses.add(fun.getEntryPoint()); + for (Address sEntry : addresses) { + Address dEntry = translateStaticToDynamic(sEntry); + println(" " + sEntry + " ( -> " + dEntry + ")"); + emu.inject(dEntry, source); + } + } + } + } + + schedule.execute(trace, emu, monitor); + } + + /////////////////////////////////////////// + // Configuration and support kruft below // + /////////////////////////////////////////// + + public record Watch(String expression, TypeRec type, Settings settings) { + } + + interface Setting { + void set(Settings settings); + } + + public record EnumSetting(EnumSettingsDefinition def, int value) implements Setting { + @Override + public void set(Settings settings) { + def.setChoice(settings, value); + } + } + + class TypeLoader extends StructuredSleigh { + protected TypeLoader(Program program) { + super(program); + } + + @Override + protected DataType type(String path) { + return super.type(path); + } + } + + public record TypeRec(String path) { + DataType get(TypeLoader loader) { + return loader.type(path); + } + } + + TypeRec type(String path) { + return new TypeRec(path); + } + + static Setting set(EnumSettingsDefinition def, int value) { + return new EnumSetting(def, value); + } + + static Watch watch(String expression, TypeRec type, Setting... settings) { + Settings settingsImpl = new SettingsImpl(); + for (Setting set : settings) { + set.set(settingsImpl); + } + return new Watch(expression, type, settingsImpl); + } + + class CheckRow implements AddressableRowObject { + private final TraceSchedule schedule; + private final Address pc; + private final List> values; + + public CheckRow(TraceSchedule schedule, Address pc, + List> values) { + this.schedule = schedule; + this.pc = pc; + this.values = values; + } + + @Override + public Address getAddress() { + TraceProgramView view = getCurrentView(); + if (view == null) { + return Address.NO_ADDRESS; + } + Address st = translateDynamicToStatic(pc); + return st == null ? Address.NO_ADDRESS : st; + } + } + + public interface TypedDisplay extends ColumnDisplay { + int compareTyped(R r1, R r2); + + @Override + @SuppressWarnings("unchecked") + default int compare(AddressableRowObject o1, AddressableRowObject o2) { + return compareTyped((R) o1, (R) o2); + } + + T getTypedValue(R r); + + @Override + @SuppressWarnings("unchecked") + default T getColumnValue(AddressableRowObject rowObject) { + return getTypedValue((R) rowObject); + } + } + + public class CheckRowScheduleDisplay implements TypedDisplay { + @Override + public int compareTyped(CheckRow r1, CheckRow r2) { + return r1.schedule.compareTo(r2.schedule); + } + + @Override + public TraceSchedule getTypedValue(CheckRow row) { + return row.schedule; + } + + @Override + public String getColumnName() { + return "Schedule"; + } + + @Override + public Class getColumnClass() { + return TraceSchedule.class; + } + } + + public class CheckRowCounterDisplay implements TypedDisplay { + @Override + public int compareTyped(CheckRow r1, CheckRow r2) { + return r1.pc.compareTo(r2.pc); + } + + @Override + public Address getTypedValue(CheckRow row) { + return row.pc; + } + + @Override + public String getColumnName() { + return "Counter"; + } + + @Override + public Class
getColumnClass() { + return Address.class; + } + } + + public class CheckRowWatchDisplay implements TypedDisplay { + private final boolean isBigEndian = currentProgram.getLanguage().isBigEndian(); + private final Watch watch; + private final DataType type; + private final int index; + private final Class valueClass; + + public CheckRowWatchDisplay(TypeLoader loader, Watch watch, int index) { + this.watch = watch; + this.type = watch.type.get(loader); + this.index = index; + this.valueClass = type.getValueClass(watch.settings); + } + + private Object getObjectValue(CheckRow r) { + try { + Pair p = r.values.get(index); + Address addr = p.getRight(); + byte[] bytes = p.getLeft(); + return type.getValue(new ByteMemBufferImpl(addr, bytes, isBigEndian), + watch.settings, bytes.length); + } + catch (Exception e) { + return "Err: " + e.getMessage(); + } + } + + private String getStringValue(CheckRow r) { + try { + Pair p = r.values.get(index); + Address addr = p.getRight(); + byte[] bytes = p.getLeft(); + return type.getRepresentation(new ByteMemBufferImpl(addr, bytes, isBigEndian), + watch.settings, bytes.length); + } + catch (Exception e) { + return "Err: " + e.getMessage(); + } + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public int compareTyped(CheckRow r1, CheckRow r2) { + if (Comparable.class.isAssignableFrom(valueClass)) { + Object v1 = getObjectValue(r1); + Object v2 = getObjectValue(r2); + return ((Comparable) v1).compareTo(v2); + } + String s1 = getStringValue(r1); + String s2 = getStringValue(r2); + return s1.compareTo(s2); + } + + @Override + public String getTypedValue(CheckRow row) { + return getStringValue(row); + } + + @Override + public String getColumnName() { + return watch.expression; + } + + @Override + @SuppressWarnings("unchecked") + public Class getColumnClass() { + return (Class) valueClass; + } + } + + private final class CheckRowChooser implements TableChooserExecutor { + @Override + public String getButtonName() { + return "Go To"; + } + + @Override + public boolean execute(AddressableRowObject rowObject) { + CheckRow row = (EmuDeskCheckScript.CheckRow) rowObject; + try { + emulate(row.schedule, monitor); + } + catch (CancelledException e) { + // Just be done + } + return false; + } + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/GotoStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/GotoStmt.java new file mode 100644 index 0000000000..335fe2ff30 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/GotoStmt.java @@ -0,0 +1,39 @@ +/* ### + * 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.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; +import ghidra.pcode.struct.StructuredSleigh.RVal; + +public class GotoStmt extends AbstractStmt { + + private final RValInternal target; + + protected GotoStmt(StructuredSleigh ctx, RVal target) { + super(ctx); + this.target = (RValInternal) target; + } + + @Override + protected StringTree generate(Label next, Label fall) { + StringTree st = new StringTree(); + st.append("goto ["); + st.append(target.generate()); + st.append("];\n"); + return st; + } + +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.java index aa11c3f41f..d064b92c73 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.java @@ -29,9 +29,9 @@ class ReturnStmt extends AbstractStmt { @Override protected StringTree generate(Label next, Label fall) { StringTree st = new StringTree(); - st.append("return "); + st.append("return ["); st.append(target.generate()); - st.append(";\n"); + st.append("];\n"); return st; } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java index d0c2767156..470e63f809 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java @@ -1636,17 +1636,28 @@ public class StructuredSleigh { * Generate a "return" statement * *

- * This models (in part) a C-style return from the current target function to its callee. It + * This models (in part) a C-style return from the current target function to its caller. It * simply generates the "return" Sleigh statement, which is an indirect branch to the given - * target. The target must be at an address in the processor's code space. + * target. Target is typically popped from the stack or read from a link register. * *

* Contrast with {@link #_result(RVal)} + * + * @param target the offset of the target */ protected void _return(RVal target) { new ReturnStmt(this, target); } + /** + * Generate a "goto" statement to another address in the processor's code space + * + * @param target the offset of the target address + */ + protected void _goto(RVal target) { + new GotoStmt(this, target); + } + /** * Get the method lookup for this context * diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java index 70c41dae1d..0481a12a6e 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java @@ -230,11 +230,11 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest StructuredSleigh ss = new TestStructuredSleigh() { @StructuredUserop public void my_userop() { - _return(lit(0xdeadbeefL, 8).deref()); + _return(lit(0xdeadbeefL, 8)); } }; SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); - assertEquals("return (* 0xdeadbeef:8);\n", myUserop.getBody()); + assertEquals("return [0xdeadbeef:8];\n", myUserop.getBody()); // TODO: Test that the generated code compiles in a slaspec file. // It's rejected for injects because "return" is not valid there. }