GP-3716: Fix context flow in Emulator's decoder

This commit is contained in:
Dan 2023-08-11 12:10:48 -04:00
parent 5888ac64e1
commit 7b97d1899c
5 changed files with 160 additions and 65 deletions

View File

@ -309,8 +309,7 @@ public class PatchStep implements Step {
@Override
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
throws CancelledException {
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", sleigh + ";");
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
emuThread.stepPatch(sleigh);
}
@Override

View File

@ -946,28 +946,29 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
}
@Test
public void testITECC_VMOVCCF32() throws Throwable {
public void testIT_ContextFlow() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "ARM:LE:32:v8T")) {
TraceThread thread = initTrace(tb, """
pc = 0x00400000;
sp = 0x00110000;
s1 = 0x12341234;
r0 = 0x12341234;
r1 = 0x43214321;
r7 = 0xbeef;
CY = 1;
""",
List.of(
"ite cc"));
//"vmov.cc.f32 s1,0xbf000000"));
try (Transaction tx = tb.startTransaction()) {
tb.trace.getMemoryManager()
.putBytes(0, tb.addr(0x00400002), tb.buf(0xfe, 0xee, 0x00, 0x0a));
}
"it cc",
"mov r0,r7", // Assembler doesn't handle context flow
"mov r1,r7"));
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.stepInstruction();
emuThread.stepPcodeOp(); // decode
assertEquals("vmov.cc.f32 s1,0xbf000000", emuThread.getInstruction().toString());
emuThread.stepPcodeOp(); // decode second
assertEquals("mov.cc r0,r7", emuThread.getInstruction().toString());
emuThread.finishInstruction();
emuThread.stepPcodeOp(); // decode third
assertEquals("mov r1,r7", emuThread.getInstruction().toString());
emuThread.finishInstruction();
try (Transaction tx = tb.startTransaction()) {
@ -976,8 +977,54 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
assertEquals(BigInteger.valueOf(0x00400006),
TraceSleighUtils.evaluate("pc", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x12341234), // Unaffected
TraceSleighUtils.evaluate("s1", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x12341234), // r0 Unaffected
TraceSleighUtils.evaluate("r0", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xbeef), // r1 Affected
TraceSleighUtils.evaluate("r1", tb.trace, 1, thread, 0));
}
}
@Test
public void testITE_ContextFlow() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "ARM:LE:32:v8T")) {
TraceThread thread = initTrace(tb, """
pc = 0x00400000;
sp = 0x00110000;
r0 = 0x12341234;
r1 = 0x43214321;
r7 = 0xbeef;
CY = 1;
""",
List.of(
"ite cc",
"mov r0,r7", // Assembler doesn't handle context flow
"mov r1,r7", // "
"mov r2,r7"));
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.stepInstruction();
emuThread.stepPcodeOp(); // decode second
assertEquals("mov.cc r0,r7", emuThread.getInstruction().toString());
emuThread.finishInstruction();
emuThread.stepPcodeOp(); // decode third
assertEquals("mov.cs r1,r7", emuThread.getInstruction().toString());
emuThread.finishInstruction();
emuThread.stepPcodeOp(); // decode fourth
assertEquals("mov r2,r7", emuThread.getInstruction().toString());
emuThread.finishInstruction();
try (Transaction tx = tb.startTransaction()) {
emu.writeDown(tb.host, 1, 1);
}
assertEquals(BigInteger.valueOf(0x00400008),
TraceSleighUtils.evaluate("pc", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x12341234), // r0 Unaffected
TraceSleighUtils.evaluate("r0", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xbeef), // r1 Affected
TraceSleighUtils.evaluate("r1", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0xbeef), // r2 Affected
TraceSleighUtils.evaluate("r2", tb.trace, 1, thread, 0));
}
}
}

View File

@ -13,14 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.math.BigInteger;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.gui.watch.WatchRow;
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.services.DebuggerWatchesService;
import ghidra.app.tablechooser.*;
import ghidra.debug.flatapi.FlatDebuggerAPI;
import ghidra.docking.settings.*;
@ -28,6 +32,7 @@ import ghidra.pcode.emu.BytesPcodeThread;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
@ -40,63 +45,33 @@ 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.NumericUtilities;
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.
*/
@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<Watch> 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?
protected List<Watch> collectWatches() {
DebuggerWatchesService watchService =
state.getTool().getService(DebuggerWatchesService.class);
List<Watch> watches = new ArrayList<>();
for (WatchRow row : watchService.getWatches()) {
DataType type = row.getDataType();
watches.add(new Watch(row.getExpression(),
type == null ? null : type(type.getName()), row.getSettings()));
}
return watches;
}
@Override
protected void run() throws Exception {
List<Watch> watches = collectWatches();
Trace trace = emulateLaunch(currentProgram, currentAddress);
TracePlatform platform = trace.getPlatformManager().getHostPlatform();
long snap = 0;
@ -114,7 +89,12 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
for (Watch w : watches) {
PcodeExpression ce = SleighProgramCompiler
.compileExpression((SleighLanguage) platform.getLanguage(), w.expression);
tableDialog.addCustomColumn(new CheckRowWatchDisplay(loader, w, compiled.size()));
if (w.type != null) {
tableDialog.addCustomColumn(new CheckRowWatchDisplay(loader, w, compiled.size()));
}
else {
tableDialog.addCustomColumn(new CheckRowRawDisplay(w, compiled.size()));
}
compiled.add(ce);
}
@ -166,6 +146,13 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
tableDialog.add(createRow());
}
@Override
public void stepPatch(String sleigh) {
super.stepPatch(sleigh);
position = position.patched(thread, language, sleigh);
tableDialog.add(createRow());
}
public CheckRow createRow() {
List<Pair<byte[], ValueLocation>> values = new ArrayList<>();
for (PcodeExpression exp : compiled) {
@ -334,6 +321,51 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
}
}
public class CheckRowRawDisplay implements TypedDisplay<CheckRow, String> {
private final boolean isBigEndian = currentProgram.getLanguage().isBigEndian();
private final Watch watch;
private final int index;
public CheckRowRawDisplay(Watch watch, int index) {
this.watch = watch;
this.index = index;
}
@Override
public String getColumnName() {
return watch.expression;
}
@Override
public Class<String> getColumnClass() {
return String.class;
}
private String getStringValue(CheckRow r) {
Pair<byte[], ValueLocation> p = r.values.get(index);
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
byte[] bytes = p.getLeft();
if (addr == null || !addr.getAddressSpace().isMemorySpace()) {
BigInteger asBigInt =
Utils.bytesToBigInteger(bytes, bytes.length, isBigEndian, false);
return "0x" + asBigInt.toString(16);
}
return "{ " + NumericUtilities.convertBytesToString(bytes, " ") + " }";
}
@Override
public int compareTyped(CheckRow r1, CheckRow r2) {
String s1 = getStringValue(r1);
String s2 = getStringValue(r2);
return s1.compareTo(s2);
}
@Override
public String getTypedValue(CheckRow r) {
return getStringValue(r);
}
}
public class CheckRowWatchDisplay implements TypedDisplay<CheckRow, Object> {
private final boolean isBigEndian = currentProgram.getLanguage().isBigEndian();
private final Watch watch;
@ -349,6 +381,9 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
}
private Object getObjectValue(CheckRow r) {
if (type == null) {
return null;
}
try {
Pair<byte[], ValueLocation> p = r.values.get(index);
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
@ -412,7 +447,7 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
@Override
public boolean execute(AddressableRowObject rowObject) {
CheckRow row = (EmuDeskCheckScript.CheckRow) rowObject;
CheckRow row = (CheckRow) rowObject;
try {
emulate(row.schedule, monitor);
}

View File

@ -444,6 +444,12 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
}
@Override
public void stepPatch(String sleigh) {
PcodeProgram prog = getMachine().compileSleigh("patch", sleigh + ";");
executor.execute(prog, library);
}
/**
* Start execution of the instruction or inject at the program counter
*/
@ -499,10 +505,11 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
overrideCounter(counter.addWrap(decoder.getLastLengthWithDelays()));
}
if (contextreg != Register.NO_CONTEXT) {
RegisterValue flowCtx =
defaultContext.getFlowValue(instruction.getRegisterValue(contextreg));
RegisterValue commitCtx = getContextAfterCommits();
overrideContext(flowCtx.combineValues(commitCtx));
RegisterValue ctx = new RegisterValue(contextreg, BigInteger.ZERO)
.combineValues(defaultContext.getDefaultValue(contextreg, counter))
.combineValues(defaultContext.getFlowValue(context))
.combineValues(getContextAfterCommits());
overrideContext(ctx);
}
postExecuteInstruction();
frame = null;

View File

@ -196,6 +196,13 @@ public interface PcodeThread<T> {
*/
void skipPcodeOp();
/**
* Apply a patch to the emulator
*
* @param sleigh a line of sleigh semantic source to execute (excluding the final semicolon)
*/
void stepPatch(String sleigh);
/**
* Get the current frame, if present
*