mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-22 18:14:09 +08:00
GP-5976: Implement constant folding and other optimizations in JIT emulator.
This commit is contained in:
+6
-6
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+7
-8
@@ -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) {}
|
||||
|
||||
|
||||
+4
-4
@@ -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;
|
||||
}
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+9
-22
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+32
-42
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+57
-21
@@ -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;
|
||||
|
||||
+6
-7
@@ -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");
|
||||
|
||||
+6
-9
@@ -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.
|
||||
|
||||
+14
-5
@@ -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
|
||||
*
|
||||
|
||||
+74
-142
@@ -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
|
||||
|
||||
+6
-6
@@ -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;
|
||||
}
|
||||
|
||||
+32
-21
@@ -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;
|
||||
|
||||
+111
@@ -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);
|
||||
}
|
||||
}
|
||||
+18
-7
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
+94
@@ -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?
|
||||
}
|
||||
}
|
||||
+83
-89
@@ -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
|
||||
});
|
||||
|
||||
+24
-7
@@ -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();
|
||||
|
||||
+39
-80
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-6
@@ -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
|
||||
*/
|
||||
|
||||
+42
-11
@@ -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();
|
||||
|
||||
|
||||
+8
-10
@@ -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;
|
||||
}
|
||||
|
||||
+31
-58
@@ -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;
|
||||
}
|
||||
|
||||
+277
-192
File diff suppressed because it is too large
Load Diff
+268
-191
File diff suppressed because it is too large
Load Diff
+106
@@ -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();
|
||||
}
|
||||
}
|
||||
+107
@@ -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();
|
||||
}
|
||||
}
|
||||
+14
-21
@@ -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
|
||||
|
||||
+17
-15
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+15
-5
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+53
-1
@@ -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 — 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.
|
||||
|
||||
+1
-13
@@ -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
|
||||
|
||||
+11
-13
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-46
@@ -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 — 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());
|
||||
|
||||
+1
-16
@@ -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
|
||||
|
||||
-6
@@ -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
|
||||
*
|
||||
|
||||
+157
-21
@@ -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
|
||||
|
||||
+16
-40
@@ -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.
|
||||
*
|
||||
|
||||
+19
-23
@@ -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
|
||||
|
||||
+80
-40
@@ -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
Reference in New Issue
Block a user