GP-5976: Implement constant folding and other optimizations in JIT emulator.

This commit is contained in:
Dan
2026-09-02 18:36:58 +00:00
parent c55ee84ba3
commit 9d186a3e30
256 changed files with 11499 additions and 6268 deletions
@@ -4,9 +4,9 @@
* 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.
@@ -31,13 +31,13 @@ public class TaintEmuUnixFileSystem extends AbstractEmuUnixFileSystem<Pair<byte[
*/
public static class UntaintedFileContents implements EmuFileContents<TaintVec> {
@Override
public long read(long offset, TaintVec buf, long fileSize) {
public int read(long offset, TaintVec buf, long fileSize) {
buf.setEmpties();
return buf.length;
}
@Override
public long write(long offset, TaintVec buf, long curSize) {
public int write(long offset, TaintVec buf, long curSize) {
return 0; // I don't care
}
@@ -57,13 +57,13 @@ public class TaintEmuUnixFileSystem extends AbstractEmuUnixFileSystem<Pair<byte[
}
@Override
public long read(long offset, TaintVec buf, long fileSize) {
public int read(long offset, TaintVec buf, long fileSize) {
buf.setArray(filename, offset);
return buf.length;
}
@Override
public long write(long offset, TaintVec buf, long curSize) {
public int write(long offset, TaintVec buf, long curSize) {
return 0; // I don't care
}
@@ -4,9 +4,9 @@
* 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.
@@ -27,14 +27,11 @@ import ghidra.taint.model.TaintVec;
/**
* A library for performing Taint Analysis on a Linux-amd64 program that reads from tainted files
*
* <p>
* This library is not currently accessible from the UI. It can be used with scripts by overriding a
* taint emulator's userop library factory method.
*
* <p>
* TODO: A means of adding and configuring userop libraries in the UI.
*
* <p>
* TODO: Example scripts.
*/
@@ -51,12 +48,14 @@ public class TaintFileReadsLinuxAmd64SyscallLibrary
super(machine, fs, program);
}
@Override
public Pair<byte[], TaintVec> unix_read(PcodeExecutorState<Pair<byte[], TaintVec>> state,
@PcodeUserop
@EmuSyscall(value = "read", override = true)
public Pair<byte[], TaintVec> unix_read(
@OpState PcodeExecutorState<Pair<byte[], TaintVec>> state,
Pair<byte[], TaintVec> fd, Pair<byte[], TaintVec> bufPtr,
Pair<byte[], TaintVec> count) {
Pair<byte[], TaintVec> result = super.unix_read(state, fd, bufPtr, count);
Pair<byte[], TaintVec> result = abstract_unix_read(state, fd, bufPtr, count);
TaintVec taintResult = result.getRight();
// TODO: Some representation of a "min" function. For now, just mix everything
@@ -80,7 +80,7 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg
.getProjectData()
.getRootFolder()
.createFile("emu_example", program, monitor);
try (Transaction tx = program.openTransaction("Init")) {
try (Transaction _ = program.openTransaction("Init")) {
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
entry = space.getAddress(0x00400000);
Address dataEntry = space.getAddress(0x00600000);
@@ -167,7 +167,7 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg
*/
TraceTimeManager time = trace.getTimeManager();
TraceSnapshot snapshot = time.getSnapshot(0, true);
try (Transaction tx = trace.openTransaction("Emulate")) {
try (Transaction _ = trace.openTransaction("Emulate")) {
for (int i = 0; i < 10; i++) {
println("Executing: " + thread.getCounter());
thread.stepInstruction();
@@ -4,9 +4,9 @@
* 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.
@@ -66,12 +66,12 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]>
*
* <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.
* employ the {@link ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpExecutor} 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 executor the calling thread's executor
* @param start the offset of the first character
* @return the length of the string in bytes
*/
@@ -108,11 +108,9 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]>
/**
* 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
@@ -4,9 +4,9 @@
* 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.
@@ -24,28 +24,26 @@ import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary;
import ghidra.pcode.emu.sys.EmuSyscallLibrary;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.SleighPcodeUseropDefinition.BuilderStage1;
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 conflicts among userops when several
* libraries are composed.
*
* methods. {@link EmuSyscallLibrary#emu_syscall} is the system call dispatcher, and it requires
* that each system call implement
* {@link ghidra.pcode.emu.sys.EmuSyscallLibrary.EmuSyscallDefinition}. System call libraries
* typically implement that interface by annotating p-code userops with
* {@link ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary.EmuSyscall}. This allows system
* calls to be implemented via Java callback or Sleigh. Conventionally, the Java method names of
* system calls should be <em>platform</em>_<em>name</em>. This is to prevent name conflicts among
* userops when several libraries are composed.
* <p>
* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in
* {@link EmuLinuxX86SyscallUseropLibrary} and {@link EmuLinuxAmd64SyscallUseropLibrary},
@@ -53,7 +51,6 @@ import ghidra.program.model.listing.Program;
* 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.
@@ -61,74 +58,21 @@ import ghidra.program.model.listing.Program;
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. Use the system call analyzer on your program to populate this space. 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)}.
* Notably, it applies the calling convention of the functions placed in the syscall overlay.
*
* @param machine the emulator
* @param program the program being emulated
* @param script the script
*/
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");
}
}
/**
* {@inheritDoc}
*
* 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(PcodeExecutorState<byte[]> state, Reason reason) {
return Utils.bytesToLong(state.getVar(regRAX, reason), 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;
}
/**
@@ -152,31 +96,28 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
/**
* 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
* composed libraries.
*
* The {@link ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary.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 composed libraries.
* <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 executor the emulator's underlying p-code executor
* @param str a pointer to the start of the buffer
* @param end a pointer to the end (exclusive) of the buffer
* @implNote Because we have concrete {@code byte[]}, we could use {@link 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.
*/
@PcodeUserop
@EmuSyscall("write")
public void demo_write(@OpExecutor PcodeExecutor<byte[]> executor, 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.toLong(str, Purpose.LOAD);
long endLong = arithmetic.toLong(end, Purpose.OTHER);
@@ -191,7 +132,8 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
/**
* The nested class for syscalls implemented using Structured Sleigh. Note that no matter the
* implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the
* implementation type, the Java method is annotated with
* {@link ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary.EmuSyscall}. We declare the
* class public so that the annotation processor can access the methods. Alternatively, we could
* override {@link #getMethodLookup()} to provide the processor private access.
*/
@@ -218,4 +160,13 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary<byte[]>
write.call(str, end);
}
}
// Finally, the actual syscall userop, which we implement in plain Sleigh.
@PcodeUserop
public SleighPcodeUseropDefinition syscall(BuilderStage1 builder) {
return builder.params().body(_ -> """
RAX = emu_syscall(RAX);
""").build();
}
}
@@ -32,6 +32,7 @@ import ghidra.pcode.emu.BytesPcodeThread;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.pcode.utils.Utils;
@@ -169,8 +170,9 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
}
};
for (SleighPcodeUseropDefinition<?> inject : new Injects().generate().values()) {
String source = inject.getBody();
for (PcodeUseropDefinition<?> inject : new Injects().generate().values()) {
String source =
inject instanceof SleighPcodeUseropDefinition sleigh ? sleigh.getBody() : "(java)";
println("Injecting " + inject.getName() + ":\n" + source);
for (Symbol sym : currentProgram.getSymbolTable()
.getExternalSymbols(inject.getName())) {
@@ -190,9 +192,9 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI
schedule.execute(trace, emu, monitor);
}
///////////////////////////////////////////
// ////////////////////////////////////////
// Configuration and support kruft below //
///////////////////////////////////////////
// ////////////////////////////////////////
public record Watch(String expression, TypeRec type, Settings settings) {}
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
import ghidra.pcode.exec.FixedSleighPcodeUseropDefinition;
import ghidra.pcode.exec.SleighPcodeUseropDefinition;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.pcode.exec.SleighPcodeUseropDefinition.SignatureDef;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.program.model.lang.LanguageID;
@@ -61,7 +61,7 @@ public class StandAloneStructuredSleighScript extends GhidraScript {
*/
language = (SleighLanguage) getLanguage(new LanguageID("DATA:BE:64:default"));
Map<String, SleighPcodeUseropDefinition<Object>> ops = new LookupStructuredSleigh() {
Map<String, PcodeUseropDefinition<Object>> ops = new LookupStructuredSleigh() {
/**
* Add two in-memory vectors of 16 longs and store the result in memory
*
@@ -101,8 +101,8 @@ public class StandAloneStructuredSleighScript extends GhidraScript {
/*
* Now, dump the generated Sleigh source
*/
for (SleighPcodeUseropDefinition<?> userop : ops.values()) {
if (!(userop instanceof FixedSleighPcodeUseropDefinition<?> fixed)) {
for (PcodeUseropDefinition<?> userop : ops.values()) {
if (!(userop instanceof FixedSleighPcodeUseropDefinition fixed)) {
println("WARN: Unexpected userop type for " + userop.getName());
continue;
}
@@ -4,9 +4,9 @@
* 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.
@@ -70,7 +70,7 @@ public class StandAloneSyscallEmuExampleScript extends GhidraScript {
program =
new ProgramDB("syscall_example", language,
language.getCompilerSpecByID(new CompilerSpecID("gcc")), this);
try (Transaction tx = program.openTransaction("Init")) {
try (Transaction _ = program.openTransaction("Init")) {
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
entry = space.getAddress(0x00400000);
Address dataEntry = space.getAddress(0x00600000);
@@ -4,9 +4,9 @@
* 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.
@@ -24,13 +24,10 @@ 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.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutor;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.SleighPcodeUseropDefinition;
import ghidra.pcode.exec.SleighPcodeUseropDefinition.BuilderStage1;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
/**
@@ -40,8 +37,6 @@ import ghidra.program.model.listing.Program;
*/
public class EmuLinuxAmd64SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscallUseropLibrary<T> {
protected final Register regRAX;
protected FileDataTypeManager clib64;
/**
@@ -55,7 +50,6 @@ public class EmuLinuxAmd64SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscal
public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program) {
super(machine, fs, program);
regRAX = machine.getLanguage().getRegister("RAX");
}
/**
@@ -70,7 +64,6 @@ public class EmuLinuxAmd64SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscal
public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine<T> machine, EmuUnixFileSystem<T> fs,
Program program, EmuUnixUser user) {
super(machine, fs, program, user);
regRAX = machine.getLanguage().getRegister("RAX");
}
@Override
@@ -91,16 +84,10 @@ public class EmuLinuxAmd64SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscal
clib64.close();
}
@Override
public long readSyscallNumber(PcodeExecutorState<T> state, Reason reason) {
return machine.getArithmetic().toLong(state.getVar(regRAX, reason), Purpose.OTHER);
}
@Override
protected boolean returnErrno(PcodeExecutor<T> executor, int errno) {
executor.getState()
.setVar(regRAX,
executor.getArithmetic().fromConst(-errno, regRAX.getMinimumByteSize()));
return true;
@PcodeUserop
public SleighPcodeUseropDefinition syscall(BuilderStage1 builder) {
return builder.params().body(_ -> """
RAX = emu_syscall(RAX);
""").build();
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -20,19 +20,18 @@ import java.util.Collection;
import java.util.List;
import generic.jar.ResourceFile;
import ghidra.app.util.PseudoInstruction;
import ghidra.framework.Application;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.jit.decode.CanDecode;
import ghidra.pcode.emu.unix.EmuUnixFileSystem;
import ghidra.pcode.emu.unix.EmuUnixUser;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* A system call library simulating Linux for x86 (32-bit)
@@ -40,8 +39,6 @@ import ghidra.program.model.pcode.PcodeOp;
* @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;
@@ -70,8 +67,6 @@ public class EmuLinuxX86SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscallU
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
@@ -92,45 +87,40 @@ public class EmuLinuxX86SyscallUseropLibrary<T> extends AbstractEmuLinuxSyscallU
clib32.close();
}
@Override
public long readSyscallNumber(PcodeExecutorState<T> state, Reason reason) {
return machine.getArithmetic().toLong(state.getVar(regEAX, reason), Purpose.OTHER);
}
@Override
protected boolean returnErrno(PcodeExecutor<T> executor, int errno) {
executor.getState()
.setVar(regEAX,
executor.getArithmetic().fromConst(-errno, regEAX.getMinimumByteSize()));
return true;
}
/**
* Implement this to detect and interpret the {@code INT 0x80} instruction as the syscall
* convention
*
* @param executor to receive the executor
* @param library to receive the userop library, presumably replete with syscalls
* @param number the interrupt number
* @return the address of the fall-through, to hack the {@link PcodeOp#CALLIND}
* @param out the output varnode
* @param intNo the interrupt number
*/
@PcodeUserop
public T swi(@OpExecutor PcodeExecutor<T> executor, @OpLibrary PcodeUseropLibrary<T> library,
T number) {
PcodeArithmetic<T> arithmetic = executor.getArithmetic();
long intNo = arithmetic.toLong(number, Purpose.OTHER);
@PcodeUserop(canInline = true)
public void swi(@OpExecutor PcodeExecutor<T> executor, @OpLibrary PcodeUseropLibrary<T> library,
@OpOutput Varnode out, int intNo) {
// A CALLIND follows to the return of swi().... OK.
// We'll just cause that to "fall through" instead
// Thus, we need the instruction, and we must compile invocation-specific p-code
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, executor.getReason());
PcodeThreadExecutor<T> te = (PcodeThreadExecutor<T>) executor;
int pcSize = regEIP.getNumBytes();
int iLen = te.getThread().getInstruction().getLength();
next = arithmetic.binaryOp(PcodeOp.INT_ADD, pcSize, pcSize, next, pcSize,
arithmetic.fromConst(iLen, pcSize));
syscall(executor, library);
return next;
if (!(executor instanceof CanDecode decoder)) {
throw new PcodeExecutionException(
"Cannot interpret swi(0x80) without the instruction decoder");
}
PseudoInstruction instruction = decoder.decodeInstruction();
Address next = instruction.getAddress().add(instruction.getLength());
PcodeProgram prog =
SleighProgramCompiler.compileUserop(executor.getLanguage(), "swi",
List.of(SleighPcodeUseropDefinition.OUT_SYMBOL_NAME), """
EAX = emu_syscall(EAX);
%s = 0x%x;
""".formatted(
SleighPcodeUseropDefinition.OUT_SYMBOL_NAME, next.getOffset()),
library, List.of(out));
executor.execute(prog, library);
}
else {
throw new PcodeExecutionException("Unknown interrupt: 0x%x".formatted(intNo));
}
throw new PcodeExecutionException("Unknown interrupt: 0x" + Long.toString(intNo, 16));
}
}
@@ -35,7 +35,6 @@ 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
@@ -55,9 +54,38 @@ public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcode
return AnnotationUtilities.collectAnnotatedMethods(EmuSyscall.class, cls);
}
private static Method chooseOrThrow(Method m1, Method m2) {
Class<?> cls1 = m1.getDeclaringClass();
Class<?> cls2 = m2.getDeclaringClass();
if (cls1 != cls2) {
EmuSyscall an1 = m1.getAnnotation(EmuSyscall.class);
EmuSyscall an2 = m2.getAnnotation(EmuSyscall.class);
if (cls1.isAssignableFrom(cls2) && an2.override()) {
return m2;
}
if (cls2.isAssignableFrom(cls1) && an1.override()) {
return m1;
}
}
throw new IllegalArgumentException("Duplicate @" +
EmuSyscall.class.getSimpleName() + " annotated methods with name " + m1.getName());
}
private static Map<String, Method> resolveOverrides(Set<Method> methods) {
Map<String, Method> result = new HashMap<>();
for (Method method : methods) {
String name = method.getName();
Method exists = result.get(name);
if (exists != null) {
method = chooseOrThrow(exists, method);
}
result.put(name, method);
}
return result;
}
/**
* 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 ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop @PcodeUserop}.
@@ -65,11 +93,25 @@ public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcode
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EmuSyscall {
/**
* The name of the syscall, which must match the actual name given in the syscall map.
*
* @return the name
* @see EmuSyscallLibrary#loadSyscallNumberMap(String)
*/
String value();
}
private final SyscallPcodeUseropDefinition<T> syscallUserop =
new SyscallPcodeUseropDefinition<>(this);
/**
* Set to indicate this name overrides the same name inherited from any
* superclass/interface.
* <p>
* If names collide from the same class, or that from the extension class does not have this
* flag, an error occurs at library construction.
*
* @return true to allow overrides.
*/
boolean override() default false;
}
protected final PcodeMachine<T> machine;
protected final CompilerSpec cSpec;
@@ -81,7 +123,11 @@ public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcode
protected final Collection<DataTypeManager> additionalArchives;
/**
* Construct a new library including the "syscall" userop
* Construct a new library including the "emu_syscall" userop
* <p>
* Note that the final library must export the system call entry point. Often, this is the
* "syscall" userop. That userop must read the system call number, pass it as an argument to
* "emu_syscall," and store the result according to the ABI of the target platform.
*
* @param machine the machine using this library
* @param program a program from which to derive syscall configuration, conventions, etc.
@@ -121,12 +167,12 @@ public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcode
/**
* Export a userop as a system call
*
* @param number the opIndex assigned to the userop
* @param number the syscall number
* @param opdef the userop
* @param convention the syscall calling convention for the emulated platform
* @return the syscall definition
*/
public UseropEmuSyscallDefinition<T> newBoundSyscall(long number,
protected UseropEmuSyscallDefinition<T> newBoundSyscall(long number,
PcodeUseropDefinition<T> opdef, PrototypeModel convention) {
return new UseropEmuSyscallDefinition<>(number, opdef, program, convention, dtMachineWord);
}
@@ -136,8 +182,8 @@ public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcode
new DualHashBidiMap<>(EmuSyscallLibrary.loadSyscallNumberMap(program));
Map<Long, PrototypeModel> mapConventions =
EmuSyscallLibrary.loadSyscallConventionMap(program);
Set<Method> methods = collectSyscalls(cls);
for (Method m : methods) {
Map<String, Method> methods = resolveOverrides(collectSyscalls(cls));
for (Method m : methods.values()) {
String name = m.getAnnotation(EmuSyscall.class).value();
Long number = mapNames.getKey(name);
if (number == null) {
@@ -151,12 +197,7 @@ public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcode
" must also be a p-code userop");
}
PrototypeModel convention = mapConventions.get(number);
EmuSyscallDefinition<T> existed =
syscallMap.put(number, newBoundSyscall(number, opdef, convention));
if (existed != null) {
throw new IllegalArgumentException("Duplicate @" +
EmuSyscall.class.getSimpleName() + " annotated methods with name " + name);
}
syscallMap.put(number, newBoundSyscall(number, opdef, convention));
}
}
@@ -164,11 +205,6 @@ public abstract class AnnotatedEmuSyscallUseropLibrary<T> extends AnnotatedPcode
mapAndBindSyscalls(this.getClass());
}
@Override
public PcodeUseropDefinition<T> getSyscallUserop() {
return syscallUserop;
}
@Override
public Map<Long, EmuSyscallDefinition<T>> getSyscalls() {
return syscallMap;
@@ -4,9 +4,9 @@
* 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.
@@ -17,7 +17,6 @@ package ghidra.pcode.emu.sys;
/**
* A concrete in-memory bytes store for simulated file contents
*
* <p>
* Note that currently, the total contents cannot exceed a Java array, so the file must remain less
* than 2GB in size.
@@ -28,21 +27,21 @@ public class BytesEmuFileContents implements EmuFileContents<byte[]> {
protected byte[] content = new byte[INIT_CONTENT_SIZE];
@Override
public synchronized long read(long offset, byte[] buf, long fileSize) {
public synchronized int read(long offset, byte[] buf, long fileSize) {
// We're using an in-memory array, so limited to int offsets
if (offset > Integer.MAX_VALUE) {
throw new EmuIOException("Offset is past end of file");
}
long len = Math.min(buf.length, fileSize - offset);
int len = (int) Math.min(buf.length, fileSize - offset);
if (len < 0) {
throw new EmuIOException("Offset is past end of file");
}
System.arraycopy(content, (int) offset, buf, 0, (int) len);
System.arraycopy(content, (int) offset, buf, 0, len);
return len;
}
@Override
public synchronized long write(long offset, byte[] buf, long curSize) {
public synchronized int write(long offset, byte[] buf, long curSize) {
long newSize = offset + buf.length;
if (newSize > Integer.MAX_VALUE || newSize < 0) {
throw new EmuIOException("File size cannot exceed " + Integer.MAX_VALUE + " bytes");
@@ -4,9 +4,9 @@
* 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.
@@ -17,13 +17,11 @@ package ghidra.pcode.emu.sys;
/**
* The content store to back a simulated file
*
* <p>
* TODO: Could/should this just be the same interface as an execute state? If so, we'd need to
* formalize the store interface and require one for each address space in the state. Sharing that
* interface may not be a good idea.... I think implementors can use a common realization if that
* suits them.
*
* <p>
* TODO: Actually, a better idea might be to introduce an address factory with custom spaces into
* the emulator. Then a library/file could just create an address space and use the state to store
@@ -39,9 +37,9 @@ public interface EmuFileContents<T> {
* @param offset the offset in the file to read
* @param buf the destination buffer, whose size must be known
* @param fileSize the size of the file
* @return the number of bytes (not necessarily concrete) read
* @return the number of bytes read
*/
long read(long offset, T buf, long fileSize);
int read(long offset, T buf, long fileSize);
/**
* Write values from the given buffer into the file
@@ -49,13 +47,12 @@ public interface EmuFileContents<T> {
* @param offset the offset in the file to write
* @param buf the source buffer, whose size must be known
* @param curSize the current size of the file
* @return the number of bytes (not necessarily concrete) written
* @return the number of bytes written
*/
long write(long offset, T buf, long curSize);
int write(long offset, T buf, long curSize);
/**
* Erase the contents
*
* <p>
* Note that the file's size will be set to 0, so actual erasure of the contents may not be
* necessary, but if the contents are expensive to store, they ought to be disposed.
@@ -4,9 +4,9 @@
* 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.
@@ -30,7 +30,7 @@ import ghidra.pcode.exec.PcodeArithmetic.Purpose;
public class EmuProcessExitedException extends EmuSystemException {
/**
* Attempt to concretize a value and convert it to hex
* Attempt to concretize a value and convert it to decimal
*
* @param <T> the type of the status
* @param arithmetic the arithmetic to operate on the value
@@ -51,20 +51,29 @@ public class EmuProcessExitedException extends EmuSystemException {
/**
* 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
* @param status the status code
*/
public <T> EmuProcessExitedException(PcodeArithmetic<T> arithmetic, T status) {
super("Process exited with status " + tryConcereteToString(arithmetic, status));
this.status = status;
}
/**
* Construct a process-exited exception with the given status code
*
* @param status the status code
*/
public EmuProcessExitedException(long status) {
super("Process exited with status %d".formatted(status));
this.status = status;
}
/**
* Get the status code as a {@code T} of the throwing machine
*
@@ -16,16 +16,17 @@
package ghidra.pcode.emu.sys;
import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.pcode.emu.jit.folding.MaskedBytes;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Function;
@@ -36,16 +37,15 @@ 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, PcodeUseropLibrary)}, 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 userops as system calls.
* {@link #emu_syscall(PcodeExecutor, PcodeUseropLibrary, PcodeOp, Varnode)}, 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 userops as system calls.
*
* @param <T> the type of data processed by the system calls, typically {@code byte[]}
*/
@@ -143,77 +143,6 @@ public interface EmuSyscallLibrary<T> extends PcodeUseropLibrary<T> {
.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getCallingConvention()));
}
/**
* The {@link EmuSyscallLibrary#syscall(PcodeExecutor, PcodeUseropLibrary)} 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,
PcodeOp op, Varnode outVar, List<Varnode> inVars) {
syslib.syscall(executor, library);
}
@Override
public boolean isFunctional() {
return false;
}
@Override
public boolean hasSideEffects() {
return true;
}
@Override
public boolean modifiesContext() {
return false;
}
@Override
public boolean canInlinePcode() {
return false;
}
@Override
public Class<?> getOutputType() {
return void.class;
}
@Override
public PcodeUseropLibrary<?> getDefiningLibrary() {
return syslib;
}
@Override
public Method getJavaMethod() {
try {
return syslib.getClass()
.getMethod("syscall", PcodeExecutor.class, PcodeUseropLibrary.class);
}
catch (NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
}
/**
* The definition of a system call
*
@@ -230,83 +159,86 @@ public interface EmuSyscallLibrary<T> extends PcodeUseropLibrary<T> {
}
/**
* In case this is not an {@link AnnotatedEmuSyscallUseropLibrary} or
* {@link AnnotatedPcodeUseropLibrary}, get the definition of the "syscall" userop for inclusion
* in the {@link PcodeUseropLibrary}.
*
* A Java-callback implementation of "emu_syscall" that reads the syscall number and dispatches
* to the appropriate system call implementation at run time.
* <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
* @param reason the reason for reading state, probably {@link Reason#EXECUTE_READ}, but should
* be taken from the executor
* @return the system call number
*/
long readSyscallNumber(PcodeExecutorState<T> state, Reason reason);
/**
* 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(PcodeExecutorState, Reason)}, retrieve the relevant system call
* definition, and invoke it.
* This is the normal behavior for the interpretation-based p-code emulator anyway. However, for
* an execution engine that attempts to resolve system calls "just in time," it may be necessary
* to explicitly defer to this implementation to prevent any further attempt to resolve the
* system call, when it becomes impossible to do so.
*
* @param executor the executor
* @param library the library
* @param library the p-code userop library (often including exported system calls)
* @param syscallNumber the system call number
*/
@PcodeUserop
default void syscall(@OpExecutor PcodeExecutor<T> executor,
@OpLibrary PcodeUseropLibrary<T> library) {
long syscallNumber = readSyscallNumber(executor.getState(), executor.getReason());
@PcodeUserop(canInline = false)
default void emu_rt_syscall(@OpExecutor PcodeExecutor<T> executor,
@OpLibrary PcodeUseropLibrary<T> library, long syscallNumber) {
EmuSyscallDefinition<T> syscall = getSyscalls().get(syscallNumber);
if (syscall == null) {
throw new EmuInvalidSystemCallException(syscallNumber);
}
syscall.invoke(executor, library);
}
/**
* The Sleigh code to defer to run-time system call resolution
*/
SleighPcodeUseropDefinition FALLBACK_EMU_SYSCALL =
SleighPcodeUseropDefinition.FACTORY.define("emu_syscall")
.params("number")
.body(_ -> """
__op_output = emu_rt_syscall(number);
""")
.build();
/**
* 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. The extended or composed library must provide the "syscall" and/or other
* relevant userops defined in the Sleigh. That "syscall" userop should invoke this userop,
* passing the system call number then place the result where it belongs according to the target
* ABI.
*
* @param executor the executor
* @param library the library
* @param op the callother userop
* @param syscallNumber the varnode containing the syscall number
* @implNote At run time, the logic here will do exactly as it says. For the
* interpretation-based emulator, nothing special happens. We read the system call
* number and defer to {@link #emu_rt_syscall}, which looks it up and executes it. The
* try-catch for {@link ConcretionError} appears totally useless.
* <p>
* For the JIT-accelerated emulator, things are more interesting. Because
* {@link PcodeUserop#canInline()} is set here, we receive this callback during the
* decode phase of the translation. We read the system call number and defer to
* {@link #emu_rt_syscall} as before. (Note that {@link PcodeUserop#canInline()} being
* false does not prevent us from invoking it from Java, even though the engine is
* currently trying to inline us.) The try-catch thus protects the attempt to
* concretize the syscall number. During decode, {@code T := }{@link MaskedBytes}, so
* if the syscall turns out to be a constant (which it often does), then we can
* dispatch it at decode time. That syscall (or at least the logic that reads
* arguments and stores the result) can thus be inlined. If concretization fails, then
* we fall back to invoking {#emu_rt_syscall} at run time.
*/
@PcodeUserop(canInline = true)
default void emu_syscall(@OpExecutor PcodeExecutor<T> executor,
@OpLibrary PcodeUseropLibrary<T> library, @OpOp PcodeOp op, Varnode syscallNumber) {
T tSyscallNo = executor.getState().getVar(syscallNumber, executor.getReason());
try {
syscall.invoke(executor, library);
long lSyscallNo = executor.getArithmetic().toLong(tSyscallNo, Purpose.OTHER);
emu_rt_syscall(executor, library, lSyscallNo);
}
catch (PcodeExecutionException e) {
if (!handleError(executor, e)) {
throw e;
}
catch (ConcretionError e) {
FALLBACK_EMU_SYSCALL.<T> cast().execute(executor, library, op);
}
}
/**
* Get the map of syscalls by number
*
* <p>
* Note this method will be invoked for every emulated syscall, so it should be a simple
* Note this method will be invoked for every interpreted 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
@@ -4,9 +4,9 @@
* 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.
@@ -41,15 +41,15 @@ public class PairedEmuFileContents<L, R> implements EmuFileContents<Pair<L, R>>
}
@Override
public long read(long offset, Pair<L, R> buf, long fileSize) {
long result = left.read(offset, buf.getLeft(), fileSize);
public int read(long offset, Pair<L, R> buf, long fileSize) {
int result = left.read(offset, buf.getLeft(), fileSize);
right.read(offset, buf.getRight(), fileSize);
return result;
}
@Override
public long write(long offset, Pair<L, R> buf, long curSize) {
long result = left.write(offset, buf.getLeft(), curSize);
public int write(long offset, Pair<L, R> buf, long curSize) {
int result = left.write(offset, buf.getLeft(), curSize);
right.write(offset, buf.getRight(), curSize);
return result;
}
@@ -15,15 +15,18 @@
*/
package ghidra.pcode.emu.sys;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.lifecycle.Unfinished;
import ghidra.pcode.emu.sys.EmuSyscallLibrary.EmuSyscallDefinition;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropSymbolMap;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.VoidDataType;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
@@ -31,7 +34,6 @@ import ghidra.program.model.pcode.*;
/**
* 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
@@ -57,7 +59,7 @@ public class UseropEmuSyscallDefinition<T> implements EmuSyscallDefinition<T> {
return dtPointer;
}
protected final PcodeOp op; // fabricated for analyses that provide originating op info
protected PcodeOp op; // fabricate the CALLOTHER, so the executor can choose what to do
protected final PcodeUseropDefinition<T> opdef;
protected final List<Varnode> inVars;
protected final Varnode outVar;
@@ -83,43 +85,52 @@ public class UseropEmuSyscallDefinition<T> implements EmuSyscallDefinition<T> {
" cannot be used as a syscall");
}
DataType[] locs = new DataType[inputCount + 1];
for (int i = 0; i < locs.length; i++) {
locs[0] = opdef.getOutputType() == void.class ? VoidDataType.dataType : dtMachineWord;
for (int i = 1; i < locs.length; i++) {
locs[i] = dtMachineWord;
}
VariableStorage[] vss = convention.getStorageLocations(program, locs, false, false);
outVar = getSingleVnStorage(vss[0]);
inVars = Arrays.asList(new Varnode[inputCount]);
Varnode[] opIns = new Varnode[inputCount + 1];
opIns[0] = new Varnode(program.getAddressFactory().getConstantAddress(number), 4);
for (int i = 0; i < inputCount; i++) {
Varnode vnIn = getSingleVnStorage(vss[i + 1]);
inVars.set(i, vnIn);
opIns[i + 1] = vnIn;
}
op = new PcodeOp(new SequenceNumber(Address.NO_ADDRESS, 0), PcodeOp.CALLOTHER, opIns,
outVar);
inVars = Stream.of(vss).skip(1).map(this::getSingleVnStorage).toList();
}
/**
* Assert variable storage is a single varnode, and get that varnode
* Assert variable storage is empty or a single varnode, and get that varnode
*
* @param vs the storage
* @return the single varnode
* @return the single varnode, or null if empty
*/
protected Varnode getSingleVnStorage(VariableStorage vs) {
Varnode[] vns = vs.getVarnodes();
if (vns.length != 1) {
Unfinished.TODO();
return switch (vns.length) {
case 0 -> null;
case 1 -> vns[0];
default -> Unfinished.TODO();
};
}
PcodeOp constructOp(SleighLanguage language, PcodeUseropLibrary<?> library) {
PcodeUseropSymbolMap userops = library.getSymbols(language);
int opNumber = userops.getUseropIndex(opdef.getName());
if (opNumber == -1) {
throw new AssertionError();
}
return vns[0];
Varnode[] opIns = Stream.concat(
Stream.of(new Varnode(language.getAddressFactory().getConstantAddress(opNumber), 4)),
inVars.stream()).toArray(Varnode[]::new);
return new PcodeOp(new SequenceNumber(Address.NO_ADDRESS, 0), PcodeOp.CALLOTHER, opIns,
outVar);
}
@Override
public void invoke(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library) {
SleighLanguage language = executor.getLanguage();
if (op == null) {
op = constructOp(language, library);
}
try {
opdef.execute(executor, library, op, outVar, inVars);
executor.execute(List.of(op), library);
}
catch (PcodeExecutionException e) {
throw e;
@@ -0,0 +1,111 @@
/* ###
* 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.Purpose;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.pcode.PcodeOp;
/**
* A file descriptor for non-concrete types
*
* @param <T> the type
*/
public class AbsEmuUnixFileHandle<T> extends AbstractEmuUnixFileHandle<T> {
private T offset;
public AbsEmuUnixFileHandle(PcodeMachine<T> machine, CompilerSpec cSpec, EmuUnixFile<T> file,
Set<OpenFlag> flags, EmuUnixUser user) {
super(machine, cSpec, file, flags, user);
long off = flags.contains(OpenFlag.O_APPEND) ? file.getStat().st_size : 0;
this.offset = arithmetic.fromConst(off, offsetBytes);
}
/**
* Advance the handle's offset (negative to rewind)
*
* @param len the number of bytes to advance
*/
protected void advanceOffset(T len) {
int sizeofLen = (int) arithmetic.sizeOf(len);
offset =
arithmetic.binaryOp(PcodeOp.INT_ADD, offsetBytes, offsetBytes, offset, sizeofLen, len);
}
protected boolean isPositive(T len) {
int sizeofLen = (int) arithmetic.sizeOf(len);
return arithmetic.isTrue(
arithmetic.binaryOp(PcodeOp.INT_SLESS, 1, sizeofLen, arithmetic.fromConst(0, sizeofLen),
sizeofLen, len),
Purpose.OTHER);
}
@Override
public T getAbstractOffset() {
return offset;
}
@Override
public long getOffset() {
return arithmetic.toLong(offset, Purpose.OTHER);
}
@Override
public void seek(T offset) throws EmuIOException {
// TODO: Bounds check?
this.offset = offset;
}
@Override
public void seek(long offset) throws EmuIOException {
seek(arithmetic.fromConst(offset, offsetBytes));
}
@Override
public T readAbstract(T buf) throws EmuIOException {
checkReadable();
T len = file.read(arithmetic, offset, buf);
if (isPositive(len)) {
advanceOffset(len);
}
return len;
}
@Override
public int read(T buf) throws EmuIOException {
return (int) arithmetic.toLong(readAbstract(buf), Purpose.OTHER);
}
@Override
public T writeAbstract(T buf) throws EmuIOException {
checkWritable();
T len = file.write(arithmetic, offset, buf);
if (isPositive(len)) {
advanceOffset(len);
}
return len;
}
@Override
public int write(T buf) throws EmuIOException {
return (int) arithmetic.toLong(writeAbstract(buf), Purpose.OTHER);
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -22,7 +22,6 @@ import ghidra.util.MathUtilities;
/**
* 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.
@@ -37,7 +36,6 @@ public abstract class AbstractEmuUnixFile<T> implements EmuUnixFile<T> {
/**
* 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.
@@ -79,18 +77,31 @@ public abstract class AbstractEmuUnixFile<T> implements EmuUnixFile<T> {
return stat;
}
@Override
public int read(long offset, T buf) {
return contents.read(offset, buf, stat.st_size);
}
@Override
public T read(PcodeArithmetic<T> arithmetic, T offset, T buf) {
long off = arithmetic.toLong(offset, Purpose.OTHER);
long len = contents.read(off, buf, stat.st_size);
long len = read(off, buf); // Assure signed extension
return arithmetic.fromConst(len, (int) arithmetic.sizeOf(offset));
}
@Override
public int write(long offset, T buf) {
int len = contents.write(offset, buf, stat.st_size);
if (len > 0) {
stat.st_size = MathUtilities.unsignedMax(stat.st_size, offset + len);
}
return len;
}
@Override
public T write(PcodeArithmetic<T> arithmetic, T offset, T buf) {
long off = arithmetic.toLong(offset, Purpose.OTHER);
long len = contents.write(off, buf, stat.st_size);
stat.st_size = MathUtilities.unsignedMax(stat.st_size, off + len);
long len = write(off, buf); // Assure signed extension
return arithmetic.fromConst(len, (int) arithmetic.sizeOf(offset));
}
@@ -0,0 +1,94 @@
/* ###
* 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.program.model.lang.CompilerSpec;
/**
* Abstract for a file descriptor associated with a file on a simulated UNIX file system
*
* @param <T> the type
*/
public abstract class AbstractEmuUnixFileHandle<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;
/**
* Construct a new handle on the given file
*
* @see AbstractEmuUnixSyscallUseropLibrary#createHandle(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 AbstractEmuUnixFileHandle(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
}
/**
* Get the file opened to this handle
*
* @return the file
*/
public EmuUnixFile<T> getFile() {
return file;
}
/**
* 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");
}
}
@Override
public EmuUnixFileStat stat() {
return file.getStat();
}
@Override
public void close() {
// TODO: Let the file know a handle was closed?
}
}
@@ -22,8 +22,9 @@ 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.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.data.StringDataInstance;
@@ -33,11 +34,9 @@ import ghidra.program.model.mem.MemBuffer;
/**
* An abstract library of UNIX system calls, suitable for use with any processor
*
* <p>
* See the UNIX manual pages for more information about each specific system call, error numbers,
* etc.
*
* <p>
* TODO: The rest of the system calls common to UNIX.
*
@@ -105,7 +104,6 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary<T>
/**
* Claim the lowest available file descriptor number for the given descriptor object
*
* <p>
* The descriptor will be added to the descriptor table for the claimed number
*
@@ -206,121 +204,115 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary<T>
}
}
/**
* Place the errno into the machine as expected by the simulated platform's ABI
*
* @param executor the executor for the thread running this system call
* @param errno the error number
* @return true if the errno was successfully placed
*/
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;
}
/**
* The UNIX {@code exit} system call
*
* <p>
* This just throws an exception, which the overall simulator or script should catch.
*
* @param status the status code
* @return never
* @throws EmuProcessExitedException always
*/
@PcodeUserop
@PcodeUserop(functional = true)
@EmuSyscall("exit")
public T unix_exit(T status) {
throw new EmuProcessExitedException(machine.getArithmetic(), status);
public void unix_exit(long status) {
throw new EmuProcessExitedException(status);
}
protected T abstract_unix_read(PcodeExecutorState<T> state, T fd, T bufPtr, T count) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
try {
int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER);
EmuUnixFileDescriptor<T> desc = findFd(ifd);
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
int icount = (int) arithmetic.toLong(count, Purpose.OTHER);
T buf = arithmetic.fromConst(0, icount);
int result = desc.read(buf);
machine.getSharedState().setVar(space, bufPtr, result, true, buf);
return arithmetic.fromConst((long) result, intSize);
}
catch (EmuUnixException e) {
// TODO: Does this generalize to all UNIX, or just Linux x86/amd64?
return arithmetic.fromConst((long) -e.getErrno(), intSize);
}
}
/**
* The UNIX {@code read} system call
*
* @param state to receive the thread's state
* @param fd the file descriptor
* @param bufPtr the pointer to the buffer to receive the data
* @param count the number of bytes to read
* @return the number of bytes successfully read
*/
@PcodeUserop
@PcodeUserop(functional = true, signed = true)
@EmuSyscall("read")
public T unix_read(@OpState PcodeExecutorState<T> state, T fd, T bufPtr, T count) {
public int unix_read(int fd, long bufPtr, int count) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER);
EmuUnixFileDescriptor<T> desc = findFd(ifd);
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
// TODO: Not ideal to require concrete size, but gets unwieldy to leave it abstract
int size = (int) arithmetic.toLong(count, Purpose.OTHER);
T buf = arithmetic.fromConst(0, size);
T result = desc.read(buf);
int iresult = (int) arithmetic.toLong(result, Purpose.OTHER);
state.setVar(space, bufPtr, iresult, true, buf);
return result;
try {
EmuUnixFileDescriptor<T> desc = findFd(fd);
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
T buf = arithmetic.fromConst(0, count);
int result = desc.read(buf);
machine.getSharedState().setVar(space, bufPtr, result, true, buf);
return result;
}
catch (EmuUnixException e) {
// TODO: Does this generalize to all UNIX, or just Linux x86/amd64?
return -e.getErrno();
}
}
/**
* The UNIX {@code write} system call
*
* @param state to receive the thread's state
* @param fd the file descriptor
* @param bufPtr the pointer to the buffer of data to write
* @param count the number of bytes to write
* @return the number of bytes successfully written
*/
@PcodeUserop
@PcodeUserop(functional = true, signed = true)
@EmuSyscall("write")
public T unix_write(@OpState PcodeExecutorState<T> state, T fd, T bufPtr, T count) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER);
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 = (int) arithmetic.toLong(count, Purpose.OTHER);
T buf = state.getVar(space, bufPtr, size, true, Reason.EXECUTE_READ);
// TODO: Write back into state? "write" shouldn't touch the buffer....
return desc.write(buf);
public int unix_write(int fd, long bufPtr, int count) {
try {
EmuUnixFileDescriptor<T> desc = findFd(fd);
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
T buf =
machine.getSharedState().getVar(space, bufPtr, count, true, Reason.EXECUTE_READ);
return desc.write(buf);
}
catch (EmuUnixException e) {
return -e.getErrno();
}
}
/**
* The UNIX {@code open} system call
*
* @param state to receive the thread's state
* @param pathnamePtr the file's path (pointer to character string)
* @param flags the flags
* @param mode the mode
* @return the file descriptor
*/
@PcodeUserop
@PcodeUserop(functional = true, signed = true)
@EmuSyscall("open")
public T unix_open(@OpState PcodeExecutorState<T> state, T pathnamePtr, T flags, T mode) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int iflags = (int) arithmetic.toLong(flags, Purpose.OTHER);
int imode = (int) arithmetic.toLong(mode, Purpose.OTHER);
long pathnameOff = arithmetic.toLong(pathnamePtr, Purpose.OTHER);
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
SettingsImpl settings = new SettingsImpl();
MemBuffer buffer = state.getConcreteBuffer(space.getAddress(pathnameOff), Purpose.OTHER);
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);
public int unix_open(long pathnamePtr, int flags, int mode) {
try {
AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace();
SettingsImpl settings = new SettingsImpl();
MemBuffer buffer = machine.getSharedState()
.getConcreteBuffer(space.getAddress(pathnamePtr), Purpose.OTHER);
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(flags), user, mode);
return claimFd(createHandle(file, flags));
}
catch (EmuUnixException e) {
return -e.getErrno();
}
}
/**
@@ -329,30 +321,32 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary<T>
* @param fd the file descriptor
* @return 0 for success
*/
@PcodeUserop
@PcodeUserop(functional = true, signed = true)
@EmuSyscall("close")
public T unix_close(T fd) {
PcodeArithmetic<T> arithmetic = machine.getArithmetic();
int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER);
// 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);
public int unix_close(int fd) {
try {
// TODO: Some fs.close or file.close, when all handles have released it?
EmuUnixFileDescriptor<T> desc = releaseFd(fd);
desc.close();
return 0;
}
catch (EmuUnixException e) {
return -e.getErrno();
}
}
/**
* The UNIX {@code group_exit} system call
*
* <p>
* This just throws an exception, which the overall simulator or script should catch.
*
* @param status the status code
* @throws EmuProcessExitedException always
*/
@PcodeUserop
@PcodeUserop(functional = true)
@EmuSyscall("group_exit")
public void unix_group_exit(T status) {
throw new EmuProcessExitedException(machine.getArithmetic(), status);
public void unix_group_exit(long status) {
throw new EmuProcessExitedException(status);
}
/**
@@ -368,7 +362,6 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary<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 from
@@ -385,6 +378,7 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary<T>
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));
_if(tmp_ret.ltis(0), () -> _result(tmp_ret));
tmp_total.addiTo(tmp_ret);
_if(tmp_ret.ltiu(tmp_len), () -> _break()); // We got less than this buffer
});
@@ -4,9 +4,9 @@
* 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.
@@ -30,8 +30,6 @@ public abstract class AbstractStreamEmuUnixFileHandle<T> implements EmuUnixFileD
protected final PcodeArithmetic<T> arithmetic;
protected final int offsetBytes;
private final T offset;
/**
* Construct a new handle
*
@@ -42,12 +40,16 @@ public abstract class AbstractStreamEmuUnixFileHandle<T> implements EmuUnixFileD
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;
public T getAbstractOffset() {
return arithmetic.fromConst(0, offsetBytes);
}
@Override
public long getOffset() {
return 0;
}
@Override
@@ -55,6 +57,21 @@ public abstract class AbstractStreamEmuUnixFileHandle<T> implements EmuUnixFileD
// No effect
}
@Override
public void seek(long offset) throws EmuIOException {
// No effect
}
@Override
public T readAbstract(T buf) throws EmuIOException {
return arithmetic.fromConst(read(buf), offsetBytes);
}
@Override
public T writeAbstract(T buf) throws EmuIOException {
return arithmetic.fromConst(write(buf), offsetBytes);
}
@Override
public EmuUnixFileStat stat() {
return Unfinished.TODO();
@@ -4,9 +4,9 @@
* 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.
@@ -20,25 +20,16 @@ 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.exec.PcodeArithmetic.Purpose;
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
* A concrete file descriptor
*
* @param <T> the type of values stored in 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;
public class DefaultEmuUnixFileHandle<T> extends AbstractEmuUnixFileHandle<T> {
long offset;
/**
* Construct a new handle on the given file
@@ -52,90 +43,58 @@ public class DefaultEmuUnixFileHandle<T> implements EmuUnixFileDescriptor<T> {
*/
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);
}
/**
* Get the file opened to this handle
*
* @return the file
*/
public EmuUnixFile<T> getFile() {
return file;
}
/**
* 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 = (int) arithmetic.sizeOf(len);
offset =
arithmetic.binaryOp(PcodeOp.INT_ADD, offsetBytes, offsetBytes, offset, sizeofLen, len);
super(machine, cSpec, file, flags, user);
this.offset = flags.contains(OpenFlag.O_APPEND) ? file.getStat().st_size : 0;
}
@Override
public T getOffset() {
public long getOffset() {
return offset;
}
@Override
public T getAbstractOffset() {
return arithmetic.fromConst(offset, offsetBytes);
}
@Override
public void seek(T offset) throws EmuIOException {
seek(arithmetic.toLong(offset, Purpose.OTHER));
}
@Override
public void seek(long offset) throws EmuIOException {
// TODO: Where does bounds check happen?
this.offset = offset;
}
@Override
public T read(T buf) throws EmuIOException {
public int 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);
int len = file.read(offset, buf);
if (len > 0) {
offset += len;
}
T len = file.write(arithmetic, offset, buf);
advanceOffset(len);
return len;
}
@Override
public EmuUnixFileStat stat() {
return file.getStat();
public T readAbstract(T buf) throws EmuIOException {
return arithmetic.fromConst(read(buf), offsetBytes);
}
@Override
public void close() {
// TODO: Let the file know a handle was closed?
public int write(T buf) throws EmuIOException {
checkWritable();
int len = file.write(offset, buf);
if (len > 0) {
offset += len;
}
return len;
}
@Override
public T writeAbstract(T buf) throws EmuIOException {
return arithmetic.fromConst(write(buf), offsetBytes);
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -20,7 +20,6 @@ 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.
@@ -31,7 +30,6 @@ 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
@@ -43,7 +41,6 @@ public interface EmuUnixFile<T> {
/**
* 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.
@@ -56,8 +53,19 @@ public interface EmuUnixFile<T> {
T read(PcodeArithmetic<T> arithmetic, T offset, T buf);
/**
* Write contents into the file starting at the given offset from the given buffer
* 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 offset the offset
* @param buf the buffer
* @return the number of bytes read
*/
int read(long 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.
@@ -69,6 +77,18 @@ public interface EmuUnixFile<T> {
*/
T write(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 offset the offset
* @param buf the buffer
* @return the number of bytes written
*/
int write(long offset, T buf);
/**
* Erase the contents of the file
*/
@@ -4,9 +4,9 @@
* 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.
@@ -15,6 +15,7 @@
*/
package ghidra.pcode.emu.unix;
import ghidra.lifecycle.Experimental;
import ghidra.pcode.emu.sys.EmuIOException;
/**
@@ -22,6 +23,7 @@ import ghidra.pcode.emu.sys.EmuIOException;
*
* @param <T> the type of values stored in the file
*/
@Experimental
public interface EmuUnixFileDescriptor<T> {
/**
* The default file descriptor for stdin (standard input)
@@ -37,20 +39,31 @@ public interface EmuUnixFileDescriptor<T> {
int FD_STDERR = 2;
/**
* Get the current offset of the file, or 0 if not applicable
*
* @return the offset
* {@return the current offset of the file, or 0 if not applicable}
*/
T getOffset();
T getAbstractOffset();
/**
* See to the given offset
* {@return the current offset of the file, or 0 if not applicable}
*/
long getOffset();
/**
* Seek to the given offset
*
* @param offset the desired offset
* @throws EmuIOException if an error occurred
*/
void seek(T offset) throws EmuIOException;
/**
* Seek to the given offset
*
* @param offset the desired offset
* @throws EmuIOException if an error occurred
*/
void seek(long offset) throws EmuIOException;
/**
* Read from the file opened by this handle
*
@@ -58,19 +71,37 @@ public interface EmuUnixFileDescriptor<T> {
* @return the number of bytes read
* @throws EmuIOException if an error occurred
*/
T read(T buf) throws EmuIOException;
T readAbstract(T buf) throws EmuIOException;
/**
* Read into the file opened by this handle
* 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
*/
int read(T buf) throws EmuIOException;
/**
* Write 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;
T writeAbstract(T buf) throws EmuIOException;
/**
* Obtain the {@code stat} structure of the file opened by this handle
* Write into the file opened by this handle
*
* @param buf the source buffer
* @return the number of bytes written
* @throws EmuIOException if an error occurred
*/
int write(T buf) throws EmuIOException;
/**
* {@return the {@code stat} structure of the file opened by this handle}
*/
EmuUnixFileStat stat();
@@ -4,9 +4,9 @@
* 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.
@@ -67,7 +67,6 @@ public class IOStreamEmuUnixFileHandle extends AbstractStreamEmuUnixFileHandle<b
/**
* Construct a proxy for a host resource
*
* <p>
* <b>WARNING:</b> Think carefully before proxying any host resource to a temperamental target
* program.
@@ -85,13 +84,12 @@ public class IOStreamEmuUnixFileHandle extends AbstractStreamEmuUnixFileHandle<b
}
@Override
public byte[] read(byte[] buf) throws EmuIOException {
public int read(byte[] buf) throws EmuIOException {
if (input == null) {
return arithmetic.fromConst(0, offsetBytes);
return 0;
}
try {
int result = input.read(buf);
return arithmetic.fromConst(result, offsetBytes);
return input.read(buf);
}
catch (IOException e) {
throw new EmuIOException("Could not read host input stream", e);
@@ -99,13 +97,13 @@ public class IOStreamEmuUnixFileHandle extends AbstractStreamEmuUnixFileHandle<b
}
@Override
public byte[] write(byte[] buf) throws EmuIOException {
public int write(byte[] buf) throws EmuIOException {
if (output == null) {
return arithmetic.fromConst(0, offsetBytes);
return 0;
}
try {
output.write(buf);
return arithmetic.fromConst(buf.length, offsetBytes);
return buf.length;
}
catch (IOException e) {
throw new EmuIOException("Could not write host output stream", e);
@@ -4,9 +4,9 @@
* 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.
@@ -52,8 +52,10 @@ class IndexExpr extends Expr implements LValInternal {
st.append(base.generate(this));
st.append(" + (");
st.append(index.generate(this));
st.append("*");
st.append(Integer.toString(elemLen));
if (elemLen != 1) {
st.append("*");
st.append(Integer.toString(elemLen));
}
st.append("))");
return st;
}
@@ -30,8 +30,8 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.lifecycle.Internal;
import ghidra.pcode.emu.unix.AbstractEmuUnixSyscallUseropLibrary;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.pcode.exec.SleighPcodeUseropDefinition.BuilderStage1;
import ghidra.pcode.exec.SleighPcodeUseropDefinition.Factory;
import ghidra.pcode.floatformat.FloatFormatFactory;
import ghidra.pcode.struct.DefaultVar.Check;
import ghidra.program.model.address.AddressSpace;
@@ -46,14 +46,12 @@ import utilities.util.AnnotationUtilities;
/**
* The primary class for using the "structured sleigh" DSL
*
* <p>
* This provides some conveniences for generating Sleigh source code, which is otherwise completely
* typeless and lacks basic control structure. In general, the types are not used so much for type
* checking as they are for easing access to fields of C structures, array indexing, etc.
* Furthermore, it becomes possible to re-use code when data types differ among platforms, so long
* as those variations are limited to field offsets and type sizes.
*
* <p>
* Start by declaring an extension of {@link StructuredSleigh}. Then put any necessary "forward
* declarations" as fields of the class. Then declare methods annotated with
@@ -75,14 +73,12 @@ import utilities.util.AnnotationUtilities;
* }
* }
* </pre>
*
* <p>
* This will simply generate the source "{@code r0 = 0xdeadbeef:4}", but it also provides all the
* scaffolding to compile and invoke the userop as in a {@link PcodeUseropLibrary}. Internal methods
* -- which essentially behave like macros -- may be used, so only annotate methods to export as
* userops. For a more complete and practical example of using structured sleigh in a userop
* library, see {@link AbstractEmuUnixSyscallUseropLibrary}.
*
* <p>
* Structured sleigh is also usable in a more standalone manner:
*
@@ -103,7 +99,6 @@ import utilities.util.AnnotationUtilities;
* System.out.println(myUserop.programFor(new Varnode(r0.getAddress(), r0.getNumBytes()), List.of(),
* PcodeUseropLibrary.NIL));
* </pre>
*
* <p>
* Known limitations:
* <ul>
@@ -128,7 +123,6 @@ public class StructuredSleigh {
/**
* "Export" a method as a p-code userop implemented using p-code compiled from structured Sleigh
*
* <p>
* This is applied to methods used to generate Sleigh source code. Take note that the method is
* only invoked once (for a given library instance) to generate code. Thus, beware of
@@ -141,12 +135,10 @@ public class StructuredSleigh {
* r0.set(Random.nextLong()); // BAD: Die rolled once at compile time
* }
* </pre>
*
* <p>
* The random number will be generated once at structured Sleigh compilation time, and then that
* same number used on every invocation of the p-code userop. Instead, this userop should be
* implemented using a Java callback, i.e., {@link AnnotatedPcodeUseropLibrary.PcodeUserop}.
*
* <p>
* The userop may accept parameters and return a result. To accept parameters, declare them in
* the Java method signature and annotate them with {@link Param}. To return a result, name the
@@ -162,12 +154,11 @@ public class StructuredSleigh {
* The data type path for the "return type" of the userop. See
* {@link StructuredSleigh#type(String)}.
*/
String type() default "void";
String type() default "undefined";
}
/**
* Declare a parameter of the p-code userop
*
* <p>
* This is attached to parameters of methods annotated with {@link StructuredUserop}, providing
* the type and name of the parameter. The Java type of the parameter must be {@link Var}. For
@@ -191,7 +182,6 @@ public class StructuredSleigh {
/**
* The name of the parameter in the output Sleigh code
*
* <p>
* If the variable is referenced via {@link StructuredSleigh#s(String)} or
* {@link StructuredSleigh#e(String)}, then is it necessary to specify the name used in the
@@ -206,7 +196,6 @@ public class StructuredSleigh {
/**
* The declaration of an "imported" userop
*
* <p>
* Because Sleigh is typeless, structured Sleigh needs additional type information about the
* imported userop. The referenced userop may be implemented by another library and may be a
@@ -238,7 +227,6 @@ public class StructuredSleigh {
/**
* Generate an invocation of the userop
*
* <p>
* If the userop has a result type, then the resulting statement will also have a value. If
* the user has a {@code void} result type, the "value" should not be used. Otherwise, a
@@ -263,7 +251,6 @@ public class StructuredSleigh {
/**
* Cast the value to the given type
*
* <p>
* This functions like a C-style pointer cast. There are no implied operations or
* conversions. Notably, casting between integers and floats is just a re-interpretation of
@@ -276,7 +263,6 @@ public class StructuredSleigh {
/**
* Generate a dereference (in the C sense)
*
* <p>
* The value is treated as an address, and the result is essentially a variable in the given
* target address space.
@@ -813,7 +799,6 @@ public class StructuredSleigh {
/**
* Generate a field offset
*
* <p>
* This departs subtly from expected C semantics. This value's type is assumed to be a
* pointer to a {@link Composite}. That type is retrieved and the field located. This then
@@ -821,7 +806,6 @@ public class StructuredSleigh {
* a pointer to the type of the field. The C equivalent is "{@code &(val->field)}".
* Essentially, it's just address computation. Note that this operator will fail if the type
* is not a pointer. It cannot be used directly on the {@link Composite} type.
*
* <p>
* TODO: Allow direct use on the composite type? Some mechanism for dealing with bitfields?
* Bitfields cannot really work if this is just pointer manipulation. If it's also allowed
@@ -838,7 +822,6 @@ public class StructuredSleigh {
/**
* Generate an array index
*
* <p>
* This departs subtly from expected C semantics. This value's type is assumed to be a
* pointer to the element type. The size of the element type is computed, and this generates
@@ -846,8 +829,6 @@ public class StructuredSleigh {
* the result is the same as this value's type. The C equivalent is "{@code &(val[index])}".
* Essentially, it's just address computation. Note that this operator will fail if the type
* is not a pointer. It cannot be used on an {@link Array} type.
*
*
* <p>
* TODO: Allow use of {@link Array} type? While it's possible for authors to specify pointer
* types for their variables, the types of fields they access may not be under their
@@ -972,7 +953,6 @@ public class StructuredSleigh {
protected interface Label {
/**
* Borrow this label
*
* <p>
* This should be used whenever a statement (or its children) may need to generate a goto
* using the "next" label passed into it. If "next" is the fall-through label, this will
@@ -986,7 +966,6 @@ public class StructuredSleigh {
/**
* Generate code for this label
*
* <p>
* This must be the last method called on the label, because it relies on knowing whether or
* not the label is actually used. (The Sleigh compiler rejects code if it contains unused
@@ -1137,7 +1116,6 @@ public class StructuredSleigh {
/**
* The virtual fall-through label
*
* <p>
* The idea is that no one should ever need to generate labels or gotos to achieve fall-through.
* Any attempt to do so probably indicates an implementation error where code generation failed
@@ -1178,7 +1156,6 @@ public class StructuredSleigh {
// Used only for variable name validation
final PcodeParser parser;
final SleighLanguage language;
private final Factory factory;
private BlockStmt root;
// Used to determine statement binding, e.g., for "_break" and "_result"
@@ -1214,7 +1191,6 @@ public class StructuredSleigh {
protected StructuredSleigh(CompilerSpec cs) {
this.language = (SleighLanguage) cs.getLanguage();
this.parser = SleighProgramCompiler.createParser(language);
this.factory = new Factory(language);
this.dtm = new StandAloneDataTypeManager("/", cs.getDataOrganization());
addDataTypeSource(dtm);
@@ -1282,7 +1258,6 @@ public class StructuredSleigh {
/**
* Declare a local variable with the given name and type
*
* <p>
* If the variable has no definitive type, but has a known size, use e.g.,
* {@link Undefined8DataType} or {@link #type(String)} with "{@code /undefined8}". If the
@@ -1301,7 +1276,6 @@ public class StructuredSleigh {
/**
* Declare a local variable with the given name and initial value
*
* <p>
* The type is taken from that of the initial value.
*
@@ -1377,7 +1351,6 @@ public class StructuredSleigh {
/**
* Generate a literal (or immediate or constant) value
*
* <p>
* <b>WARNING:</b> Passing a literal int that turns out to be negative (easy to do in hex
* notation) can be perilous. For example, 0xdeadbeef will actually result in 0xffffffffdeadbeef
@@ -1425,7 +1398,6 @@ public class StructuredSleigh {
/**
* Generate Sleigh code
*
* <p>
* This is similar in concept to inline assembly. It allows the embedding of Sleigh code into
* Structured Sleigh that is otherwise impossible or inconvenient to state. No effort is made to
@@ -1440,7 +1412,6 @@ public class StructuredSleigh {
/**
* Generate a Sleigh expression
*
* <p>
* This is similar in concept to inline assembly, except it also has a value. It allows the
* embedding of Sleigh code into Structured Sleigh that is otherwise impossible or inconvenient
@@ -1476,7 +1447,6 @@ public class StructuredSleigh {
/**
* Generate an "else if" clause for the wrapped "if" statement
*
* <p>
* This is shorthand for {@code _else(_if(...))} but avoids the unnecessary nesting of
* parentheses.
@@ -1498,7 +1468,6 @@ public class StructuredSleigh {
/**
* Generate an "if" statement
*
* <p>
* The body is usually a lambda containing additional statements, predicated on this statement's
* condition, so that it resembles Java / C syntax:
@@ -1508,7 +1477,6 @@ public class StructuredSleigh {
* r1.set(1);
* });
* </pre>
*
* <p>
* The returned "wrapper" provides for additional follow-on syntax, e.g.:
*
@@ -1532,7 +1500,6 @@ public class StructuredSleigh {
/**
* Generate a "while" statement
*
* <p>
* The body is usually a lambda containing the controlled statements, so that it resembles Java
* / C syntax:
@@ -1553,7 +1520,6 @@ public class StructuredSleigh {
/**
* Generate a "for" statement
*
* <p>
* The body is usually a lambda containing the controlled statements, so that it resembles Java
* / C syntax:
@@ -1566,7 +1532,6 @@ public class StructuredSleigh {
* total.addiTo(temp);
* });
* </pre>
*
* <p>
* TIP: If the number of repetitions is known at generation time, consider using a standard Java
* for loop, as a sort of Structured Sleigh macro. For example, to broadcast element 0 to an
@@ -1578,7 +1543,6 @@ public class StructuredSleigh {
* arr.index(i).deref().set(arr.index(0).deref());
* }
* </pre>
*
* <p>
* Instead of generating a loop, this will generate 15 Sleigh statements.
*
@@ -1593,7 +1557,6 @@ public class StructuredSleigh {
/**
* Generate a "break" statement
*
* <p>
* This must appear in the body of a loop statement. It binds to the innermost loop statement in
* which it appears, generating code to leave that loop.
@@ -1604,7 +1567,6 @@ public class StructuredSleigh {
/**
* Generate a "continue" statement
*
* <p>
* This must appear in the body of a loop statement. It binds to the innermost loop statement in
* which it appears, generating code to immediately repeat the loop, skipping the remainder of
@@ -1616,7 +1578,6 @@ public class StructuredSleigh {
/**
* Generate a "result" statement
*
* <p>
* This is semantically similar to a C "return" statement, but is named differently to avoid
* confusion with Sleigh's return statement. When this is code implementing a p-code userop,
@@ -1634,12 +1595,10 @@ public class StructuredSleigh {
/**
* Generate a "return" statement
*
* <p>
* This models (in part) a C-style return from the current target function to its caller. It
* simply generates the "return" Sleigh statement, which is an indirect branch to the given
* target. Target is typically popped from the stack or read from a link register.
*
* <p>
* Contrast with {@link #_result(RVal)}
*
@@ -1660,7 +1619,6 @@ public class StructuredSleigh {
/**
* Get the method lookup for this context
*
* <p>
* If the annotated methods cannot be accessed by {@link StructuredSleigh}, this method must be
* overridden. It should simply return {@link MethodHandles#lookup()}. This is necessary when
@@ -1673,7 +1631,7 @@ public class StructuredSleigh {
return MethodHandles.lookup();
}
private <T> SleighPcodeUseropDefinition<T> compile(StructuredUserop annot, Lookup lookup,
private SleighPcodeUseropDefinition compile(StructuredUserop annot, Lookup lookup,
Method method) {
if (annot == null) {
throw new IllegalArgumentException("Method " + method + " is missing @" +
@@ -1691,7 +1649,7 @@ public class StructuredSleigh {
throw new IllegalArgumentException("Cannot access " + method + " having @" +
StructuredUserop.class.getSimpleName() + " annotation. Override getMethodLookup()");
}
BuilderStage1 builder = factory.define(method.getName());
BuilderStage1 builder = SleighPcodeUseropDefinition.FACTORY.define(method.getName());
DataType retType = type(annot.type());
@@ -1733,7 +1691,7 @@ public class StructuredSleigh {
}
});
StringTree source = root.generate(FALL, FALL);
builder.body(args -> source.toString());
builder.body(_ -> source.toString());
return builder.build();
}
@@ -1743,43 +1701,58 @@ public class StructuredSleigh {
* @param <T> the type of values used by the userops. For sleigh, this can be anything.
* @param into the destination map, usually belonging to a {@link PcodeUseropLibrary}.
*/
public <T> void generate(Map<String, ? super SleighPcodeUseropDefinition<T>> into) {
public <T> void generate(Map<String, PcodeUseropDefinition<T>> into) {
Lookup lookup = getMethodLookup();
Class<? extends StructuredSleigh> cls = this.getClass();
Set<Method> methods =
CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls));
Set<Method> methods = CACHE_BY_CLASS.computeIfAbsent(cls, _ -> collectDefinitions(cls));
for (Method m : methods) {
into.put(m.getName(), doGenerate(lookup, m));
into.put(m.getName(), doGenerate(lookup, m).cast());
}
}
/**
* Generate the userop for a given Java method
*
* @param <T> the type of values used by the userop. For sleigh, this can be anything.
* @param m the method exported as a userop
* @return the userop
*/
public <T> SleighPcodeUseropDefinition<T> generate(Method m) {
public SleighPcodeUseropDefinition generate(Method m) {
return doGenerate(getMethodLookup(), m);
}
protected <T> SleighPcodeUseropDefinition<T> doGenerate(Lookup lookup, Method m) {
/**
* Generate the userop for the Java method with the given name
* <p>
* If more than one annotated method with the given name exists, one is chosen arbitrarily.
*
* @param name the method name exported as a userop
* @return the userop
*/
public SleighPcodeUseropDefinition generate(String name) {
Class<? extends StructuredSleigh> cls = this.getClass();
Method method =
CACHE_BY_CLASS.computeIfAbsent(this.getClass(), _ -> collectDefinitions(cls))
.stream()
.filter(m -> m.getName().equals(name))
.findAny()
.orElseThrow();
return generate(method);
}
protected SleighPcodeUseropDefinition doGenerate(Lookup lookup, Method m) {
return compile(m.getAnnotation(StructuredUserop.class), lookup, m);
}
/**
* Generate all the exported userops and return them in a map
*
* <p>
* This is typically only used when not part of a larger {@link PcodeUseropLibrary}, for example
* to aid in developing a Sleigh module or for generating injects.
*
* @param <T> the type of values used by the userop. For sleigh, this can be anything.
* @return the userop
*/
public <T> Map<String, SleighPcodeUseropDefinition<T>> generate() {
Map<String, SleighPcodeUseropDefinition<T>> ops = new HashMap<>();
public <T> Map<String, PcodeUseropDefinition<T>> generate() {
Map<String, PcodeUseropDefinition<T>> ops = new HashMap<>();
generate(ops);
return ops;
}
@@ -0,0 +1,106 @@
/* ###
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.lang.invoke.MethodHandles;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.jit.*;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint;
import ghidra.pcode.exec.PcodeUseropLibrary;
public class JitLinuxAmd64SyscallUseropLibraryTest extends EmuLinuxAmd64SyscallUseropLibraryTest {
protected final class LinuxAmd64JitPcodeEmulator extends JitPcodeEmulator {
public LinuxAmd64JitPcodeEmulator() {
super(program.getLanguage(), new JitConfiguration(), MethodHandles.lookup());
}
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
syscalls = new EmuLinuxAmd64SyscallUseropLibrary<>(this, fs, program) {
@Override
public void unix_exit(long status) {
fail("Linux-amd64 shoud use group_exit");
super.unix_exit(status);
}
@Override
public void unix_group_exit(long status) {
checkStackIsDirect();
super.unix_group_exit(status);
}
@Override
public int unix_read(int fd, long bufPtr, int count) {
checkStackIsDirect();
return super.unix_read(fd, bufPtr, count);
}
@Override
public int unix_write(int fd, long bufPtr, int count) {
checkStackIsDirect();
return super.unix_write(fd, bufPtr, count);
}
@Override
public int unix_open(long pathnamePtr, int flags, int mode) {
checkStackIsDirect();
return super.unix_open(pathnamePtr, flags, mode);
}
@Override
public int unix_close(int fd) {
checkStackIsDirect();
return super.unix_close(fd);
}
};
capture = new CaptureUseropLibrary();
return syscalls.compose(capture);
}
@Override
protected JitPcodeThread createThread(String name) {
return new JitPcodeThread(name, this) {
int count = 0;
@Override
public void count(int instructions, int trailingOps) {
count += (instructions + trailingOps);
if (count > 1000) {
fail("Probably an infinite loop");
}
super.count(instructions, trailingOps);
}
};
}
}
void checkStackIsDirect() {
Throwable here = new Throwable();
StackTraceElement oneUp = here.getStackTrace()[2]; // The userop and checkStack
assertEquals(EntryPoint.class.getName(), oneUp.getClassName());
assertEquals("run", oneUp.getMethodName());
}
@Override
protected PcodeEmulator createEmulator() {
return new LinuxAmd64JitPcodeEmulator();
}
}
@@ -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.emu.linux;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.lang.invoke.MethodHandles;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.jit.*;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint;
import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest.CaptureUseropLibrary;
import ghidra.pcode.exec.PcodeUseropLibrary;
public class JitLinuxX86SyscallUseropLibraryTest extends EmuLinuxX86SyscallUseropLibraryTest {
protected final class LinuxX86JitPcodeEmulator extends JitPcodeEmulator {
public LinuxX86JitPcodeEmulator() {
super(program.getLanguage(), new JitConfiguration(), MethodHandles.lookup());
}
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
syscalls = new EmuLinuxX86SyscallUseropLibrary<byte[]>(this, fs, program) {
@Override
public void unix_exit(long status) {
checkStackIsDirect();
super.unix_exit(status);
}
@Override
public void unix_group_exit(long status) {
fail("Linux-x86 shoud use exit");
super.unix_group_exit(status);
}
@Override
public int unix_read(int fd, long bufPtr, int count) {
checkStackIsDirect();
return super.unix_read(fd, bufPtr, count);
}
@Override
public int unix_write(int fd, long bufPtr, int count) {
checkStackIsDirect();
return super.unix_write(fd, bufPtr, count);
}
@Override
public int unix_open(long pathnamePtr, int flags, int mode) {
checkStackIsDirect();
return super.unix_open(pathnamePtr, flags, mode);
}
@Override
public int unix_close(int fd) {
checkStackIsDirect();
return super.unix_close(fd);
}
};
capture = new CaptureUseropLibrary();
return syscalls.compose(capture);
}
@Override
protected JitPcodeThread createThread(String name) {
return new JitPcodeThread(name, this) {
int count = 0;
@Override
public void count(int instructions, int trailingOps) {
count += (instructions + trailingOps);
if (count > 1000) {
fail("Probably an infinite loop");
}
super.count(instructions, trailingOps);
}
};
}
}
void checkStackIsDirect() {
Throwable here = new Throwable();
StackTraceElement oneUp = here.getStackTrace()[2]; // The userop and checkStack
assertEquals(EntryPoint.class.getName(), oneUp.getClassName());
assertEquals("run", oneUp.getMethodName());
}
@Override
protected PcodeEmulator createEmulator() {
return new LinuxX86JitPcodeEmulator();
}
}
@@ -26,8 +26,7 @@ import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.*;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.SleighPcodeUseropDefinition.BuilderStage1;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataTypeConflictHandler;
import ghidra.program.model.data.PointerDataType;
@@ -44,26 +43,25 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
/**
* A library with two 4-argument syscalls.
*
* <p>
* For x86:LE:64:default:gcc, the storage for the 4th argument varies by calling convention. For
* __stdcall, it's RCX. For syscall, it's R10. Both syscalls just return the 4th argument. Thus,
* it's possible to detect whether the emulator heeds conventions by binding each to a different
* convention, then invoking them with distinct values placed in RCX and R10 and verifying that
* the correct value shows in RAX, the return register.
* {@code __stdcall}, it's RCX. For syscall, it's R10. Both syscalls just return the 4th
* argument. Thus, it's possible to detect whether the emulator heeds conventions by binding
* each to a different convention, then invoking them with distinct values placed in RCX and R10
* and verifying that the correct value shows in RAX, the return register.
*/
protected final class SyscallTestUseropLibrary
protected static final class SyscallTestUseropLibrary
extends AnnotatedEmuSyscallUseropLibrary<byte[]> {
protected final Register regRAX;
public SyscallTestUseropLibrary(PcodeMachine<byte[]> machine, Program program) {
super(machine, program);
regRAX = machine.getLanguage().getRegister("RAX");
}
@Override
public long readSyscallNumber(PcodeExecutorState<byte[]> state, Reason reason) {
return machine.getArithmetic().toLong(state.getVar(regRAX, reason), Purpose.OTHER);
@PcodeUserop
public SleighPcodeUseropDefinition syscall(BuilderStage1 builder) {
return builder.params().body(_ -> """
RAX = emu_syscall(RAX);
""").build();
}
@PcodeUserop
@@ -77,11 +75,6 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
public byte[] test_syscall1(byte[] arg0, byte[] arg1, byte[] arg2, byte[] arg3) {
return arg3;
}
@Override
public boolean handleError(PcodeExecutor<byte[]> executor, PcodeExecutionException err) {
return false;
}
}
protected final class SyscallTestPcodeEmulator extends PcodeEmulator {
@@ -129,7 +122,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
start = space.getAddress(0x00400000);
size = 0x1000;
try (Transaction tx = program.openTransaction("Initialize")) {
try (Transaction _ = program.openTransaction("Initialize")) {
block = program.getMemory()
.createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY,
false);
@@ -178,7 +171,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
@Test
public void testSyscallWithStdcallConvention() throws Exception {
try (Transaction tx = program.openTransaction("Initialize")) {
try (Transaction _ = program.openTransaction("Initialize")) {
asm.assemble(start,
"MOV RAX,0",
"MOV RCX,0xbeef", // Will be clobbered with RIP by SYSCALL
@@ -204,7 +197,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
@Test
public void testSyscallWithSyscallConvention() throws Exception {
try (Transaction tx = program.openTransaction("Initialize")) {
try (Transaction _ = program.openTransaction("Initialize")) {
asm.assemble(start,
"MOV RAX,1",
"MOV RCX,0xdead", // Will be clobbered with RIP by SYSCALL
@@ -26,6 +26,7 @@ import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.processors.sleigh.SleighException;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.*;
import ghidra.pcode.struct.StructuredSleigh;
import ghidra.program.model.lang.*;
@@ -34,7 +35,7 @@ import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.test.ToyProgramBuilder;
public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest {
private Language toy;
private SleighLanguage toy;
private CompilerSpec cs;
private Register r0;
@@ -51,7 +52,8 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
@Before
public void setUp() throws Exception {
toy = getLanguageService().getLanguage(new LanguageID(ToyProgramBuilder._TOY64_BE));
toy = (SleighLanguage) getLanguageService()
.getLanguage(new LanguageID(ToyProgramBuilder._TOY64_BE));
cs = toy.getDefaultCompilerSpec();
r0 = toy.getRegister("r0");
}
@@ -64,7 +66,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
_result(param_1.muli(2));
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("__op_output = (param_1 * 0x2:4);\n", myUserop.getBody());
}
@@ -89,7 +91,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
_result(vR0.muli(2));
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("__op_output = (r0 * 0x2:4);\n", myUserop.getBody());
}
@@ -102,13 +104,13 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
_result(myVar.muli(2));
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("""
local my_var:4;
__op_output = (my_var * 0x2:4);
""", myUserop.getBody());
// Verify the source compiles
myUserop.programFor(List.of(new Varnode(r0.getAddress(), r0.getNumBytes())),
myUserop.programFor(toy, List.of(new Varnode(r0.getAddress(), r0.getNumBytes())),
PcodeUseropLibrary.NIL);
}
@@ -120,7 +122,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
// Don't need to do anything
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("", myUserop.getBody());
}
@@ -136,7 +138,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
});
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("""
if 0x1:1 goto <L1>;
tmp = 0x2:4;
@@ -157,7 +159,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
});
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("""
if (!0x1:1) goto <L1>;
tmp = 0x1:4;
@@ -178,7 +180,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
_result(sum);
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("""
local i:4;
local sum:4;
@@ -209,7 +211,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
_result(sum);
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("""
local i:4;
local sum:4;
@@ -233,7 +235,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
_return(lit(0xdeadbeefL, 8));
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
assertEquals("return [0xdeadbeef:8];\n", myUserop.getBody());
// TODO: Test that the generated code compiles in a slaspec file.
// It's rejected for injects because "return" is not valid there.
@@ -246,9 +248,9 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest
public void my_userop() {
}
};
SleighPcodeUseropDefinition<Object> myUserop = ss.generate().get("my_userop");
PcodeProgram program =
myUserop.programFor(SleighPcodeUseropDefinition.EMPTY_ARGS, PcodeUseropLibrary.nil());
SleighPcodeUseropDefinition myUserop = ss.generate("my_userop");
PcodeProgram program = myUserop.programFor(toy, SleighPcodeUseropDefinition.EMPTY_ARGS,
PcodeUseropLibrary.nil());
assertTrue(program.getCode().isEmpty());
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -120,7 +120,7 @@ public class SemisparseByteArray {
if (blockOffset != 0) {
throw new IllegalArgumentException("Offset must be at block boundary");
}
return blocks.computeIfAbsent(blockNum, n -> new byte[BLOCK_SIZE]);
return blocks.computeIfAbsent(blockNum, _ -> new byte[BLOCK_SIZE]);
}
/**
@@ -188,6 +188,16 @@ public class SemisparseByteArray {
return result;
}
/**
* {@return the full set of defined ranges}
*
* Use of the returned span set is not thread safe. The client must not attempt to mutate the
* set, or else there may be undefined behavior.
*/
public ULongSpanSet getInitialized() {
return defined;
}
/**
* Check if a range is completely initialized
*
@@ -262,7 +272,7 @@ public class SemisparseByteArray {
// Write out portion of first block (could be full block)
long blockNum = Long.divideUnsigned(loc, BLOCK_SIZE);
int blockOffset = (int) Long.remainderUnsigned(loc, BLOCK_SIZE);
byte[] block = blocks.computeIfAbsent(blockNum, n -> new byte[BLOCK_SIZE]);
byte[] block = blocks.computeIfAbsent(blockNum, _ -> new byte[BLOCK_SIZE]);
int amt = Math.min(length, BLOCK_SIZE - blockOffset);
System.arraycopy(data, offset, block, blockOffset, amt);
@@ -273,7 +283,7 @@ public class SemisparseByteArray {
if (blockNum == 0) {
throw new BufferOverflowException();
}
block = blocks.computeIfAbsent(blockNum, n -> new byte[BLOCK_SIZE]);
block = blocks.computeIfAbsent(blockNum, _ -> new byte[BLOCK_SIZE]);
amt = Math.min(length - cur, BLOCK_SIZE);
System.arraycopy(data, cur + offset, block, 0, amt);
cur += amt;
@@ -22,6 +22,7 @@ import ghidra.app.emulator.Emulator;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighParserContext;
import ghidra.app.util.PseudoInstruction;
import ghidra.pcode.emu.jit.decode.CanDecode;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
@@ -36,13 +37,11 @@ import ghidra.util.Msg;
/**
* The default implementation of {@link PcodeThread} suitable for most applications
*
* <p>
* When emulating on concrete state, consider using {@link ModifiedPcodeThread}, 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
@@ -54,7 +53,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<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.
@@ -75,7 +73,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* 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.
@@ -98,7 +95,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* 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
@@ -117,21 +113,19 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* 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(functional = true)
@PcodeUserop(functional = true, canInterrupt = true)
public void emu_swi() {
thread.swi();
}
/**
* Notify the client of a failed Sleigh inject compilation.
*
* <p>
* To avoid pestering the client during emulator set-up, a service may effectively defer
* notifying the user of Sleigh compilation errors by replacing the erroneous injects with
@@ -139,21 +133,20 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
* the client be notified.
*/
@PcodeUserop(functional = true)
public void emu_injection_err() {
public static void emu_injection_err() {
throw new InjectionErrorPcodeExecutionException(null, null);
}
}
/**
* 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.
*
* @param <T> the type of variables in the emulator
*/
public static class PcodeThreadExecutor<T> extends PcodeExecutor<T> {
public static class PcodeThreadExecutor<T> extends PcodeExecutor<T> implements CanDecode {
volatile boolean suspended = false;
protected final DefaultPcodeThread<T> thread;
@@ -234,6 +227,11 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
public DefaultPcodeThread<T> getThread() {
return thread;
}
@Override
public PseudoInstruction decodeInstruction() {
return thread.decoder.decodeInstruction(thread.counter, thread.context);
}
}
private final String name;
@@ -316,7 +314,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* 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)}.
@@ -607,7 +604,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* Extension point: Extra behavior before executing an instruction
*
* <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.
@@ -617,7 +613,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* Extension point: Extra behavior after executing an instruction
*
* <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.
@@ -724,7 +719,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* 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.
*
@@ -762,7 +756,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* Perform checks on a requested LOAD
*
* <p>
* Throw an exception if the LOAD should cause an interrupt.
*
@@ -776,7 +769,6 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
/**
* Perform checks on a requested STORE
*
* <p>
* Throw an exception if the STORE should cause an interrupt.
*
@@ -132,6 +132,11 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
return false;
}
@Override
public boolean canInterrupt() {
return true;
}
@Override
public boolean hasSideEffects() {
return true;
@@ -147,6 +152,16 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
return false;
}
@Override
public boolean isOutSigned() {
return false;
}
@Override
public boolean isInSigned(int index) {
return false;
}
@Override
public Class<?> getOutputType() {
return void.class;
@@ -23,6 +23,7 @@ import org.objectweb.asm.ClassWriter;
import ghidra.pcode.emu.jit.analysis.*;
import ghidra.pcode.emu.jit.decode.JitPassageDecoder;
import ghidra.pcode.emu.jit.folding.FoldRevalidator;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass;
@@ -30,14 +31,12 @@ import ghidra.pcode.exec.PcodeExecutorState;
/**
* The Just-in-Time (JIT) translation engine that powers the {@link JitPcodeEmulator}.
*
* <p>
* This is the translation engine from "any" machine language into JVM bytecode. The same caveats
* that apply to interpretation-based p-code emulation apply to JIT-accelerated emulation: Ghidra
* must have a Sleigh specification for the emulation target language, there must be userop
* libraries (built-in or user-provided) defining any userops encountered during the course of
* execution, all dependent code must be loaded or stubbed out, etc.
*
* <p>
* A passage is decoded at a desired entry point using the {@link JitPassageDecoder}. This compiler
* then translates the passage into bytecode. It will produce a classfile which is then loaded and
@@ -55,14 +54,14 @@ import ghidra.pcode.exec.PcodeExecutorState;
* execution of the translated passage produces exactly the same effect on the emulation state as
* interpretation of the same p-code passage. The run method returns the next entry point to execute
* or {@code null} when the emulator must look up the next entry point.
*
* <p>
* Translation of a passage takes place in distinct phases. See each respective class for details of
* its design and implementation:
*
* <ol>
* <li>Decode: {@link JitPassageDecoder}</li>
* <li>Control Flow Analysis: {@link JitControlFlowModel}</li>
* <li>Data Flow Analysis: {@link JitDataFlowModel}</li>
* <li>Reachability Analysis: {@link JitReachabilityModel}</li>
* <li>Variable Scope Analysis: {@link JitVarScopeModel}</li>
* <li>Type Assignment: {@link JitTypeModel}</li>
* <li>Variable Allocation: {@link JitAllocationModel}</li>
@@ -70,11 +69,26 @@ import ghidra.pcode.exec.PcodeExecutorState;
* <li>Code Generation: {@link JitCodeGenerator}</li>
* </ol>
*
* <h2>Decode</h2>
* <p>
* Decoding is seeded at the emulator's current program counter. It takes each seed and decodes
* instructions linearly, until it encounters an instruction without fall through. This constitutes
* a stride. As it encounters control transfer instructions, it queues up additional branch targets
* as seeds. Once it runs out of seeds, or expends a passage-size allowance, it produces the final
* passage. See {@link JitPassageDecoder}.
* <p>
* The decoder also performs constant folding as it goes, which necessitates some interpretation of
* the p-code it decodes. This allows it to follow direct branches that may otherwise present as
* indirect, e.g., the target address is loaded from a table, or has to be loaded by two or more
* instructions. Some of these constants can be invalidated after decode if alternative pathways are
* found. Those that remain valid are included with the passage for further optimization downstream.
* See {@link FoldRevalidator}.
*
* <h2>Control Flow Analysis</h2>
* <p>
* Some rudimentary control flow analysis is performed during decode, but the output of decode is a
* passage, i.e., collection of <em>strides</em>, not basic blocks. The control flow analysis breaks
* each stride down into basic blocks at the p-code level. Note that a single instruction's pcode
* each stride down into basic blocks at the p-code level. Note that a single instruction's p-code
* (as well as any user instrumentation on that instruction's address) may have complex control
* flow. Additionally, branches that leave an instruction preclude execution of its remaining
* p-code. Thus, p-code basic blocks do not coincide precisely with instruction-level basic blocks.
@@ -87,7 +101,19 @@ import ghidra.pcode.exec.PcodeExecutorState;
* read before it is written produces a "missing" variable. Those missing variables are converted to
* <em>phi</em> nodes and later resolved during inter-block analysis. The graph is also able to
* consider aliasing, partial accesses, overlapping accesses, etc., by synthesizing operations to
* model those effects. See {@link JitDataFlowModel}.
* model those effects. This phase also applies folded constants to the use-def graph. Ops with
* folded outputs are re-written to copies. Ops with folded inputs are re-written as if they took
* literal constants. Conditional branches with a folded predicate are re-written as unconditional
* branches or nops, etc. See {@link JitDataFlowModel}.
*
* <h2>Reachability Analysis</h2>
* <p>
* Generally, the passage decoder would not decode instructions that are not reachable, since it
* follows the targets of control transfer instructions. However, it is possible 1) That a userop or
* injection includes unreachable p-code blocks, or 2) After folding constants and re-writing
* branches, some blocks are now known to be unreachable, at least from the decode seed.
* Reachability analysis walks the new control-flow graph and marks only those blocks and edges that
* are reachable. Downstream analysis will ignore unreachable blocks and edges.
*
* <h2>Variable Scope Analysis</h2>
* <p>
@@ -161,6 +187,8 @@ public class JitCompiler {
PRINT_CFM,
/** Print the ops of each basic block in SSA (sort of) form */
PRINT_DFM,
/** Print the list of reachable basic blocks */
PRINT_RM,
/** Print the list of live variables for each basic block */
PRINT_VSM,
/** Print each synthetic operation, e.g., catenation, subpiece, phi */
@@ -169,22 +197,25 @@ public class JitCompiler {
PRINT_OUM,
/** Enable ASM's trace for each generated classfile */
TRACE_CLASS,
/** Enable per-op outline of bytecode generation */
DEEP_TRACE,
/** Save the generated {@code .class} file to disk for offline examination */
DUMP_CLASS;
}
/**
* The set of enabled diagnostic toggles.
*
* <p>
* In production, this should be empty.
*/
public static final EnumSet<Diag> ENABLE_DIAGNOSTICS = EnumSet.noneOf(Diag.class);
public static final EnumSet<Diag> ENABLE_DIAGNOSTICS =
EnumSet.noneOf(Diag.class);
//EnumSet.of(Diag.PRINT_PASSAGE, Diag.DUMP_CLASS, Diag.PRINT_OUM);
//EnumSet.of(Diag.PRINT_PASSAGE, Diag.DEEP_TRACE);
/**
* Exclude a given address offset from ASM's {@link ClassWriter#COMPUTE_MAXS} and
* {@link ClassWriter#COMPUTE_FRAMES}.
*
* <p>
* Unfortunately, when automatic computation of frames and maxes fails, the ASM library offers
* little in terms of diagnostics. It usually crashes with an NPE or an AIOOBE. Worse, when this
@@ -192,7 +223,6 @@ public class JitCompiler {
* identify the address of the passage seed that causes such a failure and set this variable to
* its offset. This will prevent ASM from attempting this computation so that it at least prints
* the trace and dumps out the classfile to disk (if those {@link Diag}nostics are enabled).
*
* <p>
* Once the trace/classfile is obtained, set this back to -1 and then apply debug prints in the
* crashing method. Since it's probably in the ASM library, you'll need to use your IDE /
@@ -213,7 +243,6 @@ public class JitCompiler {
/**
* Construct a p-code to bytecode translator.
*
* <p>
* In general, this should only be used by the JIT emulator and its test suite.
*
@@ -248,22 +277,26 @@ public class JitCompiler {
if (ENABLE_DIAGNOSTICS.contains(Diag.PRINT_DFM)) {
dfm.dumpResult();
}
JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm);
JitReachabilityModel rm = new JitReachabilityModel(context, cfm, dfm);
if (ENABLE_DIAGNOSTICS.contains(Diag.PRINT_RM)) {
rm.dumpResult();
}
JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm, rm);
if (ENABLE_DIAGNOSTICS.contains(Diag.PRINT_VSM)) {
vsm.dumpResult();
}
JitTypeModel tm = new JitTypeModel(dfm);
JitAllocationModel am = new JitAllocationModel(context, dfm, vsm, tm);
JitOpUseModel oum = new JitOpUseModel(context, cfm, dfm, vsm);
JitOpUseModel oum = new JitOpUseModel(context, cfm, dfm, rm, vsm);
if (ENABLE_DIAGNOSTICS.contains(Diag.PRINT_SYNTH)) {
dfm.dumpSynth();
}
if (ENABLE_DIAGNOSTICS.contains(Diag.PRINT_OUM)) {
oum.dumpResult();
}
JitTypeModel tm = new JitTypeModel(dfm, oum);
JitAllocationModel am = new JitAllocationModel(context, dfm, vsm, oum, tm);
JitCodeGenerator<?> gen =
new JitCodeGenerator<>(lookup, context, cfm, dfm, vsm, tm, am, oum);
new JitCodeGenerator<>(lookup, context, cfm, dfm, rm, vsm, tm, am, oum);
return gen.load();
}
@@ -15,8 +15,11 @@
*/
package ghidra.pcode.emu.jit;
import java.util.EnumSet;
import java.util.Set;
import ghidra.pcode.emu.jit.op.JitCopyOp;
/**
* The configuration for a JIT-accelerated emulator.
*
@@ -32,6 +35,8 @@ import java.util.Set;
* limit is exceeded, the ASM library throws an exception. When this happens, the
* compiler will retry the whole process, but with this configuration parameter halved.
* @param maxPassageStrides The maximum number of strides to include.
* @param foldConstants See {@link Opt#FOLD_CONSTANTS}
* @param removeUnreachableBlocks See {@link Opt#REMOVE_UNREACHABLE_BLOCKS}
* @param removeUnusedOperations See {@link Opt#REMOVE_UNUSED_OPERATIONS}
* @param emitCounters See {@link Opt#EMIT_COUNTERS}
* @param logStackTraces See {@link Opt#LOG_STACK_TRACES}
@@ -40,6 +45,8 @@ public record JitConfiguration(
int maxPassageInstructions,
int maxPassageOps,
int maxPassageStrides,
boolean foldConstants,
boolean removeUnreachableBlocks,
boolean removeUnusedOperations,
boolean emitCounters,
boolean logStackTraces) {
@@ -48,6 +55,20 @@ public record JitConfiguration(
* Fluent specifiers for the boolean options of {@link JitConfiguration}
*/
public enum Opt {
/**
* Attempt to fold constants. This will re-write ops that output a folded constant as a
* {@link JitCopyOp copy}, and any op that takes a folded constant input as taking that
* const directly. To see benefits, this should be used with
* {@link #REMOVE_UNUSED_OPERATIONS}.
*/
FOLD_CONSTANTS,
/**
* Remove p-code basic blocks that cannot be reached. The ASM library or classfile API may
* remove unreachable blocks on its own, regardless of this option. If this option is
* enabled, the JIT compiler will remove unreachable blocks, <em>before</em> analyzing for
* unused operations, permitting more code removal.
*/
REMOVE_UNREACHABLE_BLOCKS,
/**
* Some p-code ops produce outputs that are never used later. One common case is flags
* computed from arithmetic operations. If this option is enabled, the JIT compiler will
@@ -69,7 +90,7 @@ public record JitConfiguration(
* Construct a default configuration
*/
public JitConfiguration() {
this(1000, 5000, 10, true, true, false);
this(1000, 5000, 10, true, true, true, true, false);
}
/**
@@ -79,6 +100,8 @@ public record JitConfiguration(
*/
public JitConfiguration(Set<Opt> opts) {
this(1000, 5000, 10,
opts.contains(Opt.FOLD_CONSTANTS),
opts.contains(Opt.REMOVE_UNREACHABLE_BLOCKS),
opts.contains(Opt.REMOVE_UNUSED_OPERATIONS),
opts.contains(Opt.EMIT_COUNTERS),
opts.contains(Opt.LOG_STACK_TRACES));
@@ -92,4 +115,33 @@ public record JitConfiguration(
public JitConfiguration(Opt... opts) {
this(Set.of(opts));
}
public Set<Opt> opts() {
Set<Opt> opts = EnumSet.noneOf(Opt.class);
if (foldConstants) {
opts.add(Opt.FOLD_CONSTANTS);
}
if (removeUnusedOperations) {
opts.add(Opt.REMOVE_UNUSED_OPERATIONS);
}
if (emitCounters) {
opts.add(Opt.EMIT_COUNTERS);
}
if (logStackTraces) {
opts.add(Opt.LOG_STACK_TRACES);
}
return opts;
}
public JitConfiguration enable(Opt opt) {
Set<Opt> set = opts();
set.add(opt);
return new JitConfiguration(set);
}
public JitConfiguration disable(Opt opt) {
Set<Opt> set = opts();
set.remove(opt);
return new JitConfiguration(set);
}
}
@@ -28,6 +28,7 @@ import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.BlockSplitter;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitDataFlowModel;
import ghidra.pcode.emu.jit.decode.JitPassageDecoder;
import ghidra.pcode.emu.jit.folding.FoldedState;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.op.OpGen;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
@@ -45,7 +46,6 @@ import ghidra.program.util.ProgramContextImpl;
/**
* A selection of instructions decoded from an emulation target, the generated p-code ops, and
* associated metadata.
*
* <p>
* Note that the generated p-code ops include those injected by the emulator's client using
* {@link PcodeMachine#inject(Address, String)} and {@link PcodeThread#inject(Address, String)},
@@ -57,7 +57,6 @@ public class JitPassage extends PcodeProgram {
/**
* Check if a given p-code op could fall through
*
* <p>
* Conditional branches and non-branching ops are the only ones that can fall through. Note that
* for JIT purposes, a {@link PcodeOp#CALL CALL} op <em>does not</em> fall through! For
@@ -86,7 +85,6 @@ public class JitPassage extends PcodeProgram {
/**
* An address-context pair
*
* <p>
* Because decode is sensitive to the contextreg value, we have to consider that visiting the
* same address with a different context could produce a completely different stride. Thus, we
@@ -229,7 +227,6 @@ public class JitPassage extends PcodeProgram {
/**
* Indicates whether this branch represents a fall-through case.
*
* <p>
* Note that the {@link #from()} may not be an actual branching p-code op when
* {@code isFall} is true. A "fall-through" branch happens in two cases. First, and most
@@ -256,7 +253,6 @@ public class JitPassage extends PcodeProgram {
/**
* A branch as analyzed within an instruction step
*
* <p>
* After intra-instruction reachability is determined and this branch is to be added to the
* whole passage, it will be "upgraded" to a {@link PBranch}.
@@ -266,7 +262,6 @@ public class JitPassage extends PcodeProgram {
/**
* A branch as analyzed within a passage
*
* <p>
* Many implement this via {@link RBranch}.
*/
@@ -282,26 +277,25 @@ public class JitPassage extends PcodeProgram {
*
* @return the reachability
*/
Reachability reach();
CtxReach reach();
}
/**
* Describes the manner in which something is reachable, wrt. dynamic context changes <em>within
* an instruction step</em>.
*
* <p>
* At the moment, the only way context can be changed dynamically is via a p-code userop. Such
* ops must have the {@link PcodeUserop#modifiesContext()} attribute set. If such an op is known
* to have been executed when finishing an instruction (either by branch or fall-through), we
* must exit the compiled passage.
*/
public enum Reachability {
public enum CtxReach {
/**
* There is at least one path to reach it. None of them modify the context dynamically.
*/
WITHOUT_CTXMOD {
@Override
public Reachability combine(Reachability that) {
public CtxReach combine(CtxReach that) {
return switch (that) {
case null -> this;
case WITHOUT_CTXMOD -> WITHOUT_CTXMOD;
@@ -321,7 +315,7 @@ public class JitPassage extends PcodeProgram {
*/
MAYBE_CTXMOD {
@Override
public Reachability combine(Reachability that) {
public CtxReach combine(CtxReach that) {
return MAYBE_CTXMOD;
}
@@ -335,7 +329,7 @@ public class JitPassage extends PcodeProgram {
*/
WITH_CTXMOD {
@Override
public Reachability combine(Reachability that) {
public CtxReach combine(CtxReach that) {
return switch (that) {
case null -> this;
case WITHOUT_CTXMOD -> MAYBE_CTXMOD;
@@ -356,7 +350,7 @@ public class JitPassage extends PcodeProgram {
* @param that the other reachability
* @return the "or" of both
*/
public abstract Reachability combine(Reachability that);
public abstract CtxReach combine(CtxReach that);
/**
* Check if it is possible for this block to be reached without a context modification.
@@ -403,7 +397,7 @@ public class JitPassage extends PcodeProgram {
* @param reach see {@link RBranch#reach()}
* @return the branch
*/
public RIntBranch withReach(Reachability reach) {
public RIntBranch withReach(CtxReach reach) {
return new RIntBranch(from, to, isFall, reach);
}
}
@@ -416,28 +410,44 @@ public class JitPassage extends PcodeProgram {
* @param isFall see {@link IntBranch#isFall()}
* @param reach see {@link RBranch#reach()}
*/
public record RIntBranch(PcodeOp from, PcodeOp to, boolean isFall, Reachability reach)
implements IntBranch, RBranch {}
public record RIntBranch(PcodeOp from, PcodeOp to, boolean isFall, CtxReach reach)
implements IntBranch, RBranch {
/**
* Convert this external branch into an indirect one
* <p>
* This is called whenever a once-folded branch is no longer foldable.
*
* @return the resulting indirect branch
*/
public RIndBranch toIndBranch() {
if (!(to instanceof DecodedPcodeOp decTo)) {
throw new AssertionError();
}
return new RIndBranch(from, decTo.at.rvCtx, reach);
}
}
/**
* A branch to an address (and context value) not in the same passage
*
* <p>
* When execution encounters this branch, the {@link JitCompiledPassage#run(int) run} method
* sets the emulator's program counter and context to the {@link #to() branch target} and
* returns the appropriate entry point for further execution.
*
* <p>
* Note that this branch type is used by the decoder to track queued decode seeds as well.
* External branches that get decoded are changed into internal branches.
*/
public interface ExtBranch extends Branch {
/**
* The target address-context pair
*
* @return the target
* {@return the target address-context pair}
*/
AddrCtx to();
/**
* {@return the constant-folding state at the branch}
*/
FoldedState state();
}
/**
@@ -445,16 +455,18 @@ public class JitPassage extends PcodeProgram {
*
* @param from see {@link ExtBranch#from()}
* @param to see {@link ExtBranch#to()}
* @param state the constant-folding state at the branch
*/
public record SExtBranch(PcodeOp from, AddrCtx to) implements ExtBranch, SBranch {
public record SExtBranch(PcodeOp from, AddrCtx to, FoldedState state)
implements ExtBranch, SBranch {
/**
* Upgrade this branch to an {@link RExtBranch} for inclusion in the passage.
*
* @param reach see {@link RBranch#reach()}
* @return the branch
*/
public RExtBranch withReach(Reachability reach) {
return new RExtBranch(from, to, reach);
public RExtBranch withReach(CtxReach reach) {
return new RExtBranch(from, to, state, reach);
}
}
@@ -463,13 +475,13 @@ public class JitPassage extends PcodeProgram {
*
* @param from see {@link ExtBranch#from()}
* @param to see {@link ExtBranch#to()}
* @param state the constant-folding state at the branch
* @param reach see {@link RBranch#reach()}
*/
public record RExtBranch(PcodeOp from, AddrCtx to, Reachability reach)
public record RExtBranch(PcodeOp from, AddrCtx to, FoldedState state, CtxReach reach)
implements ExtBranch, RBranch {
/**
* Convert this external branch into an internal one
*
* <p>
* This is called whenever it becomes the case that an external target is decoded an added
* to the passage, making it an internal branch. Notably, this happens when selecting a seed
@@ -482,16 +494,25 @@ public class JitPassage extends PcodeProgram {
public RIntBranch toIntBranch(PcodeOp to) {
return new RIntBranch(from, to, false, reach);
}
/**
* Convert this external branch into an indirect one
* <p>
* This is called whenever a once-folded branch is no longer foldable.
*
* @return the resulting indirect branch
*/
public RIndBranch toIndBranch() {
return new RIndBranch(from, to.rvCtx, reach);
}
}
/**
* A branch to a dynamic address
*
* <p>
* When execution encounters this branch, the {@link JitCompiledPassage#run(int) run} method
* will set the emulator's program counter to the computed address and its context to
* {@link #flowCtx()}, then return the appropriate entry point for further execution.
*
* <p>
* TODO: Some analysis may be possible to narrow the possible addresses to a known few and then
* treat this as several {@link IntBranch}es; however, I worry this is too expensive for what it
@@ -519,7 +540,7 @@ public class JitPassage extends PcodeProgram {
* @param reach see {@link RBranch#reach()}
* @return the branch
*/
public RIndBranch withReach(Reachability reach) {
public RIndBranch withReach(CtxReach reach) {
return new RIndBranch(from, flowCtx, reach);
}
}
@@ -531,28 +552,24 @@ public class JitPassage extends PcodeProgram {
* @param flowCtx see {@link IndBranch#flowCtx()}
* @param reach see {@link RBranch#reach()}
*/
public record RIndBranch(PcodeOp from, RegisterValue flowCtx, Reachability reach)
public record RIndBranch(PcodeOp from, RegisterValue flowCtx, CtxReach reach)
implements IndBranch, RBranch {}
/**
* A "branch" representing an error
*
* <p>
* When execution encounters this branch, the {@link JitCompiledPassage#run(int) run} method
* throws an exception. This branch is used to encode error conditions that may not actually be
* encountered at run time. Some cases are:
*
* <ul>
* <li>An instruction decode error &mdash; synthesized as a {@link DecodeErrorPcodeOp}</li>
* <li>An {@link PcodeOp#UNIMPLEMENTED unimplemented} instruction</li>
* <li>A {@link PcodeOp#CALLOTHER call} to an undefined userop</li>
* </ul>
*
* <p>
* The decoder and translator may encounter such an error, but unless execution actually reaches
* the error, the emulator need not crash. Thus, we note the error and generate code that will
* actually throw it in the translation, only if it's actually encountered.
*
* <p>
* Note that the {@link OpGen} for the specific p-code op generating the error will decide what
* exception type to throw.
@@ -638,7 +655,6 @@ public class JitPassage extends PcodeProgram {
/**
* Check if this op represents the start of an instruction
*
* <p>
* If this p-code op was produced by an inject, this will return false! It only returns true
* for an op that is genuinely the first op in the result of {@link Instruction#getPcode()}.
@@ -660,19 +676,16 @@ public class JitPassage extends PcodeProgram {
/**
* A synthetic p-code op that represents a return from the {@link JitCompiledPassage#run(int)}
* method.
*
* <p>
* When execution encounters this op (and the corresponding {@link ExtBranch}), the emulator's
* program counter and context values are set to the {@link ExtBranch#to() branch target}, and
* the appropriate entry point is returned.
*
* <p>
* This is used in a few ways: The simplest, though perhaps not obvious, way is when the decoder
* encounters an existing entry point. We avoid re-translating the same instructions by forcing
* the stride to end. However, the last instruction in that stride would have fall through,
* causing dangling control flow. To mitigate that, we append a synthetic exit op to return the
* existing entry point. The emulator can then resume execution accordingly.
*
* <p>
* The next is even less obvious. When the emulation client (or user) injects Sleigh, a common
* mistake is to forget control flow. The decoder detects this when "falling through" does not
@@ -680,14 +693,12 @@ public class JitPassage extends PcodeProgram {
* translated passage. While it still results in an endless loop (just like the
* interpretation-based emulator), it's easier to interrupt and diagnose when we exit the
* translation between each "iteration."
*
* <p>
* The last is a small hack: The decoder needs to know whether each instruction (possibly
* instrumented by an inject) falls through. To do this, it appends an exit op to the very end
* of the instruction's (and inject's) ops and performs rudimentary control flow analysis (see
* {@link BlockSplitter}). It then seeks a path from start to exit. If one is found, it has fall
* through. This "probe" op is <em>not</em> included in the decoded stride.
*
*/
public static class ExitPcodeOp extends PcodeOp {
/**
@@ -720,7 +731,6 @@ public class JitPassage extends PcodeProgram {
/**
* A synthetic op representing the initial seed of a decoded passage.
*
* <p>
* Because we use a queue of {@link ExtBranch}es as the seed queue, and the initial seed has no
* real {@link Branch#from()}, we synthesize a {@link PcodeOp#BRANCH branch op} from the entry
@@ -740,13 +750,11 @@ public class JitPassage extends PcodeProgram {
/**
* A synthetic p-code op meant to encode "no operation"
*
* <p>
* P-code does not have a NOP opcode, because there's usually no reason to produce such. A NOP
* machine instruction just produces an empty list of p-code ops, denoting "no operation."
* However, for bookkeeping purposes in our JIT translator, we occasionally need some op to hold
* an important place, but that op needs to do nothing. We use this in two situations:
*
* <ul>
* <li>An instruction (possibly because of an inject) that does nothing. Yes, essentially a NOP
* machine instruction. Because another op may target this instruction, and {@link Branch}es
@@ -779,7 +787,6 @@ public class JitPassage extends PcodeProgram {
/**
* A synthetic p-code op denoting a decode error
*
* <p>
* The decoder may encounter several decode errors as it selects and decodes the passage. An
* instruction is selected because the JIT believes it <em>may</em> be executed by the emulator.
@@ -949,11 +956,18 @@ public class JitPassage extends PcodeProgram {
}
}
/**
* @param op the p-code op
* @param idx the operand index, -1 being output, inputs indexed 0-up
*/
public record Operand(PcodeOp op, int idx) {}
private final List<Instruction> instructions;
private final AddrCtx entry;
private final PcodeUseropLibrary<Object> decodeLibrary;
private final PcodeUseropLibrary<?> decodeLibrary;
private final Map<PcodeOp, PBranch> branches;
private final Map<PcodeOp, AddrCtx> entries;
private final Map<Operand, byte[]> folded;
private final Register contextreg;
private final ProgramContextImpl defaultContext;
@@ -971,16 +985,19 @@ public class JitPassage extends PcodeProgram {
* @param instructions see {@link #getInstructions()}
* @param branches see {@link #getBranches()}
* @param entries see {@link #getOpEntry(PcodeOp)}
* @param folded see {@link #getFoldedOperand(PcodeOp, int)}
*/
public JitPassage(SleighLanguage language, AddrCtx entry, List<PcodeOp> code,
PcodeUseropLibrary<Object> decodeLibrary, List<Instruction> instructions,
Map<PcodeOp, PBranch> branches, Map<PcodeOp, AddrCtx> entries) {
PcodeUseropLibrary<?> decodeLibrary, List<Instruction> instructions,
Map<PcodeOp, PBranch> branches, Map<PcodeOp, AddrCtx> entries,
Map<Operand, byte[]> folded) {
super(language, code, decodeLibrary.getSymbols(language));
this.entry = entry;
this.decodeLibrary = decodeLibrary;
this.instructions = instructions;
this.branches = branches;
this.entries = entries;
this.folded = folded;
this.contextreg = language.getContextBaseRegister();
@@ -995,7 +1012,6 @@ public class JitPassage extends PcodeProgram {
/**
* Get all of the instructions in the passage.
*
* <p>
* These are grouped by stride. Within each stride, the instructions are listed in decode order.
* The strides are ordered by seed address-context pair, with context value taking precedence.
@@ -1008,7 +1024,6 @@ public class JitPassage extends PcodeProgram {
/**
* {@inheritDoc}
*
* <p>
* Conventionally, the first instruction of the program is the entry. Note this might
* <em>not</em> be the initial seed. If the decoded passage contains a branch to an address
@@ -1024,7 +1039,6 @@ public class JitPassage extends PcodeProgram {
/**
* Get the initial seed of this passage.
*
* <p>
* This is informational only. It should be used in naming things and/or in diagnostics.
*
@@ -1036,7 +1050,6 @@ public class JitPassage extends PcodeProgram {
/**
* Get the userop library that was used during decode of the passage
*
* <p>
* This often wraps the emulator's userop library. Downstream components, namely the
* {@link JitDataFlowModel}, will need this when translating {@link PcodeOp#CALLOTHER calls} to
@@ -1044,7 +1057,7 @@ public class JitPassage extends PcodeProgram {
*
* @return the library
*/
public PcodeUseropLibrary<Object> getDecodeLibrary() {
public PcodeUseropLibrary<?> getDecodeLibrary() {
return decodeLibrary;
}
@@ -1087,7 +1100,6 @@ public class JitPassage extends PcodeProgram {
/**
* Check if a given p-code op is the first of an instruction.
*
* <p>
* <b>NOTE</b>: If an instruction is at an address with an inject, then the first op produced by
* the inject is considered the "entry" to the instruction. This is to ensure that any control
@@ -1101,6 +1113,24 @@ public class JitPassage extends PcodeProgram {
return entries.get(op);
}
/**
* Check if a given p-code op's operand was folded to a constant
*
* @param op the p-code op
* @param opIdx the operand idx, inputs indexed 0-up, -1 to indicate output
* @return non-null constant value, if it was folded
*/
public byte[] getFoldedOperand(PcodeOp op, int opIdx) {
return folded.get(new Operand(op, opIdx));
}
/**
* {@return the map of all folded operands (TESTING ONLY)}
*/
public Map<Operand, byte[]> allFoldedOperands() {
return folded;
}
/**
* If the given p-code op is known to cause an error, e.g., an unimplemented instruction, get
* the error message.
@@ -44,14 +44,12 @@ import ghidra.util.Msg;
/**
* An extension of {@link PcodeEmulator} that applies Just-in-Time (JIT) translation to accelerate
* execution.
*
* <p>
* This is meant as a near drop-in replacement for the class it extends. Aside from some additional
* configuration, and some annotations you might add to a {@link PcodeUseropLibrary}, you can simply
* replace {@code new PcodeEmulator()} with {@code new JitPcodeEmulator(...)}.
* replace "{@code new PcodeEmulator()}" with "{@code new JitPcodeEmulator(...)}."
*
* <h1>A JIT-Accelerated P-code Emulator for the Java Virtual Machine</h1>
*
* <p>
* There are two major tasks to achieving JIT-accelerated p-code emulation: 1) The translation of
* p-code to a suitable target's machine language, and 2) The selection, decoding, and cache
@@ -60,7 +58,6 @@ import ghidra.util.Msg;
* different than targeting native machine language.
*
* <h2>Terminology</h2>
*
* <p>
* Because of the potential for confusion of terms with similar meanings from similar disciplines,
* and to distinguish our particular use of the terms, we establish some definitions up front:
@@ -155,14 +152,12 @@ public class JitPcodeEmulator extends PcodeEmulator {
/**
* This emulator's cache of passage translations, incl. all entry points.
*
* <p>
* TODO: Invalidation of entries. One possible complication is any thread may still have an
* instance of one, and could possibly be executing it. Perhaps this could be a weak hash map,
* and they'll stay alive by virtue of the instances pointing to their classes? Still, we might
* like to impose a total size max, which would have to be implemented among the threads. Other
* reasons we may need to invalidate include:
*
* <ol>
* <li>Self-modifying code (we'll probably want to provide a configuration toggle given how
* expensive that may become).</li>
@@ -235,13 +230,11 @@ public class JitPcodeEmulator extends PcodeEmulator {
/**
* {@inheritDoc}
*
* <p>
* Userops can be optimized by the JIT translator under certain circumstances. To read more, see
* {@link JitDataFlowUseropLibrary}. DO NOT extend that library. The internals use it to wrap
* the library you provide here, but its documentation describes when and how the JIT translator
* optimizes invocations to your userops.
*
* <p>
* <b>WARNING</b>: Userops that accept floating point types via direct invocation should be
* careful that the sizes match exactly. That is, if you pass a {@code float} argument to a
@@ -259,7 +252,6 @@ public class JitPcodeEmulator extends PcodeEmulator {
/**
* Check if the emulator already has translated a given entry point.
*
* <p>
* This is used by the decoder to detect if it should end a stride before reaching its natural
* end (i.e., a non-fall-through instruction.) This was a design decision to reduce
@@ -286,7 +278,6 @@ public class JitPcodeEmulator extends PcodeEmulator {
/**
* Translate a new passage starting at the given seed.
*
* <p>
* Note the compiler must provide an entry to the resulting passage at the requested seed. It
* and any additional entry points are placed into the code cache. Each thread executing the
@@ -327,7 +318,6 @@ public class JitPcodeEmulator extends PcodeEmulator {
/**
* Get the entry prototype for a given address and contextreg value.
*
* <p>
* An <b>entry prototype</b> is a class representing a translated passage and an index
* identifying the point at which to enter the passage. The compiler numbers each entry point it
@@ -335,7 +325,6 @@ public class JitPcodeEmulator extends PcodeEmulator {
* point indices are entered into the code cache for each translated passage. If no entry point
* exists for the requested address and contextreg value, the emulator will decode and translate
* a new passage at the requested seed.
*
* <p>
* It's a bit odd to take the thread's decoder for a machine-level thing; however, all thread
* decoders ought to have the same behavior. The particular thread's decoder will have better
@@ -416,7 +405,6 @@ public class JitPcodeEmulator extends PcodeEmulator {
/**
* {@inheritDoc}
*
* <p>
* <b>TODO</b>: The JIT-accelerated emulator does not currently implement access breakpoints.
* Furthermore, because JIT generated code is granted direct access to the emulator's state
@@ -32,13 +32,11 @@ import ghidra.program.model.listing.ProgramContext;
/**
* A JIT-accelerated thread of p-code emulation
*
* <p>
* This class implements the actual JIT-accelerated execution loop. In contrast to the normal
* per-instruction Fetch-Execute-Store loop inherited from {@link DefaultPcodeThread}, this thread's
* {@link #run()} method implements a per-<em>passage</em> Fetch-Decode-Translate-Execute loop.
*
*
* <h2>Fetch</h2>
* <p>
* The Fetch step involves checking the code cache for an existing translation at the thread's
@@ -84,11 +82,9 @@ public class JitPcodeThread extends BytesPcodeThread {
/**
* This thread's cache of translations instantiated for this thread.
*
* <p>
* As an optimization, the translator generates classes which pre-fetch portions of the thread's
* state. Thus, the class must be instantiated for each particular thread needing to execute it.
*
* <p>
* TODO: Invalidation of entries. There are several reasons an entry may need to be invalidated:
* Expiration, eviction, or perhaps because the {@link EntryPointPrototype} (from the emulator)
@@ -98,7 +94,6 @@ public class JitPcodeThread extends BytesPcodeThread {
/**
* Create a thread
*
* <p>
* This should only be called by the emulator and its test suites.
*
@@ -119,7 +114,6 @@ public class JitPcodeThread extends BytesPcodeThread {
/**
* Create the passage decoder
*
* <p>
* This is an extension point in case the decoder needs to be replaced with a further extension.
*
@@ -184,7 +178,6 @@ public class JitPcodeThread extends BytesPcodeThread {
/**
* Check if the <em>emulator</em> has an entry prototype for the given address and contextreg
* value.
*
* <p>
* This simply passes through to the emulator. It does not matter whether or not this thread has
* instantiated the prototype or not. If any thread has caused the emulator to translate the
@@ -200,12 +193,10 @@ public class JitPcodeThread extends BytesPcodeThread {
/**
* Get the translated and instantiated entry point for the given address and contextreg value.
*
* <p>
* An <b>entry point</b> is an instance of a class representing a translated passage and an
* index identifying the point at which to enter the passage. In essence, it is an instance of
* an <b>entry prototype</b> for this thread.
*
* <p>
* This will first check the cache for an existing instance. Then, it will delegate to the
* emulator. The emulator will check its cache for an existing translation. If one is found, we
@@ -226,7 +217,6 @@ public class JitPcodeThread extends BytesPcodeThread {
/**
* {@inheritDoc}
*
* <p>
* We override only this method to accelerate execution using JIT translation. Implementing
* single stepping via JIT doesn't make much sense from an efficiency standpoint. However, this
@@ -258,7 +248,6 @@ public class JitPcodeThread extends BytesPcodeThread {
/**
* This is called before each basic block is executed.
*
* <p>
* This gives the thread an opportunity to track and control execution, if desired. It provides
* the number of instructions and additional p-code ops about to be completed. If the counts
@@ -38,7 +38,6 @@ import ghidra.program.model.pcode.Varnode;
/**
* Type variable allocation phase for JIT-accelerated emulation.
*
* <p>
* The implements the Variable Allocation phase of the {@link JitCompiler} using a very simple
* placement and another "voting" algorithm to decide the allocated JVM variable types. We place/map
@@ -52,10 +51,8 @@ import ghidra.program.model.pcode.Varnode;
* local variable allocated for {@code RAX}. Note that variables which occupy only part of a
* coalesced varnode always vote for a JVM {@code int}, because of the shifting and masking required
* to extract that part.
*
* <p>
* The allocation process is very simple, presuming successful type assignment:
*
* <ol>
* <li>Vote Tabulation</li>
* <li>Index Reservation</li>
@@ -76,11 +73,9 @@ import ghidra.program.model.pcode.Varnode;
* 1. RAX = FLOAT_ADD RCX, RDX
* 2. EAX = FLOAT_ADD EBX, 0x3f800000:4 # 1.0f
* </pre>
*
* <p>
* Several values and variables are at play here. We tabulate the type assignments and resulting
* votes:
*
* <table border="1">
* <tr>
* <th>SSA Var</th>
@@ -123,7 +118,7 @@ import ghidra.program.model.pcode.Varnode;
* <td>{@code long}</td>
* </tr>
* </table>
*
* <p>
* The registers {@code RCX}, {@code RDX}, and {@code EBX} are trivially allocated as locals of JVM
* types {@code double}, {@code double}, and {@code float}, respectively. It is also worth noting
* that {@code 0x3f800000} is allocated as a {@code float} constant in the classfile's constant
@@ -173,7 +168,6 @@ public class JitAllocationModel {
/**
* The descriptor of a p-code variable
*
* <p>
* This is just a logical grouping of a varnode and its assigned p-code type.
*/
@@ -219,6 +213,7 @@ public class JitAllocationModel {
private final JitDataFlowModel dfm;
private final JitVarScopeModel vsm;
private final JitOpUseModel oum;
private final JitTypeModel tm;
private final SleighLanguage language;
@@ -234,12 +229,14 @@ public class JitAllocationModel {
* @param context the analysis context
* @param dfm the data flow model
* @param vsm the variable scope model
* @param oum the p-code op use model
* @param tm the type model
*/
public JitAllocationModel(JitAnalysisContext context, JitDataFlowModel dfm,
JitVarScopeModel vsm, JitTypeModel tm) {
JitVarScopeModel vsm, JitOpUseModel oum, JitTypeModel tm) {
this.dfm = dfm;
this.vsm = vsm;
this.oum = oum;
this.tm = tm;
this.endian = context.getEndian();
@@ -287,7 +284,6 @@ public class JitAllocationModel {
/**
* A content for assigning a type to a varnode
*
* <p>
* Because several SSA variables can share one varnode, we let each cast a vote to determine the
* JVM type of the local(s) allocated to it.
@@ -310,7 +306,7 @@ public class JitAllocationModel {
* @param type the type
*/
public void vote(JitType type) {
map.compute(type.ext(), (t, v) -> v == null ? 1 : v + 1);
map.compute(type.ext(), (_, v) -> v == null ? 1 : v + 1);
}
/**
@@ -474,11 +470,11 @@ public class JitAllocationModel {
private void analyze() {
for (JitVal v : dfm.allValues()) {
if (v instanceof JitVarnodeVar vv && !(v instanceof JitMemoryVar)) {
if (oum.isUsed(v) && v instanceof JitVarnodeVar vv && !(v instanceof JitMemoryVar)) {
Varnode vn = vv.varnode();
Varnode coalesced = vsm.getCoalesced(vn);
TypeContest tc =
typeContests.computeIfAbsent(coalesced, __ -> new TypeContest());
typeContests.computeIfAbsent(coalesced, _ -> new TypeContest());
if (vn.equals(coalesced)) {
tc.vote(tm.typeOf(v));
}
@@ -525,7 +521,9 @@ public class JitAllocationModel {
* keying the handlers some by an alternative (e.g., varnode when available), but that's
* for later exploration.
*/
handlers.put(v, createHandler(v));
if (oum.isUsed(v)) {
handlers.put(v, createHandler(v));
}
}
}
@@ -16,6 +16,7 @@
package ghidra.pcode.emu.jit.analysis;
import java.util.*;
import java.util.function.Predicate;
import ghidra.pcode.emu.jit.*;
import ghidra.pcode.emu.jit.JitCompiler.Diag;
@@ -23,13 +24,14 @@ import ghidra.pcode.emu.jit.JitPassage.*;
import ghidra.pcode.emu.jit.decode.DecoderForOneStride;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.util.CountsPcodeOps;
import ghidra.pcode.emu.util.PcodeOpCounter;
import ghidra.pcode.exec.PcodeProgram;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.SequenceNumber;
/**
* The control flow analysis for JIT-accelerated emulation.
*
* <p>
* This implements the Control Flow Analysis phase of the {@link JitCompiler}. Some rudimentary
* analysis is performed during passage decoding &mdash; note the {@link BlockSplitter} is exported
@@ -39,7 +41,6 @@ import ghidra.program.model.pcode.SequenceNumber;
* all the branches it encounters and includes them as metadata in the passage. Because branches
* need to record the source and target p-code op, the decoder is well suited. Additionally, it has
* to compute these anyway, and we'd rather avoid duplicative work by this analyzer.
*
* <p>
* The decoded passage contains a good deal of information, but the primary inputs at this point are
* the ordered list of p-code ops and the branches. This model's primary responsibility is to break
@@ -50,12 +51,10 @@ import ghidra.program.model.pcode.SequenceNumber;
* all that are recorded by the decoder. Thus, it is also this model's responsibility to create the
* fall-through branches. These will occur to represent the "false" case of any conditional
* branches, and to represent "unconditional fall through."
*
* <p>
* The algorithm for this is fairly straightforward and has been implemented primarily in
* {@link BlockSplitter}. Most everything else in this class is data management and the types
* representing the model.
*
* <p>
* <b>NOTE:</b> It is technically possible for a userop to branch, but this analysis does not
* consider that. Instead, the emulator will decide how to handle those. Conventionally, I'd rather
@@ -66,7 +65,6 @@ public class JitControlFlowModel {
/**
* An exception thrown when control flow might run off the edge of the passage.
*
* <p>
* By definition a passage is a collection of strides, and each stride is terminated by some op
* without fall through (or else a synthesized {@link ExitPcodeOp}. In particular, the last
@@ -89,12 +87,10 @@ public class JitControlFlowModel {
/**
* A flow from one block to another
*
* <p>
* This is just a wrapper around an {@link IntBranch} that allows us to quickly identify what
* two blocks it connects. Note that to connect two blocks in the passage, the branch must by
* definition be an {@link IntBranch}.
*
* <p>
* If this flow represents entry into the passage, then {@link #from()} and {@link #branch()}
* may be null
@@ -121,14 +117,13 @@ public class JitControlFlowModel {
/**
* A basic block of p-code
*
* <p>
* This follows the formal definition of a basic block, but at the p-code level. All flows into
* the block enter at its first op, and all flows out of the block exit at its last op. The
* block also contains information about these flows as well as branches out of the passage via
* this block.
*/
public static class JitBlock extends PcodeProgram {
public static class JitBlock extends PcodeProgram implements CountsPcodeOps {
private Map<IntBranch, BlockFlow> flowsFrom = new HashMap<>();
private Map<IntBranch, BlockFlow> flowsTo = new HashMap<>();
private List<IntBranch> branchesFrom = new ArrayList<>();
@@ -147,19 +142,14 @@ public class JitControlFlowModel {
public JitBlock(PcodeProgram program, List<PcodeOp> code) {
super(program, List.copyOf(code));
int instructions = 0;
int trailingOps = 0;
PcodeOpCounter counter = new PcodeOpCounter();
for (PcodeOp op : code) {
if (op instanceof DecodedPcodeOp dec && dec.isInstructionStart()) {
instructions++;
trailingOps = 0;
}
else if (op instanceof DecodedPcodeOp) {
trailingOps++;
if (op instanceof DecodedPcodeOp dec) {
counter.countOp(dec.isInstructionStart());
}
}
this.instructions = instructions;
this.trailingOps = trailingOps;
this.instructions = counter.instructionCount();
this.trailingOps = counter.trailingOpCount();
}
@Override
@@ -183,7 +173,6 @@ public class JitControlFlowModel {
/**
* Get the sequence number of the first op
*
* <p>
* This is used for display and testing purposes only.
*
@@ -195,7 +184,6 @@ public class JitControlFlowModel {
/**
* Get the sequence number of the last op
*
* <p>
* This is used for display and testing purposes only.
*
@@ -266,26 +254,25 @@ public class JitControlFlowModel {
*
* @return the block, or {@code null}
*/
public JitBlock getFallFrom() {
public BlockFlow getFallFrom() {
return flowsFrom.values()
.stream()
.filter(f -> f.branch.isFall())
.findAny()
.map(f -> f.to)
.orElse(null);
}
/**
* Check if there is an internal non-fall-through branch to this block
*
* <p>
* This is used by the {@link JitCodeGenerator} to determine whether or not a block's
* bytecode needs to be labeled.
*
* @param predicate additional predicate to test the flow
* @return true if this block is targeted by a branch
*/
public boolean hasJumpTo() {
return flowsTo.values().stream().anyMatch(f -> !f.branch.isFall());
public boolean hasJumpTo(Predicate<BlockFlow> predicate) {
return flowsTo.values().stream().anyMatch(f -> !f.branch.isFall() && predicate.test(f));
}
/**
@@ -299,8 +286,7 @@ public class JitControlFlowModel {
}
/**
* Get the number of instructions represented in this block
*
* {@inheritDoc}
* <p>
* This may get dicey as blocks are not necessarily split on instruction boundaries.
* Nevertheless, we seek to count the number of instructions executed at runtime, so that we
@@ -309,23 +295,13 @@ public class JitControlFlowModel {
*
* @see JitCompiledPassage#count(int, int)
* @see JitPcodeThread#count(int, int)
* @return the instruction count
*/
@Override
public int instructionCount() {
return instructions;
}
/**
* Get the number of trailing ops in this block
*
* <p>
* It is possible a block represents only partial execution of an instruction. Though
* {@link #instructionCount()} will count this partial instruction, we can tell how far we
* got into it by examining this value. With this, we should be able to replay an execution
* to exactly the same p-code op step.
*
* @return the trailing op count
*/
@Override
public int trailingOpCount() {
return trailingOps;
}
@@ -333,7 +309,6 @@ public class JitControlFlowModel {
/**
* A class that splits a sequence of ops and associated branches into basic blocks.
*
* <p>
* This is the kernel of control flow analysis. It first indexes the branches by source and
* target op. Note that only non-fall-through branches are known at this point. Then, it
@@ -344,7 +319,6 @@ public class JitControlFlowModel {
* should have fall through (conditional or unconditional) by examining its last op. It adds a
* new fall-through branch if so. The end of the p-code op list is presumed a split point. If
* that final block "should have" fall through, an {@link UnterminatedFlowException} is thrown.
*
* <p>
* Once all the splitting is done, we have the blocks and all the branches (internal or
* external) that leave each block. We then compute all the branches (internal) that enter each
@@ -362,7 +336,6 @@ public class JitControlFlowModel {
/**
* Construct a new block splitter to process the given program
*
* <p>
* No analysis is performed in the constructor. The client must call
* {@link #addBranches(Collection)} and then {@link #splitBlocks()}.
@@ -377,7 +350,6 @@ public class JitControlFlowModel {
/**
* Notify the splitter of the given branches before analysis
*
* <p>
* The splitter immediately indexes the given branches by source and target op.
*
@@ -522,7 +494,6 @@ public class JitControlFlowModel {
/**
* Construct the control flow model.
*
* <p>
* Analysis is performed as part of constructing the model.
*
@@ -543,7 +514,7 @@ public class JitControlFlowModel {
@Override
protected IntBranch newFallthroughIntBranch(PcodeOp from, PcodeOp to) {
// Decoder should already have inserted fall-through protectors
return new RIntBranch(from, to, true, Reachability.WITHOUT_CTXMOD);
return new RIntBranch(from, to, true, CtxReach.WITHOUT_CTXMOD);
}
};
splitter.addBranches(passage.getBranches().values());
@@ -34,20 +34,17 @@ import ghidra.program.model.pcode.Varnode;
/**
* A p-code arithmetic for interpreting p-code and constructing a use-def graph
*
* <p>
* This is used for intra-block data flow analysis. We leverage the same API as is used for concrete
* p-code interpretation, but we use it for an abstraction. The type of the interpretation is
* {@code T:=}{@link JitVal}, which can consist of constants and variables in the use-def graph. The
* arithmetic must be provided to the {@link JitDataFlowExecutor}. The intra-block portions of the
* use-def graph are populated as each block is interpreted by the executor.
*
* <p>
* The general strategy for each of the arithmetic operations is to 1) generate the output SSA
* variable for the op, 2) generate the op node for the generated output and given inputs, 3) enter
* the op into the use-def graph as the definition of its output, 4) record the inputs and used by
* the new op, and finally 5) return the generated output.
*
* <p>
* There should only need to be one of these per data flow model, not per block.
*/
@@ -92,7 +89,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Remove {@code amt} bytes from the right of the <em>varnode</em>.
*
* <p>
* "Right" is considered with respect to the machine endianness. If it is little endian, then
* the byte are shaved from the <em>left</em> of the value. This should be used when getting
@@ -122,7 +118,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Remove {@code amt} bytes from the left of the <em>varnode</em>.
*
* <p>
* "Left" is considered with respect to the machine endianness. If it is little endian, then the
* byte are shaved from the <em>right</em> of the value. This should be used when getting values
@@ -177,13 +172,12 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Try to produce a simplified {@link JitSynthSubPieceOp} or {@link JitCatenateOp}
*
* <p>
* This takes an input, subpiece offset, and output variable. If the input variable is the
* result of another subpiece, the result can be a single simplified subpiece. Similarly, if the
* input is the result of a catenation, then the result can be a simplified catenation, or
* possibly subpiece.
*
* <p>
* If either of these situations applies, and simplification is possible, this returns a
* non-null result, and that result is added to the use-def graph specifying the given output
* variable as the simplified output. Otherwise, the result is null and the caller should create
@@ -221,7 +215,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Construct the result of taking the subpiece
*
* <p>
* If the input is another subpiece or a catenation, the result may be simplified. In
* particular, the subpiece of a catenation may be a smaller catenation. No matter the case, the
@@ -265,7 +258,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Remove {@code amt} bytes from the right of the value.
*
* <p>
* The value is unaffected by the machine endianness, except to designate the output varnode.
*
@@ -279,7 +271,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Remove {@code amt} bytes from the left of the value.
*
* <p>
* The value is unaffected by the machine endianness, except to designate the output varnode.
*
@@ -293,7 +284,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Compute the subpiece of a value.
*
* <p>
* The result is added to the use-def graph. The output varnode is computed from the input
* varnode and the subpiece parameters. This is used to handle variable retrieval when an access
@@ -303,7 +293,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
* MOV RAX, qword ptr [...]
* MOV dword ptr [...], EAX
* </pre>
*
* <p>
* The second line reads {@code EAX}, which consists of only the lower part of {@code RAX}.
* Thus, we synthesize a subpiece op. These are distinct from an actual {@link PcodeOp#SUBPIECE}
@@ -329,7 +318,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* Construct the catenation of the given values to form the given output varnode.
*
* <p>
* The result is added to the use-def graph. This is used to handle variable retrieval when the
* pattern of accesses indicates catenation. Consider the x86 assembly:
@@ -339,7 +327,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
* MOV AL, byte ptr [...]
* MOV word ptr [...], AX
* </pre>
*
* <p>
* On the third line, the value in {@code AX} is the catenation of whatever values were written
* into {@code AH} and {@code AL}. Thus, we synthesize a catenation op node in the use-def
@@ -377,7 +364,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* {@inheritDoc}
*
* <p>
* We override this to record the {@link JitStoreOp store} op into the use-def graph. As
* "output" we just return {@code inValue}. The executor will call
@@ -397,7 +383,6 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
/**
* {@inheritDoc}
*
* <p>
* We override this to record the {@link JitLoadOp load} op into the use-def graph. For our
* {@code inValue}, the {@link JitDataFlowState state} will have just returned the
@@ -30,7 +30,6 @@ import ghidra.program.model.pcode.Varnode;
/**
* An encapsulation of the per-block data flow analysis done by {@link JitDataFlowModel}
*
* <p>
* One of these is created for each basic block in the passage. This does both the intra-block
* analysis and encapsulates parts of the inter-block analysis. The class also contains and provides
@@ -63,7 +62,6 @@ public class JitDataFlowBlockAnalyzer {
/**
* Perform the intra-block analysis for this block
*
* <p>
* This just runs the block p-code through the analytic interpreter. See
* {@link JitDataFlowModel}'s section on intra-block analysis.
@@ -75,7 +73,6 @@ public class JitDataFlowBlockAnalyzer {
/**
* The initial entry into the recursive phi option seeking algorithm
*
* <p>
* See {@link JitDataFlowModel}'s section on inter-block analysis. This will modify the given
* phi op in place, adding to it each found option. There is also more details than discussed in
@@ -93,7 +90,6 @@ public class JitDataFlowBlockAnalyzer {
/**
* Fill options in for the given phi op
*
* <p>
* If our block is an entry, add that as a possible option. <em>Additionally</em>, consider each
* upstream block (dependency) as an option, recursively. Recursion will naturally terminate if
@@ -114,7 +110,6 @@ public class JitDataFlowBlockAnalyzer {
/**
* Consider the given flow as an option for the given phi op, and fill it
*
* <p>
* If we've already visited the given block, we return immediately, without further recursion.
* Otherwise, we examine the varnode output state of this block for suitable definitions. If
@@ -204,7 +199,6 @@ public class JitDataFlowBlockAnalyzer {
/**
* Get the latest definition of the given varnode, synthesizing ops is required.
*
* <p>
* NOTE: May produce phi nodes that need additional inter-block analysis
*
@@ -15,26 +15,29 @@
*/
package ghidra.pcode.emu.jit.analysis;
import java.math.BigInteger;
import java.util.Map;
import java.util.Objects;
import ghidra.pcode.emu.jit.JitPassage;
import ghidra.pcode.emu.jit.JitPassage.*;
import ghidra.pcode.emu.jit.op.*;
import ghidra.pcode.emu.jit.var.JitFailVal;
import ghidra.pcode.emu.jit.var.JitVal;
import ghidra.pcode.emu.jit.var.*;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.opbehavior.BinaryOpBehavior;
import ghidra.pcode.opbehavior.UnaryOpBehavior;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* A modification to {@link PcodeExecutor} that is specialized for the per-block data flow analysis.
*
* <p>
* Normally, the p-code executor follows all of the control-flow branching, as you would expect in
* the interpretation-based p-code emulator. For analysis, we do not intend to actually follow
* branches. These should only ever occur at the end of a basic block, anyway.
*
* <p>
* We do record the branch ops into the graph as {@link JitOp op nodes}. A conditional branch
* naturally participates in the data flow, as it uses the definition of its predicate varnode.
@@ -44,7 +47,6 @@ import ghidra.program.model.pcode.Varnode;
* use-def graph. For that to work, every p-code op must be entered into it. For bookkeeping, and
* because the code generator will need them, we look up the {@link Branch} records created by the
* passage decoder and store them in their respective branch op nodes.
*
* <p>
* This is all accomplished by overriding {@link #executeBranch(PcodeOp, PcodeFrame)} and similar
* branch execution methods. Additionally, we override {@link #badOp(PcodeOp)} and
@@ -52,10 +54,18 @@ import ghidra.program.model.pcode.Varnode;
* inherited implementations will throw exceptions. We need not throw an exception until/unless we
* reach such bad code a run time. So, we enter them into the use-def graph as op nodes from which
* we later generate the code to throw the exception.
* <p>
* Much of the "re-writes" from constant folding occurs here. Instead of actually replacing the
* p-code ops, we wrap them in a different {@link JitOp} than usual. For outputs that get folded, we
* re-write to a {@link JitCopy}. For an folded inputs, we keep the op but replace those inputs with
* constants, as if they had been literals. The result breaks the use-def graph apart, which will
* permit op-use analysis to remove the actual folded computations, among other unneeded things.
*/
class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
private final JitDataFlowModel dfm;
private final JitPassage passage;
private final Map<PcodeOp, PBranch> branches;
private final BytesPcodeArithmetic bytes;
/**
* Construct an executor from the given context
@@ -69,12 +79,13 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
PcodeExecutorState<JitVal> state) {
super(context.getLanguage(), dfm.getArithmetic(), state, Reason.EXECUTE_READ);
this.dfm = dfm;
this.branches = context.getPassage().getBranches();
this.passage = context.getPassage();
this.branches = passage.getBranches();
this.bytes = BytesPcodeArithmetic.forLanguage(state.getLanguage());
}
/**
* Record a branch or call op into the use-def graph
*
* <p>
* We do not need to compute the branch target, because that op was already computed by the
* passage decoder. Past attempts to perform that computation here failed when dealing with
@@ -91,7 +102,6 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
/**
* Record a conditional branch op into the use-def graph
*
* <p>
* While we can lookup the {@link Branch} target as in
* {@link #executeBranch(PcodeOp, PcodeFrame)}, we must still obtain the predicate's definition
@@ -101,7 +111,6 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
*/
protected void recordConditionalBranch(PcodeOp op) {
RBranch branch = (RBranch) Objects.requireNonNull(branches.get(op));
final JitVal cond;
if (op instanceof ExitPcodeOp) {
cond = JitFailVal.INSTANCE;
@@ -111,12 +120,21 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
cond = state.getVar(condVar, reason);
}
dfm.notifyOp(new JitCBranchOp(op, branch, cond));
if (cond instanceof JitConstVal cc) {
if (cc.value().equals(BigInteger.ZERO)) {
dfm.notifyOp(new JitNopOp(op));
}
else {
dfm.notifyOp(new JitBranchOp(op, branch));
}
}
else {
dfm.notifyOp(new JitCBranchOp(op, branch, cond));
}
}
/**
* Record an indirect branch op into the use-def graph
*
* <p>
* The {@link IndBranch} will have the target decode context, but the address is dynamic. We
* have to obtain the target varnode's definition and use it.
@@ -126,8 +144,13 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
protected void recordIndirectBranch(PcodeOp op) {
Varnode offVar = getIndirectBranchTarget(op);
JitVal offset = state.getVar(offVar, reason);
RIndBranch branch = (RIndBranch) Objects.requireNonNull(branches.get(op));
dfm.notifyOp(new JitBranchIndOp(op, offset, branch));
RBranch branch = (RBranch) Objects.requireNonNull(branches.get(op));
if (offset instanceof JitConstVal) {
dfm.notifyOp(new JitBranchOp(op, branch));
}
else {
dfm.notifyOp(new JitBranchIndOp(op, offset, branch));
}
}
@Override
@@ -137,12 +160,27 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
@Override
public void executeConditionalBranch(PcodeOp op, PcodeFrame frame) {
recordConditionalBranch(op);
byte[] foldedCond = passage.getFoldedOperand(op, OPIDX_CBRANCH_PRED);
if (foldedCond == null) {
recordConditionalBranch(op);
}
else if (bytes.isTrue(foldedCond, Purpose.CONDITION)) {
recordBranch(op);
}
else {
dfm.notifyOp(new JitNopOp(op));
}
}
@Override
public void executeIndirectBranch(PcodeOp op, PcodeFrame frame) {
recordIndirectBranch(op);
protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) {
byte[] foldedTarget = passage.getFoldedOperand(op, OPIDX_BRANCH_TARGET);
if (foldedTarget == null) {
recordIndirectBranch(op);
}
else {
recordBranch(op);
}
}
@Override
@@ -150,14 +188,112 @@ class JitDataFlowExecutor extends PcodeExecutor<JitVal> {
recordBranch(op);
}
@Override
public void executeIndirectCall(PcodeOp op, PcodeFrame frame) {
recordIndirectBranch(op);
/**
* We examine and apply folding here so that re-written ops are entered into the use-def graph
* for further analysis.
* <p>
* Were we to instead apply this during code generation, we'd miss the opportunity to remove
* unnecessary ops.
*
* @param op the p-code op whose output to examine
* @return true if the output can be folded
*/
boolean tryNotifyFoldedOutput(PcodeOp op) {
Varnode outVar = op.getOutput();
byte[] foldedOut = passage.getFoldedOperand(op, -1);
if (foldedOut != null) {
JitOutVar out = dfm.generateOutVar(op.getOutput());
JitVal outConst = arithmetic.fromConst(foldedOut);
dfm.notifyOp(new JitCopyOp(op, out, outConst));
/**
* Don't just put the const into the state. Put the re-written copy's output. Otherwise,
* the copy op will not get marked as "used" by block retirement, intervening
* callothers, etc. Ops that use the result as input will have those inputs re-written,
* anyway. This re-written op may still be removed if that output is never actually
* used.
*/
state.setVar(outVar, out);
return true;
}
return false;
}
/**
* We examine and apply input folding here so that computed outputs resulting in folded
* constants no longer appear "used," permitting their removal by later analysis.
* <p>
* Were we to instead apply this during code generation, we'd miss the opportunity to remove the
* ops computing the folded constant.
*
* @param op the p-code op
* @param index the input operand index (0-up)
* @return true if the input can be folded
*/
JitVal getFoldedOrVarInput(PcodeOp op, int index) {
byte[] folded = passage.getFoldedOperand(op, index);
if (folded != null) {
return arithmetic.fromConst(folded);
}
return state.getVar(op.getInput(index), reason);
}
@Override
public void executeReturn(PcodeOp op, PcodeFrame frame) {
recordIndirectBranch(op);
public void executeUnaryOp(PcodeOp op, UnaryOpBehavior b) {
if (tryNotifyFoldedOutput(op)) {
return;
}
JitVal in1 = getFoldedOrVarInput(op, 0);
JitVal out = arithmetic.unaryOp(op, in1);
Varnode outVar = op.getOutput();
state.setVar(outVar, out);
}
@Override
public void executeBinaryOp(PcodeOp op, BinaryOpBehavior b) {
if (tryNotifyFoldedOutput(op)) {
return;
}
JitVal in1 = getFoldedOrVarInput(op, 0);
JitVal in2 = getFoldedOrVarInput(op, 1);
JitVal out = arithmetic.binaryOp(op, in1, in2);
Varnode outVar = op.getOutput();
state.setVar(outVar, out);
}
// Callother is handled in JitDataFlowUseropLibrary.WrappedUseropDefinition
@Override
public void executeLoad(PcodeOp op) {
/**
* LATER: Try folding LOAD, but only on addresses that are near this instruction. That is a
* heuristic for likely constant memory, e.g., a constant pool in ARM. That loaded range
* would need to be considered for invalidation in the same manner as any range in the
* decoded passage.
*/
AddressSpace space = getLoadStoreSpace(op);
// If folded, both breaks up the use-def graph and permits gen to use direct access.
JitVal offset = getFoldedOrVarInput(op, OPIDX_DEREF_OFFSET);
Varnode outVar = op.getOutput();
beforeLoad(op, space, offset, outVar.getSize());
JitVal out = state.getVar(space, offset, outVar.getSize(), true, reason);
JitVal mod = arithmetic.modAfterLoad(op, space, offset, out);
state.setVar(outVar, mod);
afterLoad(op, space, offset, outVar.getSize(), mod);
}
@Override
public void executeStore(PcodeOp op) {
AddressSpace space = getLoadStoreSpace(op);
JitVal offset = getFoldedOrVarInput(op, OPIDX_DEREF_OFFSET);
// If folded, just breaks up the use-def graph
Varnode valVar = getStoreValue(op); // for size
JitVal val = getFoldedOrVarInput(op, OPIDX_STORE_VALUE);
JitVal mod = arithmetic.modBeforeStore(op, space, offset, val);
beforeStore(op, space, offset, valVar.getSize(), mod);
state.setVar(space, offset, valVar.getSize(), true, mod);
afterStore(op, space, offset, valVar.getSize(), mod);
}
@Override
@@ -17,6 +17,7 @@ package ghidra.pcode.emu.jit.analysis;
import java.io.*;
import java.util.*;
import java.util.stream.Stream;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.lifecycle.Internal;
@@ -38,7 +39,6 @@ import ghidra.program.model.pcode.Varnode;
/**
* The data flow analysis for JIT-accelerated emulation.
*
* <p>
* This implements the Data Flow Analysis phase of the {@link JitCompiler}. The result is a use-def
* graph. The graph follows Static Single Assignment (SSA) form, in that each definition of a
@@ -55,7 +55,6 @@ import ghidra.program.model.pcode.Varnode;
* ADD RAX, RDX
* MOV qword ptr [...], RAX
* </pre>
*
* <p>
* Ignoring RAM, there are two varnodes at play, named for the registers they represent: {@code RAX}
* and {@code RDX}. However, there are three variables. The first is an instance of {@code RAX},
@@ -67,7 +66,6 @@ import ghidra.program.model.pcode.Varnode;
* define {@code RAX}<sub>2</sub>. The last {@code MOV} instruction uses {@code RAX}<sub>2</sub>. If
* we plot each instruction and variable in a graph, drawing edges for each use and definition, we
* get a use-def graph.
*
* <p>
* Our analysis produces a use-def graph for the passage's p-code (not instructions) in two steps:
* First, we analyze each basic block independently. There are a lot of nuts and bolts in the
@@ -83,7 +81,6 @@ import ghidra.program.model.pcode.Varnode;
* fresh {@link JitDataFlowState}, so that its result has no dependency on the interpretation of any
* other block, except in the numbering of variable identifiers; those must be unique across the
* model.
*
* <p>
* During interpretation, varnode accesses generate value nodes. When a constant varnode is
* accessed, it simply creates a {@link JitConstVal}. When an op produces an output, it generates a
@@ -111,7 +108,6 @@ import ghidra.program.model.pcode.Varnode;
* encountered, or we encounter a block with no inward flows, we do not recurse. An
* {@link JitInputVar input} variable is generated whenever we encounter a passage entry, indicating
* the variable could be defined outside the passage.
*
* <p>
* Note that the resulting phi ops may not adhere precisely to the formal definition of <em>phi
* node</em>. A phi op may have only one option. The recursive part of the option seeking algorithm
@@ -124,16 +120,6 @@ import ghidra.program.model.pcode.Varnode;
*/
public class JitDataFlowModel {
/**
* Create a list of {@link JitTypeBehavior#ANY ANY}s having the same size as the list of values.
*
* @param inVals the values, e.g., of each parameter to a userop
* @return the list
*/
static List<JitTypeBehavior> allAny(List<JitVal> inVals) {
return inVals.stream().map(v -> JitTypeBehavior.ANY).toList();
}
private final JitAnalysisContext context;
private final JitControlFlowModel cfm;
@@ -153,7 +139,6 @@ public class JitDataFlowModel {
/**
* Construct the data flow model.
*
* <p>
* Analysis is performed as part of constructing the model.
*
@@ -292,7 +277,6 @@ public class JitDataFlowModel {
/**
* Get the use-def op node for the given p-code op
*
* <p>
* NOTE: When used in testing, if the passage is manufactured from a {@link PcodeProgram}, the
* decoder will re-write the p-code ops as {@link DecodedPcodeOp}s. Be sure to pass an op to
@@ -307,16 +291,14 @@ public class JitDataFlowModel {
}
/**
* Get all the op nodes, whether from a p-code op or synthesized.
* {@return all the op nodes, whether from a p-code op or synthesized.}
*
* @return the ops.
* @see JitDataFlowModel
*/
Collection<JitOp> allOps() {
Set<JitOp> all = new LinkedHashSet<>();
all.addAll(ops.values());
all.addAll(synthNodes);
return all;
Stream<JitOp> allOps() {
return Stream.concat(
ops.values().stream(),
synthNodes.stream());
}
/**
@@ -327,8 +309,8 @@ public class JitDataFlowModel {
*/
protected class ValCollector extends HashSet<JitVal> implements JitOpUpwardVisitor {
public ValCollector() {
for (PcodeOp op : passage.getCode()) {
JitOp jitOp = getJitOp(op);
// Don't just use passage ops, because we need synthetic (phi) outputs, too.
for (JitOp jitOp : (Iterable<JitOp>) allOps()::iterator) {
visitOp(jitOp);
if (jitOp instanceof JitDefOp defOp) {
visitVal(defOp.out());
@@ -390,29 +372,25 @@ public class JitDataFlowModel {
/**
* Construct the use-def graph
*
* @implNote Just visit the blocks in any order. Use input placeholders and glue them together
* afterward.
* <p>
* I considered unrolling each loop at least once to avoid certain multi-equals stuff.
* I don't think that'll be necessary. If we pre-load the registers into local
* variables, then we'll always be reading and writing to those locals, so no worries
* about multi-equals.
*/
protected void analyze() {
/**
* Just visit the blocks in any order. Use input placeholders and glue them together
* afterward.
*
* I considered unrolling each loop at least once to avoid certain multi-equals stuff. I
* don't think that'll be necessary. If we pre-load the registers into local variables, then
* we'll always be reading and writing to those locals, so no worries about multi-equals.
*/
for (JitBlock block : cfm.getBlocks()) {
getOrCreateAnalyzer(block).doIntrablock();
}
/**
* Now, work out the inter-block flows.
*/
analyzeInterblock(phiNodes);
}
/**
* Perform the inter-block analysis.
*
* <p>
* This is called by {@link #analyze()} after intra-block analysis.
*
@@ -474,7 +452,6 @@ public class JitDataFlowModel {
/**
* A diagnostic tool for visualizing the use-def graph.
*
* <p>
* NOTE: This is only as complete as it needed to be for me to diagnose whatever issue I was
* having at the time.
@@ -634,7 +611,6 @@ public class JitDataFlowModel {
/**
* Generate a graphviz .dot file to visualize the use-def graph.
*
* <p>
* <b>WARNING:</b> This is an internal diagnostic that is only as complete as it needed to be.
*
@@ -35,7 +35,6 @@ import ghidra.util.Msg;
/**
* An implementation of {@link PcodeExecutorState} for per-block data flow interpretation
*
* <p>
* In p-code interpretation, this interface's purpose is to store the current value of varnodes in
* the emulation/interpretation state. Here we implement it using {@code T:=}{@link JitVal}, and
@@ -48,7 +47,6 @@ import ghidra.util.Msg;
* part of a varnode) access, this state will synthesize {@link JitPhiOp phi} ops. See
* {@link #setVar(AddressSpace, JitVal, int, boolean, JitVal) setVar} and
* {@link #getVar(AddressSpace, JitVal, int, boolean, Reason) getVar} for details.
*
* <p>
* This state only serves to analyze data flow through register and unique variables. Because we
* know these are only accessible to the thread, we stand to save much execution time by bypassing
@@ -97,7 +95,6 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
/**
* Clear all definition entries in the given per-space map for the given varnode
*
* <p>
* Any entries completely covered by the given varnode (including an exact match) are
* removed from the map. Those partially covered will be replaced by subpieces of their
@@ -194,7 +191,6 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
/**
* Set one or more definition entries in the given map for the given varnode to the given
* value
*
* <p>
* Ordinary, this just sets the one varnode to the given value; however, if the given value
* is the output of a {@link JitCatenateOp catenation}, then each input part is entered into
@@ -275,12 +271,10 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
/**
* Get an ordered list of all values involved in the latest definition of the given varnode.
*
* <p>
* In the simplest case, the list consists of exactly one SSA variable whose varnode exactly
* matches that requested. In other cases, e.g., when only a subregister is defined, the
* list may have several entries, some of which may be {@link JitMissingVar missing}.
*
* <p>
* The list is ordered according to machine endianness. That is for little endian, the
* values are ordered from least to most significant parts of the varnode defined. This is
@@ -348,7 +342,6 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
/**
* Get the value of the given varnode
*
* <p>
* This is the implementation of
* {@link JitDataFlowState#getVar(AddressSpace, JitVal, int, boolean, Reason)}, but only for
@@ -417,7 +410,6 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
/**
* {@inheritDoc}
*
* <p>
* This and {@link #getVar(AddressSpace, JitVal, int, boolean, Reason)} are where we connect the
* interpretation to the maps of definitions we keep in this state. We examine the varnode's
@@ -427,23 +419,29 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
* such variables are handled by {@link JitMemoryOutVar}. Such output variables are actually
* passed in as {@code val} here, but need only be stored in a map if they are register or
* unique variables.
*
* @implNote We use this only to log possible storage bypasses. All uniques will be bypassed.
* Registers must be written, but it is safe to bypass subsequent loads. Actually,
* with a pre-load of register values and a try-finally to write them, we optimize
* register access, too. Turns out registers and uniques get the same treatment, at
* least for now, partly for debugging purposes, but also because with crossbuilds,
* uniques must outlive their generating instruction. LATER: Can we examine an
* instruction's named blocks to determine which uniques need saving? LATER: It might
* also be possible (with significant changes to the strategy in
* {@link JitVarScopeModel}) to be more selective in which variables become live and
* are retired. But we must avoid the situation where we might "save" a variable that
* was never restored or written. That would effectively erase a variable that should
* have been left unmodified.
* <p>
* Memory must be written. Unless we can determine for sure the memory is
* non-volatile, we must presume volatile, so no bypassing is allowed. LATER: We might
* consider assuming stack-based accesses are non-volatile, but I'm not sure that is
* appropriate either. Technically one thread may launch another, providing a ref to a
* stack variable it knows will live for the entire thread's life.
*/
@Override
public void setVar(AddressSpace space, JitVal offset, int size, boolean quantize,
JitVal val) {
/**
* We use this only to log possible storage bypasses. All uniques will be bypassed.
* Registers must be written, but it is safe to bypass subsequent loads. Actually, perhaps
* with a pre-load of register values and a try-finally to write them, we can optimize
* register access, too. Might also make sense to do that for uniques, just for debugging
* purposes.
*
* Memory must be written. Unless we can determine for sure the memory is non-volatile, we
* must presume volatile, so no bypassing is allowed. TODO: We might consider assuming
* stack-based accesses are non-volatile, but I'm not sure that is appropriate either.
* Technically one thread may launch another, providing a ref to a stack variable it knows
* will live for the entire thread's life.
*/
if (space.isConstantSpace()) {
Msg.warn(this, "Witnessed write to constant space! Ignoring.");
return;
@@ -499,7 +497,6 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
/**
* {@inheritDoc}
*
* <p>
* This and {@link #setVar(AddressSpace, JitVal, int, boolean, JitVal)} are where we connect the
* interpretation to the maps of definitions we keep in this state. We examine the varnode's
@@ -584,7 +581,6 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
/**
* Capture the current state of intra-block analysis.
*
* <p>
* This may be required for follow-up op-use analysis by a {@link JitCallOtherOpIf} invoked
* using the standard strategy. All live varnodes <em>at the time of the call</em> must be
@@ -19,7 +19,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.IntStream;
import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState;
import ghidra.pcode.emu.jit.decode.DecoderUseropLibrary;
@@ -34,7 +34,6 @@ import ghidra.program.model.pcode.Varnode;
/**
* A wrapper around a userop library that places {@link PcodeOp#CALLOTHER callother} ops into the
* use-def graph
*
* <p>
* This is the library provided to
* {@link JitDataFlowExecutor#execute(PcodeProgram, PcodeUseropLibrary)} to cooperate with in the
@@ -44,10 +43,8 @@ import ghidra.program.model.pcode.Varnode;
* use-def graph takes careful notice of variables and their definiting ops, there are two possible
* op nodes: {@link JitCallOtherOp} when no output operand is given and {@link JitCallOtherDefOp}
* when an output operand is given.
*
* <p>
* We employ several different strategies to handle a p-code userop:
*
* <ul>
* <li><b>Standard</b>: Invocation of the userop in the same fashion as the interpreted p-code
* emulator. Any live variables have to be written into the {@link JitBytesPcodeExecutorState state}
@@ -65,7 +62,6 @@ import ghidra.program.model.pcode.Varnode;
* well when the inputs are registers or uniques allocated in JVM locals. The return value can be
* handled similarly.</li>
* </ul>
*
* <p>
* The default strategy for all userops is Standard. Implementors should set the attributes of
* {@link PcodeUserop} and adjust the parameters of the userop's method accordingly. To allow
@@ -84,9 +80,9 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
* The wrapper of a specific userop definition
*/
protected class WrappedUseropDefinition implements PcodeUseropDefinition<JitVal> {
private final PcodeUseropDefinition<Object> decOp;
private final PcodeUseropDefinition<?> decOp;
public WrappedUseropDefinition(PcodeUseropDefinition<Object> decOp) {
public WrappedUseropDefinition(PcodeUseropDefinition<?> decOp) {
this.decOp = decOp;
}
@@ -107,33 +103,59 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
}
/**
* The output and input types of a userop, defined using a Java callback
*
* @param out the type behavior of the userop's output, or null if it returns {@code void}.
* @param ins the type behavior or each input argument
*/
record InOutTypes(JitTypeBehavior out, List<JitTypeBehavior> ins) {
/**
* Indicates that the userop's type behaviors aren't known
* <p>
* Perhaps they are partially-known, but some property of the method prevents direct
* invocation, and so we indicate unknown to ensure a more conservative strategy is
* applied.
*
* @param hasOutput true if the return type is not {@code void}
* @param inputCount the number of input arguments the userop can accept
* @return the fallback types, i.e., everything is {@link JitTypeBehavior#ANY}, unless
* the method returns {@code void}, in which case the output type is
* {@code null}.
*/
static InOutTypes fallback(boolean hasOutput, int inputCount) {
return new InOutTypes(hasOutput ? JitTypeBehavior.ANY : null,
IntStream.range(0, inputCount).mapToObj(_ -> JitTypeBehavior.ANY).toList());
}
}
/**
* Get the input and output types for this userop.
* <p>
* If the number of arguments matches the userop's Java method, map each argument value to
* the type behavior for its corresponding parameter.
*
* <p>
* This is used by the {@link JitTypeModel} to assign types to JVM locals in order to reduce
* the number of type casts. In the case of direct invocation, this enters type information
* from the userop's Java definition into the analysis.
*
* <p>
* If the parameter count doesn't match, we just map the arguments to
* {@link JitTypeBehavior#ANY} and let the error surface at run time. We need not throw the
* exception until/unless the invocation is actually executed. Similarly, if any parameter's
* type is not supported, or the userop cannot be invoked directly, we just map all
* arguments to {@link JitTypeBehavior#ANY}, because the generator will apply standard
* invocation, which does not benefit from type analysis.
* type or the output type is not supported, or the userop cannot be invoked directly, we
* just map all arguments to {@link JitTypeBehavior#ANY}, because the generator will apply
* standard invocation, which does not benefit from type analysis.
*
* @param inVals the input arguments
* @return the map from argument value (SSA variable) to parameter type behavior
*/
private List<JitTypeBehavior> getInputTypes(List<JitVal> inVals) {
private InOutTypes getInOutTypes(boolean hasOutput, List<JitVal> inVals) {
int inputCount = getInputCount();
if (inputCount != inVals.size()) { // includes inputCount == -1 (variadic)
return JitDataFlowModel.allAny(inVals);
return InOutTypes.fallback(hasOutput, inVals.size());
}
Method method = decOp.getJavaMethod();
if (method == null) {
return JitDataFlowModel.allAny(inVals);
return InOutTypes.fallback(hasOutput, inVals.size());
}
List<JitTypeBehavior> result = new ArrayList<>();
Parameter[] parameters = method.getParameters();
@@ -141,29 +163,23 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
Parameter p = parameters[i];
JitTypeBehavior type = JitTypeBehavior.forJavaType(p.getType());
if (type == null) {
return JitDataFlowModel.allAny(inVals);
return InOutTypes.fallback(hasOutput, inVals.size());
}
result.add(type);
}
return Collections.unmodifiableList(result);
}
/**
* Get the type behavior from the userop's Java method
*
* <p>
* If the userop is not backed by a Java method, or its output type is not supported, this
* return {@link JitTypeBehavior#ANY}.
*
* @return the type behavior
*/
private JitTypeBehavior getOutputTypeBehavior() {
return JitTypeBehavior.forJavaType(getOutputType());
Class<?> outCls = getOutputType();
if (outCls == null) {
return InOutTypes.fallback(hasOutput, inVals.size());
}
JitTypeBehavior outType = JitTypeBehavior.forJavaType(outCls);
if (outType == null && hasOutput) {
return InOutTypes.fallback(hasOutput, inVals.size());
}
return new InOutTypes(outType, Collections.unmodifiableList(result));
}
/**
* {@inheritDoc}
*
* <p>
* This "execution" is part of the intra-block analysis. This is the analytic interpretation
* of the invocation, not the actual run time invocation. This derives type information
@@ -194,20 +210,29 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
dfm.notifyOp(new JitNopOp(op));
return;
}
JitDataFlowState state = (JitDataFlowState) executor.getState();
List<JitVal> inVals = Stream.of(op.getInputs())
.skip(1)
.map(inVn -> state.getVar(inVn, executor.getReason()))
.toList();
List<JitTypeBehavior> inTypes = getInputTypes(inVals);
JitDataFlowExecutor exec = (JitDataFlowExecutor) executor;
if (exec.tryNotifyFoldedOutput(op)) {
// A folded constant would not be present were the userop not suitable for removal
return;
}
JitDataFlowState state = (JitDataFlowState) exec.getState();
List<JitVal> inVals = new ArrayList<>();
int n = op.getNumInputs();
for (int i = 1; i < n; i++) {
inVals.add(exec.getFoldedOrVarInput(op, i));
}
Varnode outVn = op.getOutput();
InOutTypes types = getInOutTypes(outVn != null, inVals);
if (outVn == null) {
dfm.notifyOp(new JitCallOtherOp(op, decOp, inVals, inTypes, state.captureState()));
dfm.notifyOp(
new JitCallOtherOp(op, decOp, inVals, types.ins, state.captureState()));
}
else {
JitOutVar out = dfm.generateOutVar(outVn);
dfm.notifyOp(new JitCallOtherDefOp(op, out, getOutputTypeBehavior(), decOp, inVals,
inTypes, state.captureState()));
dfm.notifyOp(new JitCallOtherDefOp(op, out, types.out, decOp, inVals, types.ins,
state.captureState()));
state.setVar(outVn, out);
}
}
@@ -217,6 +242,11 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
return decOp.isFunctional();
}
@Override
public boolean canInterrupt() {
return decOp.canInterrupt();
}
@Override
public boolean hasSideEffects() {
return decOp.hasSideEffects();
@@ -232,6 +262,16 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
return decOp.canInlinePcode();
}
@Override
public boolean isOutSigned() {
return decOp.isOutSigned();
}
@Override
public boolean isInSigned(int index) {
return decOp.isInSigned(index);
}
@Override
public Class<?> getOutputType() {
return decOp.getOutputType();

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