mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-3716: Fix context flow in Emulator's decoder
This commit is contained in:
parent
5888ac64e1
commit
7b97d1899c
5 changed files with 160 additions and 65 deletions
|
@ -309,8 +309,7 @@ public class PatchStep implements Step {
|
||||||
@Override
|
@Override
|
||||||
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
|
public <T> void execute(PcodeThread<T> emuThread, Stepper stepper, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", sleigh + ";");
|
emuThread.stepPatch(sleigh);
|
||||||
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -946,28 +946,29 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testITECC_VMOVCCF32() throws Throwable {
|
public void testIT_ContextFlow() throws Throwable {
|
||||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "ARM:LE:32:v8T")) {
|
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "ARM:LE:32:v8T")) {
|
||||||
TraceThread thread = initTrace(tb, """
|
TraceThread thread = initTrace(tb, """
|
||||||
pc = 0x00400000;
|
pc = 0x00400000;
|
||||||
sp = 0x00110000;
|
sp = 0x00110000;
|
||||||
s1 = 0x12341234;
|
r0 = 0x12341234;
|
||||||
|
r1 = 0x43214321;
|
||||||
|
r7 = 0xbeef;
|
||||||
CY = 1;
|
CY = 1;
|
||||||
""",
|
""",
|
||||||
List.of(
|
List.of(
|
||||||
"ite cc"));
|
"it cc",
|
||||||
//"vmov.cc.f32 s1,0xbf000000"));
|
"mov r0,r7", // Assembler doesn't handle context flow
|
||||||
|
"mov r1,r7"));
|
||||||
try (Transaction tx = tb.startTransaction()) {
|
|
||||||
tb.trace.getMemoryManager()
|
|
||||||
.putBytes(0, tb.addr(0x00400002), tb.buf(0xfe, 0xee, 0x00, 0x0a));
|
|
||||||
}
|
|
||||||
|
|
||||||
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0);
|
BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0);
|
||||||
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
|
||||||
emuThread.stepInstruction();
|
emuThread.stepInstruction();
|
||||||
emuThread.stepPcodeOp(); // decode
|
emuThread.stepPcodeOp(); // decode second
|
||||||
assertEquals("vmov.cc.f32 s1,0xbf000000", emuThread.getInstruction().toString());
|
assertEquals("mov.cc r0,r7", emuThread.getInstruction().toString());
|
||||||
|
emuThread.finishInstruction();
|
||||||
|
emuThread.stepPcodeOp(); // decode third
|
||||||
|
assertEquals("mov r1,r7", emuThread.getInstruction().toString());
|
||||||
emuThread.finishInstruction();
|
emuThread.finishInstruction();
|
||||||
|
|
||||||
try (Transaction tx = tb.startTransaction()) {
|
try (Transaction tx = tb.startTransaction()) {
|
||||||
|
@ -976,8 +977,54 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
|
||||||
|
|
||||||
assertEquals(BigInteger.valueOf(0x00400006),
|
assertEquals(BigInteger.valueOf(0x00400006),
|
||||||
TraceSleighUtils.evaluate("pc", tb.trace, 1, thread, 0));
|
TraceSleighUtils.evaluate("pc", tb.trace, 1, thread, 0));
|
||||||
assertEquals(BigInteger.valueOf(0x12341234), // Unaffected
|
assertEquals(BigInteger.valueOf(0x12341234), // r0 Unaffected
|
||||||
TraceSleighUtils.evaluate("s1", tb.trace, 1, thread, 0));
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,18 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
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.BytesDebuggerPcodeEmulator;
|
||||||
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.app.services.DebuggerWatchesService;
|
||||||
import ghidra.app.tablechooser.*;
|
import ghidra.app.tablechooser.*;
|
||||||
import ghidra.debug.flatapi.FlatDebuggerAPI;
|
import ghidra.debug.flatapi.FlatDebuggerAPI;
|
||||||
import ghidra.docking.settings.*;
|
import ghidra.docking.settings.*;
|
||||||
|
@ -28,6 +32,7 @@ import ghidra.pcode.emu.BytesPcodeThread;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
|
||||||
import ghidra.pcode.struct.StructuredSleigh;
|
import ghidra.pcode.struct.StructuredSleigh;
|
||||||
|
import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.DataType;
|
||||||
import ghidra.program.model.listing.Function;
|
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.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.NumericUtilities;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE: Testing with bash: set_shellopts
|
|
||||||
*/
|
|
||||||
public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI {
|
public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI {
|
||||||
|
|
||||||
public class Injects extends StructuredSleigh {
|
public class Injects extends StructuredSleigh {
|
||||||
Var RAX = lang("RAX", type("void *"));
|
|
||||||
Var RSP = lang("RSP", type("void *"));
|
|
||||||
|
|
||||||
protected Injects() {
|
protected Injects() {
|
||||||
super(currentProgram);
|
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(
|
protected List<Watch> collectWatches() {
|
||||||
watch("RAX", type("int")),
|
DebuggerWatchesService watchService =
|
||||||
watch("RCX", type("int"),
|
state.getTool().getService(DebuggerWatchesService.class);
|
||||||
set(FormatSettingsDefinition.DEF, FormatSettingsDefinition.DECIMAL)),
|
List<Watch> watches = new ArrayList<>();
|
||||||
watch("RSP", type("void *")));
|
for (WatchRow row : watchService.getWatches()) {
|
||||||
// TODO: Snarf from Watches window?
|
DataType type = row.getDataType();
|
||||||
|
watches.add(new Watch(row.getExpression(),
|
||||||
|
type == null ? null : type(type.getName()), row.getSettings()));
|
||||||
|
}
|
||||||
|
return watches;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void run() throws Exception {
|
protected void run() throws Exception {
|
||||||
|
List<Watch> watches = collectWatches();
|
||||||
|
|
||||||
Trace trace = emulateLaunch(currentProgram, currentAddress);
|
Trace trace = emulateLaunch(currentProgram, currentAddress);
|
||||||
TracePlatform platform = trace.getPlatformManager().getHostPlatform();
|
TracePlatform platform = trace.getPlatformManager().getHostPlatform();
|
||||||
long snap = 0;
|
long snap = 0;
|
||||||
|
@ -114,7 +89,12 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
|
||||||
for (Watch w : watches) {
|
for (Watch w : watches) {
|
||||||
PcodeExpression ce = SleighProgramCompiler
|
PcodeExpression ce = SleighProgramCompiler
|
||||||
.compileExpression((SleighLanguage) platform.getLanguage(), w.expression);
|
.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);
|
compiled.add(ce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +146,13 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
|
||||||
tableDialog.add(createRow());
|
tableDialog.add(createRow());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stepPatch(String sleigh) {
|
||||||
|
super.stepPatch(sleigh);
|
||||||
|
position = position.patched(thread, language, sleigh);
|
||||||
|
tableDialog.add(createRow());
|
||||||
|
}
|
||||||
|
|
||||||
public CheckRow createRow() {
|
public CheckRow createRow() {
|
||||||
List<Pair<byte[], ValueLocation>> values = new ArrayList<>();
|
List<Pair<byte[], ValueLocation>> values = new ArrayList<>();
|
||||||
for (PcodeExpression exp : compiled) {
|
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> {
|
public class CheckRowWatchDisplay implements TypedDisplay<CheckRow, Object> {
|
||||||
private final boolean isBigEndian = currentProgram.getLanguage().isBigEndian();
|
private final boolean isBigEndian = currentProgram.getLanguage().isBigEndian();
|
||||||
private final Watch watch;
|
private final Watch watch;
|
||||||
|
@ -349,6 +381,9 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object getObjectValue(CheckRow r) {
|
private Object getObjectValue(CheckRow r) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Pair<byte[], ValueLocation> p = r.values.get(index);
|
Pair<byte[], ValueLocation> p = r.values.get(index);
|
||||||
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
|
Address addr = p.getRight() == null ? null : p.getRight().getAddress();
|
||||||
|
@ -412,7 +447,7 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean execute(AddressableRowObject rowObject) {
|
public boolean execute(AddressableRowObject rowObject) {
|
||||||
CheckRow row = (EmuDeskCheckScript.CheckRow) rowObject;
|
CheckRow row = (CheckRow) rowObject;
|
||||||
try {
|
try {
|
||||||
emulate(row.schedule, monitor);
|
emulate(row.schedule, monitor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* 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()));
|
overrideCounter(counter.addWrap(decoder.getLastLengthWithDelays()));
|
||||||
}
|
}
|
||||||
if (contextreg != Register.NO_CONTEXT) {
|
if (contextreg != Register.NO_CONTEXT) {
|
||||||
RegisterValue flowCtx =
|
RegisterValue ctx = new RegisterValue(contextreg, BigInteger.ZERO)
|
||||||
defaultContext.getFlowValue(instruction.getRegisterValue(contextreg));
|
.combineValues(defaultContext.getDefaultValue(contextreg, counter))
|
||||||
RegisterValue commitCtx = getContextAfterCommits();
|
.combineValues(defaultContext.getFlowValue(context))
|
||||||
overrideContext(flowCtx.combineValues(commitCtx));
|
.combineValues(getContextAfterCommits());
|
||||||
|
overrideContext(ctx);
|
||||||
}
|
}
|
||||||
postExecuteInstruction();
|
postExecuteInstruction();
|
||||||
frame = null;
|
frame = null;
|
||||||
|
|
|
@ -196,6 +196,13 @@ public interface PcodeThread<T> {
|
||||||
*/
|
*/
|
||||||
void skipPcodeOp();
|
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
|
* Get the current frame, if present
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue