mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-30 16:11:04 +00:00
GP-3716: Fix context flow in Emulator's decoder
This commit is contained in:
parent
5888ac64e1
commit
7b97d1899c
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user