GP-1208: Implement linux-x86/-amd64 system call simulators

This commit is contained in:
Dan 2022-05-20 11:05:53 -04:00
parent 4b600847eb
commit b33800ecba
144 changed files with 12712 additions and 804 deletions

View file

@ -0,0 +1,211 @@
/* ###
* 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.
*/
//An example emulation script that integrates well with the Debgger UI.
//It provides the set-up code and then demonstrates some use cases.
//It should work with any x64 program, but some snippets may require specific conditions.
//It should be easily ported to other platforms just by adjusting register names.
//@author
//@category Emulation
//@keybinding
//@menupath
//@toolbar
import java.nio.charset.Charset;
import java.util.List;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.services.ProgramManager;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.pcode.utils.Utils;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.database.UndoableTransaction;
public class DebuggerEmuExampleScript extends GhidraScript {
private final static Charset UTF8 = Charset.forName("utf8");
@Override
protected void run() throws Exception {
/*
* First, get all the services and stuff:
*/
PluginTool tool = state.getTool();
ProgramManager programManager = tool.getService(ProgramManager.class);
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
SleighLanguage language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default"));
/*
* I'll generate a new program, because I don't want to require the user to pick something
* specific.
*/
Address entry;
Address injectHere;
Program program = null;
try {
program =
new ProgramDB("emu_example", language, language.getDefaultCompilerSpec(), this);
// Save the program into the project so it has a URL for the trace's static mapping
tool.getProject()
.getProjectData()
.getRootFolder()
.createFile("emu_example", program, monitor);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
entry = space.getAddress(0x00400000);
Address dataEntry = space.getAddress(0x00600000);
Memory memory = program.getMemory();
memory.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
Assembler asm = Assemblers.getAssembler(program);
InstructionIterator ii = asm.assemble(entry,
"MOV RCX, 0x" + dataEntry,
"MOV RAX, 1",
"SYSCALL",
"MOV RAX, 2",
"SYSCALL");
ii.next(); // drop MOV RCX
injectHere = ii.next().getAddress();
memory.createInitializedBlock(".data", dataEntry, 0x1000, (byte) 0, monitor, false);
memory.setBytes(dataEntry, "Hello, World!\n".getBytes(UTF8));
}
program.save("Init", monitor);
// Display the program in the UI
programManager.openProgram(program);
}
finally {
if (program != null) {
program.release(this);
}
}
/*
* Now, load the program into a trace. This doesn't copy any bytes, it just sets up a static
* mapping. The emulator will know how to read through to the mapped program. We use a
* utility, which is the same used by the "Emulate Program" action in the UI. It will load
* the program, allocate a stack, and initialize the first thread to the given entry.
*/
Trace trace = null;
try {
trace = ProgramEmulationUtils.launchEmulationTrace(program, entry, this);
// Display the trace in the UI
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
}
finally {
if (trace != null) {
trace.release(this);
}
}
// Get the initial thread
TraceThread traceThread = trace.getThreadManager().getAllThreads().iterator().next();
traceManager.activateThread(traceThread);
/*
* Instead of using the UI's emulator, this script will create its own with a custom
* library. This emulator will still know how to integrate with the UI, reading through to
* open programs and writing state back into the trace.
*/
DebuggerTracePcodeEmulator emulator = new DebuggerTracePcodeEmulator(tool, trace, 0, null) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this);
}
};
// Conventionally, emulator threads are named after their trace thread's path.
PcodeThread<byte[]> thread = emulator.getThread(traceThread.getPath(), true);
/*
* Inject a call to our custom print userop. Otherwise, the language itself will never
* invoke it.
*/
emulator.inject(injectHere, List.of(
"print_utf8(RCX);",
"emu_exec_decoded();"));
/*
* Run the experiment: This should interrupt on the second SYSCALL, because any value other
* than 1 calls emu_swi.
*
* For demonstration, we'll record a trace snapshot for every step of emulation. This is not
* ordinarily recommended except for very small experiments. A more reasonable approach in
* practice may be to snapshot on specific breakpoints.
*/
TraceTimeManager time = trace.getTimeManager();
TraceSnapshot snapshot = time.getSnapshot(0, true);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate", true)) {
for (int i = 0; i < 10; i++) {
println("Executing: " + thread.getCounter());
thread.stepInstruction();
snapshot =
time.createSnapshot("Stepped to " + thread.getCounter());
emulator.writeDown(trace, snapshot.getKey(), 0, false);
}
printerr("We should not have completed 10 steps!");
}
catch (InterruptPcodeExecutionException e) {
println("Terminated via interrupt. Good.");
}
// Display the final snapshot in the UI
traceManager.activateSnap(snapshot.getKey());
/*
* Inspect the machine. You can always do this by accessing the state directly, but for
* anything other than simple variables, you may find compiling an expression more
* convenient.
*
* This works the same as in the stand-alone case.
*/
println("RCX = " +
Utils.bytesToLong(thread.getState().getVar(language.getRegister("RCX")), 8,
language.isBigEndian()));
println("RCX = " + Utils.bytesToLong(
SleighProgramCompiler.compileExpression(language, "RCX").evaluate(thread.getExecutor()),
8, language.isBigEndian()));
println("RCX+4 = " +
Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RCX+4")
.evaluate(thread.getExecutor()),
8, language.isBigEndian()));
/*
* To evaluate a Sleigh expression against the trace: The result is the same as evaluating
* directly against the emulator, but these work with any trace, no matter the original data
* source (live target, emulated, imported, etc.) It's also built into utilities, making it
* easier to use.
*/
println("RCX+4 (trace) = " +
TraceSleighUtils.evaluate("RCX+4", trace, snapshot.getKey(), traceThread, 0));
}
}

View file

@ -0,0 +1,131 @@
/* ###
* 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.nio.charset.Charset;
import java.util.List;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
import ghidra.pcode.exec.*;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.pcode.Varnode;
/**
* A userop library for the emulator
*
* <p>
* If you do not have need of a custom userop library, use {@link PcodeUseropLibrary#NIL}. These
* libraries allow you to implement userop, including those declared by the language. Without these,
* the emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also
* define new userops, which can be invoked from Sleigh code injected into the emulator.
*
* <p>
* These libraries can have both Java-callback and p-code implementations of userops. If only using
* p-code implementations, the library can be parameterized with type {@code <T>} and just pass that
* over to {@link AnnotatedPcodeUseropLibrary}. Because this will demo a Java callback that assumes
* concrete bytes, we will fix the library's type to {@code byte[]}.
*
* <p>
* Methods in this class (not including those in its nested classes) are implemented as Java
* callbacks.
*/
public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]> {
private final static Charset UTF8 = Charset.forName("utf8");
private final SleighLanguage language;
private final GhidraScript script;
private final AddressSpace space;
public DemoPcodeUseropLibrary(SleighLanguage language, GhidraScript script) {
this.language = language;
this.script = script;
this.space = language.getDefaultSpace();
new DemoStructuredPart(language.getDefaultCompilerSpec()).generate(ops);
}
/**
* Treats the input as an offset to a C-style string and prints it to the console
*
* <p>
* Because we want to dereference start, we will need access to the emulator's state, so we
* employ the {@link OpState} annotation. {@code start} takes the one input we expect. Because
* its type is the value type rather than {@link Varnode}, we will get the input's value.
* Similarly, we can just return the resulting value, and the emulator will place that into the
* output variable for us.
*
* @param state the calling thread's state
* @param start the offset of the first character
* @return the length of the string in bytes
*/
@PcodeUserop
public byte[] print_utf8(@OpState PcodeExecutorStatePiece<byte[], byte[]> state,
byte[] start) {
long offset = Utils.bytesToLong(start, start.length, language.isBigEndian());
long end = offset;
while (state.getVar(space, end, 1, true)[0] != 0) {
end++;
}
if (end == offset) {
script.println("");
return Utils.longToBytes(0, Long.BYTES, language.isBigEndian());
}
byte[] bytes = state.getVar(space, offset, (int) (end - offset), true);
String str = new String(bytes, UTF8);
script.println(str);
return Utils.longToBytes(end - offset, Long.BYTES, language.isBigEndian());
}
/**
* Methods in this class are implemented using p-code compiled from Structured Sleigh
*/
public class DemoStructuredPart extends StructuredSleigh {
final Var RAX = lang("RAX", type("long"));
final Var RCX = lang("RAX", type("byte *"));
final UseropDecl emu_swi = userop(type("void"), "emu_swi", List.of());
protected DemoStructuredPart(CompilerSpec cs) {
super(cs);
}
/**
* Not really a syscall dispatcher
*
* <p>
* In cases where the userop expects parameters, you would annotate them with {@link Param}
* and use them just like other {@link Var}s. See the javadocs.
*
* <p>
* This is just a cheesy demo: If RAX is 1, then this method computes the number of bytes in
* the C-style string pointed to by RCX and stores the result in RAX. Otherwise, interrupt
* the emulator. See {@link DemoSyscallLibrary} for actual system call simulation.
*/
@StructuredUserop
public void syscall() {
_if(RAX.eq(1), () -> {
Var i = local("i", RCX);
_while(i.deref().neq(0), () -> {
i.inc();
});
RAX.set(i.subi(RAX));
})._else(() -> {
emu_swi.call();
});
}
}
}

View file

@ -0,0 +1,211 @@
/* ###
* 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.nio.charset.Charset;
import java.util.Collection;
import ghidra.app.script.GhidraScript;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibrary;
import ghidra.pcode.emu.linux.EmuLinuxX86SyscallUseropLibrary;
import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
import ghidra.pcode.exec.*;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
/**
* A userop library that includes system call simulation
*
* <p>
* Such a library needs to implement {@link EmuSyscallLibrary}. Here we extend
* {@link AnnotatedEmuSyscallUseropLibrary}, which allows us to implement it using annotated
* methods. {@link EmuSyscallLibrary#syscall(PcodeExecutor, PcodeUseropLibrary)} is the system call
* dispatcher, and it requires that each system call implement {@link EmuSyscallDefinition}. System
* call libraries typically implement that interface by annotating p-code userops with
* {@link EmuSyscall}. This allows system calls to be implemented via Java callback or Structured
* Sleigh. Conventionally, the Java method names of system calls should be
* <em>platform</em>_<em>name</em>. This is to prevent name-space pollution of userops.
*
* <p>
* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in
* {@link EmuLinuxX86SyscallUseropLibrary} and {@link EmuLinuxAmd64SyscallUseropLibrary},
* respectively. The type hierarchy is designed to facilitate the implementation of related systems
* without (too much) code duplication. Because they derive from the annotation-based
* implementations, you can add missing system calls by extending one and adding annotated methods
* as needed.
*
* <p>
* For demonstration, this will implement one from scratch for no particular operating system, but
* it will borrow many conventions from linux-amd64.
*/
public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]> {
private final static Charset UTF8 = Charset.forName("utf8");
// Implement all the required plumbing first:
/**
* An exception type for "user errors." These errors should be communicated back to the target
* program rather than causing the emulator to interrupt. This is a bare minimum implementation.
* In practice more information should be communicated internally, in case things go further
* wrong. Also, a hierarchy of exceptions may be appropriate.
*/
static class UserError extends PcodeExecutionException {
private final int errno;
public UserError(int errno) {
super("errno: " + errno);
this.errno = errno;
}
}
private final Register regRAX;
private final GhidraScript script;
/**
* Because the system call numbering is derived from the "syscall" overlay on OTHER space, a
* program is required. The system call analyzer must be applied to it. The program and its
* compiler spec are also used to derive (what it can of) the system call ABI. Notably, it
* applies the calling convention of the functions placed in syscall overlay. Those parts which
* cannot (yet) be derived from the program are instead implemented as abstract methods of this
* class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and
* {@link #handleError(PcodeExecutor, PcodeExecutionException)}.
*
* @param machine the emulator
* @param program the program being emulated
*/
public DemoSyscallLibrary(PcodeMachine<byte[]> machine, Program program, GhidraScript script) {
super(machine, program);
this.script = script;
this.regRAX = machine.getLanguage().getRegister("RAX");
if (regRAX == null) {
throw new AssertionError("This library only works on x64 targets");
}
}
/**
* The dispatcher doesn't know where the system call number is stored. It relies on this method
* to read that number from the state. Here we'll assume the target is x64 and RAX contains the
* syscall number.
*/
@Override
public long readSyscallNumber(PcodeExecutorStatePiece<byte[], byte[]> state) {
return Utils.bytesToLong(state.getVar(regRAX), regRAX.getNumBytes(),
machine.getLanguage().isBigEndian());
}
/**
* If the error is a user error, put the errno into the machine as expected by the target
* program. Here we negate the errno and put it into RAX. If it's not a user error, we return
* false letting the dispatcher know it should interrupt the emulator.
*/
@Override
public boolean handleError(PcodeExecutor<byte[]> executor, PcodeExecutionException err) {
if (err instanceof UserError) {
executor.getState()
.setVar(regRAX, executor.getArithmetic()
.fromConst(-((UserError) err).errno, regRAX.getNumBytes()));
return true;
}
return false;
}
/**
* Support for Structured Sleigh is built-in. To enable it, override this method and instantiate
* the appropriate (usually nested) class.
*/
@Override
protected StructuredPart newStructuredPart() {
return new DemoStructuredPart();
}
@Override
protected Collection<DataTypeManager> getAdditionalArchives() {
// Add platform-specific data type archives, if needed
return super.getAdditionalArchives();
}
// Now, implement some system calls!
// First, a Java callback example
/**
* Write a buffer of utf-8 characters to the console
*
* <p>
* The {@link EmuSyscall} annotation allows us to specify the system call name, because the
* userop name should be prefixed with the platform name, to avoid naming collisions among
* userops.
*
* <p>
* For demonstration, we will export this as a system call, though that is not required for
* {@link DemoStructuredPart#demo_console(StructuredSleigh.Var)} to invoke it. It does need to
* be a userop, but it doesn't need to be a syscall.
*
* @param str a pointer to the start of the buffer
* @param end a pointer to the end (exclusive) of the buffer
*/
@PcodeUserop
@EmuSyscall("write")
public void demo_write(byte[] str, byte[] end) {
AddressSpace space = machine.getLanguage().getDefaultSpace();
/**
* Because we have concrete {@code byte[]}, we could use Utils.bytesToLong, but for
* demonstration, here's how it can be done if we extended
* {@link AnnotatedEmuSyscallUseropLibrary}{@code <T>} instead. If the value cannot be made
* concrete, an exception will be thrown. For abstract types, it's a good idea to save a
* copy of the arithmetic as a field at library construction time.
*/
PcodeArithmetic<byte[]> arithmetic = machine.getArithmetic();
long strLong = arithmetic.toConcrete(str).longValue();
long endLong = arithmetic.toConcrete(end).longValue();
byte[] stringBytes =
machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true);
String string = new String(stringBytes, UTF8);
script.println(string);
}
// Second, a Structured Sleigh example
/**
* The nested class for syscall implemented using StructuredSleigh. Note that no matter the
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare it
* public so that the annotation processor can access the methods. Alternatively, we could
* override {@link #getMethodLookup()}.
*/
public class DemoStructuredPart extends StructuredPart {
UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *"));
/**
* Write a C-style string to the console
*
* @param str the null-terminated utf-8 string
*/
@StructuredUserop
@EmuSyscall("console")
public void demo_console(@Param(type = "char *") Var str) {
// Measure the string's length and then invoke write
Var end = local("end", type("char *"));
_for(end.set(str), end.deref().neq(0), end.inc(), () -> {
});
write.call(str, end);
}
}
}

View file

@ -0,0 +1,179 @@
/* ###
* 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.
*/
//An example emulation script that uses a stand-alone emulator.
//It provides the set-up code and then demonstrates some use cases.
//@author
//@category Emulation
//@keybinding
//@menupath
//@toolbar
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.LanguageID;
public class StandAloneEmuExampleScript extends GhidraScript {
private final static Charset UTF8 = Charset.forName("utf8");
private SleighLanguage language;
private PcodeEmulator emulator;
@Override
protected void run() throws Exception {
/*
* Create an emulator and start a thread
*/
language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default"));
emulator = new PcodeEmulator(language) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new DemoPcodeUseropLibrary(language, StandAloneEmuExampleScript.this);
}
// Uncomment this to see instructions printed as they are decoded
/*
protected BytesPcodeThread createThread(String name) {
return new BytesPcodeThread(name, this) {
@Override
protected SleighInstructionDecoder createInstructionDecoder(
PcodeExecutorState<byte[]> sharedState) {
return new SleighInstructionDecoder(language, sharedState) {
@Override
public Instruction decodeInstruction(Address address,
RegisterValue context) {
Instruction instruction = super.decodeInstruction(address, context);
println("Decoded " + address + ": " + instruction);
return instruction;
}
};
}
};
}
*/
};
PcodeThread<byte[]> thread = emulator.newThread();
// The emulator composes the full library for each thread
PcodeUseropLibrary<byte[]> library = thread.getUseropLibrary();
AddressSpace dyn = language.getDefaultSpace();
/*
* Assemble a little test program and write it into the emulator
*
* We're not really going to implement system calls here. We're just using it to demonstrate
* the implementation of a language-defined userop.
*/
Address entry = dyn.getAddress(0x00400000);
Assembler asm = Assemblers.getAssembler(language);
CodeBuffer buffer = new CodeBuffer(asm, entry);
buffer.assemble("MOV RCX, 0xdeadbeef");
Address injectHere = buffer.getNext();
buffer.assemble("MOV RAX, 1");
buffer.assemble("SYSCALL");
buffer.assemble("MOV RAX, 2"); // Induce the interrupt we need to terminate
buffer.assemble("SYSCALL");
byte[] code = buffer.getBytes();
emulator.getSharedState().setVar(dyn, entry.getOffset(), code.length, true, code);
/*
* Initialize other parts of the emulator and thread state. Note the use of the L suffix on
* 0xdeadbeefL, because Java with sign extend the (negative) int to a long otherwise.
*/
byte[] hw = "Hello, World!\n".getBytes(UTF8);
emulator.getSharedState().setVar(dyn, 0xdeadbeefL, hw.length, true, hw);
PcodeProgram init = SleighProgramCompiler.compileProgram(language, "init", List.of(
"RIP = 0x" + entry + ";",
"RSP = 0x00001000;"),
library);
thread.getExecutor().execute(init, library);
thread.overrideContextWithDefault();
thread.reInitialize();
/*
* Inject a call to our custom print userop. Otherwise, the language itself will never
* invoke it.
*/
emulator.inject(injectHere, List.of(
"print_utf8(RCX);",
"emu_exec_decoded();"));
/*
* Run the experiment: This should interrupt on the second SYSCALL, because any value other
* than 1 calls emu_swi.
*/
try {
thread.stepInstruction(10);
printerr("We should not have completed 10 steps!");
}
catch (InterruptPcodeExecutionException e) {
println("Terminated via interrupt. Good.");
}
/*
* Inspect the machine. You can always do this by accessing the state directly, but for
* anything other than simple variables, you may find compiling an expression more
* convenient.
*/
println("RCX = " +
Utils.bytesToLong(thread.getState().getVar(language.getRegister("RCX")), 8,
language.isBigEndian()));
println("RCX = " + Utils.bytesToLong(
SleighProgramCompiler.compileExpression(language, "RCX").evaluate(thread.getExecutor()),
8, language.isBigEndian()));
println("RCX+4 = " +
Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RCX+4")
.evaluate(thread.getExecutor()),
8, language.isBigEndian()));
}
public static class CodeBuffer {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final Assembler asm;
private final Address entry;
public CodeBuffer(Assembler asm, Address entry) {
this.asm = asm;
this.entry = entry;
}
public Address getNext() {
return entry.add(baos.size());
}
public byte[] assemble(String line)
throws AssemblySyntaxException, AssemblySemanticException, IOException {
byte[] bytes = asm.assembleLine(getNext(), line);
baos.write(bytes);
return bytes;
}
public byte[] getBytes() {
return baos.toByteArray();
}
}
}

View file

@ -0,0 +1,112 @@
/* ###
* 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.
*/
//An example script for using Structured Sleigh stand alone
//@author
//@category Sleigh
//@keybinding
//@menupath
//@toolbar
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.util.Map;
import java.util.stream.Collectors;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
import ghidra.pcode.exec.SleighPcodeUseropDefinition;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.program.model.lang.LanguageID;
public class StandAloneStructuredSleighScript extends GhidraScript {
private SleighLanguage language;
/**
* This exists mostly so we can access the methods of anonymous nested classes deriving from
* this one. The "compiler" will need to be able to access the methods, and that's not
* ordinarily allowed since anonymous classes are implicitly "private." Conveniently, it also
* allows us to implement a default constructor, so that can be elided where used, too.
*/
class LookupStructuredSleigh extends StructuredSleigh {
protected LookupStructuredSleigh() {
super(language.getDefaultCompilerSpec());
}
@Override
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
}
@Override
protected void run() throws Exception {
/*
* If you have a target language in mind, perhaps use it, but DATA provides a minimal
* context
*/
language = (SleighLanguage) getLanguage(new LanguageID("DATA:BE:64:default"));
Map<String, SleighPcodeUseropDefinition<Object>> ops = new LookupStructuredSleigh() {
/**
* Add two in-memory vectors of 16 longs and store the result in memory
*
* @param d pointer to the destination vector
* @param s1 pointer to the first operand vector
* @param s2 pointer to the second operand vector
*/
@StructuredUserop
public void vector_add(
@Param(name = "d", type = "int *") Var d,
@Param(name = "s1", type = "int *") Var s1,
@Param(name = "s2", type = "int *") Var s2) {
// Use Java's "for" to generate an unrolled loop
// We could choose a Sleigh loop, instead. Consider both emu and analysis tradeoffs
for (int i = 0; i < 16; i++) {
// This will generate +0 on the first elements, but whatever
d.index(i).deref().set(s1.index(i).deref().addi(s2.index(i).deref()));
}
}
@StructuredUserop
public void memcpy(
@Param(name = "d", type = "void *") Var d,
@Param(name = "s", type = "void *") Var s,
@Param(name = "n", type = "long") Var n) { // size_t is not built-in
Var i = local("i", type("long"));
// Note that these 2 casts don't generate Sleigh statements
Var db = d.cast(type("byte *"));
Var sb = s.cast(type("byte *"));
// Must use a Sleigh loop here
_for(i.set(0), i.ltiu(n), i.inc(), () -> {
db.index(i).deref().set(sb.index(i).deref());
});
}
}.generate();
/*
* Now, dump the generated Sleigh source
*/
for (SleighPcodeUseropDefinition<?> userop : ops.values()) {
print(userop.getName() + "(");
print(userop.getInputs().stream().collect(Collectors.joining(",")));
print(") {\n");
for (String line : userop.getLines()) {
print(line);
}
print("}\n\n");
}
}
}

View file

@ -0,0 +1,233 @@
/* ###
* 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.
*/
//An example emulation script that uses a stand-alone emulator with syscalls.
//It provides the set-up code and then demonstrates some use cases.
//@author
//@category Emulation
//@keybinding
//@menupath
//@toolbar
import java.nio.charset.Charset;
import java.util.List;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.sys.EmuInvalidSystemCallException;
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataTypeConflictHandler;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.database.UndoableTransaction;
public class StandAloneSyscallEmuExampleScript extends GhidraScript {
private final static Charset UTF8 = Charset.forName("utf8");
Program program = null;
@Override
protected void run() throws Exception {
/*
* First, get all the services and stuff:
*/
SleighLanguage language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default"));
/*
* I'll generate a new program, because I don't want to require the user to pick something
* specific. It won't be displayed, though, so we'll just release it when we're done.
*/
Address entry;
try {
/*
* "gcc" is the name of the compiler spec, but we're really interested in the Linux
* syscall calling conventions.
*/
program =
new ProgramDB("syscall_example", language,
language.getCompilerSpecByID(new CompilerSpecID("gcc")), this);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
entry = space.getAddress(0x00400000);
Address dataEntry = space.getAddress(0x00600000);
Memory memory = program.getMemory();
memory.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
Assembler asm = Assemblers.getAssembler(program);
asm.assemble(entry,
"MOV RDI, 0x" + dataEntry,
"MOV RAX, 1",
"SYSCALL",
"MOV RAX, 20",
"SYSCALL");
memory.createInitializedBlock(".data", dataEntry, 0x1000, (byte) 0, monitor, false);
memory.setBytes(dataEntry, "Hello, World!\n".getBytes(UTF8));
/*
* Because "pointer" is a built-in type, and the emulator does not modify the
* program, we must ensure it has been resolved on the program's data type manager.
*/
program.getDataTypeManager()
.resolve(PointerDataType.dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
/*
* We must also populate the system call numbering map. Ordinarily, this would be done
* using the system call analyzer or another script. Here, we'll just fake it out.
*/
AddressSpace other =
program.getAddressFactory().getAddressSpace(SpaceNames.OTHER_SPACE_NAME);
MemoryBlock blockSyscall = program.getMemory()
.createUninitializedBlock(EmuSyscallLibrary.SYSCALL_SPACE_NAME,
other.getAddress(0), 0x1000, true);
blockSyscall.setPermissions(true, false, true);
AddressSpace syscall = program.getAddressFactory()
.getAddressSpace(EmuSyscallLibrary.SYSCALL_SPACE_NAME);
/*
* The system call names must match those from the EmuSyscall annotations in the
* system call library, in our case from DemoSyscallLibrary. Because the x64
* compiler specs define a "syscall" convention, we'll apply it. The syscall
* dispatcher will use that convention to fetch the parameters out of the machine
* state, pass them into the system call defintion, and store the result back into
* the machine.
*/
// Map system call 0 to "write"
program.getFunctionManager()
.createFunction("write", syscall.getAddress(0),
new AddressSet(syscall.getAddress(0)), SourceType.USER_DEFINED)
.setCallingConvention(EmuSyscallLibrary.SYSCALL_CONVENTION_NAME);
// Map system call 1 to "console"
program.getFunctionManager()
.createFunction("console", syscall.getAddress(1),
new AddressSet(syscall.getAddress(1)), SourceType.USER_DEFINED)
.setCallingConvention(EmuSyscallLibrary.SYSCALL_CONVENTION_NAME);
}
/*
* Create an emulator and start a thread
*/
PcodeEmulator emulator = new PcodeEmulator(language) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new DemoSyscallLibrary(this, program,
StandAloneSyscallEmuExampleScript.this);
}
// Uncomment this to see instructions printed as they are decoded
/*
@Override
protected BytesPcodeThread createThread(String name) {
return new BytesPcodeThread(name, this) {
@Override
protected SleighInstructionDecoder createInstructionDecoder(
PcodeExecutorState<byte[]> sharedState) {
return new SleighInstructionDecoder(language, sharedState) {
@Override
public Instruction decodeInstruction(Address address,
RegisterValue context) {
Instruction instruction = super.decodeInstruction(address, context);
println("Decoded " + address + ": " + instruction);
return instruction;
}
};
}
};
}
*/
};
PcodeThread<byte[]> thread = emulator.newThread();
// The emulator composes the full library for each thread
PcodeUseropLibrary<byte[]> library = thread.getUseropLibrary();
/*
* The library has a reference to the program and uses it to derive types and the system
* call numbering. However, the emulator itself does not have access to the program. If we
* followed the pattern in DebuggerEmuExampleScript, the emulator would have its state bound
* (indirectly) to the program. We'll need to copy the bytes in. Because we created blocks
* that were 0x1000 bytes in size, we can be fast and loose with our buffer. Ordinarily, you
* may want to copy in chunks rather than taking entire memory blocks at a time.
*/
byte[] data = new byte[0x1000];
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (!block.isInitialized()) {
continue; // Skip the syscall/OTHER block
}
Address addr = block.getStart();
block.getBytes(addr, data);
emulator.getSharedState()
.setVar(addr.getAddressSpace(), addr.getOffset(), data.length, true, data);
}
/*
* Initialize the thread
*/
PcodeProgram init = SleighProgramCompiler.compileProgram(language, "init", List.of(
"RIP = 0x" + entry + ";",
"RSP = 0x00001000;"),
library);
thread.getExecutor().execute(init, library);
thread.overrideContextWithDefault();
thread.reInitialize();
/*
* Run the experiment: This should interrupt on the second SYSCALL, because we didn't
* provide a system call name in OTHER space for 20.
*/
try {
thread.stepInstruction(10);
printerr("We should not have completed 10 steps!");
}
catch (EmuInvalidSystemCallException e) {
println("Terminated via invalid syscall. Good.");
}
/*
* Inspect the machine. You can always do this by accessing the state directly, but for
* anything other than simple variables, you may find compiling an expression more
* convenient.
*/
println("RDI = " +
Utils.bytesToLong(thread.getState().getVar(language.getRegister("RDI")), 8,
language.isBigEndian()));
println("RDI = " + Utils.bytesToLong(
SleighProgramCompiler.compileExpression(language, "RDI")
.evaluate(thread.getExecutor()),
8, language.isBigEndian()));
println("RDI+4 = " +
Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RDI+4")
.evaluate(thread.getExecutor()),
8, language.isBigEndian()));
}
finally {
if (program != null) {
program.release(this);
}
}
}
}

View file

@ -88,11 +88,11 @@ public abstract class DebuggerGoToTrait {
if (space == null) {
throw new IllegalArgumentException("No such address space: " + spaceName);
}
SleighExpression expr = SleighProgramCompiler.compileExpression(slang, expression);
PcodeExpression expr = SleighProgramCompiler.compileExpression(slang, expression);
return goToSleigh(space, expr);
}
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, SleighExpression expression) {
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, PcodeExpression expression) {
AsyncPcodeExecutor<byte[]> executor = TracePcodeUtils.executorForCoordinates(current);
CompletableFuture<byte[]> result = expression.evaluate(executor);
return result.thenApply(offset -> {

View file

@ -58,7 +58,7 @@ public class WatchRow {
private String typePath;
private DataType dataType;
private SleighExpression compiled;
private PcodeExpression compiled;
private TraceMemoryState state;
private Address address;
private AddressSet reads;
@ -208,7 +208,7 @@ public class WatchRow {
@Override
public PcodeFrame execute(PcodeProgram program,
SleighUseropLibrary<Pair<byte[], Address>> library) {
PcodeUseropLibrary<Pair<byte[], Address>> library) {
depsState.reset();
return super.execute(program, library);
}

View file

@ -36,8 +36,8 @@ public abstract class AbstractReadsTargetPcodeExecutorState
abstract class AbstractReadsTargetCachedSpace extends CachedSpace {
public AbstractReadsTargetCachedSpace(Language language, AddressSpace space,
TraceMemorySpace source, long snap) {
super(language, space, source, snap);
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
protected abstract void fillUninitialized(AddressSet uninitialized);
@ -47,15 +47,15 @@ public abstract class AbstractReadsTargetPcodeExecutorState
}
protected AddressSet computeUnknown(AddressSet uninitialized) {
return uninitialized.subtract(source.getAddressesWithState(snap, uninitialized,
return uninitialized.subtract(backing.getAddressesWithState(snap, uninitialized,
s -> s != null && s != TraceMemoryState.UNKNOWN));
}
@Override
public byte[] read(long offset, int size) {
if (source != null) {
if (backing != null) {
AddressSet uninitialized =
addrSet(cache.getUninitialized(offset, offset + size - 1));
addrSet(bytes.getUninitialized(offset, offset + size - 1));
if (uninitialized.isEmpty()) {
return super.read(offset, size);
}
@ -63,7 +63,7 @@ public abstract class AbstractReadsTargetPcodeExecutorState
fillUninitialized(uninitialized);
AddressSet unknown =
computeUnknown(addrSet(cache.getUninitialized(offset, offset + size - 1)));
computeUnknown(addrSet(bytes.getUninitialized(offset, offset + size - 1)));
if (!unknown.isEmpty()) {
warnUnknown(unknown);
}

View file

@ -40,8 +40,8 @@ public class ReadsTargetMemoryPcodeExecutorState
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
TraceMemorySpace source, long snap) {
super(language, space, source, snap);
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
@Override
@ -108,7 +108,7 @@ public class ReadsTargetMemoryPcodeExecutorState
" bytes");
}
// write(lower - shift, data, 0 ,read);
cache.putData(lower - shift, data, 0, read);
bytes.putData(lower - shift, data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);

View file

@ -28,7 +28,7 @@ import ghidra.program.model.pcode.Varnode;
* An executor which can perform (some of) its work asynchronously
*
* <p>
* Note that a future returned from, e.g., {@link #executeAsync(SleighProgram, SleighUseropLibrary)}
* Note that a future returned from, e.g., {@link #executeAsync(SleighProgram, PcodeUseropLibrary)}
* may complete before the computation has actually been performed. They complete when all of the
* operations have been scheduled, and the last future has been written into the state. (This
* typically happens when any branch conditions have completed). Instead, a caller should read from
@ -46,7 +46,7 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
}
public CompletableFuture<Void> stepOpAsync(PcodeOp op, PcodeFrame frame,
SleighUseropLibrary<CompletableFuture<T>> library) {
PcodeUseropLibrary<CompletableFuture<T>> library) {
if (op.getOpcode() == PcodeOp.CBRANCH) {
return executeConditionalBranchAsync(op, frame);
}
@ -55,7 +55,7 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
}
public CompletableFuture<Void> stepAsync(PcodeFrame frame,
SleighUseropLibrary<CompletableFuture<T>> library) {
PcodeUseropLibrary<CompletableFuture<T>> library) {
try {
return stepOpAsync(frame.nextOp(), frame, library);
}
@ -80,12 +80,12 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
}
public CompletableFuture<Void> executeAsync(PcodeProgram program,
SleighUseropLibrary<CompletableFuture<T>> library) {
PcodeUseropLibrary<CompletableFuture<T>> library) {
return executeAsync(program.code, program.useropNames, library);
}
protected CompletableFuture<Void> executeAsyncLoop(PcodeFrame frame,
SleighUseropLibrary<CompletableFuture<T>> library) {
PcodeUseropLibrary<CompletableFuture<T>> library) {
if (frame.isFinished()) {
return AsyncUtils.NIL;
}
@ -94,7 +94,7 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
}
public CompletableFuture<Void> executeAsync(List<PcodeOp> code,
Map<Integer, String> useropNames, SleighUseropLibrary<CompletableFuture<T>> library) {
Map<Integer, String> useropNames, PcodeUseropLibrary<CompletableFuture<T>> library) {
PcodeFrame frame = new PcodeFrame(language, code, useropNames);
return executeAsyncLoop(frame, library);
}

View file

@ -83,4 +83,9 @@ public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<Completab
}
return arithmetic.toConcrete(cond.getNow(null), isContextreg);
}
@Override
public CompletableFuture<T> sizeOf(CompletableFuture<T> value) {
return value.thenApply(v -> arithmetic.sizeOf(v));
}
}

View file

@ -169,7 +169,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
protected List<PcodeRow> format(List<String> sleigh) {
SleighLanguage language = (SleighLanguage) getToyBE64Language();
PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", sleigh,
SleighUseropLibrary.nil());
PcodeUseropLibrary.nil());
PcodeExecutor<byte[]> executor =
new PcodeExecutor<>(language, PcodeArithmetic.BYTES_BE, null);
PcodeFrame frame = executor.begin(prog);

View file

@ -55,7 +55,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
Trace trace = recorder.getTrace();
SleighLanguage language = (SleighLanguage) trace.getBaseLanguage();
SleighExpression expr = SleighProgramCompiler
PcodeExpression expr = SleighProgramCompiler
.compileExpression(language, "r0 + r1");
Register r0 = language.getRegister("r0");
@ -99,7 +99,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
SleighLanguage language = (SleighLanguage) trace.getBaseLanguage();
PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test",
List.of("r2 = r0 + r1;"), SleighUseropLibrary.NIL);
List.of("r2 = r0 + r1;"), PcodeUseropLibrary.NIL);
Register r0 = language.getRegister("r0");
Register r1 = language.getRegister("r1");
@ -119,7 +119,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
AsyncPcodeExecutor<byte[]> executor = new AsyncPcodeExecutor<>(
language, AsyncWrappedPcodeArithmetic.forLanguage(language), asyncState);
waitOn(executor.executeAsync(prog, SleighUseropLibrary.nil()));
waitOn(executor.executeAsync(prog, PcodeUseropLibrary.nil()));
waitOn(asyncState.getVar(language.getRegister("r2")));
assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2")));

View file

@ -36,16 +36,12 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
@Override
public byte[] read(long offset, int size) {
RangeSet<UnsignedLong> uninitialized =
cache.getUninitialized(offset, offset + size - 1);
bytes.getUninitialized(offset, offset + size - 1);
if (!uninitialized.isEmpty()) {
size = checkUninitialized(source, space.getAddress(offset), size,
size = checkUninitialized(backing, space.getAddress(offset), size,
addrSet(uninitialized));
if (source != null) {
readUninitializedFromSource(uninitialized);
}
}
return readCached(offset, size);
return super.read(offset, size);
}
}
@ -55,10 +51,10 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState
}
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace source, long snap) {
return new CheckedCachedSpace(language, space, source, snap);
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CheckedCachedSpace(language, space, backing, snap);
}
protected abstract int checkUninitialized(TraceMemorySpace source, Address start, int size,
protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size,
AddressSet uninitialized);
}

View file

@ -39,16 +39,16 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState
}
@Override
protected int checkUninitialized(TraceMemorySpace source, Address start, int size,
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size,
AddressSet uninitialized) {
if (source == null) {
if (backing == null) {
if (!uninitialized.contains(start)) {
return (int) uninitialized.getMinAddress().subtract(start);
}
throw excFor(uninitialized);
}
// TODO: Could find first instead?
AddressSetView unknown = uninitialized.subtract(getKnown(source));
AddressSetView unknown = uninitialized.subtract(getKnown(backing));
if (unknown.isEmpty()) {
return size;
}

View file

@ -16,27 +16,20 @@
package ghidra.pcode.exec.trace;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorState;
import ghidra.pcode.exec.BytesPcodeArithmetic;
import ghidra.pcode.exec.AbstractBytesPcodeExecutorState;
import ghidra.pcode.exec.BytesPcodeExecutorStateSpace;
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.mem.Memory;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.MemBufferAdapter;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
/**
* A state which reads bytes from a trace, but caches writes internally.
@ -47,41 +40,7 @@ import ghidra.util.Msg;
* later time.
*/
public class TraceCachedWriteBytesPcodeExecutorState
extends AbstractLongOffsetPcodeExecutorState<byte[], CachedSpace> {
protected class StateMemBuffer implements MemBufferAdapter {
protected final Address address;
protected final CachedSpace source;
public StateMemBuffer(Address address, CachedSpace source) {
this.address = address;
this.source = source;
}
@Override
public Address getAddress() {
return address;
}
@Override
public Memory getMemory() {
return null;
}
@Override
public boolean isBigEndian() {
return trace.getBaseLanguage().isBigEndian();
}
@Override
public int getBytes(ByteBuffer buffer, int addressOffset) {
byte[] data = source.read(address.getOffset() + addressOffset, buffer.remaining());
buffer.put(data);
return data.length;
}
}
protected final Map<AddressSpace, CachedSpace> spaces = new HashMap<>();
extends AbstractBytesPcodeExecutorState<TraceMemorySpace, CachedSpace> {
protected final Trace trace;
protected final long snap;
@ -90,136 +49,53 @@ public class TraceCachedWriteBytesPcodeExecutorState
public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
super(trace.getBaseLanguage());
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
}
protected static class CachedSpace {
protected final SemisparseByteArray cache = new SemisparseByteArray();
public static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
protected final RangeSet<UnsignedLong> written = TreeRangeSet.create();
protected final Language language; // For logging diagnostic
protected final AddressSpace space;
protected final TraceMemorySpace source;
protected final long snap;
public CachedSpace(Language language, AddressSpace space, TraceMemorySpace source,
public CachedSpace(Language language, AddressSpace space, TraceMemorySpace backing,
long snap) {
this.language = language;
this.space = space;
this.source = source;
super(language, space, backing);
this.snap = snap;
}
public void write(long offset, byte[] buffer, int srcOffset, int length) {
cache.putData(offset, buffer, srcOffset, length);
@Override
public void write(long offset, byte[] val, int srcOffset, int length) {
super.write(offset, val, srcOffset, length);
UnsignedLong uLoc = UnsignedLong.fromLongBits(offset);
UnsignedLong uEnd = UnsignedLong.fromLongBits(offset + length);
written.add(Range.closedOpen(uLoc, uEnd));
}
public static long lower(Range<UnsignedLong> rng) {
return rng.lowerBoundType() == BoundType.CLOSED
? rng.lowerEndpoint().longValue()
: rng.lowerEndpoint().longValue() + 1;
}
public static long upper(Range<UnsignedLong> rng) {
return rng.upperBoundType() == BoundType.CLOSED
? rng.upperEndpoint().longValue()
: rng.upperEndpoint().longValue() - 1;
}
protected void readUninitializedFromSource(RangeSet<UnsignedLong> uninitialized) {
@Override
protected void readUninitializedFromBacking(RangeSet<UnsignedLong> uninitialized) {
if (!uninitialized.isEmpty()) {
// TODO: Warn or bail when reading UNKNOWN bytes
// NOTE: Read without regard to gaps
// NOTE: Cannot write those gaps, though!!!
Range<UnsignedLong> toRead = uninitialized.span();
assert toRead.hasUpperBound() && toRead.hasLowerBound();
long lower = lower(toRead);
long upper = upper(toRead);
ByteBuffer buf = ByteBuffer.allocate((int) (upper - lower + 1));
source.getBytes(snap, space.getAddress(lower), buf);
backing.getBytes(snap, space.getAddress(lower), buf);
for (Range<UnsignedLong> rng : uninitialized.asRanges()) {
long l = lower(rng);
long u = upper(rng);
cache.putData(l, buf.array(), (int) (l - lower), (int) (u - l + 1));
bytes.putData(l, buf.array(), (int) (l - lower), (int) (u - l + 1));
}
}
}
protected byte[] readCached(long offset, int size) {
byte[] data = new byte[size];
cache.getData(offset, data);
return data;
}
protected AddressRange addrRng(Range<UnsignedLong> rng) {
Address start = space.getAddress(lower(rng));
Address end = space.getAddress(upper(rng));
return new AddressRangeImpl(start, end);
}
protected AddressSet addrSet(RangeSet<UnsignedLong> set) {
AddressSet result = new AddressSet();
for (Range<UnsignedLong> rng : set.asRanges()) {
result.add(addrRng(rng));
}
return result;
}
protected Set<Register> getRegs(AddressSet set) {
Set<Register> regs = new TreeSet<>();
for (AddressRange rng : set) {
Register r = language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (r != null) {
regs.add(r);
}
else {
regs.addAll(Arrays.asList(language.getRegisters(rng.getMinAddress())));
}
}
return regs;
}
protected void warnState(AddressSet set, String message) {
Set<Register> regs = getRegs(set);
if (regs.isEmpty()) {
Msg.warn(this, message + ": " + set);
}
else {
Msg.warn(this, message + ": " + set + " (registers " + regs + ")");
}
}
protected void warnUninit(RangeSet<UnsignedLong> uninit) {
AddressSet uninitialized = addrSet(uninit);
Set<Register> regs = getRegs(uninitialized);
if (regs.isEmpty()) {
Msg.warn(this, "Emulator read from uninitialized state: " + uninit);
}
Msg.warn(this, "Emulator read from uninitialized state: " + uninit +
" (includes registers: " + regs + ")");
}
protected void warnUnknown(AddressSet unknown) {
Set<Register> regs = getRegs(unknown);
Msg.warn(this, "Emulator state initialized from UNKNOWN: " + unknown +
"(includes registers: " + regs + ")");
}
public byte[] read(long offset, int size) {
if (source != null) {
// TODO: Warn or bail when reading UNKNOWN bytes
// NOTE: Read without regard to gaps
// NOTE: Cannot write those gaps, though!!!
readUninitializedFromSource(cache.getUninitialized(offset, offset + size - 1));
}
RangeSet<UnsignedLong> stillUninit = cache.getUninitialized(offset, offset + size - 1);
if (!stillUninit.isEmpty()) {
warnUninit(stillUninit);
}
return readCached(offset, size);
warnAddressSet("Emulator state initialized from UNKNOWN", unknown);
}
// Must already have started a transaction
@ -238,7 +114,7 @@ public class TraceCachedWriteBytesPcodeExecutorState
long fullLen = range.upperEndpoint().longValue() - lower;
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
cache.getData(lower, data, 0, len);
bytes.getData(lower, data, 0, len);
buf.position(0);
buf.limit(len);
mem.putBytes(snap, space.getAddress(lower), buf);
@ -288,47 +164,12 @@ public class TraceCachedWriteBytesPcodeExecutorState
}
@Override
protected long offsetToLong(byte[] offset) {
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
protected TraceMemorySpace getBacking(AddressSpace space) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
}
@Override
public byte[] longToOffset(AddressSpace space, long l) {
return arithmetic.fromConst(l, space.getPointerSize());
}
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace source, long snap) {
return new CachedSpace(language, space, source, snap);
}
@Override
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space, s -> {
TraceMemorySpace tms = s.isUniqueSpace() ? null
: TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, false);
return newSpace(s, tms, snap);
});
}
@Override
protected void setInSpace(CachedSpace space, long offset, int size, byte[] val) {
assert size == val.length;
space.write(offset, val, 0, val.length);
}
@Override
protected byte[] getFromSpace(CachedSpace space, long offset, int size) {
byte[] read = space.read(offset, size);
if (read.length != size) {
Address addr = space.space.getAddress(offset);
throw new UnknownStatePcodeExecutionException("Incomplete read (" + read.length +
" of " + size + " bytes)", language, addr.add(read.length), size - read.length);
}
return read;
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CachedSpace(language, space, backing, snap);
}
}

View file

@ -59,4 +59,9 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
public BigInteger toConcrete(TraceMemoryState value, boolean isContextreg) {
throw new AssertionError("Cannot make TraceMemoryState a 'concrete value'");
}
@Override
public TraceMemoryState sizeOf(TraceMemoryState value) {
throw new AssertionError("Cannot get size of a TraceMemoryState");
}
}

View file

@ -18,10 +18,9 @@ package ghidra.pcode.exec.trace;
import com.google.common.collect.Range;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.AbstractPcodeEmulator;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.SleighUseropLibrary;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.stack.TraceStack;
@ -31,7 +30,7 @@ import ghidra.trace.model.thread.TraceThreadManager;
/**
* An emulator that can read initial state from a trace
*/
public class TracePcodeEmulator extends AbstractPcodeEmulator {
public class TracePcodeEmulator extends PcodeEmulator {
private static SleighLanguage assertSleigh(Language language) {
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Emulation requires a sleigh language");
@ -42,16 +41,12 @@ public class TracePcodeEmulator extends AbstractPcodeEmulator {
protected final Trace trace;
protected final long snap;
public TracePcodeEmulator(Trace trace, long snap, SleighUseropLibrary<byte[]> library) {
super(assertSleigh(trace.getBaseLanguage()), library);
public TracePcodeEmulator(Trace trace, long snap) {
super(assertSleigh(trace.getBaseLanguage()));
this.trace = trace;
this.snap = snap;
}
public TracePcodeEmulator(Trace trace, long snap) {
this(trace, snap, SleighUseropLibrary.nil());
}
protected PcodeExecutorState<byte[]> newState(TraceThread thread) {
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0);
}

View file

@ -73,7 +73,7 @@ public enum TraceSleighUtils {
paired);
}
public static byte[] evaluateBytes(SleighExpression expr, Trace trace, long snap,
public static byte[] evaluateBytes(PcodeExpression expr, Trace trace, long snap,
TraceThread thread, int frame) {
SleighLanguage language = expr.getLanguage();
if (trace.getBaseLanguage() != language) {
@ -84,14 +84,14 @@ public enum TraceSleighUtils {
return expr.evaluate(executor);
}
public static BigInteger evaluate(SleighExpression expr, Trace trace, long snap,
public static BigInteger evaluate(PcodeExpression expr, Trace trace, long snap,
TraceThread thread, int frame) {
byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame);
return Utils.bytesToBigInteger(bytes, bytes.length, expr.getLanguage().isBigEndian(),
false);
}
public static Pair<byte[], TraceMemoryState> evaluateBytesWithState(SleighExpression expr,
public static Pair<byte[], TraceMemoryState> evaluateBytesWithState(PcodeExpression expr,
Trace trace, long snap, TraceThread thread, int frame) {
SleighLanguage language = expr.getLanguage();
if (trace.getBaseLanguage() != language) {
@ -104,7 +104,7 @@ public enum TraceSleighUtils {
return expr.evaluate(executor);
}
public static Pair<BigInteger, TraceMemoryState> evaluateWithState(SleighExpression expr,
public static Pair<BigInteger, TraceMemoryState> evaluateWithState(PcodeExpression expr,
Trace trace, long snap, TraceThread thread, int frame) {
Pair<byte[], TraceMemoryState> bytesPair =
evaluateBytesWithState(expr, trace, snap, thread, frame);

View file

@ -28,8 +28,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.*;
import ghidra.program.model.symbol.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.symbol.DBTraceReference;
@ -40,7 +39,6 @@ import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.symbol.TraceReference;
import ghidra.trace.model.symbol.TraceSymbol;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.MemBufferAdapter;
import ghidra.util.LockHold;
import ghidra.util.Saveable;
import ghidra.util.exception.NoValueException;

View file

@ -20,8 +20,8 @@ import java.nio.ByteOrder;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.mem.MemBufferAdapter;
import ghidra.program.model.mem.Memory;
import ghidra.trace.util.MemBufferAdapter;
public class DBTraceMemBuffer implements MemBufferAdapter {
private final DBTraceMemorySpace space;

View file

@ -370,7 +370,7 @@ public class PatchStep implements Step {
protected Map<AddressSpace, SemisparseByteArray> getPatches(Language language) {
PcodeProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language,
"schedule", List.of(sleigh + ";"), SleighUseropLibrary.nil());
"schedule", List.of(sleigh + ";"), PcodeUseropLibrary.nil());
// SemisparseArray is a bit overkill, no?
Map<AddressSpace, SemisparseByteArray> result = new TreeMap<>();
for (PcodeOp op : prog.getCode()) {

View file

@ -94,8 +94,8 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0);
PcodeProgram initProg = SleighProgramCompiler.compileProgram(
(SleighLanguage) tb.language, "test", stateInit,
SleighUseropLibrary.nil());
exec.execute(initProg, SleighUseropLibrary.nil());
PcodeUseropLibrary.nil());
exec.execute(initProg, PcodeUseropLibrary.nil());
}
return thread;
}
@ -476,13 +476,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
public void testInject() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
final StringBuilder dumped = new StringBuilder();
SleighUseropLibrary<byte[]> library = new AnnotatedSleighUseropLibrary<byte[]>() {
PcodeUseropLibrary<byte[]> hexLib = new AnnotatedPcodeUseropLibrary<byte[]>() {
@Override
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@SleighUserop
@PcodeUserop
public void hexdump(byte[] in) {
dumped.append(NumericUtilities.convertBytesToString(in));
}
@ -495,7 +495,12 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
"PUSH 0xdeadbeef",
"PUSH 0xbaadf00d"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0, library);
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return hexLib;
}
};
emu.inject(tb.addr(0x00400006), List.of("hexdump(RSP);"));
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
emuThread.overrideContextWithDefault();
@ -519,13 +524,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
public void testInjectedInterrupt() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
final StringBuilder dumped = new StringBuilder();
SleighUseropLibrary<byte[]> library = new AnnotatedSleighUseropLibrary<byte[]>() {
PcodeUseropLibrary<byte[]> hexLib = new AnnotatedPcodeUseropLibrary<byte[]>() {
@Override
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@SleighUserop
@PcodeUserop
public void hexdump(byte[] in) {
dumped.append(NumericUtilities.convertBytesToString(in));
}
@ -538,7 +543,12 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
"PUSH 0xdeadbeef",
"PUSH 0xbaadf00d"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0, library);
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return hexLib;
}
};
emu.inject(tb.addr(0x00400006), List.of(
"hexdump(RSP);",
"emu_swi();",

View file

@ -214,7 +214,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest
"<else>",
" r1 = 7;",
"<done>"),
SleighUseropLibrary.NIL);
PcodeUseropLibrary.NIL);
TraceThread thread;
try (UndoableTransaction tid = b.startTransaction()) {
thread = b.getOrAddThread("Thread1", 0);
@ -222,7 +222,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest
new PcodeExecutor<>(sp.getLanguage(),
BytesPcodeArithmetic.forLanguage(b.language),
new TraceBytesPcodeExecutorState(b.trace, 0, thread, 0));
sp.execute(executor, SleighUseropLibrary.nil());
sp.execute(executor, PcodeUseropLibrary.nil());
}
Register r1 = b.language.getRegister("r1");

View file

@ -85,9 +85,9 @@ public class ToyDBTraceBuilder implements AutoCloseable {
public void exec(long snap, int frame, TraceThread thread, List<String> sleigh) {
PcodeProgram program = SleighProgramCompiler.compileProgram((SleighLanguage) language,
"builder", sleigh, SleighUseropLibrary.nil());
"builder", sleigh, PcodeUseropLibrary.nil());
TraceSleighUtils.buildByteExecutor(trace, snap, thread, frame)
.execute(program, SleighUseropLibrary.nil());
.execute(program, PcodeUseropLibrary.nil());
}
public Address addr(AddressSpace space, long offset) {

View file

@ -367,7 +367,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
@Override
public PcodeExecutor<Void> getExecutor() {
return new PcodeExecutor<>(TOY_BE_64_LANG, machine.getArithmetic(), getState()) {
public PcodeFrame execute(PcodeProgram program, SleighUseropLibrary<Void> library) {
public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary<Void> library) {
machine.record.add("x:" + name);
// TODO: Verify the actual effect
return null; //super.execute(program, library);
@ -376,7 +376,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
}
@Override
public SleighUseropLibrary<Void> getUseropLibrary() {
public PcodeUseropLibrary<Void> getUseropLibrary() {
return null;
}
@ -402,7 +402,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
protected final List<String> record = new ArrayList<>();
public TestMachine() {
super(TOY_BE_64_LANG, null, null);
super(TOY_BE_64_LANG, null);
}
@Override
@ -419,6 +419,11 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
protected PcodeExecutorState<Void> createLocalState(PcodeThread<Void> thread) {
return null;
}
@Override
protected PcodeUseropLibrary<Void> createUseropLibrary() {
return PcodeUseropLibrary.nil();
}
}
@Test

View file

@ -17,6 +17,7 @@ package ghidra.pcode.emu;
import java.lang.reflect.Constructor;
import ghidra.app.emulator.Emulator;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emulate.*;
import ghidra.pcode.exec.*;
@ -29,9 +30,23 @@ import ghidra.util.Msg;
/**
* A p-code thread which incorporates per-architecture state modifiers on concrete bytes
*
* <p>
* For a complete example of a p-code emulator, see {@link PcodeEmulator}.
*
* <p>
* TODO: "State modifiers" are a feature of the older {@link Emulator}. They are crudely
* incorporated into threads extended from this abstract class, so that they do not yet need to be
* ported to this emulator.
*/
public abstract class AbstractModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
/**
* Glue for incorporating state modifiers
*
* <p>
* This allows the modifiers to change the context and counter of the thread.
*/
protected class GlueEmulate extends Emulate {
public GlueEmulate(SleighLanguage lang, MemoryState s, BreakTable b) {
super(lang, s, b);
@ -63,6 +78,12 @@ public abstract class AbstractModifiedPcodeThread<T> extends DefaultPcodeThread<
}
}
/**
* Glue for incorporating state modifiers
*
* <p>
* This allows the modifiers to access the thread's state (memory and registers).
*/
protected class GlueMemoryState extends MemoryState {
public GlueMemoryState(Language language) {
super(language);
@ -85,6 +106,12 @@ public abstract class AbstractModifiedPcodeThread<T> extends DefaultPcodeThread<
}
}
/**
* Glue for incorporating state modifiers
*
* <p>
* This allows the modifiers to provider userop definitions.
*/
protected class GluePcodeThreadExecutor extends PcodeThreadExecutor {
public GluePcodeThreadExecutor(SleighLanguage language, PcodeArithmetic<T> arithmetic,
PcodeExecutorStatePiece<T, T> state) {
@ -93,7 +120,7 @@ public abstract class AbstractModifiedPcodeThread<T> extends DefaultPcodeThread<
@Override
public void executeCallother(PcodeOp op, PcodeFrame frame,
SleighUseropLibrary<T> library) {
PcodeUseropLibrary<T> library) {
// Prefer one in the library. Fall-back to state modifier's impl
try {
super.executeCallother(op, frame, library);
@ -112,12 +139,19 @@ public abstract class AbstractModifiedPcodeThread<T> extends DefaultPcodeThread<
protected Address savedCounter;
/**
* Construct a new thread with the given name belonging to the given machine
*
* @see PcodeMachine#newThread(String)
* @param name the name of the new thread
* @param machine the machine to which the new thread belongs
*/
public AbstractModifiedPcodeThread(String name, AbstractPcodeMachine<T> machine) {
super(name, machine);
/**
* These two exist as a way to integrate the language-specific injects that are already
* written for the established concrete emulator.
* written for {@link Emulator}.
*/
emulate = new GlueEmulate(language, new GlueMemoryState(language),
new BreakTableCallBack(language));
@ -162,7 +196,7 @@ public abstract class AbstractModifiedPcodeThread<T> extends DefaultPcodeThread<
}
/**
* Called by the legacy state modifier to retrieve concrete bytes from the thread's state
* Called by a state modifier to read concrete bytes from the thread's state
*
* @see {@link MemoryState#getChunk(byte[], AddressSpace, long, int, boolean)}
*/
@ -170,7 +204,7 @@ public abstract class AbstractModifiedPcodeThread<T> extends DefaultPcodeThread<
boolean stopOnUnintialized);
/**
* Called by the legacy state modifier to set concrete bytes in the thread's state
* Called by a state modifier to write concrete bytes to the thread's state
*
* @see {@link MemoryState#setChunk(byte[], AddressSpace, long, int)}
*/

View file

@ -1,35 +0,0 @@
/* ###
* 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.emu;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.BytesPcodeArithmetic;
import ghidra.pcode.exec.SleighUseropLibrary;
/**
* A p-code machine which executes on concrete bytes and incorporates per-architecture state
* modifiers
*/
public abstract class AbstractPcodeEmulator extends AbstractPcodeMachine<byte[]> {
public AbstractPcodeEmulator(SleighLanguage language, SleighUseropLibrary<byte[]> library) {
super(language, BytesPcodeArithmetic.forLanguage(language), library);
}
@Override
protected BytesPcodeThread createThread(String name) {
return new BytesPcodeThread(name, this);
}
}

View file

@ -25,13 +25,16 @@ import ghidra.util.classfinder.ClassSearcher;
/**
* An abstract implementation of {@link PcodeMachine} suitable as a base for most implementations
*
* <p>
* For a complete example of a p-code emulator, see {@link PcodeEmulator}.
*/
public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
protected final SleighLanguage language;
protected final PcodeArithmetic<T> arithmetic;
protected final SleighUseropLibrary<T> library;
protected final PcodeUseropLibrary<T> library;
protected final SleighUseropLibrary<T> stubLibrary;
protected final PcodeUseropLibrary<T> stubLibrary;
/* for abstract thread access */ PcodeStateInitializer initializer;
private PcodeExecutorState<T> sharedState;
@ -41,12 +44,17 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
protected final Map<Address, PcodeProgram> injects = new HashMap<>();
public AbstractPcodeMachine(SleighLanguage language, PcodeArithmetic<T> arithmetic,
SleighUseropLibrary<T> library) {
/**
* Construct a p-code machine with the given language and arithmetic
*
* @param language the processor language to be emulated
* @param arithmetic the definition of arithmetic p-code ops to be used in emulation
*/
public AbstractPcodeMachine(SleighLanguage language, PcodeArithmetic<T> arithmetic) {
this.language = language;
this.arithmetic = arithmetic;
this.library = library;
this.library = createUseropLibrary();
this.stubLibrary = createThreadStubLibrary().compose(library);
/**
@ -57,6 +65,13 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
this.initializer = getPluggableInitializer(language);
}
/**
* A factory method to create the userop library shared by all threads in this machine
*
* @return the library
*/
protected abstract PcodeUseropLibrary<T> createUseropLibrary();
@Override
public SleighLanguage getLanguage() {
return language;
@ -68,26 +83,50 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
}
@Override
public SleighUseropLibrary<T> getUseropLibrary() {
public PcodeUseropLibrary<T> getUseropLibrary() {
return library;
}
@Override
public SleighUseropLibrary<T> getStubUseropLibrary() {
public PcodeUseropLibrary<T> getStubUseropLibrary() {
return stubLibrary;
}
/**
* A factory method to create the (memory) state shared by all threads in this machine
*
* @return the shared state
*/
protected abstract PcodeExecutorState<T> createSharedState();
/**
* A factory method to create the (register) state local to the given thread
*
* @param thread the thread
* @return the thread-local state
*/
protected abstract PcodeExecutorState<T> createLocalState(PcodeThread<T> thread);
protected SleighUseropLibrary<T> createThreadStubLibrary() {
return new DefaultPcodeThread.SleighEmulationLibrary<T>(null);
/**
* A factory method to create a stub library for compiling thread-local SLEIGH source
*
* <p>
* Because threads may introduce p-code userops using libraries unique to that thread, it
* becomes necessary to at least export stub symbols, so that p-code programs can be compiled
* from SLEIGH source before the thread has necessarily been created. A side effect of this
* strategy is that all threads, though they may have independent libraries, must export
* identically-named symbols.
*
* @return the stub library for all threads
*/
protected PcodeUseropLibrary<T> createThreadStubLibrary() {
return new DefaultPcodeThread.PcodeEmulationLibrary<T>(null);
}
/**
* Extension point to override construction of this machine's threads
* A factory method to create a new thread in this machine
*
* @see #newThread(String)
* @param name the name of the new thread
* @return the new thread
*/
@ -95,6 +134,26 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
return new DefaultPcodeThread<>(name, this);
}
/**
* Search the classpath for an applicable state initializer
*
* <p>
* If found, the initializer is executed immediately upon creating this machine's shared state
* and upon creating each thread.
*
* <p>
* TODO: This isn't really being used. At one point in development it was used to initialize
* x86's FS_OFFSET and GS_OFFSET registers. Those only exist in p-code, not the real processor,
* and replace what might have been {@code segment(FS)}. There seems more utility in detecting
* when those registers are uninitialized, requiring the user to initialize them, than it is to
* silently initialize them to 0. Unless we find utility in this, it will likely be removed in
* the near future.
*
* @see #doPluggableInitialization()
* @see DefaultPcodeThread#doPluggableInitialization()
* @param language the language requiring pluggable initialization
* @return the initializer
*/
protected static PcodeStateInitializer getPluggableInitializer(Language language) {
for (PcodeStateInitializer init : ClassSearcher.getInstances(PcodeStateInitializer.class)) {
if (init.isApplicable(language)) {
@ -104,6 +163,11 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
return null;
}
/**
* Execute the initializer upon this machine, if applicable
*
* @see #getPluggableInitializer(Language)
*/
protected void doPluggableInitialization() {
if (initializer != null) {
initializer.initializeMachine(this);
@ -148,6 +212,12 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
return sharedState;
}
/**
* Check for a p-code injection (override) at the given address
*
* @param address the address, usually the program counter
* @return the injected program, most likely {@code null}
*/
protected PcodeProgram getInject(Address address) {
return injects.get(address);
}
@ -184,7 +254,9 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
/**
* TODO: The template build idea is probably more pertinent here. If a user places a
* breakpoint with the purpose of single-stepping the p-code of that instruction, it won't
* work, because that p-code is occluded by emu_exec_decoded().
* work, because that p-code is occluded by emu_exec_decoded(). I suppose this could also be
* addressed by formalizing and better exposing the notion of p-code stacks (of p-code
* frames)
*/
PcodeProgram pcode = compileSleigh("breakpoint:" + address, List.of(
"if (!(" + sleighCondition + ")) goto <nobreak>;",

View file

@ -17,7 +17,21 @@ package ghidra.pcode.emu;
import ghidra.program.model.address.AddressSpace;
/**
* A simple p-code thread that operates on concrete bytes
*
* <p>
* For a complete example of a p-code emulator, see {@link PcodeEmulator}. This is the default
* thread for that emulator.
*/
public class BytesPcodeThread extends AbstractModifiedPcodeThread<byte[]> {
/**
* Construct a new thread
*
* @see PcodeMachine#newThread(String)
* @param name the thread's name
* @param machine the machine to which the thread belongs
*/
public BytesPcodeThread(String name, AbstractPcodeMachine<byte[]> machine) {
super(name, machine);
}

View file

@ -18,11 +18,11 @@ package ghidra.pcode.emu;
import java.math.BigInteger;
import java.util.*;
import ghidra.app.emulator.Emulator;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.util.ProgramContextImpl;
@ -30,16 +30,51 @@ import ghidra.util.Msg;
/**
* The default implementation of {@link PcodeThread} suitable for most applications
*
* <p>
* When emulating on concrete state, consider using {@link AbstractModifiedPcodeThread}, so that
* state modifiers from the older {@link Emulator} are incorporated. In either case, it may be
* worthwhile to examine existing state modifiers to ensure they are appropriately represented in
* any abstract state. It may be necessary to port them.
*
* <p>
* This class implements the control-flow logic of the target machine, cooperating with the p-code
* program flow implemented by the {@link PcodeExecutor}. This implementation exists primarily in
* {@link #beginInstructionOrInject()} and {@link #advanceAfterFinished()}.
*/
public class DefaultPcodeThread<T> implements PcodeThread<T> {
protected static class SleighEmulationLibrary<T> extends AnnotatedSleighUseropLibrary<T> {
/**
* A userop library exporting some methods for emulated thread control
*
* <p>
* TODO: Since p-code userops can now receive the executor, it may be better to receive it, cast
* it, and obtain the thread, rather than binding a library to each thread.
*
* @param <T> no particular type, except to match the thread's
*/
public static class PcodeEmulationLibrary<T> extends AnnotatedPcodeUseropLibrary<T> {
private final DefaultPcodeThread<T> thread;
public SleighEmulationLibrary(DefaultPcodeThread<T> thread) {
/**
* Construct a library to control the given thread
*
* @param thread the thread
*/
public PcodeEmulationLibrary(DefaultPcodeThread<T> thread) {
this.thread = thread;
}
@SleighUserop
/**
* Execute the actual machine instruction at the current program counter
*
* <p>
* Because "injects" override the machine instruction, injects which need to defer to the
* machine instruction must invoke this userop.
*
* @see #emu_skip_decoded()
*/
@PcodeUserop
public void emu_exec_decoded() {
/**
* TODO: This idea of "pushing" a frame could be formalized, and the full stack made
@ -53,7 +88,18 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
thread.frame = saved;
}
@SleighUserop
/**
* Advance the program counter beyond the current machine instruction
*
* <p>
* Because "injects" override the machine instruction, they must specify the effect on the
* program counter, lest the thread become caught in an infinite loop on the inject. To
* emulate fall-through without executing the machine instruction, the inject must invoke
* this userop.
*
* @see #emu_exec_decoded()
*/
@PcodeUserop
public void emu_skip_decoded() {
PcodeFrame saved = thread.frame;
thread.dropInstruction();
@ -61,22 +107,46 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
thread.frame = saved;
}
@SleighUserop
/**
* Interrupt execution
*
* <p>
* This immediately throws an {@link InterruptPcodeExecutionException}. To implement
* out-of-band breakpoints, inject an invocation of this userop at the desired address.
*
* @see PcodeMachine#addBreakpoint(Address, String)
*/
@PcodeUserop
public void emu_swi() {
throw new InterruptPcodeExecutionException(null, null);
}
}
protected class PcodeThreadExecutor extends PcodeExecutor<T> {
/**
* An executor for the p-code thread
*
* <p>
* This executor checks for thread suspension and updates the program counter register upon
* execution of (external) branches.
*/
public class PcodeThreadExecutor extends PcodeExecutor<T> {
volatile boolean suspended = false;
/**
* Construct the executor
*
* @see DefaultPcodeThread#createExecutor()
* @param language the language of the containing machine
* @param arithmetic the arithmetic of the containing machine
* @param state the composite state assigned to the thread
*/
public PcodeThreadExecutor(SleighLanguage language, PcodeArithmetic<T> arithmetic,
PcodeExecutorStatePiece<T, T> state) {
super(language, arithmetic, state);
}
@Override
public void stepOp(PcodeOp op, PcodeFrame frame, SleighUseropLibrary<T> library) {
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
if (suspended) {
throw new SuspendedPcodeExecutionException(frame, null);
}
@ -87,6 +157,10 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
protected void branchToAddress(Address target) {
overrideCounter(target);
}
public Instruction getInstruction() {
return instruction;
}
}
private final String name;
@ -95,7 +169,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
protected final PcodeArithmetic<T> arithmetic;
protected final ThreadPcodeExecutorState<T> state;
protected final InstructionDecoder decoder;
protected final SleighUseropLibrary<T> library;
protected final PcodeUseropLibrary<T> library;
protected final PcodeThreadExecutor executor;
protected final Register pc;
@ -110,6 +184,13 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
protected final ProgramContextImpl defaultContext;
protected final Map<Address, PcodeProgram> injects = new HashMap<>();
/**
* Construct a new thread
*
* @see AbstractPcodeMachine#createThread(String)
* @param name the name of the thread
* @param machine the machine containing the thread
*/
public DefaultPcodeThread(String name, AbstractPcodeMachine<T> machine) {
this.name = name;
this.machine = machine;
@ -136,14 +217,34 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
this.reInitialize();
}
/**
* A factory method for the instruction decoder
*
* @param sharedState the machine's shared (memory state)
* @return
*/
protected SleighInstructionDecoder createInstructionDecoder(PcodeExecutorState<T> sharedState) {
return new SleighInstructionDecoder(language, sharedState);
}
protected SleighUseropLibrary<T> createUseropLibrary() {
return new SleighEmulationLibrary<>(this).compose(machine.library);
/**
* A factory method to create the complete userop library for this thread
*
* <p>
* The returned library must compose the containing machine's shared userop library. See
* {@link PcodeUseropLibrary#compose(PcodeUseropLibrary)}.
*
* @return the thread's complete userop library
*/
protected PcodeUseropLibrary<T> createUseropLibrary() {
return new PcodeEmulationLibrary<>(this).compose(machine.library);
}
/**
* A factory method to create the executor for this thread
*
* @return the executor
*/
protected PcodeThreadExecutor createExecutor() {
return new PcodeThreadExecutor(language, arithmetic, state);
}
@ -203,6 +304,11 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
}
/**
* Execute the initializer upon this thread, if applicable
*
* @see AbstractPcodeMachine#getPluggableInitializer(Language)
*/
protected void doPluggableInitialization() {
if (machine.initializer != null) {
machine.initializer.initializeThread(this);
@ -258,6 +364,9 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
}
/**
* Start execution of the instruction or inject at the program counter
*/
protected void beginInstructionOrInject() {
PcodeProgram inj = getInject(counter);
if (inj != null) {
@ -271,6 +380,9 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
}
/**
* Resolve a finished instruction, advancing the program counter if necessary
*/
protected void advanceAfterFinished() {
if (instruction == null) { // Frame resulted from an inject
frame = null;
@ -297,12 +409,19 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
return instruction;
}
/**
* A sanity-checking measure: Cannot start a new instruction while one is still being executed
*/
protected void assertCompletedInstruction() {
if (frame != null) {
throw new IllegalStateException("The current instruction or inject has not finished.");
}
}
/**
* A sanity-checking measure: Cannot finish an instruction unless one is currently being
* executed
*/
protected void assertMidInstruction() {
if (frame == null) {
throw new IllegalStateException("There is no current instruction to finish.");
@ -311,6 +430,10 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* An extension point for hooking instruction execution before the fact
*
* <p>
* This is currently used for incorporating state modifiers from the older {@link Emulator}
* framework. There is likely utility here when porting those to this framework.
*/
protected void preExecuteInstruction() {
// Extension point
@ -318,6 +441,10 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* An extension point for hooking instruction execution after the fact
*
* <p>
* This is currently used for incorporating state modifiers from the older {@link Emulator}
* framework. There is likely utility here when porting those to this framework.
*/
protected void postExecuteInstruction() {
// Extension point
@ -380,7 +507,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
}
@Override
public SleighUseropLibrary<T> getUseropLibrary() {
public PcodeUseropLibrary<T> getUseropLibrary() {
return library;
}
@ -389,6 +516,15 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
return state;
}
/**
* Check for a p-code injection (override) at the given address
*
* <p>
* This checks this thread's particular injects and then defers to the machine's injects.
*
* @param address the address, usually the program counter
* @return the injected program, most likely {@code null}
*/
protected PcodeProgram getInject(Address address) {
PcodeProgram inj = injects.get(address);
if (inj != null) {

View file

@ -19,9 +19,12 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction;
/**
* A means of decoding machine instructions from the bytes contained in the machine state
*/
public interface InstructionDecoder {
/**
* Decode the instruction at the given address using the given context
* Decode the instruction starting at the given address using the given context
*
* <p>
* This method cannot return null. If a decode error occurs, it must throw an exception.

View file

@ -0,0 +1,143 @@
/* ###
* 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.emu;
import java.util.List;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
/**
* A p-code machine which executes on concrete bytes and incorporates per-architecture state
* modifiers
*
* <p>
* This is a simple concrete bytes emulator suitable for unit testing and scripting. More complex
* use cases likely benefit by extending this or one of its super types. Likewise, the factory
* methods will likely instantiate classes which extend the default or one of its super types. When
* creating such an extension, it helps to refer to this default implementation to understand the
* overall architecture of an emulator. The emulator was designed using hierarchies of abstract
* classes each extension incorporating more complexity (and restrictions) finally culminating here.
* Every class should be extensible and have overridable factory methods so that those extensions
* can be incorporated into even more capable emulators. Furthermore, many components, e.g.,
* {@link PcodeExecutorState} were designed with composition in mind. Referring to examples, it is
* generally pretty easy to extend the emulator via composition. Search for references to
* {@link PairedPcodeExecutorState} to find such examples.
*
* <pre>
* emulator : PcodeMachine<T>
* - language : SleighLanguage
* - arithmetic : PcodeArithmetic<T>
* - sharedState : PcodeExecutorState<T>
* - library : PcodeUseropLibrary<T>
* - injects : Map<Address, PcodeProgram>
* - threads : List<PcodeThread<T>>
* - [0] : PcodeThread<T>
* - decoder : InstructionDecoder
* - executor : PcodeExecutor<T>
* - frame : PcodeFrame
* - localState : PcodeExecutorState<T>
* - library : PcodeUseropLibrary<T>
* - injects : Map<Address, PcodeProgram>
* - [1] ...
* </pre>
*
* <p>
* The root object of an emulator is the {@link PcodeEmulator}, usually ascribed the type
* {@link PcodeMachine}. At the very least, it must know the language of the processor it emulates.
* It then derives appropriate arithmetic definitions, a shared (memory) state, and a shared userop
* library. Initially, the machine has no threads. For many use cases creating a single
* {@link PcodeThread} suffices; however, this default implementation models multi-threaded
* execution "out of the box." Upon creation, each thread is assigned a local (register) state, and
* a userop library for controlling that particular thread. The thread's full state and userop
* library are composed from the machine's shared components and that thread's particular
* components. For state, the composition directs memory accesses to the machine's state and
* register accesses to the thread's state. (Accesses to the "unique" space are also directed to the
* thread's state.) This properly emulates the thread semantics of most platforms. For the userop
* library, composition is achieved simply via
* {@link PcodeUseropLibrary#compose(PcodeUseropLibrary)}. Thus, each invocation is directed to the
* library that exports the invoked userop.
*
* <p>
* Each thread creates an {@link InstructionDecoder} and a {@link PcodeExecutor}, providing the
* kernel of p-code emulation for that thread. That executor is bound to the thread's composed
* state, and to the machine's arithmetic. Together, the state and the arithmetic "define" all the
* p-code ops that the executor can invoke. Unsurprisingly, arithmetic operations are delegated to
* the {@link PcodeArithmetic}, and state operations (including memory operations and temporary
* variable access) are delegated to the {@link PcodeExecutorState}. The core execution loop easily
* follows: 1) decode the current instruction, 2) generate that instruction's p-code, 3) feed the
* code to the executor, 4) resolve the outcome and advance the program counter, then 5) repeat. So
* long as the arithmetic and state objects agree in type, a p-code machine can be readily
* implemented to manipulate values of that type. Both arithmetic and state are readily composed
* using {@link PairedPcodeArithmetic} and {@link PairedPcodeExecutorState} or
* {@link PairedPcodeExecutorStatePiece}.
*
* <p>
* This concrete emulator chooses a {@link BytesPcodeArithmetic} based on the endianness of the
* target language. Its threads are {@link BytesPcodeThread}. The shared and thread-local states are
* all {@link BytesPcodeExecutorState}. That state class can be extended to read through to some
* other backing object. For example, the memory state could read through to an imported program
* image, which allows the emulator's memory to be loaded lazily. The default userop library is
* empty. For many use cases, it will be necessary to override {@link #createUseropLibrary()} if
* only to implement the language-defined userops. If needed, simulation of the host operating
* system is typically achieved by implementing the {@code syscall} userop. The fidelity of that
* simulation depends on the use case. See {@link EmuSyscallLibrary} and its implementations to see
* what simulations are available "out of the box."
*
* <p>
* Alternatively, if the target program never invokes system calls directly, but rather via
* system-provided APIs, then it may suffice to stub out those imports. Typically, Ghidra will place
* a "thunk" at each import address with the name of the import. Stubbing an import is accomplished
* by injecting p-code at the import address. See {@link PcodeMachine#inject(Address, List)}. The
* inject will need to replicate the semantics of that call to the desired fidelity.
* <b>IMPORTANT:</b> The inject must also return control to the calling function, usually by
* replicating the conventions of the target platform.
*/
public class PcodeEmulator extends AbstractPcodeMachine<byte[]> {
/**
* Construct a new concrete emulator
*
* <p>
* Yes, it is customary to invoke this constructor directly.
*
* @param language the language of the target processor
*/
public PcodeEmulator(SleighLanguage language) {
super(language, BytesPcodeArithmetic.forLanguage(language));
}
@Override
protected BytesPcodeThread createThread(String name) {
return new BytesPcodeThread(name, this);
}
@Override
protected PcodeExecutorState<byte[]> createSharedState() {
return new BytesPcodeExecutorState(language);
}
@Override
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> thread) {
return new BytesPcodeExecutorState(language);
}
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return PcodeUseropLibrary.nil();
}
}

View file

@ -19,7 +19,7 @@ import java.util.Collection;
import java.util.List;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.DefaultPcodeThread.SleighEmulationLibrary;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
@ -48,16 +48,16 @@ public interface PcodeMachine<T> {
* Get the userop library common to all threads in the machine.
*
* <p>
* Note that threads may have larger libraries, but each should contain all the userops in this
* Note that threads may have larger libraries, but each contains all the userops in this
* library.
*
* @return the userop library
*/
SleighUseropLibrary<T> getUseropLibrary();
PcodeUseropLibrary<T> getUseropLibrary();
/**
* Get a userop library which at least declares all userops available in thread userop
* libraries.
* Get a userop library which at least declares all userops available in each thread userop
* library.
*
* <p>
* Thread userop libraries may have more userops than are defined in the machine's userop
@ -69,7 +69,7 @@ public interface PcodeMachine<T> {
*
* @return the stub library
*/
SleighUseropLibrary<T> getStubUseropLibrary();
PcodeUseropLibrary<T> getStubUseropLibrary();
/**
* Create a new thread with a default name in this machine
@ -134,7 +134,7 @@ public interface PcodeMachine<T> {
* will inject it at the given address. The resulting p-code <em>replaces</em> that which would
* be executed by decoding the instruction at the given address. The means the machine will not
* decode, nor advance its counter, unless the SLEIGH causes it. In most cases, the SLEIGH will
* call {@link SleighEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and
* call {@link PcodeEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and
* execute the overridden instruction.
*
* <p>
@ -165,7 +165,7 @@ public interface PcodeMachine<T> {
* <p>
* Breakpoints are implemented at the p-code level using an inject, without modification to the
* emulated image. As such, it cannot coexist with another inject. A client needing to break
* during an inject must use {@link SleighEmulationLibrary#emu_swi()} in the injected SLEIGH.
* during an inject must use {@link PcodeEmulationLibrary#emu_swi()} in the injected SLEIGH.
*
* @param address the address at which to break
* @param sleighCondition a SLEIGH expression which controls the breakpoint

View file

@ -23,7 +23,7 @@ import ghidra.util.classfinder.ExtensionPoint;
*
* <p>
* As much as possible, it's highly-recommended to use SLEIGH execution to perform any
* modifications. This will help it remain portable to various state types.
* modifications. This will help it remain agnostic to various state types.
*
* <p>
* TODO: Implement annotation-based {@link #isApplicable(Language)}?
@ -39,8 +39,8 @@ public interface PcodeStateInitializer extends ExtensionPoint {
boolean isApplicable(Language language);
/**
* The machine's memory state has just been initialized from a "real" target, and additional
* initialization is needed for SLEIGH execution
* The machine's memory state has just been initialized, and additional initialization is needed
* for SLEIGH execution
*
* <p>
* There's probably not much preparation of memory
@ -52,8 +52,8 @@ public interface PcodeStateInitializer extends ExtensionPoint {
}
/**
* The thread's register state has just been initialized from a "real" target, and additional
* initialization is needed for SLEIGH execution
* The thread's register state has just been initialized, and additional initialization is
* needed for SLEIGH execution
*
* <p>
* Initialization generally consists of setting "virtual" registers using data from the real

View file

@ -18,9 +18,10 @@ package ghidra.pcode.emu;
import java.util.List;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.DefaultPcodeThread.SleighEmulationLibrary;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction;
@ -48,6 +49,7 @@ public interface PcodeThread<T> {
/**
* Set the emulator's counter without writing to its machine state
*
* @see #overrideCounter(Address)
* @param counter the new target address
*/
void setCounter(Address counter);
@ -62,23 +64,25 @@ public interface PcodeThread<T> {
/**
* Set the emulator's counter and write the PC of its machine state
*
* @see #setCounter(Address)
* @param counter the new target address
*/
void overrideCounter(Address counter);
/**
* Adjust the emulator's parsing context without writing to its machine state
* Adjust the emulator's decoding context without writing to its machine state
*
* <p>
* As in {@link RegisterValue#assign(Register, RegisterValue)}, only those bits having a value
* in the given context are applied to the current context.
*
* @see #overrideContext(RegisterValue)
* @param context the new context
*/
void assignContext(RegisterValue context);
/**
* Adjust the emulator's parsing context without writing to its machine state
*
* @param context the new context void assignContext(RegisterValue context);
*
* /** Get the emulator's parsing context
* Get the emulator's decoding context
*
* @return the context
*/
@ -87,6 +91,7 @@ public interface PcodeThread<T> {
/**
* Adjust the emulator's parsing context and write the contextreg of its machine state
*
* @see #assignContext(RegisterValue)
* @param context the new context
*/
void overrideContext(RegisterValue context);
@ -114,12 +119,23 @@ public interface PcodeThread<T> {
*
* <p>
* Note because of the way Ghidra and Sleigh handle delay slots, the execution of an instruction
* with delay slots cannot be separated from the following instructions filling them. It and its
* slots are executed in a single "step." Stepping individual p-code ops which comprise the
* delay-slotted instruction is possible using {@link #stepPcodeOp(PcodeFrame)}.
* with delay slots cannot be separated from the following instructions filling those slots. It
* and its slotted instructions are executed in a single "step." However, stepping the
* individual p-code ops is still possible using {@link #stepPcodeOp(PcodeFrame)}.
*/
void stepInstruction();
/**
* Repeat {@link #stepInstruction()} count times
*
* @param count the number of instructions to step
*/
default void stepInstruction(long count) {
for (long i = 0; i < count; i++) {
stepInstruction();
}
}
/**
* Step emulation a single p-code operation
*
@ -130,19 +146,44 @@ public interface PcodeThread<T> {
* completed, the machine's program counter is advanced and the current frame is removed.
*
* <p>
* In order to provide the most flexibility, there is no enforcement of various emulation state
* on this method. Expect strange behavior for strange call sequences. For example, the caller
* should ensure that the given frame was in fact generated from the emulators current
* instruction. Doing otherwise may cause the emulator to advance in strange ways.
* Consider the case of a fall-through instruction: The first p-code step decodes the
* instruction and sets up the p-code frame. The second p-code step executes the first p-code op
* of the frame. Each subsequent p-code step executes the next p-code op until no ops remain.
* The final p-code step detects the fall-through result, advances the counter, and disposes the
* frame. The next p-code step is actually the first p-code step of the next instruction.
*
* <p>
* Consider the case of a branching instruction: The first p-code step decodes the instruction
* and sets up the p-code frame. The second p-code step executes the first p-code op of the
* frame. Each subsequent p-code step executes the next p-code op until an (external) branch is
* executed. That branch itself sets the program counter appropriately. The final p-code step
* detects the branch result and simply disposes the frame. The next p-code step is actually the
* first p-code step of the next instruction.
*
* <p>
* The decode step in both examples is subject to p-code injections. In order to provide the
* most flexibility, there is no enforcement of various emulation state on this method. Expect
* strange behavior for strange call sequences.
*
* <p>
* While this method heeds injects, such injects will obscure the p-code of the instruction
* itself. If the inject executes the instruction, the entire instruction will be executed when
* stepping the {@link SleighEmulationLibrary#emu_exec_decoded()} userop, since there is not
* stepping the {@link PcodeEmulationLibrary#emu_exec_decoded()} userop, since there is not
* (currently) any way to "step into" a userop.
*/
void stepPcodeOp();
/**
* Repeat {@link #stepPcodeOp()} count times
*
* @param count the number of p-code operations to step
*/
default void stepPcodeOp(long count) {
for (long i = 0; i < count; i++) {
stepPcodeOp();
}
}
/**
* Get the current frame, if present
*
@ -169,9 +210,9 @@ public interface PcodeThread<T> {
* Execute the next instruction, ignoring injects
*
* <p>
* This method should likely only be used internally. It steps the current instruction, but
* without any consideration for user injects, e.g., breakpoints. Most clients should call
* {@link #stepInstruction()} instead.
* <b>WARNING:</b> This method should likely only be used internally. It steps the current
* instruction, but without any consideration for user injects, e.g., breakpoints. Most clients
* should call {@link #stepInstruction()} instead.
*
* @throws IllegalStateException if the emulator is still in the middle of an instruction. That
* can happen if the machine is interrupted, or if the client has called
@ -201,10 +242,11 @@ public interface PcodeThread<T> {
* If there is a current instruction, drop its frame of execution
*
* <p>
* This does not revert any state changes caused by a partially-executed instruction. It is up
* to the client to revert the underlying machine state if desired. Note the thread's program
* counter will not be advanced. Likely, the next call to {@link #stepInstruction()} will
* re-start the same instruction. If there is no current instruction, this method has no effect.
* <b>WARNING:</b> This does not revert any state changes caused by a partially-executed
* instruction. It is up to the client to revert the underlying machine state if desired. Note
* the thread's program counter will not be advanced. Likely, the next call to
* {@link #stepInstruction()} will re-start the same instruction. If there is no current
* instruction, this method has no effect.
*/
void dropInstruction();
@ -216,7 +258,7 @@ public interface PcodeThread<T> {
* instruction is finished. By calling this method, you are "donating" the current Java thread
* to the emulator. This method will not likely return, but instead only terminates via
* exception, e.g., hitting a user breakpoint or becoming suspended. Depending on the use case,
* this method might be invoked from a dedicated Java thread.
* this method might be invoked from a Java thread dedicated to this emulated thread.
*/
void run();
@ -226,8 +268,8 @@ public interface PcodeThread<T> {
* <p>
* When {@link #run()} is invoked by a dedicated thread, suspending the pcode thread is the most
* reliable way to halt execution. Note the emulator will halt mid instruction. If this is not
* desired, then upon catching the exception, the dedicated thread should un-suspend the machine
* and call {@link #finishInstruction()}.
* desired, then upon catching the exception, un-suspend the p-code thread and call
* {@link #finishInstruction()} or {@link #dropInstruction()}.
*/
void setSuspended(boolean suspended);
@ -264,11 +306,12 @@ public interface PcodeThread<T> {
PcodeExecutor<T> getExecutor();
/**
* Get the userop library for controlling this thread's execution
* Get the complete userop library for this thread, including userops for controlling this
* thread
*
* @return the library
*/
SleighUseropLibrary<T> getUseropLibrary();
PcodeUseropLibrary<T> getUseropLibrary();
/**
* Get the thread's memory and register state
@ -283,6 +326,7 @@ public interface PcodeThread<T> {
/**
* Override the p-code at the given address with the given SLEIGH source for only this thread
*
* <p>
* This works the same {@link PcodeMachine#inject(Address, List)} but on a per-thread basis.
* Where there is both a machine-level and thread-level inject the thread inject takes
* precedence. Furthermore, the machine-level inject cannot be accessed by the thread-level

View file

@ -25,9 +25,15 @@ import ghidra.program.model.listing.Instruction;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* The default instruction decoder, based on SLEIGH
*
* <p>
* This simply uses a {@link Disassembler} on the machine's memory state.
*/
public class SleighInstructionDecoder implements InstructionDecoder {
// TODO: Some sort of instruction decode caching?
// Not as imported for stepping small distances
// Not as important for stepping small distances
// Could become important when dealing with "full system emulation," if we get there.
private static final String DEFAULT_ERROR = "Unknown disassembly error";
@ -43,6 +49,14 @@ public class SleighInstructionDecoder implements InstructionDecoder {
private Instruction instruction;
/**
* Construct a SLEIGH instruction decoder
*
* @see {@link DefaultPcodeThread#createInstructionDecoder(PcodeExecutorState)}
* @param language the language to decoder
* @param state the state containing the target program, probably the shared state of the p-code
* machine. It must be possible to obtain concrete buffers on this state.
*/
public SleighInstructionDecoder(Language language, PcodeExecutorState<?> state) {
this.state = state;
addrFactory = language.getAddressFactory();
@ -67,6 +81,11 @@ public class SleighInstructionDecoder implements InstructionDecoder {
return instruction;
}
/**
* Compute the "length" of an instruction, including any delay-slotted instructions that follow
*
* @return the length
*/
protected int computeLength() {
int length = instruction.getLength();
int slots = instruction.getDelaySlotDepth();

View file

@ -20,16 +20,35 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.MemBuffer;
/**
* A p-code executor state that multiplexes shared and thread-local states for use in a
* multi-threaded emulator
*
* @param <T> the type of values stored in the states
*/
public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
protected final PcodeExecutorState<T> sharedState;
protected final PcodeExecutorState<T> localState;
/**
* Create a multiplexed state
*
* @see {@link DefaultPcodeThread#DefaultPcodeThread(String, AbstractPcodeMachine)}
* @param sharedState the shared part of the state
* @param localState the thread-local part of the state
*/
public ThreadPcodeExecutorState(PcodeExecutorState<T> sharedState,
PcodeExecutorState<T> localState) {
this.sharedState = sharedState;
this.localState = localState;
}
/**
* Decide whether or not access to the given space is directed to thread-local state
*
* @param space the space
* @return true for thread-local state, false for shared state
*/
protected boolean isThreadLocalSpace(AddressSpace space) {
return space.isRegisterSpace() || space.isUniqueSpace();
}
@ -71,10 +90,20 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
return sharedState.getConcreteBuffer(address);
}
/**
* Get the shared state
*
* @return the shared state
*/
public PcodeExecutorState<T> getSharedState() {
return sharedState;
}
/**
* Get the thread-local state
*
* @return the thread-local state
*/
public PcodeExecutorState<T> getLocalState() {
return localState;
}

View file

@ -0,0 +1,109 @@
/* ###
* 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.emu.linux;
import java.util.*;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.unix.*;
import ghidra.pcode.emu.unix.EmuUnixFileSystem.OpenFlag;
import ghidra.program.model.listing.Program;
/**
* An abstract library of Linux system calls, suitable for use with any processor
*
* @param <T> the type of values processed by the library
*/
public abstract class AbstractEmuLinuxSyscallUseropLibrary<T>
extends AbstractEmuUnixSyscallUseropLibrary<T> {
public static final int O_MASK_RDWR = 0x3;
public static final int O_RDONLY = 0x0;
public static final int O_WRONLY = 0x1;
public static final int O_RDWR = 0x2;
public static final int O_CREAT = 0x40;
public static final int O_TRUNC = 0x200;
public static final int O_APPEND = 0x400;
/**
* TODO: A map from simulator-defined errno to Linux-defined errno
*
* <p>
* TODO: These may be applicable to all Linux, not just amd64....
*/
protected static final Map<Errno, Integer> ERRNOS = Map.ofEntries(
Map.entry(Errno.EBADF, 9));
/**
* Construct a new library
*
* @param machine the machine emulating the hardware
* @param fs the file system to export to the user-space program
* @param program a program containing the syscall definitions and conventions, likely the
* target program
*/
public AbstractEmuLinuxSyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program) {
super(machine, fs, program);
}
/**
* Construct a new library
*
* @param machine the machine emulating the hardware
* @param fs the file system to export to the user-space program
* @param program a program containing the syscall definitions and conventions, likely the
* target program
* @param user the "current user" to simulate
*/
public AbstractEmuLinuxSyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program, EmuUnixUser user) {
super(machine, fs, program, user);
}
@Override
protected Set<OpenFlag> convertFlags(int flags) {
EnumSet<OpenFlag> result = EnumSet.noneOf(OpenFlag.class);
int rdwr = flags & O_MASK_RDWR;
if (rdwr == O_RDONLY) {
result.add(OpenFlag.O_RDONLY);
}
if (rdwr == O_WRONLY) {
result.add(OpenFlag.O_WRONLY);
}
if (rdwr == O_RDWR) {
result.add(OpenFlag.O_RDWR);
}
if ((flags & O_CREAT) != 0) {
result.add(OpenFlag.O_CREAT);
}
if ((flags & O_TRUNC) != 0) {
result.add(OpenFlag.O_TRUNC);
}
if ((flags & O_APPEND) != 0) {
result.add(OpenFlag.O_APPEND);
}
return result;
}
@Override
protected int getErrno(Errno err) {
Integer errno = ERRNOS.get(err);
if (errno == null) {
throw new AssertionError("Do not know errno value for " + err);
}
return errno;
}
}

View file

@ -0,0 +1,104 @@
/* ###
* 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.emu.linux;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.unix.EmuUnixFileSystem;
import ghidra.pcode.emu.unix.EmuUnixUser;
import ghidra.pcode.exec.PcodeExecutor;
import ghidra.pcode.exec.PcodeExecutorStatePiece;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
/**
* A system call library simulating Linux for amd64 / x86_64
*
* @param <T> the type of values processed by the library
*/
public class EmuLinuxAmd64SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscallUseropLibrary<T> {
protected final Register regRAX;
protected FileDataTypeManager clib64;
/**
* Construct the system call library for Linux-amd64
*
* @param machine the machine emulating the hardware
* @param fs the file system to export to the user-space program
* @param program a program containing syscall definitions and conventions, likely the target
* program
*/
public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program) {
super(machine, fs, program);
regRAX = machine.getLanguage().getRegister("RAX");
}
/**
* Construct the system call library for Linux-amd64
*
* @param machine the machine emulating the hardware
* @param fs the file system to export to the user-space program
* @param program a program containing syscall definitions and conventions, likely the target
* program
* @param user the "current user" to simulate
*/
public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program, EmuUnixUser user) {
super(machine, fs, program, user);
regRAX = machine.getLanguage().getRegister("RAX");
}
@Override
protected Collection<DataTypeManager> getAdditionalArchives() {
try {
ResourceFile file =
Application.findDataFileInAnyModule("typeinfo/generic/generic_clib_64.gdt");
clib64 = FileDataTypeManager.openFileArchive(file, false);
return List.of(clib64);
}
catch (IOException e) {
throw new AssertionError(e);
}
}
@Override
protected void disposeAdditionalArchives() {
clib64.close();
}
@Override
public long readSyscallNumber(PcodeExecutorStatePiece<T, T> state) {
return machine.getArithmetic().toConcrete(state.getVar(regRAX)).longValue();
}
@Override
protected boolean returnErrno(PcodeExecutor<T> executor, int errno) {
executor.getState()
.setVar(regRAX,
executor.getArithmetic().fromConst(-errno, regRAX.getMinimumByteSize()));
return true;
}
}

View file

@ -0,0 +1,127 @@
/* ###
* 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.emu.linux;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.pcode.emu.DefaultPcodeThread;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.unix.EmuUnixFileSystem;
import ghidra.pcode.emu.unix.EmuUnixUser;
import ghidra.pcode.exec.*;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
/**
* A system call library simulating Linux for x86 (32-bit)
*
* @param <T> the type of values processed by the library
*/
public class EmuLinuxX86SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscallUseropLibrary<T> {
protected final Register regEIP;
protected final Register regEAX;
protected FileDataTypeManager clib32;
/**
* Construct the system call library for Linux-x86
*
* @param machine the machine emulating the hardware
* @param fs the file system to export to the user-space program
* @param program a program containing syscall definitions and conventions, likely the target
* program
*/
public EmuLinuxX86SyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program) {
this(machine, fs, program, EmuUnixUser.DEFAULT_USER);
}
/**
* Construct the system call library for Linux-x86
*
* @param machine the machine emulating the hardware
* @param fs the file system to export to the user-space program
* @param program a program containing syscall definitions and conventions, likely the target
* program
* @param user the "current user" to simulate
*/
public EmuLinuxX86SyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program, EmuUnixUser user) {
super(machine, fs, program, user);
regEIP = machine.getLanguage().getRegister("EIP");
regEAX = machine.getLanguage().getRegister("EAX");
}
@Override
protected Collection<DataTypeManager> getAdditionalArchives() {
try {
ResourceFile file =
Application.findDataFileInAnyModule("typeinfo/generic/generic_clib.gdt");
clib32 = FileDataTypeManager.openFileArchive(file, false);
return List.of(clib32);
}
catch (IOException e) {
throw new AssertionError(e);
}
}
@Override
protected void disposeAdditionalArchives() {
clib32.close();
}
@Override
public long readSyscallNumber(PcodeExecutorStatePiece<T, T> state) {
return machine.getArithmetic().toConcrete(state.getVar(regEAX)).longValue();
}
@Override
protected boolean returnErrno(PcodeExecutor<T> executor, int errno) {
executor.getState()
.setVar(regEAX,
executor.getArithmetic().fromConst(-errno, regEAX.getMinimumByteSize()));
return true;
}
@PcodeUserop
public T swi(@OpExecutor PcodeExecutor<T> executor, @OpLibrary PcodeUseropLibrary<T> library,
T number) {
PcodeArithmetic<T> arithmetic = executor.getArithmetic();
long intNo = arithmetic.toConcrete(number).longValue();
if (intNo == 0x80) {
// A CALLIND follows to the return of swi().... OK.
// We'll just make that "fall through" instead
T next = executor.getState().getVar(regEIP);
DefaultPcodeThread<T>.PcodeThreadExecutor te =
(DefaultPcodeThread<T>.PcodeThreadExecutor) executor;
int pcSize = regEIP.getNumBytes();
int iLen = te.getInstruction().getLength();
next = arithmetic.binaryOp(PcodeArithmetic.INT_ADD, pcSize, pcSize, next, pcSize,
arithmetic.fromConst(iLen, pcSize));
syscall(executor, library);
return next;
}
else {
throw new PcodeExecutionException("Unknown interrupt: 0x" + Long.toString(intNo, 16));
}
}
}

View file

@ -0,0 +1,180 @@
/* ###
* 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.emu.sys;
import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.*;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import utilities.util.AnnotationUtilities;
/**
* A syscall library wherein Java methods are exported via a special annotated
*
* <p>
* This library is both a system call and a sleigh userop library. To export a system call, it must
* also be exported as a sleigh userop. This is more conventional, as the system call dispatcher
* does not require it, however, this library uses a wrapping technique that does require it. In
* general, exporting system calls as userops will make developers and users lives easier. To avoid
* naming collisions, system calls can be exported with customized names.
*
* @param <T> the type of data processed by the library, typically {@code byte[]}
*/
public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcodeUseropLibrary<T>
implements EmuSyscallLibrary<T> {
public static final String SYSCALL_SPACE_NAME = "syscall";
protected static final Map<Class<?>, Set<Method>> CACHE_BY_CLASS = new HashMap<>();
private static Set<Method> collectSyscalls(Class<?> cls) {
return AnnotationUtilities.collectAnnotatedMethods(EmuSyscall.class, cls);
}
/**
* An annotation to export a method as a system call in the library.
*
* <p>
* The method must also be exported in the userop library, likely via {@link PcodeUserop}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EmuSyscall {
String value();
}
private final SyscallPcodeUseropDefinition<T> syscallUserop =
new SyscallPcodeUseropDefinition<>(this);
protected final PcodeMachine<T> machine;
protected final CompilerSpec cSpec;
protected final Program program;
protected final DataType dtMachineWord;
protected final Map<Long, EmuSyscallDefinition<T>> syscallMap = new HashMap<>();
protected final Collection<DataTypeManager> additionalArchives;
/**
* Construct a new library including the "syscall" userop
*
* @param machine the machine using this library
* @param program a program from which to derive syscall configuration, conventions, etc.
*/
public AnnotatedEmuSyscallUseropLibrary(PcodeMachine<T> machine, Program program) {
this.machine = machine;
this.program = program;
this.cSpec = program.getCompilerSpec();
// TODO: Take signatures / types from database
this.dtMachineWord = UseropEmuSyscallDefinition.requirePointerDataType(program);
mapAndBindSyscalls();
additionalArchives = getAdditionalArchives();
StructuredSleigh structured = newStructuredPart();
structured.generate(ops);
disposeAdditionalArchives();
mapAndBindSyscalls(structured.getClass());
}
protected Collection<DataTypeManager> getAdditionalArchives() {
return List.of();
}
protected void disposeAdditionalArchives() {
}
/**
* Create the structured-sleigh part of this library
*
* @return the structured part
*/
protected StructuredPart newStructuredPart() {
return new StructuredPart();
}
/**
* Export a userop as a system call
*
* @param opdef the userop
* @return the syscall definition
*/
public UseropEmuSyscallDefinition<T> newBoundSyscall(PcodeUseropDefinition<T> opdef,
PrototypeModel convention) {
return new UseropEmuSyscallDefinition<>(opdef, program, convention, dtMachineWord);
}
protected void mapAndBindSyscalls(Class<?> cls) {
BidiMap<Long, String> mapNames =
new DualHashBidiMap<>(EmuSyscallLibrary.loadSyscallNumberMap(program));
Map<Long, PrototypeModel> mapConventions =
EmuSyscallLibrary.loadSyscallConventionMap(program);
Set<Method> methods = collectSyscalls(cls);
for (Method m : methods) {
String name = m.getAnnotation(EmuSyscall.class).value();
Long number = mapNames.getKey(name);
if (number == null) {
Msg.warn(cls, "Syscall " + name + " has no number");
continue;
}
PcodeUseropDefinition<T> opdef = getUserops().get(m.getName());
if (opdef == null) {
throw new IllegalArgumentException("Method " + m.getName() +
" annotated with @" + EmuSyscall.class.getSimpleName() +
" must also be a p-code userop");
}
PrototypeModel convention = mapConventions.get(number);
EmuSyscallDefinition<T> existed =
syscallMap.put(number, newBoundSyscall(opdef, convention));
if (existed != null) {
throw new IllegalArgumentException("Duplicate @" +
EmuSyscall.class.getSimpleName() + " annotated methods with name " + name);
}
}
}
protected void mapAndBindSyscalls() {
mapAndBindSyscalls(this.getClass());
}
@Override
public PcodeUseropDefinition<T> getSyscallUserop() {
return syscallUserop;
}
@Override
public Map<Long, EmuSyscallDefinition<T>> getSyscalls() {
return syscallMap;
}
protected class StructuredPart extends StructuredSleigh {
protected StructuredPart() {
super(program);
addDataTypeSources(additionalArchives);
}
}
}

View file

@ -0,0 +1,37 @@
/* ###
* 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.emu.sys;
import java.io.IOException;
/**
* The simulated system interrupted with an I/O error
*
* <p>
* This exception is for I/O errors within the simulated system. If the host implementation causes a
* real {@link IOException}, it should <em>not</em> be wrapped in this exception unless, e.g., a
* simulated file system intends to proxy the real file system.
*/
public class EmuIOException extends EmuInvalidSystemCallException {
public EmuIOException(String message, Throwable cause) {
super(message, cause);
}
public EmuIOException(String message) {
super(message);
}
}

View file

@ -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.emu.sys;
/**
* The emulated program invoked a system call incorrectly
*/
public class EmuInvalidSystemCallException extends EmuSystemException {
/**
* The system call number was not valid
*
* @param number the system call number
*/
public EmuInvalidSystemCallException(long number) {
this("Invalid system call number: " + number);
}
public EmuInvalidSystemCallException(String message) {
super(message);
}
public EmuInvalidSystemCallException(String message, Throwable cause) {
super(message, null, cause);
}
}

View file

@ -0,0 +1,63 @@
/* ###
* 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.emu.sys;
import java.math.BigInteger;
import ghidra.pcode.exec.PcodeArithmetic;
/**
* A simulated process (or thread group) has exited
*/
public class EmuProcessExitedException extends EmuSystemException {
public static <T> String tryConcereteToString(PcodeArithmetic<T> arithmetic, T status) {
try {
BigInteger value = arithmetic.toConcrete(status);
return value.toString();
}
catch (Exception e) {
return status.toString();
}
}
private final Object status;
/**
* Construct a process-exited exception with the given status code
*
* <p>
* This will attempt to concretize the status according to the given arithmetic, for display
* purposes. The original status remains accessible via {@link #getStatus()}
*
* @param <T> the type values processed by the library
* @param arithmetic the machine's arithmetic
* @param status
*/
public <T> EmuProcessExitedException(PcodeArithmetic<T> arithmetic, T status) {
super("Process exited with status " + tryConcereteToString(arithmetic, status));
this.status = status;
}
/**
* Get the status code as a {@code T} of the throwing machine
*
* @return the status
*/
public Object getStatus() {
return status;
}
}

View file

@ -0,0 +1,268 @@
/* ###
* 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.emu.sys;
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.*;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.*;
/**
* A library of system calls
*
* <p>
* A system call library is a collection of p-code executable routines, invoked by a system call
* dispatcher. That dispatcher is represented by {@link #syscall(PcodeExecutor)}, and is exported as
* a sleigh userop. If this interface is "mixed in" with {@link AnnotatedPcodeUseropLibrary}, that
* userop is automatically included in the userop library. The simplest means of implementing a
* syscall library is probably via {@link AnnotatedEmuSyscallUseropLibrary}. It implements this
* interface and extends {@link AnnotatedPcodeUseropLibrary}. In addition, it provides its own
* annotation system for exporting Java methods as system calls.
*
* @param <T> the type of data processed by the system calls, typically {@code byte[]}
*/
public interface EmuSyscallLibrary<T> {
String SYSCALL_SPACE_NAME = "syscall";
String SYSCALL_CONVENTION_NAME = "syscall";
/**
* Derive a syscall number to name map from the specification in a given file.
*
* @param dataFileName the file name to be found in a modules data directory
* @return the map
* @throws IOException if the file could not be read
*/
public static Map<Long, String> loadSyscallNumberMap(String dataFileName) throws IOException {
ResourceFile mapFile = Application.findDataFileInAnyModule(dataFileName);
if (mapFile == null) {
throw new FileNotFoundException("Cannot find syscall number map: " + dataFileName);
}
Map<Long, String> result = new HashMap<>();
final BufferedReader reader =
new BufferedReader(new InputStreamReader(mapFile.getInputStream()));
String line;
while (null != (line = reader.readLine())) {
line = line.strip();
if (line.startsWith("#")) {
continue;
}
String[] parts = line.split("\\s+");
if (parts.length != 2) {
throw new IOException(
"Badly formatted syscall number map: " + dataFileName + ". Line: " + line);
}
try {
result.put(Long.parseLong(parts[0]), parts[1]);
}
catch (NumberFormatException e) {
throw new IOException("Badly formatted syscall number map: " + dataFileName, e);
}
}
return result;
}
/**
* Scrape functions from the given program's "syscall" space.
*
* @param program the program
* @return a map of syscall number to function
*/
public static Map<Long, Function> loadSyscallFunctionMap(Program program) {
AddressSpace space = program.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME);
if (space == null) {
throw new IllegalStateException(
"No syscall address space in program. Please analyze the syscalls first.");
}
Map<Long, Function> result = new HashMap<>();
SymbolIterator sit =
program.getSymbolTable().getSymbolIterator(space.getMinAddress(), true);
while (sit.hasNext()) {
Symbol s = sit.next();
if (s.getAddress().getAddressSpace() != space) {
break;
}
if (s.getSymbolType() != SymbolType.FUNCTION) {
continue;
}
result.put(s.getAddress().getOffset(), (Function) s.getObject());
}
return result;
}
/**
* Derive a syscall number to name map by scraping functions in the program's "syscall" space.
*
* @param program the program, likely analyzed for system calls already
* @return the map
*/
public static Map<Long, String> loadSyscallNumberMap(Program program) {
return loadSyscallFunctionMap(program).entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getName()));
}
/**
* Derive a syscall number to calling convention map by scraping functions in the program's
* "syscall" space.
*
* @param program
* @return
*/
public static Map<Long, PrototypeModel> loadSyscallConventionMap(Program program) {
return loadSyscallFunctionMap(program).entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getCallingConvention()));
}
/**
* The {@link EmuSyscallLibrary#syscall(PcodeExecutor)} method wrapped as a userop definition
*
* @param <T> the type of data processed by the userop, typically {@code byte[]}
*/
final class SyscallPcodeUseropDefinition<T> implements PcodeUseropDefinition<T> {
private final EmuSyscallLibrary<T> syslib;
public SyscallPcodeUseropDefinition(EmuSyscallLibrary<T> syslib) {
this.syslib = syslib;
}
@Override
public String getName() {
return "syscall";
}
@Override
public int getInputCount() {
return 0;
}
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> inVars) {
syslib.syscall(executor, library);
}
}
/**
* The definition of a system call
*
* @param <T> the type of data processed by the system call, typically {@code byte[]}.
*/
interface EmuSyscallDefinition<T> {
/**
* Invoke the system call
*
* @param executor the executor for the system/thread invoking the call
* @param library the complete sleigh userop library for the system
*/
void invoke(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library);
}
/**
* In case this is not an {@link AnnotatedEmuSyscallUseropLibrary} or
* {@link AnnotatedPcodeUseropLibrary}, get the definition of the "syscall" userop for inclusion
* in the {@link PcodeUseropLibrary}.
*
* <p>
* Implementors may wish to override this to use a pre-constructed definition. That definition
* can be easily constructed using {@link SyscallPcodeUseropDefinition}.
*
* @return the syscall userop definition
*/
default PcodeUseropDefinition<T> getSyscallUserop() {
return new SyscallPcodeUseropDefinition<>(this);
};
/**
* Retrieve the desired system call number according to the emulated system's conventions
*
* <p>
* TODO: This should go away in favor of some specification stored in the emulated program
* database. Until then, we require system-specific implementations.
*
* @param state the executor's state
* @return the system call number
*/
long readSyscallNumber(PcodeExecutorStatePiece<T, T> state);
/**
* Try to handle an error, usually by returning it to the user program
*
* <p>
* If the particular error was not expected, it is best practice to return false, causing the
* emulator to interrupt. Otherwise, some state is set in the machine that, by convention,
* communicates the error back to the user program.
*
* @param executor the executor for the thread that caused the error
* @param err the error
* @return true if execution can continue uninterrupted
*/
boolean handleError(PcodeExecutor<T> executor, PcodeExecutionException err);
/**
* The entry point for executing a system call on the given executor
*
* <p>
* The executor's state must already be prepared according to the relevant system calling
* conventions. This will determine the system call number, according to
* {@link #readSyscallNumber(PcodeExecutorStatePiece)}, retrieve the relevant system call
* definition, and invoke it.
*
* @param executor the executor
* @param library the library
*/
@PcodeUserop
default void syscall(@OpExecutor PcodeExecutor<T> executor,
@OpLibrary PcodeUseropLibrary<T> library) {
long syscallNumber = readSyscallNumber(executor.getState());
EmuSyscallDefinition<T> syscall = getSyscalls().get(syscallNumber);
if (syscall == null) {
throw new EmuInvalidSystemCallException(syscallNumber);
}
try {
syscall.invoke(executor, library);
}
catch (PcodeExecutionException e) {
if (!handleError(executor, e)) {
throw e;
}
}
}
/**
* Get the map of syscalls by number
*
* <p>
* Note this method will be invoked for every emulated syscall, so it should be a simple
* accessor. Any computations needed to create the map should be done ahead of time.
*
* @return the system call map
*/
Map<Long, EmuSyscallDefinition<T>> getSyscalls();
}

View file

@ -0,0 +1,36 @@
/* ###
* 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.emu.sys;
import ghidra.pcode.exec.PcodeExecutionException;
import ghidra.pcode.exec.PcodeFrame;
/**
* A p-code execution exception related to system simulation
*/
public class EmuSystemException extends PcodeExecutionException {
public EmuSystemException(String message) {
super(message);
}
public EmuSystemException(String message, PcodeFrame frame) {
super(message, frame);
}
public EmuSystemException(String message, PcodeFrame frame, Throwable cause) {
super(message, frame, cause);
}
}

View file

@ -0,0 +1,121 @@
/* ###
* 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.emu.sys;
import java.util.Arrays;
import java.util.List;
import ghidra.lifecycle.Unfinished;
import ghidra.pcode.emu.sys.EmuSyscallLibrary.EmuSyscallDefinition;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.Varnode;
/**
* A system call that is defined by delegating to a p-code userop
*
* <p>
* This is essentially a wrapper of the p-code userop. Knowing the number of inputs to the userop
* and by applying the calling conventions of the platform, the wrapper aliases each parameter's
* storage to its respective parameter of the userop. The userop's output is also aliased to the
* system call's return storage, again as defined by the platform's conventions.
*
* @see AnnotatedEmuSyscallUseropLibrary
* @param <T> the type of values processed by the library
*/
public class UseropEmuSyscallDefinition<T> implements EmuSyscallDefinition<T> {
/**
* Obtain the program's "pointer" data type, throwing an exception if absent
*
* @param program the program
* @return the "pointer" data type
*/
protected static DataType requirePointerDataType(Program program) {
DataType dtPointer = program.getDataTypeManager().getDataType("/pointer");
if (dtPointer == null) {
throw new IllegalArgumentException("No 'pointer' data type in " + program);
}
return dtPointer;
}
protected final PcodeUseropDefinition<T> opdef;
protected final List<Varnode> inVars;
protected final Varnode outVar;
/**
* Construct a syscall definition
*
* @see AnnotatedEmuSyscallUseropLibrary
* @param opdef the wrapped userop definition
* @param program the program, used for storage computation
* @param convention the "syscall" calling convention
* @param dtMachineWord the "pointer" data type
*/
public UseropEmuSyscallDefinition(PcodeUseropDefinition<T> opdef, Program program,
PrototypeModel convention, DataType dtMachineWord) {
this.opdef = opdef;
// getStorageLocations needs return(1) + parameters(n)
int inputCount = opdef.getInputCount();
if (inputCount < 0) {
throw new IllegalArgumentException("Variadic sleigh userop " + opdef.getName() +
" cannot be used as a syscall");
}
DataType[] locs = new DataType[inputCount + 1];
for (int i = 0; i < locs.length; i++) {
locs[i] = dtMachineWord;
}
VariableStorage[] vss = convention.getStorageLocations(program, locs, false);
outVar = getSingleVnStorage(vss[0]);
inVars = Arrays.asList(new Varnode[inputCount]);
for (int i = 0; i < inputCount; i++) {
inVars.set(i, getSingleVnStorage(vss[i + 1]));
}
}
/**
* Assert variable storage is a single varnode, and get that varnode
*
* @param vs the storage
* @return the single varnode
*/
protected Varnode getSingleVnStorage(VariableStorage vs) {
Varnode[] vns = vs.getVarnodes();
if (vns.length != 1) {
Unfinished.TODO();
}
return vns[0];
}
@Override
public void invoke(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library) {
try {
opdef.execute(executor, library, outVar, inVars);
}
catch (PcodeExecutionException e) {
throw e;
}
catch (Throwable e) {
throw new EmuSystemException("Error during syscall", null, e);
}
}
}

View file

@ -0,0 +1,65 @@
/* ###
* 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.emu.unix;
/**
* An abstract file contained in an emulated file system
*
* <p>
* Contrast this with {@link DefaultEmuUnixFileHandle}, which is a particular process's handle when
* opening the file, not the file itself.
*
* @param <T> the type of values stored in the file
*/
public abstract class AbstractEmuUnixFile<T> implements EmuUnixFile<T> {
protected final String pathname;
protected final EmuUnixFileStat stat = createStat();
/**
* Construct a new file
*
* <p>
* TODO: Technically, a file can be hardlinked to several pathnames, but for simplicity, or for
* diagnostics, we let the file know its own original name.
*
* @see AbstractEmuUnixFileSystem#newFile(String)
* @param pathname the pathname of the file
* @param mode the mode of the file
*/
public AbstractEmuUnixFile(String pathname, int mode) {
this.pathname = pathname;
stat.st_mode = mode;
}
/**
* A factory method for the file's {@code stat} structure.
*
* @return the stat structure.
*/
protected EmuUnixFileStat createStat() {
return new EmuUnixFileStat();
}
@Override
public String getPathname() {
return pathname;
}
@Override
public EmuUnixFileStat getStat() {
return stat;
}
}

View file

@ -0,0 +1,74 @@
/* ###
* 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.emu.unix;
import java.util.*;
import ghidra.pcode.emu.sys.EmuIOException;
/**
* An abstract emulated file system, exported to an emulated user-space program
*
* @param <T> the type of values stored in the file system
*/
public abstract class AbstractEmuUnixFileSystem<T> implements EmuUnixFileSystem<T> {
protected final Map<String, EmuUnixFile<T>> filesByPath = new HashMap<>();
@Override
public abstract AbstractEmuUnixFile<T> newFile(String pathname, int mode) throws EmuIOException;
@Override
public synchronized EmuUnixFile<T> createOrGetFile(String pathname, int mode)
throws EmuIOException {
return filesByPath.computeIfAbsent(pathname, p -> newFile(p, mode));
}
@Override
public synchronized EmuUnixFile<T> getFile(String pathname) throws EmuIOException {
return filesByPath.get(pathname);
}
@Override
public synchronized void putFile(String pathname, EmuUnixFile<T> file) throws EmuIOException {
filesByPath.put(pathname, file);
}
@Override
public synchronized EmuUnixFile<T> open(String pathname, Set<OpenFlag> flags, EmuUnixUser user,
int mode) throws EmuIOException {
EmuUnixFile<T> file =
flags.contains(OpenFlag.O_CREAT) ? createOrGetFile(pathname, mode) : getFile(pathname);
if (file == null) {
throw new EmuIOException("File not found: " + pathname);
}
if (flags.contains(OpenFlag.O_RDONLY) || flags.contains(OpenFlag.O_RDWR)) {
file.checkReadable(user);
}
if (flags.contains(OpenFlag.O_WRONLY) || flags.contains(OpenFlag.O_RDWR)) {
file.checkWritable(user);
if (flags.contains(OpenFlag.O_TRUNC)) {
file.truncate();
}
}
return file;
}
@Override
public synchronized void unlink(String pathname, EmuUnixUser user) throws EmuIOException {
filesByPath.remove(pathname);
}
}

View file

@ -0,0 +1,312 @@
/* ###
* 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.emu.unix;
import java.util.*;
import ghidra.docking.settings.SettingsImpl;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
import ghidra.pcode.emu.sys.EmuProcessExitedException;
import ghidra.pcode.emu.unix.EmuUnixFileSystem.OpenFlag;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.StringDataInstance;
import ghidra.program.model.data.StringDataType;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemBuffer;
/**
* An abstract library of UNIX system calls, suitable for use with any processor
*
* <p>
* TODO: The rest of the system calls common to UNIX.
*
* @param <T> the type of values processed by the library
*/
public abstract class AbstractEmuUnixSyscallUseropLibrary<T>
extends AnnotatedEmuSyscallUseropLibrary<T> {
/**
* The errno values as defined by the simulator
*
* <p>
* See a UNIX manual for their exact meaning
*/
public enum Errno {
EBADF;
}
protected final EmuUnixFileSystem<T> fs;
protected EmuUnixUser user;
protected final int intSize;
protected final NavigableSet<Integer> closedFds = new TreeSet<>();
protected final Map<Integer, EmuUnixFileDescriptor<T>> descriptors = new HashMap<>();
/**
* Construct a new library
*
* @param machine the machine emulating the hardware
* @param fs the file system to export to the user-space program
* @param program a program containing the syscall definitions and conventions, likely the
* target program
*/
public AbstractEmuUnixSyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program) {
this(machine, fs, program, EmuUnixUser.DEFAULT_USER);
}
/**
* Construct a new library
*
* @param machine the machine emulating the hardware
* @param fs a file system to export to the user-space program
* @param program a program containing the syscall definitions and conventions, likely the
* target program
* @param user the "current user" to simulate
*/
public AbstractEmuUnixSyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program, EmuUnixUser user) {
super(machine, program);
this.fs = fs;
this.user = user;
this.intSize = program.getCompilerSpec().getDataOrganization().getIntegerSize();
}
protected int lowestFd() {
Integer lowest = closedFds.pollFirst();
if (lowest != null) {
return lowest;
}
return descriptors.size();
}
protected int claimFd(EmuUnixFileDescriptor<T> desc) {
synchronized (descriptors) {
int fd = lowestFd();
putDescriptor(fd, desc);
return fd;
}
}
protected EmuUnixFileDescriptor<T> findFd(int fd) {
synchronized (descriptors) {
EmuUnixFileDescriptor<T> desc = descriptors.get(fd);
if (desc == null) {
throw new EmuUnixException("Invalid descriptor: " + fd, getErrno(Errno.EBADF));
}
return desc;
}
}
protected EmuUnixFileDescriptor<T> releaseFd(int fd) {
synchronized (descriptors) {
if (descriptors.size() + closedFds.size() - 1 == fd) {
return descriptors.remove(fd);
}
EmuUnixFileDescriptor<T> removed = descriptors.remove(fd);
if (removed == null) {
throw new EmuUnixException("Invalid descriptor: " + fd, getErrno(Errno.EBADF));
}
closedFds.add(fd);
return removed;
}
}
@Override
protected StructuredPart newStructuredPart() {
return new UnixStructuredPart();
}
/**
* Convert the flags as defined for this platform to flags understood by the simulator
*
* @param flags the platform-defined flags
* @return the simulator-defined flags
*/
protected abstract Set<OpenFlag> convertFlags(int flags);
/**
* A factory method for creating an open file handle
*
* @param file the file opened by the handle
* @param flags the open flags, as specified by the user, as defined by the platform
* @return the handle
*/
protected EmuUnixFileDescriptor<T> createHandle(EmuUnixFile<T> file, int flags) {
return new DefaultEmuUnixFileHandle<>(machine, cSpec, file, convertFlags(flags), user);
}
/**
* Get the platform-specific errno value for the given simulator-defined errno
*
* @param err the simulator-defined errno
* @return the platform-defined errno
*/
protected abstract int getErrno(Errno err);
/**
* Put a descriptor into the process' open file handles
*
* @param fd the file descriptor value
* @param desc the simulated descriptor (handle, console, etc.)
* @return the previous descriptor, which probably ought to be {@code null}
*/
public EmuUnixFileDescriptor<T> putDescriptor(int fd, EmuUnixFileDescriptor<T> desc) {
synchronized (descriptors) {
return descriptors.put(fd, desc);
}
}
protected abstract boolean returnErrno(PcodeExecutor<T> executor, int errno);
@Override
public boolean handleError(PcodeExecutor<T> executor, PcodeExecutionException err) {
if (err instanceof EmuUnixException) {
Integer errno = ((EmuUnixException) err).getErrno();
if (errno == null) {
return false;
}
return returnErrno(executor, errno);
}
return false;
}
@PcodeUserop
@EmuSyscall("exit")
public T unix_exit(T status) {
throw new EmuProcessExitedException(machine.getArithmetic(), status);
}
@PcodeUserop
@EmuSyscall("read")
public T unix_read(@OpState PcodeExecutorStatePiece<T, T> state, T fd, T bufPtr, T count) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int ifd = arithmetic.toConcrete(fd).intValue();
EmuUnixFileDescriptor<T> desc = findFd(ifd);
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
int size = arithmetic.toConcrete(count).intValue(); // TODO: Not idea to require concrete size
T buf = arithmetic.fromConst(0, size);
T result = desc.read(buf);
int iresult = arithmetic.toConcrete(result).intValue();
state.setVar(space, bufPtr, iresult, true, buf);
return result;
}
@PcodeUserop
@EmuSyscall("write")
public T unix_write(@OpState PcodeExecutorStatePiece<T, T> state, T fd, T bufPtr, T count) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int ifd = arithmetic.toConcrete(fd).intValue();
EmuUnixFileDescriptor<T> desc = findFd(ifd);
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
// TODO: Not ideal to require concrete size. What are the alternatives, though?
// TODO: size should actually be long (size_t)
int size = arithmetic.toConcrete(count).intValue();
T buf = state.getVar(space, bufPtr, size, true);
// TODO: Write back into state? "write" shouldn't touch the buffer....
return desc.write(buf);
}
@PcodeUserop
@EmuSyscall("open")
public T unix_open(@OpState PcodeExecutorStatePiece<T, T> state, T pathnamePtr, T flags,
T mode) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int iflags = arithmetic.toConcrete(flags).intValue();
int imode = arithmetic.toConcrete(mode).intValue();
long pathnameOff = arithmetic.toConcrete(pathnamePtr).longValue();
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
SettingsImpl settings = new SettingsImpl();
MemBuffer buffer = state.getConcreteBuffer(space.getAddress(pathnameOff));
StringDataInstance sdi =
new StringDataInstance(StringDataType.dataType, settings, buffer, -1);
sdi = new StringDataInstance(StringDataType.dataType, settings, buffer,
sdi.getStringLength());
// TODO: Can NPE here be mapped to a unix error
String pathname = Objects.requireNonNull(sdi.getStringValue());
EmuUnixFile<T> file = fs.open(pathname, convertFlags(iflags), user, imode);
int ifd = claimFd(createHandle(file, iflags));
return arithmetic.fromConst(ifd, intSize);
}
@PcodeUserop
@EmuSyscall("close")
public T unix_close(T fd) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int ifd = arithmetic.toConcrete(fd).intValue();
// TODO: Some fs.close or file.close, when all handles have released it?
EmuUnixFileDescriptor<T> desc = releaseFd(ifd);
desc.close();
return arithmetic.fromConst(0, intSize);
}
@PcodeUserop
@EmuSyscall("group_exit")
public void unix_group_exit(T status) {
throw new EmuProcessExitedException(machine.getArithmetic(), status);
}
protected class UnixStructuredPart extends StructuredPart {
final UseropDecl unix_read = userop(type("size_t"), "unix_read",
types("int", "void *", "size_t"));
final UseropDecl unix_write = userop(type("size_t"), "unix_write",
types("int", "void *", "size_t"));;
/**
* Inline the gather or scatter pattern for an iovec syscall
*
* <p>
* This is essentially a macro by virtue of the host (Java) language. Note that
* {@link #_result(RVal)} from here will cause the whole userop to return, not just this
* inlined portion.
*/
protected void gatherScatterIovec(Var in_fd, Var in_iovec, Var in_iovcnt,
UseropDecl subOp) {
Var tmp_i = local("tmp_i", type("size_t"));
Var tmp_total = local("tmp_total", type("size_t"));
Var tmp_ret = local("tmp_ret", type("size_t"));
_for(tmp_i.set(0), tmp_i.ltiu(in_iovcnt), tmp_i.inc(), () -> {
Var tmp_io = local("tmp_io", in_iovec.index(tmp_i));
Var tmp_base = local("tmp_base", tmp_io.field("iov_base").deref());
Var tmp_len = local("tmp_len", tmp_io.field("iov_len").deref());
tmp_ret.set(subOp.call(in_fd, tmp_base, tmp_len));
tmp_total.addiTo(tmp_ret);
_if(tmp_ret.ltiu(tmp_len), () -> _break()); // We got less than this buffer
});
_result(tmp_total);
}
@StructuredUserop(type = "size_t")
@EmuSyscall("readv")
public void unix_readv(@Param(type = "int", name = "in_fd") Var in_fd,
@Param(type = "iovec *", name = "in_iovec") Var in_iovec,
@Param(type = "size_t", name = "in_iovcnt") Var in_iovcnt) {
gatherScatterIovec(in_fd, in_iovec, in_iovcnt, unix_read);
}
@StructuredUserop(type = "size_t")
@EmuSyscall("writev")
public void unix_writev(@Param(type = "int", name = "in_fd") Var in_fd,
@Param(type = "iovec *", name = "in_iovec") Var in_iovec,
@Param(type = "size_t", name = "in_iovcnt") Var in_iovcnt) {
gatherScatterIovec(in_fd, in_iovec, in_iovcnt, unix_write);
}
}
}

View file

@ -0,0 +1,62 @@
/* ###
* 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.emu.unix;
import ghidra.lifecycle.Unfinished;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.sys.EmuIOException;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.program.model.lang.CompilerSpec;
/**
* An abstract file descriptor having no "offset," typically for stream-like files
*
* @param <T> the type of values in the file
*/
public abstract class AbstractStreamEmuUnixFileHandle<T> implements EmuUnixFileDescriptor<T> {
protected final PcodeArithmetic<T> arithmetic;
protected final int offsetBytes;
private final T offset;
/**
* Construct a new handle
*
* @see AbstractEmuUnixSyscallUseropLibrary#createHandle(int, EmuUnixFile, int)
* @param machine the machine emulating the hardware
* @param cSpec the ABI of the target platform
*/
public AbstractStreamEmuUnixFileHandle(PcodeMachine<T> machine, CompilerSpec cSpec) {
this.arithmetic = machine.getArithmetic();
this.offsetBytes = cSpec.getDataOrganization().getLongSize(); // off_t's fundamental type
this.offset = arithmetic.fromConst(0, offsetBytes);
}
@Override
public T getOffset() {
return offset;
}
@Override
public void seek(T offset) throws EmuIOException {
// No effect
}
@Override
public EmuUnixFileStat stat() {
return Unfinished.TODO();
}
}

View file

@ -0,0 +1,92 @@
/* ###
* 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.emu.unix;
import ghidra.pcode.emu.sys.EmuIOException;
import ghidra.pcode.exec.BytesPcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.program.model.lang.Language;
import ghidra.util.MathUtilities;
/**
* A concrete in-memory file system simulator suitable for UNIX programs
*/
public class BytesEmuUnixFileSystem extends AbstractEmuUnixFileSystem<byte[]> {
/**
* A concrete in-memory file suitable for UNIX programs
*/
protected static class BytesEmuUnixFile extends AbstractEmuUnixFile<byte[]> {
protected static final int INIT_CONTENT_SIZE = 1024;
protected byte[] content = new byte[INIT_CONTENT_SIZE];
/**
* Construct a new file
*
* @see BytesEmuUnixFileSystem#newFile(String)
* @param pathname the original pathname of the file
*/
public BytesEmuUnixFile(String pathname, int mode) {
super(pathname, mode);
}
@Override
public synchronized byte[] read(PcodeArithmetic<byte[]> arithmetic, byte[] offset,
byte[] buf) {
// NOTE: UNIX takes long offsets, but since we're backing with arrays, we use int
int off = arithmetic.toConcrete(offset).intValue();
int len = Math.min(buf.length, (int) stat.st_size - off);
if (len < 0) {
throw new EmuIOException("Offset is past end of file");
}
System.arraycopy(content, off, buf, 0, len);
return arithmetic.fromConst(len, offset.length);
}
@Override
public synchronized byte[] write(PcodeArithmetic<byte[]> arithmetic, byte[] offset,
byte[] buf) {
int off = arithmetic.toConcrete(offset).intValue();
if (off + buf.length > content.length) {
byte[] grown = new byte[content.length * 2];
System.arraycopy(content, 0, grown, 0, (int) stat.st_size);
content = grown;
}
System.arraycopy(buf, 0, content, off, buf.length);
// TODO: Uhh, arrays can't get larger than INT_MAX anyway
stat.st_size = MathUtilities.unsignedMax(stat.st_size, off + buf.length);
return arithmetic.fromConst(buf.length, offset.length);
}
@Override
public synchronized void truncate() {
stat.st_size = 0;
// TODO: Zero content?
}
}
/**
* Construct a new concrete simulated file system
*/
public BytesEmuUnixFileSystem() {
}
@Override
public BytesEmuUnixFile newFile(String pathname, int mode) {
return new BytesEmuUnixFile(pathname, mode);
}
}

View file

@ -0,0 +1,133 @@
/* ###
* 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.emu.unix;
import java.util.Set;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.sys.EmuIOException;
import ghidra.pcode.emu.unix.EmuUnixFileSystem.OpenFlag;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.opbehavior.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.pcode.PcodeOp;
/**
* A file descriptor associated with a file on a simulated UNIX file system
*
* @param <T> the type of values stored by the file
*/
public class DefaultEmuUnixFileHandle<T> implements EmuUnixFileDescriptor<T> {
protected final PcodeArithmetic<T> arithmetic;
protected final EmuUnixFile<T> file;
// TODO: T flags? Meh.
protected final Set<OpenFlag> flags;
protected final EmuUnixUser user;
protected final int offsetBytes;
private T offset;
/**
* Construct a new handle on the given file
*
* @see AbstractEmuUnixSyscallUseropLibrary#createHandle(int, EmuUnixFile, int)
* @param machine the machine emulating the hardware
* @param cSpec the ABI of the target platform
* @param file the file opened by this handle
* @param flags the user-specified flags, as defined by the simulator
* @param user the user that opened the file
*/
public DefaultEmuUnixFileHandle(PcodeMachine<T> machine, CompilerSpec cSpec,
EmuUnixFile<T> file, Set<OpenFlag> flags, EmuUnixUser user) {
this.arithmetic = machine.getArithmetic();
this.file = file;
this.flags = flags;
this.user = user;
this.offsetBytes = cSpec.getDataOrganization().getLongSize(); // off_t's fundamental type
this.offset = arithmetic.fromConst(0, offsetBytes);
}
/**
* Check if the file is readable, throwing {@link EmuIOException} if not
*/
public void checkReadable() {
if (!OpenFlag.isRead(flags)) {
throw new EmuIOException("File not opened for reading");
}
}
/**
* Check if the file is writable, throwing {@link EmuIOException} if not
*/
public void checkWritable() {
if (!OpenFlag.isWrite(flags)) {
throw new EmuIOException("File not opened for writing");
}
}
/**
* Advance the handle's offset (negative to rewind)
*
* @param len the number of bytes to advance
*/
protected void advanceOffset(T len) {
int sizeofLen = arithmetic.toConcrete(arithmetic.sizeOf(len)).intValue();
offset = arithmetic.binaryOp(PcodeArithmetic.INT_ADD, offsetBytes, offsetBytes, offset,
sizeofLen, len);
}
@Override
public T getOffset() {
return offset;
}
@Override
public void seek(T offset) throws EmuIOException {
// TODO: Where does bounds check happen?
this.offset = offset;
}
@Override
public T read(T buf) throws EmuIOException {
checkReadable();
T len = file.read(arithmetic, offset, buf);
advanceOffset(len);
return len;
}
@Override
public T write(T buf) throws EmuIOException {
checkWritable();
if (flags.contains(OpenFlag.O_APPEND)) {
offset = arithmetic.fromConst(file.getStat().st_size, offsetBytes);
}
T len = file.write(arithmetic, offset, buf);
advanceOffset(len);
return len;
}
@Override
public EmuUnixFileStat stat() {
return file.getStat();
}
@Override
public void close() {
// TODO: Let the file know a handle was closed?
}
}

View file

@ -0,0 +1,65 @@
/* ###
* 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.emu.unix;
import ghidra.pcode.emu.sys.EmuSystemException;
/**
* An exception for errors within UNIX sytem call libraries
*/
public class EmuUnixException extends EmuSystemException {
private final Integer errno;
public EmuUnixException(String message) {
this(message, null, null);
}
public EmuUnixException(String message, Throwable e) {
this(message, null, e);
}
public EmuUnixException(String message, Integer errno) {
this(message, errno, null);
}
/**
* Construct a new exception with an optional errno
*
* <p>
* Providing an errno allows the system call dispatcher to automatically communicate errno to
* the target program. If provided, the exception will not interrupt the emulator, because the
* target program is expected to handle it. If omitted, the dispatcher simply allows the
* exception to interrupt the emulator.
*
* @param message the message
* @param errno the errno, or {@code null}
* @param e the cause of this exception, or {@code null}
*/
public EmuUnixException(String message, Integer errno, Throwable e) {
super(message, null, e);
this.errno = errno;
}
/**
* Get the errno associated with this exception
*
* @return the errno, or {@code null}
*/
public Integer getErrno() {
return errno;
}
}

View file

@ -0,0 +1,126 @@
/* ###
* 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.emu.unix;
import ghidra.pcode.emu.sys.EmuIOException;
import ghidra.pcode.exec.PcodeArithmetic;
/**
* A simulated UNIX file
*
* <p>
* Contrast this with {@link EmuUnixFileDescriptor}, which is a process's handle to an open file,
* not the file itself.
*
* @param <T> the type of values stored in the file
*/
public interface EmuUnixFile<T> {
/**
* Get the original pathname of this file
*
* <p>
* Depending on the fidelity of the file system simulator, and the actions taken by the target
* program, the file may no longer actually exist at this path, but it ought be have been the
* pathname at some point in the file life.
*
* @return the pathname
*/
String getPathname();
/**
* Read contents from the file starting at the given offset into the given buffer
*
* <p>
* This roughly follows the semantics of the UNIX {@code read()}. While the offset and return
* value may depend on the arithmetic, the actual contents read from the file should not.
*
* @param arithmetic the arithmetic
* @param offset the offset
* @param buf the buffer
* @return the number of bytes read
*/
T read(PcodeArithmetic<T> arithmetic, T offset, T buf);
/**
* Write contents into the file starting at the given offset from the given buffer
*
* <p>
* This roughly follows the semantics of the UNIX {@code write()}. While the offset and return
* value may depend on the arithmetic, the actual contents written to the file should not.
*
* @param arithmetic the arithmetic
* @param offset the offset
* @param buf the buffer
* @return the number of bytes written
*/
T write(PcodeArithmetic<T> arithmetic, T offset, T buf);
/**
* Erase the contents of the file
*/
void truncate();
/**
* Get the file's {@code stat} structure, as defined by the simulator.
*
* @return the stat
*/
EmuUnixFileStat getStat();
/**
* Check if the given user can read this file
*
* @param user the user
* @return true if permitted, false otherwise
*/
default boolean isReadable(EmuUnixUser user) {
return getStat().hasPermissions(EmuUnixFileStat.MODE_R, user);
}
/**
* Check if the given user can write this file
*
* @param user the user
* @return true if permitted, false otherwise
*/
default boolean isWritable(EmuUnixUser user) {
return getStat().hasPermissions(EmuUnixFileStat.MODE_W, user);
}
/**
* Require the user to have read permission on this file, throwing {@link EmuIOException} if not
*
* @param user the user
*/
default void checkReadable(EmuUnixUser user) {
if (!isReadable(user)) {
throw new EmuIOException("The file " + getPathname() + " cannot be read.");
}
}
/**
* Require the user to have write permission on this file, throwing {@link EmuIOException} if
* not
*
* @param user the user
*/
default void checkWritable(EmuUnixUser user) {
if (!isWritable(user)) {
throw new EmuIOException("The file " + getPathname() + " cannot be written.");
}
}
}

View file

@ -0,0 +1,81 @@
/* ###
* 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.emu.unix;
import ghidra.pcode.emu.sys.EmuIOException;
/**
* A process's handle to a file (or other resource)
*
* @param <T> the type of values stored in the file
*/
public interface EmuUnixFileDescriptor<T> {
/**
* The default file descriptor for stdin (standard input)
*/
int FD_STDIN = 0;
/**
* The default file descriptor for stdout (standard output)
*/
int FD_STDOUT = 1;
/**
* The default file descriptor for stderr (standard error output)
*/
int FD_STDERR = 2;
/**
* Get the current offset of the file, or 0 if not applicable
*
* @return the offset
*/
T getOffset();
/**
* See to the given offset
*
* @param offset the desired offset
* @throws EmuIOException if an error occurred
*/
void seek(T offset) throws EmuIOException;
/**
* Read from the file opened by this handle
*
* @param buf the destination buffer
* @return the number of bytes read
* @throws EmuIOException if an error occurred
*/
T read(T buf) throws EmuIOException;
/**
* Read into the file opened by this handle
*
* @param buf the source buffer
* @return the number of bytes written
* @throws EmuIOException if an error occurred
*/
T write(T buf) throws EmuIOException;
/**
* Obtain the {@code stat} structure of the file opened by this handle
*/
EmuUnixFileStat stat();
/**
* Close this descriptor
*/
void close();
}

View file

@ -0,0 +1,83 @@
/* ###
* 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.emu.unix;
/**
* Collects the {@code stat} fields common to UNIX platforms
*
* <p>
* See a UNIX manual for the exact meaning of each field.
*
* <p>
* TODO: Should this be parameterized with T?
*
* <p>
* TODO: Are these specific to Linux, or all UNIX?
*/
public class EmuUnixFileStat {
/**
* The mode bit indicating read permission
*/
public static final int MODE_R = 04;
/**
* The mode bit indicating write permission
*/
public static final int MODE_W = 02;
/**
* The mode bit indicating execute permission
*/
public static final int MODE_X = 01;
public long st_dev;
public long st_ino;
public int st_mode;
public long st_nlink;
public int st_uid;
public int st_gid;
public long st_rdev;
public long st_size;
public long st_blksize;
public long st_blocks;
public long st_atim_sec;
public long st_atim_nsec;
public long st_mtim_sec;
public long st_mtim_nsec;
public long st_ctim_sec;
public long st_ctim_nsec;
/**
* Check if the given user has the requested permissions on the file described by this stat
*
* @param req the requested permissions
* @param user the user requesting permission
* @return true if permitted, false if denied
*/
public boolean hasPermissions(int req, EmuUnixUser user) {
// TODO: Care to simulate 'root'?
if ((st_mode & req) == req) {
return true;
}
if (((st_mode >> 6) & req) == req && user.uid == st_uid) {
return true;
}
if (((st_mode >> 3) & req) == req && user.gids.contains(st_gid)) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,167 @@
/* ###
* 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.emu.unix;
import java.util.*;
import ghidra.pcode.emu.sys.EmuIOException;
/**
* A simulated UNIX file system
*
* @param <T> the type of values stored in the files
*/
public interface EmuUnixFileSystem<T> {
/**
* Open flags as defined by the simulator
*
* <p>
* See a UNIX manual for the exact meaning of each.
*/
enum OpenFlag {
O_RDONLY,
O_WRONLY,
O_RDWR,
O_CREAT,
O_TRUNC,
O_APPEND;
/**
* Construct a set of flags
*
* @param flags the flags
* @return the set
*/
public static Set<OpenFlag> set(OpenFlag... flags) {
return set(Arrays.asList(flags));
}
/**
* Construct a set of flags
*
* @param flags the flags
* @return the set
*/
public static Set<OpenFlag> set(Collection<OpenFlag> flags) {
if (flags.contains(O_RDONLY) && flags.contains(O_WRONLY)) {
throw new IllegalArgumentException("Cannot be read only and write only");
}
if (flags instanceof EnumSet) {
return Collections.unmodifiableSet((EnumSet<OpenFlag>) flags);
}
return Collections.unmodifiableSet(EnumSet.copyOf(flags));
}
/**
* Check if the given flags indicate open for reading
*
* @param flags the flags
* @return true for reading
*/
public static boolean isRead(Collection<OpenFlag> flags) {
return flags.contains(OpenFlag.O_RDONLY) || flags.contains(OpenFlag.O_RDWR);
}
/**
* Check if the given flags indicate open for writing
*
* @param flags the flags
* @return true for writing
*/
public static boolean isWrite(Collection<OpenFlag> flags) {
return flags.contains(OpenFlag.O_WRONLY) || flags.contains(OpenFlag.O_RDWR);
}
}
/**
* A factory for constructing a new file (without adding it to the file system)
*
* @param pathname the path of the file
* @param the mode of the new file
* @return the new file
* @throws EmuIOException if the file cannot be constructed
*/
EmuUnixFile<T> newFile(String pathname, int mode) throws EmuIOException;
/**
* Get the named file, creating it if it doesn't already exist
*
* <p>
* This is accessed by the emulator user, not the target program.
*
* @param pathname the pathname of the requested file
* @param mode the mode of a created file. Ignored if the file exists
* @return the file
* @throws EmuIOException if an error occurred
*/
EmuUnixFile<T> createOrGetFile(String pathname, int mode) throws EmuIOException;
/**
* Get the named file
*
* <p>
* This is accessed by the emulator user, not the target program.
*
* @param pathname the pathname of the requested file
* @return the file, or {@code null} if it doesn't exist
* @throws EmuIOException if an error occurred
*/
EmuUnixFile<T> getFile(String pathname) throws EmuIOException;
/**
* Place the given file at the given location
*
* <p>
* This is accessed by the emulator user, not the target program. If the file already exists, it
* is replaced silently.
*
* @param pathname the pathname of the file
* @param file the file, presumably having the same pathname
* @throws EmuIOException if an error occurred
*/
void putFile(String pathname, EmuUnixFile<T> file) throws EmuIOException;
/**
* Remove the file at the given location
*
* <p>
* TODO: Separate the user-facing routine from the target-facing routine.
*
* <p>
* If the file does not exist, this has no effect.
*
* @param pathname the pathname of the file to unlink
* @param user the user requesting the unlink
* @throws EmuIOException if an error occurred
*/
void unlink(String pathname, EmuUnixUser user) throws EmuIOException;
/**
* Open the requested file according to the given flags and user
*
* <p>
* This is generally accessed by the target program via a {@link DefaultEmuUnixFileHandle}.
*
* @param pathname the pathname of the requested file
* @param flags the requested open flags
* @param user the user making the request
* @param mode the mode to assign the file, if created. Otherwise ignored
* @return the file
* @throws EmuIOException if an error occurred, e.g., file not found, or access denied
*/
EmuUnixFile<T> open(String pathname, Set<OpenFlag> flags, EmuUnixUser user, int mode)
throws EmuIOException;
}

View file

@ -0,0 +1,43 @@
/* ###
* 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.emu.unix;
import java.util.Collection;
import java.util.Set;
/**
* A simulated UNIX user
*/
public class EmuUnixUser {
/**
* The default (root?) user
*/
public static final EmuUnixUser DEFAULT_USER = new EmuUnixUser(0, Set.of());
public final int uid;
public final Collection<Integer> gids;
/**
* Construct a new user
*
* @param uid the user's uid
* @param gids the user's gids
*/
public EmuUnixUser(int uid, Collection<Integer> gids) {
this.uid = uid;
this.gids = gids;
}
}

View file

@ -0,0 +1,120 @@
/* ###
* 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.emu.unix;
import java.io.*;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.sys.EmuIOException;
import ghidra.program.model.lang.CompilerSpec;
/**
* A simulated file descriptor that proxies a host resource, typically a console/terminal
*/
public class IOStreamEmuUnixFileHandle extends AbstractStreamEmuUnixFileHandle<byte[]> {
/**
* Construct a proxy for the host's standard input
*
* @param machine the machine emulating the hardware
* @param cSpec the ABI of the target platform
* @return the proxy's handle
*/
public static IOStreamEmuUnixFileHandle stdin(PcodeMachine<byte[]> machine,
CompilerSpec cSpec) {
return new IOStreamEmuUnixFileHandle(machine, cSpec, System.in, null);
}
/**
* Construct a proxy for the host's standard output
*
* @param machine the machine emulating the hardware
* @param cSpec the ABI of the target platform
* @return the proxy's handle
*/
public static IOStreamEmuUnixFileHandle stdout(PcodeMachine<byte[]> machine,
CompilerSpec cSpec) {
return new IOStreamEmuUnixFileHandle(machine, cSpec, null, System.out);
}
/**
* Construct a proxy for the host's standard error output
*
* @param machine the machine emulating the hardware
* @param cSpec the ABI of the target platform
* @return the proxy's handle
*/
public static IOStreamEmuUnixFileHandle stderr(PcodeMachine<byte[]> machine,
CompilerSpec cSpec) {
return new IOStreamEmuUnixFileHandle(machine, cSpec, null, System.err);
}
protected final InputStream input;
protected final OutputStream output;
/**
* Construct a proxy for a host resource
*
* <p>
* <b>WARNING:</b> Think carefully before proxying any host resource to a temperamental target
* program.
*
* @param machine the machine emulating the hardware
* @param cSpec the ABI of the target platform
* @param input the stream representing the input side of the descriptor, if applicable
* @param output the stream representing the output side of the descriptor, if applicable
* @return the proxy's handle
*/
public IOStreamEmuUnixFileHandle(PcodeMachine<byte[]> machine, CompilerSpec cSpec,
InputStream input, OutputStream output) {
super(machine, cSpec);
this.input = input;
this.output = output;
}
@Override
public byte[] read(byte[] buf) throws EmuIOException {
if (input == null) {
return arithmetic.fromConst(0, offsetBytes);
}
try {
int result = input.read(buf);
return arithmetic.fromConst(result, offsetBytes);
}
catch (IOException e) {
throw new EmuIOException("Could not read host input stream", e);
}
}
@Override
public byte[] write(byte[] buf) throws EmuIOException {
if (output == null) {
return arithmetic.fromConst(0, offsetBytes);
}
try {
output.write(buf);
return arithmetic.fromConst(buf.length, offsetBytes);
}
catch (IOException e) {
throw new EmuIOException("Could not write host output stream", e);
}
}
@Override
public void close() {
// TODO: Is it my responsibility to close the streams?
}
}

View file

@ -0,0 +1,162 @@
/* ###
* 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.exec;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.mem.*;
import ghidra.util.Msg;
/**
* An abstract p-code executor state for storing bytes, retrieved and set as arrays.
*
* @param <B> if this state is a cache, the type of object backing each address space
* @param <S> the type of an execute state space, internally associated with an address space
*/
public abstract class AbstractBytesPcodeExecutorState<B, S extends BytesPcodeExecutorStateSpace<B>>
extends AbstractLongOffsetPcodeExecutorState<byte[], S> {
/**
* A memory buffer bound to a given space in this state
*/
protected class StateMemBuffer implements MemBufferAdapter {
protected final Address address;
protected final BytesPcodeExecutorStateSpace<B> source;
/**
* Construct a buffer bound to the given space, at the given address
*
* @param address the address
* @param source the space
*/
public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace<B> source) {
this.address = address;
this.source = source;
}
@Override
public Address getAddress() {
return address;
}
@Override
public Memory getMemory() {
throw new UnsupportedOperationException();
}
@Override
public boolean isBigEndian() {
return language.isBigEndian();
}
@Override
public int getBytes(ByteBuffer buffer, int addressOffset) {
byte[] data = source.read(address.getOffset() + addressOffset, buffer.remaining());
buffer.put(data);
return data.length;
}
}
protected final Map<AddressSpace, S> spaces = new HashMap<>();
protected final Language language;
/**
* Construct a state for the given language
*
* @param language the langauge (used for its memory model)
*/
public AbstractBytesPcodeExecutorState(Language language) {
super(language, BytesPcodeArithmetic.forLanguage(language));
this.language = language;
}
@Override
protected long offsetToLong(byte[] offset) {
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
}
@Override
public byte[] longToOffset(AddressSpace space, long l) {
return arithmetic.fromConst(l, space.getPointerSize());
}
/**
* If this state is a cache, get the object backing the given address space
*
* @param space the space
* @return the backing object
*/
protected B getBacking(AddressSpace space) {
return null;
}
/**
* Construct a new space internally associated with the given address space, having the given
* backing
*
* <p>
* As the name implies, this often simply wraps {@code S}'s constructor
*
* @param space the address space
* @param backing the backing, if applicable
* @return the new space
*/
protected abstract S newSpace(AddressSpace space, B backing);
@Override
protected S getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space, s -> {
B backing = s.isUniqueSpace() ? null : getBacking(space);
return newSpace(s, backing);
});
}
@Override
protected void setInSpace(S space, long offset, int size, byte[] val) {
if (val.length > size) {
throw new IllegalArgumentException(
"Value is larger than variable: " + val.length + " > " + size);
}
if (val.length < size) {
Msg.warn(this, "Value is smaller than variable: " + val.length + " < " + size +
". Zero extending");
val = arithmetic.unaryOp(PcodeArithmetic.INT_ZEXT, size, val.length, val);
}
space.write(offset, val, 0, size);
}
@Override
protected byte[] getFromSpace(S space, long offset, int size) {
byte[] read = space.read(offset, size);
if (read.length != size) {
throw new AccessPcodeExecutionException("Incomplete read (" + read.length +
" of " + size + " bytes)");
}
return read;
}
@Override
public MemBuffer getConcreteBuffer(Address address) {
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));
}
}

View file

@ -17,6 +17,12 @@ package ghidra.pcode.exec;
import ghidra.program.model.lang.Language;
/**
* A device in the type hierarchy that turns a suitable state piece into a state
*
* @param <T> the type of values and addresses in the state
* @param <S> the type of an execute state space, internally associated with an address space
*/
public abstract class AbstractLongOffsetPcodeExecutorState<T, S>
extends AbstractLongOffsetPcodeExecutorStatePiece<T, T, S>
implements PcodeExecutorState<T> {
@ -24,5 +30,4 @@ public abstract class AbstractLongOffsetPcodeExecutorState<T, S>
public AbstractLongOffsetPcodeExecutorState(Language language, PcodeArithmetic<T> arithmetic) {
super(language, arithmetic);
}
}

View file

@ -18,6 +18,13 @@ package ghidra.pcode.exec;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
/**
* An abstract executor state piece which internally uses {@code long} to address contents
*
* @param <A> the type used to address contents, convertible to and from {@code long}
* @param <T> the type of values stored
* @param <S> the type of an execute state space, internally associated with an address space
*/
public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
implements PcodeExecutorStatePiece<A, T> {
@ -25,6 +32,12 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
protected final PcodeArithmetic<T> arithmetic;
protected final AddressSpace uniqueSpace;
/**
* Construct a state piece for the given language and arithmetic
*
* @param language the langauge (used for its memory model)
* @param arithmetic an arithmetic used to generate default values of {@code T}
*/
public AbstractLongOffsetPcodeExecutorStatePiece(Language language,
PcodeArithmetic<T> arithmetic) {
this.language = language;
@ -32,26 +45,87 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
uniqueSpace = language.getAddressFactory().getUniqueSpace();
}
/**
* Set a value in the unique space
*
* <p>
* Some state pieces treat unique values in a way that merits a separate implementation. This
* permits the standard path to be overridden.
*
* @param offset the offset in unique space to store the value
* @param size the number of bytes to write (the size of the value)
* @param val the value to store
*/
protected void setUnique(long offset, int size, T val) {
S s = getForSpace(uniqueSpace, true);
setInSpace(s, offset, size, val);
}
/**
* Get a value from the unique space
*
* Some state pieces treat unique values in a way that merits a separate implementation. This
* permits the standard path to be overridden.
*
* @param offset the offset in unique space to get the value
* @param size the number of bytes to read (the size of the value)
* @return the read value
*/
protected T getUnique(long offset, int size) {
S s = getForSpace(uniqueSpace, false);
return getFromSpace(s, offset, size);
}
/**
* Get the internal space for the given address space
*
* @param space the address space
* @param toWrite in case internal spaces are generated lazily, this indicates the space must be
* present, because it is going to be written to.
* @return the space, or {@code null}
*/
protected abstract S getForSpace(AddressSpace space, boolean toWrite);
/**
* Set a value in the given space
*
* @param space the address space
* @param offset the offset within the space
* @param size the number of bytes to write (the size of the value)
* @param val the value to store
*/
protected abstract void setInSpace(S space, long offset, int size, T val);
/**
* Get a value from the given space
*
* @param space the address space
* @param offset the offset within the space
* @param size the number of bytes to read (the size of the value)
* @return the read value
*/
protected abstract T getFromSpace(S space, long offset, int size);
/**
* In case spaces are generated lazily, and we're reading from a space that doesn't yet exist,
* "read" a default value.
*
* <p>
* By default, the returned value is 0, which should be reasonable for all implementations.
*
* @param size the number of bytes to read (the size of the value)
* @return the default value
*/
protected T getFromNullSpace(int size) {
return arithmetic.fromConst(0, size);
}
/**
* Convert an offset of type {@code A} to {@code long}
*
* @param offset the offset as an {@code A}
* @return the offset as a long
*/
protected abstract long offsetToLong(A offset);
@Override

View file

@ -17,15 +17,33 @@ package ghidra.pcode.exec;
import ghidra.program.model.address.AddressSpace;
/**
* An executor state decorator which transforms the offset type
*
* @param <A> the offset type of the decorator
* @param <B> the offset type of the delegate
* @param <T> the type of values
*/
public abstract class AbstractOffsetTransformedPcodeExecutorState<A, B, T>
implements PcodeExecutorStatePiece<A, T> {
private final PcodeExecutorStatePiece<B, T> state;
/**
* Construct a decorator around the given delegate
*
* @param state the delegate
*/
public AbstractOffsetTransformedPcodeExecutorState(PcodeExecutorStatePiece<B, T> state) {
this.state = state;
}
/**
* Transform an offset of type {@code A} to type {@code B}
*
* @param offset the offset as an {@code A}
* @return the offset as a {@code B}
*/
protected abstract B transformOffset(A offset);
@Override

View file

@ -21,8 +21,21 @@ import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.program.model.address.Address;
/**
* A rider arithmetic that reports the address of the control value
*
* <p>
* This is intended for use as the right side of a {@link PairedPcodeArithmetic}. Note that constant
* and unique spaces are never returned. Furthermore, any computation performed on a value,
* producing a temporary value, philosophically does not exist at any address in the state. Thus,
* every operation in this arithmetic results in {@code null}. The accompanying state
* {@link AddressOfPcodeExecutorState} does the real "address of" logic.
*/
public enum AddressOfPcodeArithmetic implements PcodeArithmetic<Address> {
// NB: No temp value has a real address
/**
* The singleton instance.
*/
INSTANCE;
@Override
@ -55,4 +68,9 @@ public enum AddressOfPcodeArithmetic implements PcodeArithmetic<Address> {
public BigInteger toConcrete(Address value, boolean isContextreg) {
throw new AssertionError("Should not attempt to concretize 'address of'");
}
@Override
public Address sizeOf(Address value) {
return fromConst(value.getAddressSpace().getSize() / 8, SIZEOF_SIZEOF);
}
}

View file

@ -23,11 +23,26 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.MemBuffer;
/**
* A rider state piece that reports the address of the control value
*
* <p>
* This is intended for use as the right side of a {@link PairedPcodeExecutorState} or
* {@link PairedPcodeExecutorStatePiece}. Except for unique spaces, sets are ignored, and gets
* simply echo back the address of the requested read. In unique spaces, the "address of" is treated
* as the value, so that values transiting unique space can correctly have their source addresses
* reported.
*/
public class AddressOfPcodeExecutorState
implements PcodeExecutorStatePiece<byte[], Address> {
private final boolean isBigEndian;
private Map<Long, Address> unique = new HashMap<>();
/**
* Construct an "address of" state piece
*
* @param isBigEndian true if the control language is big endian
*/
public AddressOfPcodeExecutorState(boolean isBigEndian) {
this.isBigEndian = isBigEndian;
}

View file

@ -0,0 +1,557 @@
/* ###
* 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.exec;
import java.lang.annotation.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.reflect.TypeUtils;
import ghidra.program.model.pcode.Varnode;
import utilities.util.AnnotationUtilities;
/**
* A userop library wherein Java methods are exported via a special annotation
*
* @param <T> the type of data processed by the library
*/
public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibrary<T> {
private static final Map<Class<?>, Set<Method>> CACHE_BY_CLASS = new HashMap<>();
private static Set<Method> collectDefinitions(
Class<? extends AnnotatedPcodeUseropLibrary<?>> cls) {
return AnnotationUtilities.collectAnnotatedMethods(PcodeUserop.class, cls);
}
private enum ParamAnnotProc {
EXECUTOR(OpExecutor.class, PcodeExecutor.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posExecutor;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posExecutor = pos;
}
},
STATE(OpState.class, PcodeExecutorStatePiece.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posState;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posState = pos;
}
},
LIBRARY(OpLibrary.class, PcodeUseropLibrary.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posLib;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posLib = pos;
}
},
OUTPUT(OpOutput.class, Varnode.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posOut;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posOut = pos;
}
};
static boolean processParameter(AnnotatedPcodeUseropDefinition<?> opdef, int i,
Parameter p) {
ParamAnnotProc only = null;
for (ParamAnnotProc proc : ParamAnnotProc.values()) {
if (proc.hasAnnot(p)) {
if (only != null) {
throw new IllegalArgumentException("Parameter can have at most one of " +
Stream.of(ParamAnnotProc.values())
.map(pr -> "@" + pr.annotCls.getSimpleName())
.collect(Collectors.toList()));
}
only = proc;
}
}
if (only == null) {
return false;
}
only.processParameterPerAnnot(opdef, i, p);
return true;
}
private final Class<? extends Annotation> annotCls;
private final Class<?> paramCls;
private ParamAnnotProc(Class<? extends Annotation> annotCls, Class<?> paramCls) {
this.annotCls = annotCls;
this.paramCls = paramCls;
}
abstract int getPos(AnnotatedPcodeUseropDefinition<?> opdef);
abstract void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos);
boolean hasAnnot(Parameter p) {
return p.getAnnotation(annotCls) != null;
}
void processParameterPerAnnot(AnnotatedPcodeUseropDefinition<?> opdef, int i,
Parameter p) {
if (getPos(opdef) != -1) {
throw new IllegalArgumentException(
"Can only have one parameter with @" + annotCls.getSimpleName());
}
if (!p.getType().isAssignableFrom(paramCls)) {
throw new IllegalArgumentException("Parameter " + p.getName() + " with @" +
annotCls.getSimpleName() + " must acccept " + paramCls.getSimpleName());
}
setPos(opdef, i);
}
}
/**
* A wrapped, annotated Java method, exported as a userop definition
*
* @param <T> the type of data processed by the userop
*/
protected static abstract class AnnotatedPcodeUseropDefinition<T>
implements PcodeUseropDefinition<T> {
protected static <T> AnnotatedPcodeUseropDefinition<T> create(PcodeUserop annot,
AnnotatedPcodeUseropLibrary<T> library, Class<T> opType, Lookup lookup,
Method method) {
if (annot.variadic()) {
return new VariadicAnnotatedPcodeUseropDefinition<>(library, opType, lookup,
method);
}
else {
return new FixedArgsAnnotatedPcodeUseropDefinition<>(library, opType, lookup,
method);
}
}
protected final Method method;
private final MethodHandle handle;
private int posExecutor = -1;
private int posState = -1;
private int posLib = -1;
private int posOut = -1;
public AnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary<T> library,
Class<T> opType, Lookup lookup, Method method) {
initStarting();
this.method = method;
try {
this.handle = lookup.unreflect(method).bindTo(library);
}
catch (IllegalAccessException e) {
throw new IllegalArgumentException(
"Cannot access " + method + " having @" +
PcodeUserop.class.getSimpleName() +
" annotation. Override getMethodLookup()");
}
Class<?> rType = method.getReturnType();
if (rType != void.class && !opType.isAssignableFrom(rType)) {
throw new IllegalArgumentException(
"Method " + method.getName() + " with @" +
PcodeUserop.class.getSimpleName() +
" annotation must return void or a type assignable to " +
opType.getSimpleName());
}
Parameter[] params = method.getParameters();
for (int i = 0; i < params.length; i++) {
Parameter p = params[i];
boolean processed = ParamAnnotProc.processParameter(this, i, p);
if (!processed) {
processNonAnnotatedParameter(opType, i, p);
}
}
initFinished();
}
@Override
public String getName() {
return method.getName();
}
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> inVars) {
validateInputs(inVars);
PcodeExecutorStatePiece<T, T> state = executor.getState();
List<Object> args = Arrays.asList(new Object[method.getParameterCount()]);
if (posExecutor != -1) {
args.set(posExecutor, executor);
}
if (posState != -1) {
args.set(posState, state);
}
if (posLib != -1) {
args.set(posLib, library);
}
if (posOut != -1) {
args.set(posOut, outVar);
}
placeInputs(executor, args, inVars);
try {
@SuppressWarnings("unchecked")
T result = (T) handle.invokeWithArguments(args);
if (result != null && outVar != null) {
state.setVar(outVar, result);
}
}
catch (PcodeExecutionException e) {
throw e;
}
catch (Throwable e) {
throw new PcodeExecutionException("Error executing userop", null, e);
}
}
protected void initStarting() {
// Optional override
}
protected abstract void processNonAnnotatedParameter(Class<T> opType, int i,
Parameter p);
protected void initFinished() {
// Optional override
}
protected void validateInputs(List<Varnode> inVars) throws PcodeExecutionException {
// Optional override
}
protected abstract void placeInputs(PcodeExecutor<T> executor, List<Object> args,
List<Varnode> inVars);
}
/**
* An annotated userop with a fixed number of arguments
*
* @param <T> the type of data processed by the userop
*/
protected static class FixedArgsAnnotatedPcodeUseropDefinition<T>
extends AnnotatedPcodeUseropDefinition<T> {
private List<Integer> posIns;
private Set<Integer> posTs;
public FixedArgsAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary<T> library,
Class<T> opType, Lookup lookup, Method method) {
super(library, opType, lookup, method);
}
@Override
protected void initStarting() {
posIns = new ArrayList<>();
posTs = new HashSet<>();
}
@Override
protected void processNonAnnotatedParameter(Class<T> opType, int i, Parameter p) {
if (p.getType().equals(Varnode.class)) {
// Just use the Varnode by default
}
else if (p.getType().isAssignableFrom(opType)) {
posTs.add(i);
}
else {
throw new IllegalArgumentException("Input parameter " + p.getName() +
" of userop " + method.getName() + " must be " +
Varnode.class.getSimpleName() + " or accept " + opType.getSimpleName());
}
posIns.add(i);
}
@Override
protected void validateInputs(List<Varnode> inVars)
throws PcodeExecutionException {
if (inVars.size() != posIns.size()) {
throw new PcodeExecutionException(
"Incorrect input parameter count for userop " +
method.getName() + ". Expected " + posIns.size() + " but got " +
inVars.size());
}
}
@Override
protected void placeInputs(PcodeExecutor<T> executor, List<Object> args,
List<Varnode> inVars) {
PcodeExecutorStatePiece<T, T> state = executor.getState();
for (int i = 0; i < posIns.size(); i++) {
int pos = posIns.get(i);
if (posTs.contains(pos)) {
args.set(pos, state.getVar(inVars.get(i)));
}
else {
args.set(pos, inVars.get(i));
}
}
}
@Override
public int getInputCount() {
return posIns.size();
}
}
/**
* An annotated userop with a variable number of arguments
*
* @param <T> the type of data processed by the userop
*/
protected static class VariadicAnnotatedPcodeUseropDefinition<T>
extends AnnotatedPcodeUseropDefinition<T> {
private int posIns;
private Class<T> opType;
public VariadicAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary<T> library,
Class<T> opType, Lookup lookup, Method method) {
super(library, opType, lookup, method);
}
@Override
protected void initStarting() {
posIns = -1;
opType = null;
}
@Override
protected void processNonAnnotatedParameter(Class<T> opType, int i, Parameter p) {
if (posIns != -1) {
throw new IllegalArgumentException(
"Only one non-annotated parameter is allowed to receive the inputs");
}
if (p.getType().equals(Varnode[].class)) {
// Just pass inVars as is
}
else if (p.getType().isAssignableFrom(Array.newInstance(opType, 0).getClass())) {
this.opType = opType;
}
else {
throw new IllegalArgumentException(
"Variadic userop must receive inputs as T[] or " +
Varnode.class.getSimpleName() + "[]");
}
posIns = i;
}
@Override
protected void initFinished() {
if (posIns == -1) {
throw new IllegalArgumentException(
"Variadic userop must have a parameter for the inputs");
}
}
@Override
protected void placeInputs(PcodeExecutor<T> executor, List<Object> args,
List<Varnode> inVars) {
PcodeExecutorStatePiece<T, T> state = executor.getState();
if (opType != null) {
Stream<T> ts = inVars.stream().map(state::getVar);
@SuppressWarnings("unchecked")
Object valsArr = ts.toArray(l -> (T[]) Array.newInstance(opType, l));
args.set(posIns, valsArr);
}
else {
args.set(posIns, inVars.toArray(Varnode[]::new));
}
}
@Override
public int getInputCount() {
return -1;
}
}
/**
* An annotation to export a Java method as a userop in the library.
*
* <p>
* Ordinarily, each parameter receives an input to the userop. Each parameter may be annotated
* with at most one of {@link OpExecutor}, {@link OpState}, {@link OpLibrary}, or
* {@link OpOutput} to change what it receives. If {@link #variadic()} is false, non-annotated
* parameters receive the inputs to the userop in matching order. Conventionally, annotated
* parameters should be placed first or last. Parameters accepting inputs must have type either
* {@link Varnode} or assignable from {@code T}. A parameter of type {@link Varnode} will
* receive the input {@link Varnode}. A parameter that is assignable from {@code T} will receive
* the input value. If it so happens that {@code T} is assignable from {@link Varnode}, the
* parameter will receive the {@link Varnode}, not the value. <b>NOTE:</b> Receiving a value
* instead of a variable may lose its size. Depending on the type of the value, that size may or
* may not be recoverable.
*
* <p>
* If {@link #variadic()} is true, then a single non-annotated parameter receives all inputs in
* order. This parameter must have a type {@link Varnode}{@code []} to receive variables or have
* type assignable from {@code T[]} to receive values.
*
* <p>
* Note that there is no annotation to receive the "thread," because threads are not a concept
* known to the p-code executor or userop libraries, in general. In most cases, receiving the
* executor and/or state (which are usually bound to a specific thread) is sufficient. The
* preferred means of exposing thread-specific userops is to construct a library bound to that
* specific thread. That strategy should preserve compile-time type safety. Alternatively, you
* can receive the executor or state, cast it to your specific type, and use an accessor to get
* its thread.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PcodeUserop {
/**
* Set to true to receive all inputs in an array
*/
boolean variadic() default false;
}
/**
* An annotation to receive the executor itself into a parameter
*
* <p>
* The annotated parameter must have a type assignable from {@link PcodeExecutor} with parameter
* {@code <T>} matching that of the actual executor. TODO: No "bind-time" check of the type
* parameter is performed. An incorrect parameter will likely cause a {@link ClassCastException}
* despite the lack of any compiler warnings.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpExecutor {
}
/**
* An annotation to receive the executor's state into a parameter
*
* <p>
* The annotated parameter must have a type assignable from {@link PcodeExecutorStatePiece} with
* parameters {@code <T,T>} matching that of the executor. TODO: No "bind-time" check of the
* type parameters is performed. An incorrect parameter will likely cause a
* {@link ClassCastException} despite the lack of any compiler warnings.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpState {
}
/**
* An annotation to receive the complete library into a parameter
*
* <p>
* Because the library defining the userop may be composed with other libraries, it is not
* sufficient to use the "{@code this}" reference to obtain the library. If the library being
* used for execution needs to be passed to a dependent component of execution, it must be the
* complete library, not just the one defining the userop. This annotation allows a userop
* definition to receive the complete library.
*
* <p>
* The annotated parameter must have a type assignable from {@link PcodeUseropLibrary} with
* parameter {@code <T>} matching that of the executor. TODO: No "bind-time" check of the type
* parameters is performed. An incorrect parameter will likely cause a
* {@link ClassCastException} despite the lack of any compiler warnings.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpLibrary {
}
/**
* An annotation to receive the output varnode into a parameter
*
* <p>
* The annotated parameter must have a type assignable from {@link Varnode}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpOutput {
}
protected Map<String, PcodeUseropDefinition<T>> ops = new HashMap<>();
private Map<String, PcodeUseropDefinition<T>> unmodifiableOps =
Collections.unmodifiableMap(ops);
/**
* Default constructor, usually invoked implicitly
*/
public AnnotatedPcodeUseropLibrary() {
Lookup lookup = getMethodLookup();
Class<T> opType = getOperandType();
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<? extends AnnotatedPcodeUseropLibrary<T>> cls = (Class) this.getClass();
Set<Method> methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls));
for (Method m : methods) {
ops.put(m.getName(), AnnotatedPcodeUseropDefinition
.create(m.getAnnotation(PcodeUserop.class), this, opType, lookup, m));
}
}
/**
* Determine the operand type by examining the type substituted for {@code T}
*
* @return the type of data processed by the userop
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Class<T> getOperandType() {
Map<TypeVariable<?>, Type> args =
TypeUtils.getTypeArguments(getClass(), AnnotatedPcodeUseropLibrary.class);
if (args == null) {
return (Class) Object.class;
}
Type type = args.get(AnnotatedPcodeUseropLibrary.class.getTypeParameters()[0]);
if (!(type instanceof Class<?>)) {
return (Class) Object.class;
}
return (Class) type;
}
/**
* An override to provide method access, if any non-public method is exported as a userop.
*
* @return a lookup that can access all {@link PcodeUserop}-annotated methods.
*/
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@Override
public Map<String, PcodeUseropDefinition<T>> getUserops() {
return unmodifiableOps;
}
}

View file

@ -1,169 +0,0 @@
/* ###
* 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.exec;
import java.lang.annotation.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.util.*;
import org.apache.commons.lang3.reflect.TypeUtils;
import ghidra.program.model.pcode.Varnode;
public abstract class AnnotatedSleighUseropLibrary<T> implements SleighUseropLibrary<T> {
private static final Map<Class<?>, Set<Method>> CACHE_BY_CLASS = new HashMap<>();
private static Set<Method> collectDefinitions(
Class<? extends AnnotatedSleighUseropLibrary<?>> cls) {
Set<Method> defs = new HashSet<>();
collectDefinitions(cls, defs, new HashSet<>());
return defs;
}
private static void collectDefinitions(Class<?> cls, Set<Method> defs, Set<Class<?>> visited) {
if (!visited.add(cls)) {
return;
}
Class<?> superCls = cls.getSuperclass();
if (superCls != null) {
collectDefinitions(superCls, defs, visited);
}
for (Class<?> superIf : cls.getInterfaces()) {
collectDefinitions(superIf, defs, visited);
}
collectClassDefinitions(cls, defs);
}
private static void collectClassDefinitions(Class<?> cls, Set<Method> defs) {
for (Method method : cls.getDeclaredMethods()) {
SleighUserop annot = method.getAnnotation(SleighUserop.class);
if (annot == null) {
continue;
}
defs.add(method);
}
}
static class AnnotatedSleighUseropDefinition<T> implements SleighUseropDefinition<T> {
private final Method method;
private final MethodHandle handle;
public AnnotatedSleighUseropDefinition(AnnotatedSleighUseropLibrary<T> library,
Class<T> opType, Lookup lookup, Method method) {
this.method = method;
try {
this.handle = lookup.unreflect(method).bindTo(library);
}
catch (IllegalAccessException e) {
throw new AssertionError("Cannot access " + method + " having @" +
SleighUserop.class.getSimpleName() + " annotation. Override getMethodLookup()");
}
for (Class<?> ptype : method.getParameterTypes()) {
if (Varnode.class.isAssignableFrom(ptype)) {
continue;
}
if (opType.isAssignableFrom(ptype)) {
continue;
}
throw new IllegalArgumentException(
"pcode userops can only take Varnode inputs");
}
}
@Override
public String getName() {
return method.getName();
}
@Override
public int getOperandCount() {
return method.getParameterCount();
}
@Override
public void execute(PcodeExecutorStatePiece<T, T> state, Varnode outVar,
List<Varnode> inVars) {
// outVar is ignored
List<Object> args = Arrays.asList(new Object[inVars.size()]);
Class<?>[] ptypes = method.getParameterTypes();
for (int i = 0; i < args.size(); i++) {
if (Varnode.class.isAssignableFrom(ptypes[i])) {
args.set(i, inVars.get(i));
}
else {
args.set(i, state.getVar(inVars.get(i)));
}
}
try {
handle.invokeWithArguments(args);
}
catch (PcodeExecutionException e) {
throw e;
}
catch (Throwable e) {
throw new PcodeExecutionException("Error executing userop", null, e);
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SleighUserop {
}
Map<String, SleighUseropDefinition<T>> ops = new HashMap<>();
public AnnotatedSleighUseropLibrary() {
Lookup lookup = getMethodLookup();
Class<T> opType = getOperandType();
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<? extends AnnotatedSleighUseropLibrary<T>> cls = (Class) this.getClass();
Set<Method> methods;
synchronized (CACHE_BY_CLASS) {
methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls));
}
for (Method m : methods) {
ops.put(m.getName(), new AnnotatedSleighUseropDefinition<>(this, opType, lookup, m));
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Class<T> getOperandType() {
Map<TypeVariable<?>, Type> args =
TypeUtils.getTypeArguments(getClass(), AnnotatedSleighUseropLibrary.class);
if (args == null) {
return (Class) Object.class;
}
Type type = args.get(AnnotatedSleighUseropLibrary.class.getTypeParameters()[0]);
if (!(type instanceof Class<?>)) {
return (Class) Object.class;
}
return (Class) type;
}
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@Override
public Map<String, SleighUseropDefinition<T>> getUserops() {
return ops;
}
}

View file

@ -20,6 +20,12 @@ import java.math.BigInteger;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
/**
* A p-code arithmetic that operates on {@link BigInteger} values
*
* <p>
* Note: it appears this class is no longer used anywhere, which means it's probably not tested.
*/
@Deprecated(forRemoval = true) // TODO: Not getting used
public enum BigIntegerPcodeArithmetic implements PcodeArithmetic<BigInteger> {
INSTANCE;
@ -54,4 +60,10 @@ public enum BigIntegerPcodeArithmetic implements PcodeArithmetic<BigInteger> {
public BigInteger toConcrete(BigInteger value, boolean isContextreg) {
return value;
}
@Override
public BigInteger sizeOf(BigInteger value) {
// NOTE: Determining the minimum necessary size to contain it is not correct.
throw new AssertionError("Size is not known");
}
}

View file

@ -22,13 +22,39 @@ import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Language;
/**
* A p-code arithmetic that operates on byte array values
*
* <p>
* The arithmetic interprets the arrays as big- or little-endian values, then performs the
* arithmetic as specified by the p-code operation.
*/
public enum BytesPcodeArithmetic implements PcodeArithmetic<byte[]> {
BIG_ENDIAN(true), LITTLE_ENDIAN(false);
/**
* The instance which interprets arrays as big-endian values
*/
BIG_ENDIAN(true),
/**
* The instance which interprets arrays as little-endian values
*/
LITTLE_ENDIAN(false);
/**
* Obtain the instance for the given endianness
*
* @param bigEndian true for {@link #BIG_ENDIAN}, false of {@link #LITTLE_ENDIAN}
* @return the arithmetic
*/
public static BytesPcodeArithmetic forEndian(boolean bigEndian) {
return bigEndian ? BIG_ENDIAN : LITTLE_ENDIAN;
}
/**
* Obtain the instance for the given language's endianness
*
* @param language the language
* @return the arithmetic
*/
public static BytesPcodeArithmetic forLanguage(Language language) {
return forEndian(language.isBigEndian());
}
@ -94,4 +120,9 @@ public enum BytesPcodeArithmetic implements PcodeArithmetic<byte[]> {
public BigInteger toConcrete(byte[] value, boolean isContextreg) {
return Utils.bytesToBigInteger(value, value.length, isBigEndian || isContextreg, false);
}
@Override
public byte[] sizeOf(byte[] value) {
return fromConst(value.length, SIZEOF_SIZEOF);
}
}

View file

@ -0,0 +1,40 @@
/* ###
* 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.exec;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
/**
* A plain concrete state suitable for simple emulation, without any backing objects
*/
public class BytesPcodeExecutorState
extends AbstractBytesPcodeExecutorState<Void, BytesPcodeExecutorStateSpace<Void>> {
/**
* Construct a state for the given language
*
* @param langauge the language (used for its memory model)
*/
public BytesPcodeExecutorState(Language language) {
super(language);
}
@Override
protected BytesPcodeExecutorStateSpace<Void> newSpace(AddressSpace space, Void backing) {
return new BytesPcodeExecutorStateSpace<>(language, space, backing);
}
}

View file

@ -0,0 +1,173 @@
/* ###
* 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.exec;
import java.util.*;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.util.Msg;
/**
* A p-code executor state space for storing bytes, retrieved and set as arrays.
*
* @param <B> if this space is a cache, the type of object backing this space
*/
public class BytesPcodeExecutorStateSpace<B> {
protected final SemisparseByteArray bytes = new SemisparseByteArray();
protected final Language language; // for logging diagnostics
protected final AddressSpace space;
protected final B backing;
/**
* Construct an internal space for the given address space
*
* @param language the language, for logging diagnostics
* @param space the address space
* @param backing the backing object, possibly {@code null}
*/
public BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing) {
this.language = language;
this.space = space;
this.backing = backing;
}
/**
* Write a value at the given offset
*
* @param offset the offset
* @param val the value
*/
public void write(long offset, byte[] val, int srcOffset, int length) {
bytes.putData(offset, val, srcOffset, length);
}
/**
* Utility for handling uninitialized ranges: Get the lower endpoint
*
* @param rng the range
* @return the lower endpoint
*/
public long lower(Range<UnsignedLong> rng) {
return rng.lowerBoundType() == BoundType.CLOSED
? rng.lowerEndpoint().longValue()
: rng.lowerEndpoint().longValue() + 1;
}
/**
* Utility for handling uninitialized ranges: Get the upper endpoint
*
* @param rng the range
* @return the upper endpoint
*/
public long upper(Range<UnsignedLong> rng) {
return rng.upperBoundType() == BoundType.CLOSED
? rng.upperEndpoint().longValue()
: rng.upperEndpoint().longValue() - 1;
}
/**
* Extension point: Read from backing into this space, when acting as a cache.
*
* @param uninitialized the ranges which need to be read.
*/
protected void readUninitializedFromBacking(RangeSet<UnsignedLong> uninitialized) {
}
/**
* Read a value from cache (or raw space if not acting as a cache) at the given offset
*
* @param offset the offset
* @param size the number of bytes to read (the size of the value)
* @return the bytes read
*/
protected byte[] readBytes(long offset, int size) {
byte[] data = new byte[size];
bytes.getData(offset, data);
return data;
}
protected AddressRange addrRng(Range<UnsignedLong> rng) {
Address start = space.getAddress(lower(rng));
Address end = space.getAddress(upper(rng));
return new AddressRangeImpl(start, end);
}
protected AddressSet addrSet(RangeSet<UnsignedLong> set) {
AddressSet result = new AddressSet();
for (Range<UnsignedLong> rng : set.asRanges()) {
result.add(addrRng(rng));
}
return result;
}
protected Set<Register> getRegs(AddressSet set) {
Set<Register> regs = new TreeSet<>();
for (AddressRange rng : set) {
Register r = language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (r != null) {
regs.add(r);
}
else {
regs.addAll(Arrays.asList(language.getRegisters(rng.getMinAddress())));
}
}
return regs;
}
protected void warnAddressSet(String message, AddressSet set) {
Set<Register> regs = getRegs(set);
if (regs.isEmpty()) {
Msg.warn(this, message + ": " + set);
}
else {
Msg.warn(this, message + ": " + set + " (registers " + regs + ")");
}
}
protected void warnUninit(RangeSet<UnsignedLong> uninit) {
AddressSet uninitialized = addrSet(uninit);
warnAddressSet("Emulator read from uninitialized state", uninitialized);
}
/**
* Read a value from the space at the given offset
*
* <p>
* If this space is not acting as a cache, this simply delegates to
* {@link #readBytes(long, int)}. Otherwise, it will first ensure the cache covers the requested
* value.
*
* @param offset the offset
* @param size the number of bytes to read (the size of the value)
* @return the bytes read
*/
public byte[] read(long offset, int size) {
if (backing != null) {
readUninitializedFromBacking(bytes.getUninitialized(offset, offset + size - 1));
}
RangeSet<UnsignedLong> stillUninit = bytes.getUninitialized(offset, offset + size - 1);
if (!stillUninit.isEmpty()) {
warnUninit(stillUninit);
}
return readBytes(offset, size);
}
}

View file

@ -0,0 +1,70 @@
/* ###
* 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.exec;
import java.util.*;
/**
* A p-code userop library composed of other libraries
*
* @param <T> the type of values processed by the library
*/
public class ComposedPcodeUseropLibrary<T> implements PcodeUseropLibrary<T> {
/**
* Obtain a map representing the composition of userops from all the given libraries
*
* <p>
* Name collisions are not allowed. If any two libraries export the same symbol, even if the
* definitions happen to do the same thing, it is an error.
*
* @param <T> the type of values processed by the libraries
* @param libraries the libraries whose userops to collect
* @return the resulting map
*/
public static <T> Map<String, PcodeUseropDefinition<T>> composeUserops(
Collection<PcodeUseropLibrary<T>> libraries) {
Map<String, PcodeUseropDefinition<T>> userops = new HashMap<>();
for (PcodeUseropLibrary<T> lib : libraries) {
for (PcodeUseropDefinition<T> def : lib.getUserops().values()) {
if (userops.put(def.getName(), def) != null) {
throw new IllegalArgumentException(
"Cannot compose libraries with conflicting definitions on " +
def.getName());
}
}
}
return userops;
}
private final Map<String, PcodeUseropDefinition<T>> userops;
/**
* Construct a composed userop library from the given libraries
*
* <p>
* This uses {@link #composeUserops(Collection)}, so its restrictions apply here, too.
*
* @param libraries the libraries
*/
public ComposedPcodeUseropLibrary(Collection<PcodeUseropLibrary<T>> libraries) {
this.userops = composeUserops(libraries);
}
@Override
public Map<String, PcodeUseropDefinition<T>> getUserops() {
return userops;
}
}

View file

@ -1,46 +0,0 @@
/* ###
* 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.exec;
import java.util.*;
public class ComposedSleighUseropLibrary<T> implements SleighUseropLibrary<T> {
public static <T> Map<String, SleighUseropDefinition<T>> composeUserops(
Collection<SleighUseropLibrary<T>> libraries) {
Map<String, SleighUseropDefinition<T>> userops = new HashMap<>();
for (SleighUseropLibrary<T> lib : libraries) {
for (SleighUseropDefinition<T> def : lib.getUserops().values()) {
if (userops.put(def.getName(), def) != null) {
throw new IllegalArgumentException(
"Cannot compose libraries with conflicting definitions on " +
def.getName());
}
}
}
return userops;
}
private final Map<String, SleighUseropDefinition<T>> userops;
public ComposedSleighUseropLibrary(Collection<SleighUseropLibrary<T>> libraries) {
this.userops = composeUserops(libraries);
}
@Override
public Map<String, SleighUseropDefinition<T>> getUserops() {
return userops;
}
}

View file

@ -15,6 +15,12 @@
*/
package ghidra.pcode.exec;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
/**
* Exception thrown by {@link PcodeEmulationLibrary#emu_swi()}, a p-code userop exported by
* emulators for implementing breakpoints.
*/
public class InterruptPcodeExecutionException extends PcodeExecutionException {
public InterruptPcodeExecutionException(PcodeFrame frame, Throwable cause) {
super("Execution hit breakpoint", frame, cause);

View file

@ -25,20 +25,26 @@ import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
/**
* Compose an arithmetic from two.
* An arithmetic composed from two.
*
* <p>
* The new arithmetic operates on tuples where each is subject to its respective arithmetic. One
* exception is {@link #isTrue(Entry)}, which is typically used to control branches. This arithmetic
* defers to "left" arithmetic.
* defers to left ("control") arithmetic.
*
* @param <L> the type of the left element
* @param <R> the type of the right element
* @param <L> the type of the left ("control") element
* @param <R> the type of the right ("rider") element
*/
public class PairedPcodeArithmetic<L, R> implements PcodeArithmetic<Pair<L, R>> {
private final PcodeArithmetic<L> leftArith;
private final PcodeArithmetic<R> rightArith;
/**
* Construct a composed arithmetic from the given two
*
* @param leftArith the left ("control") arithmetic
* @param rightArith the right ("rider") arithmetic
*/
public PairedPcodeArithmetic(PcodeArithmetic<L> leftArith, PcodeArithmetic<R> rightArith) {
this.leftArith = leftArith;
this.rightArith = rightArith;
@ -81,10 +87,27 @@ public class PairedPcodeArithmetic<L, R> implements PcodeArithmetic<Pair<L, R>>
return leftArith.toConcrete(value.getLeft(), isContextreg);
}
@Override
public Pair<L, R> sizeOf(Pair<L, R> value) {
return Pair.of(
leftArith.sizeOf(value.getLeft()),
rightArith.sizeOf(value.getRight()));
}
/**
* Get the left ("control") arithmetic
*
* @return the arithmetic
*/
public PcodeArithmetic<L> getLeft() {
return leftArith;
}
/**
* Get the right ("rider") arithmetic
*
* @return the arithmetic
*/
public PcodeArithmetic<R> getRight() {
return rightArith;
}

View file

@ -26,6 +26,14 @@ import ghidra.program.model.mem.MemBuffer;
* A paired executor state
*
* <p>
* This composes two delegate states "left" and "write" creating a single state which instead stores
* pairs of values, where the left component has the value type of the left state, and the right
* component has the value type of the right state. Note that both states are addressed using only
* the left "control" component. Otherwise, every operation on this state is decomposed into
* operations upon the delegate states, and the final result composed from the results of those
* operations.
*
* <p>
* Where a response cannot be composed of both states, the paired state defers to the left. In this
* way, the left state controls the machine, while the right is computed in tandem. The right never
* directly controls the machine; however, by overriding
@ -43,6 +51,12 @@ public class PairedPcodeExecutorState<L, R>
private final PcodeExecutorStatePiece<L, L> left;
private final PcodeExecutorStatePiece<L, R> right;
/**
* Compose a paired state from the given left and right states
*
* @param left the state backing the left side of paired values ("control")
* @param right the state backing the right side of paired values ("rider")
*/
public PairedPcodeExecutorState(PcodeExecutorStatePiece<L, L> left,
PcodeExecutorStatePiece<L, R> right) {
super(new PairedPcodeExecutorStatePiece<>(left, right));
@ -65,10 +79,20 @@ public class PairedPcodeExecutorState<L, R>
return left.getConcreteBuffer(address);
}
/**
* Get the delegate backing the left side of paired values
*
* @return the left state
*/
public PcodeExecutorStatePiece<L, L> getLeft() {
return left;
}
/**
* Get the delegate backing the right side of paired values
*
* @return the right state
*/
public PcodeExecutorStatePiece<L, R> getRight() {
return right;
}

View file

@ -25,6 +25,14 @@ import ghidra.program.model.mem.MemBuffer;
/**
* A paired executor state piece
*
* <p>
* This compose two delegate pieces "left" and "right" creating a single piece which instead stores
* pairs of values, where the left component has the value type of the left state, and the right
* component has the value type of the right state. Both pieces must have the same address type.
* Every operation on this piece is decomposed into operations upon the delegate pieces, and the
* final result composed from the results of those operations.
*
* @see PairedPcodeExecutorState
* @param <A> the type of offset, usually the type of a controlling state
* @param <L> the type of the "left" state
* @param <R> the type of the "right" state
@ -66,10 +74,20 @@ public class PairedPcodeExecutorStatePiece<A, L, R>
return left.getConcreteBuffer(address);
}
/**
* Get the delegate backing the left side of paired values
*
* @return the left piece
*/
public PcodeExecutorStatePiece<A, L> getLeft() {
return left;
}
/**
* Get the delegate backing the right side of paired values
*
* @return the right piece
*/
public PcodeExecutorStatePiece<A, R> getRight() {
return right;
}

View file

@ -17,29 +17,118 @@ package ghidra.pcode.exec;
import java.math.BigInteger;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.pcode.opbehavior.*;
import ghidra.program.model.pcode.PcodeOp;
/**
* An interface that defines arithmetic p-code operations on values of type {@code T}.
*
* @param <T> the type of values operated on
*/
public interface PcodeArithmetic<T> {
BinaryOpBehavior INT_ADD =
(BinaryOpBehavior) OpBehaviorFactory.getOpBehavior(PcodeOp.INT_ADD);
UnaryOpBehavior INT_ZEXT =
(UnaryOpBehavior) OpBehaviorFactory.getOpBehavior(PcodeOp.INT_ZEXT);
/**
* The number of bytes needed to encode the size (in bytes) of any value
*/
int SIZEOF_SIZEOF = 8;
/**
* The arithmetic for operating on bytes in big-endian
*/
PcodeArithmetic<byte[]> BYTES_BE = BytesPcodeArithmetic.BIG_ENDIAN;
/**
* The arithmetic for operating on bytes in little-endian
*/
PcodeArithmetic<byte[]> BYTES_LE = BytesPcodeArithmetic.LITTLE_ENDIAN;
/**
* The arithmetic for operating on {@link BigInteger}s.
*/
@Deprecated(forRemoval = true) // TODO: Not getting used
PcodeArithmetic<BigInteger> BIGINT = BigIntegerPcodeArithmetic.INSTANCE;
/**
* Apply a unary operator to the given input
*
* <p>
* Note the sizes of variables are given, because values don't necessarily have an intrinsic
* size. For example, a {@link BigInteger} may have a minimum encoding size, but that does not
* necessarily reflect the size of the variable from which is was read.
*
* @param op the behavior of the operator
* @param sizeout the size (in bytes) of the output variable
* @param sizein1 the size (in bytes) of the input variable
* @param in1 the input value
* @return the output value
*/
T unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, T in1);
/**
* Apply a binary operator to the given inputs
*
* <p>
* Note the sizes of variables are given, because values don't necessarily have an intrinsic
* size. For example, a {@link BigInteger} may have a minimum encoding size, but that does not
* necessarily reflect the size of the variable from which is was read.
*
* @param op the behavior of the operator
* @param sizeout the size (in bytes) of the output variable
* @param sizein1 the size (in bytes) of the first (left) input variable
* @param in1 the first (left) input value
* @param sizein2 the size (in bytes) of the second (right) input variable
* @param in2 the second (right) input value
* @return the output value
*/
T binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, T in1, int sizein2, T in2);
/**
* Convert the given constant concrete value to type {@code T} having the given size.
*
* <p>
* Note that the size may not be applicable to {@code T}. It is given to ensure the value can be
* held in a variable of that size when passed to downstream operators or stored in the executor
* state.
*
* @param value the constant value
* @param size the size (in bytes) of the variable into which the value is to be stored
* @return the value as a {@code T}
*/
T fromConst(long value, int size);
/**
* Convert the given constant concrete value to type {@code T} having the given size.
*
* <p>
* Note that the size may not be applicable to {@code T}. It is given to ensure the value can be
* held in a variable of that size when passed to downstream operators or stored in the executor
* state.
*
* @param value the constant value
* @param size the size (in bytes) of the variable into which the value is to be stored
* @param isContextreg true to indicate the value is from the disassembly context register. If
* {@code T} represents bytes, and the value is the contextreg, then the bytes are in
* big endian, no matter the machine language's endianness.
* @return the value as a {@code T}
*/
T fromConst(BigInteger value, int size, boolean isContextreg);
/**
* Convert the given constant concrete value to type {@code T} having the given size.
*
* <p>
* The value is assumed <em>not</em> to be for the disassembly context register.
*
* @see #fromConst(BigInteger, int, boolean)
*/
default T fromConst(BigInteger value, int size) {
return fromConst(value, size, false);
}
/**
* Make concrete, if possible, the given abstract condition to a boolean value
* Convert, if possible, the given abstract condition to a concrete boolean value
*
* @param cond the abstract condition
* @return the boolean value
@ -47,7 +136,7 @@ public interface PcodeArithmetic<T> {
boolean isTrue(T cond);
/**
* Make concrete, if possible, the given abstract value
* Convert, if possible, the given abstract value to a concrete value
*
* <p>
* If the conversion is not possible, throw an exception. TODO: Decide on conventions of which
@ -74,4 +163,17 @@ public interface PcodeArithmetic<T> {
default BigInteger toConcrete(T value) {
return toConcrete(value, false);
}
/**
* Get the size in bytes, if possible, of the given abstract value
*
* <p>
* If the abstract value does not conceptually have a size, throw an exception. Note the
* returned size should itself have a size of {@link #SIZEOF_SIZEOF}. TODO: Establish
* conventions for exceptions.
*
* @param value the abstract value
* @return the size in bytes
*/
T sizeOf(T value);
}

View file

@ -15,10 +15,31 @@
*/
package ghidra.pcode.exec;
/**
* The base exception for all p-code execution errors
*
* <p>
* Exceptions caught by the executor that are not of this type are typically caught and wrapped, so
* that the frame can be recovered. The frame is important for diagnosing the error, because it
* records what the executor was doing. It essentially serves as the "line number" of the p-code
* program within the greater Java stack. Additionally, if execution of p-code is to resume, the
* frame must be recovered, and possibly stepped back one.
*/
public class PcodeExecutionException extends RuntimeException {
/*package*/ PcodeFrame frame;
/**
* Construct an execution exception
*
* <p>
* The frame is often omitted at the throw site. The executor should catch the exception, fill
* in the frame, and re-throw it.
*
* @param message the message
* @param frame if known, the frame at the time of the exception
* @param cause the exception that caused this one
*/
public PcodeExecutionException(String message, PcodeFrame frame, Throwable cause) {
super(message, cause);
this.frame = frame;
@ -36,6 +57,20 @@ public class PcodeExecutionException extends RuntimeException {
this(message, null, null);
}
/**
* Get the frame at the time of the exception
*
* <p>
* Note that the frame counter is advanced <em>before</em> execution of the p-code op. Thus, the
* counter often points to the op following the one which caused the exception. For a frame to
* be present and meaningful, the executor must intervene between the throw and the catch. In
* other words, if you're invoking the executor, you should always expect to see a frame. If you
* are implementing, e.g., a userop, then it is possible to catch an exception without frame
* information populated. You might instead retrieve the frame from the executor, if you have a
* handle to it.
*
* @return the frame, possibly {@code null}
*/
public PcodeFrame getFrame() {
return frame;
}

View file

@ -19,8 +19,9 @@ import java.util.List;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.exec.SleighUseropLibrary.SleighUseropDefinition;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.pcode.opbehavior.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
@ -28,6 +29,15 @@ import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* An executor of p-code programs
*
* <p>
* This is the kernel of SLEIGH expression evaluation and p-code emulation. For a complete example
* of a p-code emulator, see {@link PcodeEmulator}.
*
* @param <T> the type of values processed by the executor
*/
public class PcodeExecutor<T> {
protected final SleighLanguage language;
protected final PcodeArithmetic<T> arithmetic;
@ -35,6 +45,13 @@ public class PcodeExecutor<T> {
protected final Register pc;
protected final int pointerSize;
/**
* Construct an executor with the given bindings
*
* @param language the processor language
* @param arithmetic an implementation of arithmetic p-code ops
* @param state an implementation of load/store p-code ops
*/
public PcodeExecutor(SleighLanguage language, PcodeArithmetic<T> arithmetic,
PcodeExecutorStatePiece<T, T> state) {
this.language = language;
@ -74,15 +91,15 @@ public class PcodeExecutor<T> {
public void executeSleighLine(String line) {
PcodeProgram program = SleighProgramCompiler.compileProgram(language,
"line", List.of(line + ";"), SleighUseropLibrary.NIL);
execute(program, SleighUseropLibrary.nil());
"line", List.of(line + ";"), PcodeUseropLibrary.NIL);
execute(program, PcodeUseropLibrary.nil());
}
public PcodeFrame begin(PcodeProgram program) {
return begin(program.code, program.useropNames);
}
public PcodeFrame execute(PcodeProgram program, SleighUseropLibrary<T> library) {
public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary<T> library) {
return execute(program.code, program.useropNames, library);
}
@ -91,7 +108,7 @@ public class PcodeExecutor<T> {
}
public PcodeFrame execute(List<PcodeOp> code, Map<Integer, String> useropNames,
SleighUseropLibrary<T> library) {
PcodeUseropLibrary<T> library) {
PcodeFrame frame = begin(code, useropNames);
finish(frame, library);
return frame;
@ -108,7 +125,7 @@ public class PcodeExecutor<T> {
* @param frame the incomplete frame
* @param library the library of userops to use
*/
public void finish(PcodeFrame frame, SleighUseropLibrary<T> library) {
public void finish(PcodeFrame frame, PcodeUseropLibrary<T> library) {
try {
while (!frame.isFinished()) {
step(frame, library);
@ -133,7 +150,7 @@ public class PcodeExecutor<T> {
}
}
public void stepOp(PcodeOp op, PcodeFrame frame, SleighUseropLibrary<T> library) {
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
OpBehavior b = OpBehaviorFactory.getOpBehavior(op.getOpcode());
if (b == null) {
badOp(op);
@ -181,7 +198,7 @@ public class PcodeExecutor<T> {
}
}
public void step(PcodeFrame frame, SleighUseropLibrary<T> library) {
public void step(PcodeFrame frame, PcodeUseropLibrary<T> library) {
try {
stepOp(frame.nextOp(), frame, library);
}
@ -300,19 +317,20 @@ public class PcodeExecutor<T> {
return frame.getUseropName(opNo);
}
public void executeCallother(PcodeOp op, PcodeFrame frame, SleighUseropLibrary<T> library) {
public void executeCallother(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
int opNo = getIntConst(op.getInput(0));
String opName = getUseropName(opNo, frame);
if (opName == null) {
throw new AssertionError(
"Pcode userop " + opNo + " is not defined");
}
SleighUseropDefinition<T> opDef = library.getUserops().get(opName);
PcodeUseropDefinition<T> opDef = library.getUserops().get(opName);
if (opDef == null) {
throw new SleighLinkException(
"Sleigh userop '" + opName + "' is not in the library " + library);
}
opDef.execute(state, op.getOutput(), List.of(op.getInputs()).subList(1, op.getNumInputs()));
opDef.execute(this, library, op.getOutput(),
List.of(op.getInputs()).subList(1, op.getNumInputs()));
}
public void executeReturn(PcodeOp op, PcodeFrame frame) {

View file

@ -17,7 +17,28 @@ package ghidra.pcode.exec;
import org.apache.commons.lang3.tuple.Pair;
/**
* An interface that provides storage for values of type {@code T}, addressed by offsets of type
* {@code T}.
*
* @param <T> the type of offsets and values
*/
public interface PcodeExecutorState<T> extends PcodeExecutorStatePiece<T, T> {
/**
* Use this state as the control, paired with the given state as the rider.
*
* <p>
* <b>CAUTION:</b> Often, the default paired state is not quite sufficient. Consider
* {@link #getVar(AddressSpace, Object, int, boolean)}. The rider on the offset may offer
* information that must be incorporated into the rider of the value just read. This is the
* case, for example, with taint propagation. In those cases, an anonymous inner class extending
* {@link PairedPcodeExecutorState} is sufficient.
*
* @param <U> the type of values and offsets stored by the rider
* @param right the rider state
* @return the paired state
*/
default <U> PcodeExecutorState<Pair<T, U>> paired(
PcodeExecutorStatePiece<T, U> right) {
return new PairedPcodeExecutorState<>(this, right);

View file

@ -20,8 +20,22 @@ import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.pcode.Varnode;
/**
* An interface that provides storage for values of type {@code T}, addressed by offsets of type
* {@code A}.
*
* @param <A> the type of offsets
* @param <T> the type of values
*/
public interface PcodeExecutorStatePiece<A, T> {
/**
* Construct a range, if only to verify the range is valid
*
* @param space the address space
* @param offset the starting offset
* @param size the length (in bytes) of the range
*/
default void checkRange(AddressSpace space, long offset, int size) {
// TODO: Perhaps get/setVar should just take an AddressRange?
try {
@ -32,46 +46,136 @@ public interface PcodeExecutorStatePiece<A, T> {
}
}
/**
* Convert the given offset from {@code long} to type {@code A}
*
* <p>
* Note, is it unlikely (and discouraged) to encode the space in {@code A}. The reason the space
* is given is to ensure the result has the correct size.
*
* @param space the space where the offset applies
* @param l the offset
* @return the same offset as type {@code A}
*/
A longToOffset(AddressSpace space, long l);
/**
* Set the value of a register variable
*
* @param reg the register
* @param val the value
*/
default void setVar(Register reg, T val) {
Address address = reg.getAddress();
setVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(), true, val);
}
/**
* Set the value of a variable
*
* @param var the variable
* @param val the value
*/
default void setVar(Varnode var, T val) {
Address address = var.getAddress();
setVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true, val);
}
/**
* Set the value of a variable
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param truncateAddressableUnit true to truncate to the language's "addressable unit"
* @param val the value
*/
void setVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit, T val);
/**
* Set the value of a variable
*
* <p>
* This method is typically used for writing memory variables.
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param truncateAddressableUnit true to truncate to the language's "addressable unit"
* @param val the value
*/
default void setVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit,
T val) {
checkRange(space, offset, size);
setVar(space, longToOffset(space, offset), size, truncateAddressableUnit, val);
}
/**
* Get the value of a register variable
*
* @param reg the register
* @return the value
*/
default T getVar(Register reg) {
Address address = reg.getAddress();
return getVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(),
true);
}
/**
* Get the value of a variable
*
* @param var the variable
* @return the value
*/
default T getVar(Varnode var) {
Address address = var.getAddress();
return getVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true);
}
/**
* Get the value of a variable
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param truncateAddressableUnit true to truncate to the language's "addressable unit"
* @return the value
*/
T getVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit);
/**
* Get the value of a variable
*
* <p>
* This method is typically used for reading memory variables.
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param truncateAddressableUnit true to truncate to the language's "addressalbe unit"
* @return the value
*/
default T getVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit) {
checkRange(space, offset, size);
return getVar(space, longToOffset(space, offset), size, truncateAddressableUnit);
}
/**
* Bind a buffer of concrete bytes at the given start address
*
* @param address the start address
* @return a buffer
*/
MemBuffer getConcreteBuffer(Address address);
/**
* Truncate the given offset to the language's "addressable unit"
*
* @param space the space where the offset applies
* @param offset the offset
* @return the truncated offset
*/
default long truncateOffset(AddressSpace space, long offset) {
return space.truncateAddressableWordOffset(offset) * space.getAddressableUnitSize();
}

View file

@ -0,0 +1,80 @@
/* ###
* 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.exec;
import java.util.List;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.pcode.PcodeOp;
/**
* A p-code program derived from, i.e., implementing, a SLEIGH expression
*/
public class PcodeExpression extends PcodeProgram {
public static final String RESULT_NAME = "___result";
protected static final PcodeUseropLibrary<?> CAPTURING =
new ValueCapturingPcodeUseropLibrary<>();
/**
* A clever means of capturing the result of the expression.
*
* @implNote The compiled source is actually {@code ___result(<expression>);} which allows us to
* capture the value (and size) of arbitrary expressions. Assigning the value to a
* temp variable instead of a userop does not quite suffice, since it requires a fixed
* size, which cannot be known ahead of time.
*
* @param <T> no type in particular, except to match the executor
*/
protected static class ValueCapturingPcodeUseropLibrary<T>
extends AnnotatedPcodeUseropLibrary<T> {
T result;
@PcodeUserop
public void ___result(T result) {
this.result = result;
}
}
/**
* Construct a p-code program from source already compiled into p-code ops
*
* @param language the language that generated the p-code
* @param code the list of p-code ops
* @param useropSymbols a map of expected userop symbols
*/
protected PcodeExpression(SleighLanguage language, List<PcodeOp> code,
Map<Integer, UserOpSymbol> useropSymbols) {
super(language, code, useropSymbols);
}
// TODO: One that can take a library, and compose the result into it
/**
* Evaluate the expression using the given executor
*
* @param <T> the type of the result
* @param executor the executor
* @return the result
*/
public <T> T evaluate(PcodeExecutor<T> executor) {
ValueCapturingPcodeUseropLibrary<T> library =
new ValueCapturingPcodeUseropLibrary<>();
execute(executor, library);
return library.result;
}
}

View file

@ -21,10 +21,29 @@ import java.util.Map;
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.util.pcode.AbstractAppender;
import ghidra.app.util.pcode.AbstractPcodeFormatter;
import ghidra.pcode.emulate.EmulateInstructionStateModifier;
import ghidra.pcode.error.LowlevelError;
import ghidra.program.model.lang.Language;
import ghidra.program.model.pcode.PcodeOp;
/**
* The executor's internal counter
*
* <p>
* To distinguish the program counter of a p-code program from the program counter of the machine it
* models, we address p-code ops by "index." When derived from an instruction, the address and index
* together form the "sequence number." Because the executor care's not about the derivation of a
* p-code program, it counts through indices. The frame carries with it the p-code ops comprising
* its current p-code program.
*
* <p>
* A p-code emulator feeds p-code to an executor by decoding one instruction at a time. Thus, the
* "current p-code program" comprises only those ops generated by a single instruction. Or else, it
* is a user-supplied p-code program, e.g., to evaluate a SLEIGH expression. The frame completes the
* program by falling-through, i.e., stepping past the final op, or by branching externally, i.e.,
* to a different machine instruction. The emulator must then update its program counter accordingly
* and proceed to the next instruction.
*/
public class PcodeFrame {
protected class MyAppender extends AbstractAppender<String> {
protected final StringBuffer buf = new StringBuffer();
@ -138,42 +157,120 @@ public class PcodeFrame {
return new MyFormatter().formatOps(language, code);
}
/**
* The index of the <em>next</em> p-code op to be executed
*
* <p>
* If the last p-code op resulted in a branch, this will instead return -1.
*
* @see #isBranch()
* @see #isFallThrough()
* @see #isFinished()
* @return the index, i.e, p-code "program counter."
*/
public int index() {
return index;
}
/**
* Get the op at the current index, and then advance that index
*
* <p>
* This is used in the execution loop to retrieve each op to execute
*
* @return the op to execute
*/
public PcodeOp nextOp() {
return code.get(advance());
}
/**
* Advance the index
*
* @return the value of the index <em>before</em> it was advanced
*/
public int advance() {
return index++;
}
/**
* Step the index back one
*
* @return the value of the index <em>before</em> it was stepped back
*/
public int stepBack() {
return index--;
}
/**
* Get the name of the userop for the given number
*
* @param userop the userop number, as encoded in the first operand of {@link PcodeOp#CALLOTHER}
* @return the name of the userop, as expressed in the SLEIGH source
*/
public String getUseropName(int userop) {
return useropNames.get(userop);
}
/**
* Get the map of userop numbers to names
*
* @return the map
*/
public Map<Integer, String> getUseropNames() {
return useropNames;
}
/**
* Check if the index has advanced past the end of the p-code program
*
* <p>
* If the index has advanced beyond the program, it implies the program has finished executing.
* In the case of instruction emulation, no branch was encountered. The machine should advance
* to the fall-through instruction.
*
* @see #isBranch()
* @see #isFinished()
* @return true if the program completed without branching
*/
public boolean isFallThrough() {
return index == code.size();
}
/**
* Check if the p-code program has executed a branch
*
* <p>
* Branches can be internal, i.e., within the current program, or external, i.e., to another
* machine instructions. This refers strictly to the latter.
*
* @see #isFallThrough()
* @see #isFinished()
* @return true if the program completed with an external branch
*/
public boolean isBranch() {
return index == -1;
}
/**
* Check if the p-code program is completely executed
*
* @see #isFallThrough()
* @see #isBranch()
* @return true if execution finished, either in fall-through or an external branch
*/
public boolean isFinished() {
return !(0 <= index && index < code.size());
}
/**
* Perform an internal branch, relative to the <em>current op</em>.
*
* <p>
* Because index advances before execution of each op, the index is adjusted by an extra -1.
*
* @param rel the adjustment to the index
*/
public void branch(int rel) {
index += rel - 1; // -1 because we already advanced
if (!(0 <= index && index <= code.size())) {
@ -181,15 +278,28 @@ public class PcodeFrame {
}
}
/**
* Complete the p-code program, indicating an external branch
*/
public void finishAsBranch() {
branched = index - 1; // -1 because we already advanced
index = -1;
}
/**
* Get all the ops in the current p-code program.
*
* @return the list of ops
*/
public List<PcodeOp> getCode() {
return code;
}
/**
* Copy the frame's code (shallow copy) into a new array
*
* @return the array of ops
*/
public PcodeOp[] copyCode() {
return code.toArray(PcodeOp[]::new);
}
@ -198,9 +308,10 @@ public class PcodeFrame {
* Get the index of the last (branch) op executed
*
* <p>
* The behavior here is a bit strange for compatibility with the established concrete emulator.
* If the program (instruction) completed with fall-through, then this will return -1. If it
* completed on a branch, then this will return the index of that branch.
* The behavior here is a bit strange for compatibility with
* {@link EmulateInstructionStateModifier}. If the p-code program (likely derived from a machine
* instruction) completed with fall-through, then this will return -1. If it completed on a
* branch, then this will return the index of that branch.
*
* @return
*/

View file

@ -26,6 +26,12 @@ import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.pcode.PcodeOp;
/**
* A p-code program to be executed by a {@link PcodeExecutor}
*
* <p>
* This is a list of p-code operations together with a map of expected userops.
*/
public class PcodeProgram {
protected static class MyAppender extends AbstractAppender<String> {
protected final PcodeProgram program;
@ -82,6 +88,12 @@ public class PcodeProgram {
}
}
/**
* Generate a p-code program from the given instruction
*
* @param instruction the instruction
* @return the p-code program.
*/
public static PcodeProgram fromInstruction(Instruction instruction) {
Language language = instruction.getPrototype().getLanguage();
if (!(language instanceof SleighLanguage)) {
@ -96,6 +108,13 @@ public class PcodeProgram {
protected final List<PcodeOp> code;
protected final Map<Integer, String> useropNames = new HashMap<>();
/**
* Construct a p-code program with the given bindings
*
* @param language the language that generated the p-code
* @param code the list of p-code ops
* @param useropSymbols a map of expected userop symbols
*/
protected PcodeProgram(SleighLanguage language, List<PcodeOp> code,
Map<Integer, UserOpSymbol> useropSymbols) {
this.language = language;
@ -112,6 +131,11 @@ public class PcodeProgram {
}
}
/**
* Get the language generating this program
*
* @return the language
*/
public SleighLanguage getLanguage() {
return language;
}
@ -120,10 +144,22 @@ public class PcodeProgram {
return code;
}
public <T> void execute(PcodeExecutor<T> executor, SleighUseropLibrary<T> library) {
/**
* Execute this program using the given executor and library
*
* @param <T> the type of values to be operated on
* @param executor the executor
* @param library the library
*/
public <T> void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library) {
executor.execute(this, library);
}
/**
* For display purposes, get the header above the frame, usually the class name
*
* @return the frame's display header
*/
protected String getHead() {
return getClass().getSimpleName();
}

View file

@ -0,0 +1,161 @@
/* ###
* 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.exec;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.pcode.Varnode;
import ghidra.sleigh.grammar.Location;
/**
* A "library" of p-code userops available to a p-code executor.
*
* <p>
* The library can provide definitions of p-code userops already declared by the executor's language
* as well as completely new userops accessible to SLEIGH/p-code compiled for the executor. The
* recommended way to implement a library is to extend {@link AnnotatedPcodeUseropLibrary}.
*
* @param <T> the type of values accepted by the p-code userops.
*/
public interface PcodeUseropLibrary<T> {
/**
* The class of the empty userop library.
*
* @see {@link PcodeUseropLibrary#nil()}
*/
final class EmptyPcodeUseropLibrary implements PcodeUseropLibrary<Object> {
@Override
public Map<String, PcodeUseropDefinition<Object>> getUserops() {
return Map.of();
}
}
/**
* The empty userop library.
*
* <p>
* Executors cannot accept {@code null} libraries. Instead, give it this empty library. To
* satisfy Java's type checker, you may use {@link #nil()} instead.
*/
PcodeUseropLibrary<?> NIL = new EmptyPcodeUseropLibrary();
/**
* The empty userop library, cast to match parameter types.
*
* @param <T> the type required by the executor
* @return the empty userop library
*/
@SuppressWarnings("unchecked")
public static <T> PcodeUseropLibrary<T> nil() {
return (PcodeUseropLibrary<T>) NIL;
}
/**
* The definition of a p-code userop.
*
* @param <T> the type of parameter accepted (and possibly returned) by the userop.
*/
interface PcodeUseropDefinition<T> {
/**
* Get the name of the userop.
*
* <p>
* This is the symbol assigned to the userop when compiling new SLEIGH code. It cannot
* conflict with existing userops (except those declared, but not defined, by the executor's
* language) or other symbols of the executor's language. If this userop is to be used
* generically across many languages, choose an unlikely name. Conventionally, these start
* with two underscores {@code __}.
*
* @return the name of the userop
*/
String getName();
/**
* Get the number of <em>input</em> operands acccepted by the userop.
*
* @return the count or -1 if the userop is variadic
*/
int getInputCount();
/**
* Invoke/execute the userop.
*
* @param executor the executor invoking this userop.
* @param library the complete library for this execution. Note the library may have been
* composed from more than the one defining this userop.
* @param outVar if invoked as an rval, the destination varnode for the userop's output.
* Otherwise, {@code null}.
* @param inVars the input varnodes as ordered in the source.
* @see AnnotatedPcodeUseropLibrary.AnnotatedPcodeUseropDefinition
*/
void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library, Varnode outVar,
List<Varnode> inVars);
}
/**
* Get all the userops defined in this library, keyed by (symbol) name.
*
* @return the map of names to defined userops
*/
Map<String, PcodeUseropDefinition<T>> getUserops();
/**
* Compose this and the given library into a new library.
*
* @param lib the other library
* @return a new library having all userops defined between the two
*/
default PcodeUseropLibrary<T> compose(PcodeUseropLibrary<T> lib) {
if (lib == null) {
return this;
}
return new ComposedPcodeUseropLibrary<>(List.of(this, lib));
}
/**
* Get named symbols defined by this library that are not already declared in the language
*
* @param language the language whose existing symbols to consider
* @return a map of new userop indices to extra userop symbols
*/
default Map<Integer, UserOpSymbol> getSymbols(SleighLanguage language) {
//Set<String> langDefedNames = new HashSet<>();
Map<Integer, UserOpSymbol> symbols = new HashMap<>();
Set<String> allNames = new HashSet<>();
int langOpCount = language.getNumberOfUserDefinedOpNames();
for (int i = 0; i < langOpCount; i++) {
String name = language.getUserDefinedOpName(i);
allNames.add(name);
}
int nextOpNo = langOpCount;
for (PcodeUseropDefinition<?> uop : new TreeMap<>(getUserops()).values()) {
String opName = uop.getName();
if (!allNames.add(opName)) {
// Real duplicates will cause a warning during execution
continue;
}
int opNo = nextOpNo++;
Location loc = new Location(getClass().getName() + ":" + opName, 0);
UserOpSymbol sym = new UserOpSymbol(loc, opName);
sym.setIndex(opNo);
symbols.put(opNo, sym);
}
return symbols;
}
}

View file

@ -1,53 +0,0 @@
/* ###
* 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.exec;
import java.util.List;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.pcode.PcodeOp;
public class SleighExpression extends PcodeProgram {
public static final String RESULT_NAME = "___result";
protected static final SleighUseropLibrary<?> CAPTURING =
new ValueCapturingSleighUseropLibrary<>();
protected static class ValueCapturingSleighUseropLibrary<T>
extends AnnotatedSleighUseropLibrary<T> {
T result;
@SleighUserop
public void ___result(T result) {
this.result = result;
}
}
protected SleighExpression(SleighLanguage language, List<PcodeOp> code,
Map<Integer, UserOpSymbol> useropSymbols) {
super(language, code, useropSymbols);
}
// TODO: One that can take a library, and compose the result into it
public <T> T evaluate(PcodeExecutor<T> executor) {
ValueCapturingSleighUseropLibrary<T> library =
new ValueCapturingSleighUseropLibrary<>();
execute(executor, library);
return library.result;
}
}

View file

@ -15,6 +15,13 @@
*/
package ghidra.pcode.exec;
import ghidra.program.model.pcode.PcodeOp;
/**
* An exception thrown by
* {@link PcodeExecutor#executeCallother(PcodeOp, PcodeFrame, PcodeUseropLibrary) when a p-code
* userop turns up missing.
*/
public class SleighLinkException extends RuntimeException {
public SleighLinkException(String message) {
super(message);

View file

@ -0,0 +1,221 @@
/* ###
* 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.exec;
import java.text.MessageFormat;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.program.model.pcode.Varnode;
/**
* A p-code userop defined using SLEIGH source
*
* @param <T> no type in particular, except to match any executor
*/
public class SleighPcodeUseropDefinition<T> implements PcodeUseropDefinition<T> {
public static final String OUT_SYMBOL_NAME = "__op_output";
/**
* A factory for building {@link SleighPcodeUseropDefinition}s.
*/
public static class Factory {
private final SleighLanguage language;
/**
* Construct a factory for the given language
*
* @param language the language
*/
public Factory(SleighLanguage language) {
this.language = language;
}
/**
* Begin building the definition for a userop with the given name
*
* @param name the name of the new userop
* @return a builder for the userop
*/
public Builder define(String name) {
return new Builder(this, name);
}
}
/**
* A builder for a particular userop
*
* @see Factory
*/
public static class Builder {
private final Factory factory;
private final String name;
private final List<String> params = new ArrayList<>();
private final List<String> lines = new ArrayList<>();
protected Builder(Factory factory, String name) {
this.factory = factory;
this.name = name;
params(OUT_SYMBOL_NAME);
}
/**
* Add parameters with the given names (to the end)
*
* @param additionalParams the additional parameter names
* @return this builder
*/
public Builder params(Collection<String> additionalParams) {
this.params.addAll(additionalParams);
return this;
}
/**
* @see #params(Collection)
*/
public Builder params(String... additionalParams) {
return this.params(Arrays.asList(additionalParams));
}
/**
* Add lines of SLEIGH source
*
* <p>
* NOTE: The lines are joined only with line separators. No semicolons (;) are added at the
* end of each line.
*
* <p>
* TODO: See if this can be made any prettier with text blocks in newer Java versions.
*
* @param additionalLines the additional lines
* @return this builder
*/
public Builder sleigh(Collection<String> additionalLines) {
this.lines.addAll(additionalLines);
return this;
}
/**
* @see #sleigh(Collection)
*/
public Builder sleigh(String... additionalLines) {
return this.sleigh(Arrays.asList(additionalLines));
}
/**
* Treat each line as a pattern as in {@link MessageFormat#format(String, Object...)},
* replacing each with the result.
*
* @param arguments the arguments to pass to the formatter
* @return this builder
*/
public Builder applyAsPattern(Object[] arguments) {
for (int i = 0; i < lines.size(); i++) {
lines.set(i, MessageFormat.format(lines.get(i), arguments));
}
return this;
}
/**
* Build the actual definition
*
* <p>
* NOTE: Compilation of the sleigh source is delayed until the first invocation, since the
* compiler must know about the varnodes used as parameters. TODO: There may be some way to
* template it at the p-code level instead of the SLEIGH source level.
*
* @param <T> no particular type, except to match the executor
* @return the definition
*/
public <T> SleighPcodeUseropDefinition<T> build() {
return new SleighPcodeUseropDefinition<>(factory.language, name, List.copyOf(params),
List.copyOf(lines));
}
}
private final SleighLanguage language;
private final String name;
private final List<String> params;
private final List<String> lines;
private final Map<List<Varnode>, PcodeProgram> cacheByArgs = new HashMap<>();
protected SleighPcodeUseropDefinition(SleighLanguage language, String name, List<String> params,
List<String> lines) {
this.language = language;
this.name = name;
this.params = params;
this.lines = lines;
}
/**
* Get the p-code program implementing this userop for the given arguments and library.
*
* <p>
* This will compile and cache a program for each new combination of arguments seen.
*
* @param outArg the output operand, if applicable
* @param inArgs the input operands
* @param library the complete userop library
* @return the p-code program to be fed to the same executor as invoked this userop, but in a
* new frame
*/
public PcodeProgram programFor(Varnode outArg, List<Varnode> inArgs,
PcodeUseropLibrary<?> library) {
List<Varnode> args = new ArrayList<>(inArgs.size() + 1);
args.add(outArg);
args.addAll(inArgs);
return cacheByArgs.computeIfAbsent(args,
a -> SleighProgramCompiler.compileUserop(language, name, params, lines, library, a));
}
@Override
public String getName() {
return name;
}
@Override
public int getInputCount() {
return params.size() - 1; // account for __op_output
}
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outArg, List<Varnode> inArgs) {
PcodeProgram program = programFor(outArg, inArgs, library);
executor.execute(program, library);
}
/**
* Get the names of the inputs in order
*
* @return the input names
*/
public List<String> getInputs() {
return params;
}
/**
* Get the lines of source that define this userop
*
* @return the lines
*/
public List<String> getLines() {
return lines;
}
}

View file

@ -15,32 +15,73 @@
*/
package ghidra.pcode.exec;
import java.util.*;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.template.ConstructTpl;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.pcodeCPort.pcoderaw.VarnodeData;
import ghidra.pcodeCPort.sleighbase.SleighBase;
import ghidra.pcodeCPort.slghsymbol.*;
import ghidra.pcodeCPort.space.AddrSpace;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.sleigh.grammar.Location;
import ghidra.util.Msg;
/**
* Methods for compiling p-code programs for various purposes
*
* <p>
* Depending on the purpose, special provisions may be necessary around the execution of the
* resulting program. Many utility methods are declared public here because they, well, they have
* utility. The main public methods of this class, however, all start with {@code compile}....
*/
public class SleighProgramCompiler {
private static final String EXPRESSION_SOURCE_NAME = "expression";
private static final String EXPRESSION_SOURCE_NAME = "expression";
public static final String NIL_SYMBOL_NAME = "__nil";
/**
* Create a p-code parser for the given language
*
* @param language the language
* @return a parser
*/
public static PcodeParser createParser(SleighLanguage language) {
return new PcodeParser(language, UniqueLayout.INJECT.getOffset(language));
}
/**
* Compile the given source into a p-code template
*
* @see #compileProgram(SleighLanguage, String, List, PcodeUseropLibrary)
* @param language the language
* @param parser the parser
* @param sourceName the name of the program, for error diagnostics
* @param text the SLEIGH source
* @return the constructor template
*/
public static ConstructTpl compileTemplate(Language language, PcodeParser parser,
String sourceName, String text) {
ConstructTpl template =
Objects.requireNonNull(parser.compilePcode(text, EXPRESSION_SOURCE_NAME, 1));
return template;
return parser.compilePcode(text, EXPRESSION_SOURCE_NAME, 1);
}
/**
* Construct a list of p-code ops from the given template
*
* @param language the language generating the template and p-code
* @param template the template
* @return the list of p-code ops
* @throws UnknownInstructionException in case of crossbuilds, the target instruction is unknown
* @throws MemoryAccessException in case of crossbuilds, the target address cannot be accessed
*/
public static List<PcodeOp> buildOps(Language language, ConstructTpl template)
throws UnknownInstructionException, MemoryAccessException {
Address zero = language.getDefaultSpace().getAddress(0);
@ -68,34 +109,192 @@ public class SleighProgramCompiler {
}
}
/**
* Add a symbol for unwanted result
*
* <p>
* This is basically a hack to avoid NPEs when no output varnode is given.
*
* @param parser the parser to add the symbol to
*/
protected static VarnodeSymbol addNilSymbol(PcodeParser parser) {
SleighSymbol exists = parser.findSymbol(NIL_SYMBOL_NAME);
if (exists != null) {
// A ClassCastException here indicates a name collision
return (VarnodeSymbol) exists;
}
long offset = parser.allocateTemp();
VarnodeSymbol nil =
new VarnodeSymbol(new Location("<util>", 0), NIL_SYMBOL_NAME, parser.getUniqueSpace(),
offset, 1);
parser.addSymbol(nil);
return nil;
}
/**
* A factory for {@code PcodeProgram}s
*
* @param <T> the type of program to build
*/
public interface PcodeProgramConstructor<T extends PcodeProgram> {
T construct(SleighLanguage language, List<PcodeOp> ops, Map<Integer, UserOpSymbol> symbols);
}
/**
* Invoke the given constructor with the given template and library symbols
*
* @param <T> the type of the p-code program
* @param ctor the constructor, often a method reference to {@code ::new}
* @param language the language producing the p-code
* @param template the p-code constructor template
* @param libSyms the map of symbols by userop ID
* @return the p-code program
*/
public static <T extends PcodeProgram> T constructProgram(PcodeProgramConstructor<T> ctor,
SleighLanguage language, ConstructTpl template, Map<Integer, UserOpSymbol> libSyms) {
try {
return ctor.construct(language, SleighProgramCompiler.buildOps(language, template),
libSyms);
}
catch (UnknownInstructionException | MemoryAccessException e) {
throw new AssertionError(e);
}
}
/**
* Compile the given SLEIGH source into a simple p-code program
*
* <p>
* This is suitable for modifying program state using SLEIGH statements. Most likely, in
* scripting, or perhaps in a SLEIGH repl. The library given during compilation must match the
* library given for execution, at least in its binding of userop IDs to symbols.
*
* @param language the language of the target p-code machine
* @param sourceName a diagnostic name for the SLEIGH source
* @param lines the lines of SLEIGH source. These are joined with line separators but no
* semicolon!
* @param library the userop library or stub library for binding userop symbols
* @return the compiled p-code program
*/
public static PcodeProgram compileProgram(SleighLanguage language, String sourceName,
List<String> lines, SleighUseropLibrary<?> library) {
List<String> lines, PcodeUseropLibrary<?> library) {
PcodeParser parser = createParser(language);
Map<Integer, UserOpSymbol> symbols = library.getSymbols(language);
addParserSymbols(parser, symbols);
ConstructTpl template =
compileTemplate(language, parser, sourceName, StringUtils.join(lines, "\n"));
try {
return new PcodeProgram(language, buildOps(language, template), symbols);
}
catch (UnknownInstructionException | MemoryAccessException e) {
throw new AssertionError(e);
}
return constructProgram(PcodeProgram::new, language, template, symbols);
}
public static SleighExpression compileExpression(SleighLanguage language, String expression) {
/**
* Compile the given SLEIGH expression into a p-code program that can evaluate it
*
* <p>
* TODO: Currently, expressions cannot be compiled for a user-supplied userop library. The
* evaluator p-code program uses its own library as a means of capturing the result; however,
* userop libraries are easily composed. It should be easy to add that feature if needed.
*
* @param language the languge of the target p-code machine
* @param expression the SLEIGH expression to be evaluated
* @return a p-code program whose {@link PcodeExpression#evaluate(PcodeExecutor)} method will
* evaluate the expression on the given executor and its state.
*/
public static PcodeExpression compileExpression(SleighLanguage language, String expression) {
PcodeParser parser = createParser(language);
Map<Integer, UserOpSymbol> symbols = SleighExpression.CAPTURING.getSymbols(language);
Map<Integer, UserOpSymbol> symbols = PcodeExpression.CAPTURING.getSymbols(language);
addParserSymbols(parser, symbols);
ConstructTpl template = compileTemplate(language, parser, EXPRESSION_SOURCE_NAME,
SleighExpression.RESULT_NAME + "(" + expression + ");");
try {
return new SleighExpression(language, buildOps(language, template), symbols);
PcodeExpression.RESULT_NAME + "(" + expression + ");");
return constructProgram(PcodeExpression::new, language, template, symbols);
}
/**
* Generate a SLEIGH symbol for context when compiling a userop definition
*
* @param language the language of the target p-code machine
* @param sleigh a means of translating address spaces between execution and compilation
* contexts
* @param opName a diagnostic name for the userop in which this parameter applies
* @param paramName the symbol name for the parameter
* @param arg the varnode to bind to the parameter symbol
* @return the named SLEIGH symbol bound to the given varnode
*/
public static VarnodeSymbol paramSym(Language language, SleighBase sleigh, String opName,
String paramName, Varnode arg) {
AddressSpace gSpace = language.getAddressFactory().getAddressSpace(arg.getSpace());
AddrSpace sSpace = sleigh.getSpace(gSpace.getUnique());
return new VarnodeSymbol(new Location(opName, 0), paramName, sSpace, arg.getOffset(),
arg.getSize());
}
/**
* Compile the definition of a p-code userop from SLEIGH source into a p-code program
*
* <p>
* TODO: Defining a userop from SLEIGH source is currently a bit of a hack. It would be nice if
* there were a formalization of SLEIGH/p-code subroutines. At the moment, the control flow for
* subroutines is handled out of band, which actually works fairly well. However, parameter
* passing and returning results is not well defined. The current solution is to alias the
* parameters to their arguments, implementing a pass-by-reference scheme. Similarly, the output
* variable is aliased to the symbol named {@link SleighPcodeUseropDefinition#OUT_SYMBOL_NAME},
* which could be problematic if no output variable is given. In this setup, the use of
* temporary variables is tenuous, since no provision is made to ensure a subroutine's
* allocation of temporary variables do not collide with those of callers lower in the stack.
* This could be partly resolved by creating a fresh unique space for each invocation, but then
* it becomes necessary to copy values from the caller's to the callee's. If we're strict about
* parameters being inputs, this is straightforward. If parameters can be used to communicate
* results, then we may need parameter attributes to indicate in, out, or inout. Of course,
* having a separate unique space per invocation implies the executor state can't simply have
* one unique space. Likely, the {@link PcodeFrame} would come to own its own unique space, but
* the {@link PcodeExecutorState} should probably still manufacture it.
*
* @param language the language of the target p-code machine
* @param opName the name of the userop (used only for diagnostics here)
* @param params the names of parameters in order. Index 0 names the output symbol, probably
* {@link SleighPcodeUseropDefinition#OUT_SYMBOL_NAME}
* @param lines the lines of SLEIGH source. These are joined with line separators but no
* semicolon!
* @param library the userop library or stub library for binding userop symbols
* @param args the varnode arguments in order. Index 0 is the output varnode.
* @return a p-code program that implements the userop for the given arguments
*/
public static PcodeProgram compileUserop(SleighLanguage language, String opName,
List<String> params, List<String> lines, PcodeUseropLibrary<?> library,
List<Varnode> args) {
PcodeParser parser = createParser(language);
Map<Integer, UserOpSymbol> symbols = library.getSymbols(language);
addParserSymbols(parser, symbols);
SleighBase sleigh = parser.getSleigh();
int count = params.size();
if (args.size() != count) {
throw new IllegalArgumentException("Mismatch of params and args sizes");
}
catch (UnknownInstructionException | MemoryAccessException e) {
throw new AssertionError(e);
VarnodeSymbol nil = addNilSymbol(parser);
VarnodeData nilVnData = nil.getFixedVarnode();
for (int i = 0; i < count; i++) {
String p = params.get(i);
Varnode a = args.get(i);
if (a == null && i == 0) { // Only allow output to be omitted
parser.addSymbol(
new VarnodeSymbol(nil.getLocation(), p, nilVnData.space, nilVnData.offset,
nilVnData.size));
}
else {
parser.addSymbol(paramSym(language, sleigh, opName, p, a));
}
}
String source = StringUtils.join(lines, "\n");
try {
ConstructTpl template = compileTemplate(language, parser, opName, source);
return constructProgram(PcodeProgram::new, language, template, symbols);
}
catch (Throwable t) {
Msg.error(SleighProgramCompiler.class, "Error trying to compile userop:\n" + source);
throw t;
}
}
}

View file

@ -1,88 +0,0 @@
/* ###
* 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.exec;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.pcode.Varnode;
import ghidra.sleigh.grammar.Location;
public interface SleighUseropLibrary<T> {
final class EmptySleighUseropLibrary implements SleighUseropLibrary<Object> {
@Override
public Map<String, SleighUseropDefinition<Object>> getUserops() {
return Map.of();
}
}
SleighUseropLibrary<?> NIL = new EmptySleighUseropLibrary();
@SuppressWarnings("unchecked")
public static <T> SleighUseropLibrary<T> nil() {
return (SleighUseropLibrary<T>) NIL;
}
interface SleighUseropDefinition<T> {
String getName();
int getOperandCount();
void execute(PcodeExecutorStatePiece<T, T> state, Varnode outVar, List<Varnode> inVars);
}
Map<String, SleighUseropDefinition<T>> getUserops();
default SleighUseropLibrary<T> compose(SleighUseropLibrary<T> lib) {
if (lib == null) {
return this;
}
return new ComposedSleighUseropLibrary<>(List.of(this, lib));
}
/**
* Get named symbols defined by this library that are not already defined in the language
*
* @param language the language whose existing symbols to consider
* @return a map of new user-op indices to extra user-op symbols
*/
default Map<Integer, UserOpSymbol> getSymbols(SleighLanguage language) {
//Set<String> langDefedNames = new HashSet<>();
Map<Integer, UserOpSymbol> symbols = new HashMap<>();
Set<String> allNames = new HashSet<>();
int langOpCount = language.getNumberOfUserDefinedOpNames();
for (int i = 0; i < langOpCount; i++) {
String name = language.getUserDefinedOpName(i);
allNames.add(name);
}
int nextOpNo = langOpCount;
for (SleighUseropDefinition<?> uop : new TreeMap<>(getUserops()).values()) {
String opName = uop.getName();
if (!allNames.add(opName)) {
// Real duplicates will cause a warning during execution
continue;
}
int opNo = nextOpNo++;
Location loc = new Location(getClass().getName() + ":" + opName, 0);
UserOpSymbol sym = new UserOpSymbol(loc, opName);
sym.setIndex(opNo);
symbols.put(opNo, sym);
}
return symbols;
}
}

View file

@ -15,6 +15,12 @@
*/
package ghidra.pcode.exec;
import ghidra.pcode.emu.PcodeThread;
/**
* An exception thrown during execution if {@link PcodeThread#setSuspended(boolean)} is invoked with
* {@code true}.
*/
public class SuspendedPcodeExecutionException extends PcodeExecutionException {
public SuspendedPcodeExecutionException(PcodeFrame frame, Throwable cause) {
super("Execution suspended by user", frame, cause);

View file

@ -0,0 +1,107 @@
/* ###
* 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.lifecycle.Internal;
import ghidra.pcode.struct.StructuredSleigh.Label;
import ghidra.pcode.struct.StructuredSleigh.Stmt;
abstract class AbstractStmt implements Stmt {
protected final StructuredSleigh ctx;
protected AbstractStmt parent;
protected AbstractStmt(StructuredSleigh ctx) {
this.ctx = ctx;
BlockStmt parent = ctx.stack.peek();
this.parent = parent;
if (parent != null) {
parent.children.add(this);
}
}
/**
* Internal: Provides the implementation of {@link RValInternal#getContext()} for
* {@link AssignStmt}
*
* @return the context
*/
public StructuredSleigh getContext() {
return ctx;
}
@Internal
protected AbstractStmt reparent(AbstractStmt newParent) {
assert parent instanceof BlockStmt;
BlockStmt parent = (BlockStmt) this.parent;
parent.children.remove(this);
this.parent = newParent;
return this;
}
/**
* Get the innermost statement of the given class containing this statement
*
* <p>
* This is used to implement "truncation" statements like "break", "continue", and "result".
*
* @param <T> the type of the statement sought
* @param cls the class of the statement sought
* @return the found statement or null
*/
@Internal
protected <T extends Stmt> T nearest(Class<T> cls) {
if (cls.isAssignableFrom(this.getClass())) {
return cls.cast(this);
}
if (parent == null) {
return null;
}
return parent.nearest(cls);
}
/**
* Generate the Sleigh code that implements this statement
*
* @param next the label receiving control immediately after this statement is executed
* @param fall the label positioned immediately after this statement in the generated code
* @return the generated Sleigh code
*/
protected abstract String generate(Label next, Label fall);
/**
* Check if the statement is or contains a single branch statement
*
* <p>
* This is to avoid the unnecessary generation of labels forming chains of unconditional gotos.
*
* @return true if so, false otherwise.
*/
protected boolean isSingleGoto() {
return false;
}
/**
* Get the label for the statement immediately following this statement
*
* <p>
* For statements that always fall-through, this is just {#link {@link StructuredSleigh#FALL}.
*
* @return the label
*/
protected Label getNext() {
return ctx.FALL;
}
}

View file

@ -0,0 +1,55 @@
/* ###
* 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.RVal;
class ArithBinExpr extends BinExpr {
enum Op {
ORB("||"),
ORI("|"),
XORB("^^"),
XORI("^"),
ANDB("&&"),
ANDI("&"),
SHLI("<<"),
SHRIU(">>"),
SHRIS("s>>"),
ADDI("+"),
ADDF("f+"),
SUBI("-"),
SUBF("f-"),
MULI("*"),
MULF("f*"),
DIVIU("/"),
DIVIS("s/"),
DIVF("f/"),
REMIU("%"),
REMIS("s%"),
;
private final String str;
Op(String str) {
this.str = str;
}
}
protected ArithBinExpr(StructuredSleigh ctx, RVal lhs, Op op, RVal rhs) {
// TODO: Should take more general of two types?
super(ctx, lhs, op.str, rhs, lhs.getType());
}
}

View file

@ -0,0 +1,65 @@
/* ###
* 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.*;
import ghidra.program.model.data.DataType;
/**
* An assignment statement
*/
class AssignStmt extends AbstractStmt implements RValInternal, StmtWithVal {
private final LValInternal lhs;
private final RValInternal rhs;
private final DataType type;
private AssignStmt(StructuredSleigh ctx, LVal lhs, RVal rhs, DataType type) {
super(ctx);
this.lhs = (LValInternal) lhs;
this.rhs = (RValInternal) rhs;
this.type = type;
}
public AssignStmt(StructuredSleigh ctx, LVal lhs, RVal rhs) {
// NOTE: Takes the type of the left-hand side
this(ctx, lhs, rhs, lhs.getType());
}
@Override
public RVal cast(DataType type) {
return new AssignStmt(ctx, lhs, rhs, type);
}
@Override
public String toString() {
return "<Assign " + lhs + " = " + rhs + ">";
}
@Override
protected String generate(Label next, Label fall) {
return lhs.generate() + " = " + rhs.generate() + ";\n" + next.genGoto(fall);
}
@Override
public DataType getType() {
return type;
}
@Override
public String generate() {
return lhs.generate();
}
}

View file

@ -0,0 +1,47 @@
/* ###
* 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.RVal;
import ghidra.program.model.data.DataType;
class BinExpr extends Expr {
protected final RValInternal lhs;
protected final String op;
protected final RValInternal rhs;
protected BinExpr(StructuredSleigh ctx, RVal lhs, String op, RVal rhs, DataType type) {
super(ctx, type);
this.lhs = (RValInternal) lhs;
this.op = op;
this.rhs = (RValInternal) rhs;
}
@Override
public RVal cast(DataType type) {
return new BinExpr(ctx, lhs, op, rhs, type);
}
@Override
public String toString() {
return "<" + getClass().getSimpleName() + " " + lhs + " " + op + " " + rhs + ">";
}
@Override
public String generate() {
return "(" + lhs.generate() + " " + op + " " + rhs.generate() + ")";
}
}

View file

@ -0,0 +1,73 @@
/* ###
* 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 java.util.ArrayList;
import java.util.List;
import ghidra.pcode.struct.StructuredSleigh.Label;
/**
* A block statement
*/
class BlockStmt extends AbstractStmt {
List<AbstractStmt> children = new ArrayList<>();
/**
* Build a block statement
*
* @param ctx the context
* @param body the body, usually a lambda
*/
protected BlockStmt(StructuredSleigh ctx, Runnable body) {
super(ctx);
ctx.stack.push(this);
body.run();
ctx.stack.pop();
}
/**
* Add a child to this statement
*
* @param child the child statement
*/
public void addChild(AbstractStmt child) {
children.add(child);
}
@Override
protected String generate(Label next, Label fall) {
if (children.isEmpty()) {
return next.genGoto(fall);
}
StringBuilder sb = new StringBuilder();
for (AbstractStmt c : children.subList(0, children.size() - 1)) {
sb.append(c.generate(ctx.FALL, ctx.FALL));
}
sb.append(children.get(children.size() - 1).generate(next, fall));
return sb.toString();
}
@Override
protected boolean isSingleGoto() {
return children.size() == 1 && children.get(0).isSingleGoto();
}
@Override
protected Label getNext() {
return children.isEmpty() ? ctx.FALL : children.get(children.size() - 1).getNext();
}
}

Some files were not shown because too many files have changed in this diff Show more