mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-24 21:21:56 +00:00
Merge remote-tracking branch 'origin/GP-1208_Dan_emuSyscalls-4--SQUASHED'
This commit is contained in:
commit
d428ecd97a
@ -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));
|
||||
}
|
||||
}
|
131
Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java
Normal file
131
Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
211
Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java
Normal file
211
Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 -> {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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")));
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
@ -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();",
|
||||
|
@ -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");
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)}
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>;",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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?
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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?
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
catch (UnknownInstructionException | MemoryAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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() + ")";
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user