Merge remote-tracking branch

'origin/GP-3071v2_Dan_moveNewEmulator--SQUASHED'

Conflicts:
	Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java
This commit is contained in:
ghidra1 2023-02-22 18:59:02 -05:00
commit ac8676f0f6
181 changed files with 230 additions and 40 deletions

View file

@ -0,0 +1,28 @@
/* ###
* 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.
*/
apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Framework Emulation'
dependencies {
api project(':SoftwareModeling')
api project(':Generic')
api project(':Utility')
}

View file

@ -0,0 +1,6 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
src/test/resources/mock.cspec||GHIDRA||||END|
src/test/resources/mock.ldefs||GHIDRA||||END|
src/test/resources/mock.pspec||GHIDRA||||END|
src/test/resources/mock.slaspec||GHIDRA||||END|

View file

@ -0,0 +1,229 @@
/* ###
* 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 generic;
import java.util.*;
import java.util.Map.Entry;
/**
* A method outline for setting an entry in a range map where coalescing is desired
*
* @param <E> the type of entries
* @param <D> the type of range bounds
* @param <R> the type of ranges
* @param <V> the type of values
*/
public abstract class RangeMapSetter<E, D, R, V> {
/**
* Compare two values as in {@link Comparator#compare(Object, Object)}
*
* @param d1 the first value
* @param d2 the second value
* @return the result
*/
protected abstract int compare(D d1, D d2);
/**
* Get the range of the given entry
*
* @param entry the entry
* @return the range
*/
protected abstract R getRange(E entry);
/**
* Get the value of the given entry
*
* @param entry the entry
* @return the value
*/
protected abstract V getValue(E entry);
/**
* Remove an entry from the map
*
* @param entry the entry
*/
protected abstract void remove(E entry);
/**
* Get the lower bound of the range
*
* @param range the range
* @return the lower bound
*/
protected abstract D getLower(R range);
/**
* Get the upper bound of the range
*
* @param range the range
* @return the upper bound
*/
protected abstract D getUpper(R range);
/**
* Create a closed range with the given bounds
*
* @param lower the lower bound
* @param upper the upper bound
* @return the range
*/
protected abstract R toSpan(D lower, D upper);
/**
* Get the number immediately preceding the given bound
*
* @param d the bound
* @return the previous bound, or null if it doesn't exist
*/
protected abstract D getPrevious(D d);
/**
* Get the number immediately following the given bound
*
* @param d the bound
* @return the next bound, or null if it doesn't exist
*/
protected abstract D getNext(D d);
/**
* Get all entries intersecting the closed range formed by the given bounds
*
* @param lower the lower bound
* @param upper the upper bound
* @return the intersecting entries
*/
protected abstract Iterable<E> getIntersecting(D lower, D upper);
/**
* Place an entry into the map
*
* @param range the range of the entry
* @param value the value of the entry
* @return the new entry (or an existing entry)
*/
protected abstract E put(R range, V value);
/**
* Get the previous bound or this same bound, if the previous doesn't exist
*
* @param d the bound
* @return the previous or same bound
*/
protected D getPreviousOrSame(D d) {
D prev = getPrevious(d);
if (prev == null) {
return d;
}
return prev;
}
/**
* Get the next bound or this same bound, if the next doesn't exist
*
* @param d the bound
* @return the next or same bound
*/
protected D getNextOrSame(D d) {
D next = getNext(d);
if (next == null) {
return d;
}
return next;
}
/**
* Check if the two ranges are connected
*
* <p>
* The ranges are connected if they intersect, or if their bounds abut.
*
* @param r1 the first range
* @param r2 the second range
* @return true if connected
*/
protected boolean connects(R r1, R r2) {
return compare(getPreviousOrSame(getLower(r1)), getUpper(r2)) <= 0 ||
compare(getPreviousOrSame(getLower(r2)), getUpper(r1)) <= 0;
}
/**
* Entry point: Set the given range to the given value, coalescing where possible
*
* @param range the range
* @param value the value
* @return the entry containing the value
*/
public E set(R range, V value) {
return set(getLower(range), getUpper(range), value);
}
/**
* Entry point: Set the given range to the given value, coalescing where possible
*
* @param lower the lower bound
* @param upper the upper bound
* @param value the value
* @return the entry containing the value
*/
public E set(D lower, D upper, V value) {
// Go one out to find abutting ranges, too.
D prev = getPreviousOrSame(lower);
D next = getNextOrSame(upper);
Set<E> toRemove = new LinkedHashSet<>();
Map<R, V> toPut = new HashMap<>();
for (E entry : getIntersecting(prev, next)) {
R r = getRange(entry);
int cmpMin = compare(getLower(r), lower);
int cmpMax = compare(getUpper(r), upper);
boolean sameVal = Objects.equals(getValue(entry), value);
if (cmpMin <= 0 && cmpMax >= 0 && sameVal) {
return entry; // The value in this range is already set as specified
}
toRemove.add(entry);
if (cmpMin < 0) {
if (sameVal) {
// Expand the new entry to cover the one we just removed
lower = getLower(r);
}
else {
// Create a truncated entry to replace the one we just removed
toPut.put(toSpan(getLower(r), prev), getValue(entry));
}
}
if (cmpMax > 0) {
if (sameVal) {
// Expand the new entry to cover the one we just removed
upper = getUpper(r);
}
else {
// Create a truncated entry to replace the one we just removed
toPut.put(toSpan(next, getUpper(r)), getValue(entry));
}
}
}
for (E entry : toRemove) {
remove(entry);
}
E result = put(toSpan(lower, upper), value);
assert toPut.size() <= 2;
for (Entry<R, V> ent : toPut.entrySet()) {
put(ent.getKey(), ent.getValue());
}
return result;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,228 @@
/* ###
* 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 generic;
/**
* A span of unsigned longs
*
* <p>
* While the type of endpoint is {@link Long}, the domain imposes unsigned behavior. To ensure
* consistent behavior in client code, comparisons and manipulations should be performed via
* {@link #DOMAIN}, where applicable.
*/
public interface ULongSpan extends Span<Long, ULongSpan> {
ULongSpan.Domain DOMAIN = ULongSpan.Domain.INSTANCE;
ULongSpan.Empty EMPTY = Empty.INSTANCE;
ULongSpan.Impl ALL = new Impl(DOMAIN.min(), DOMAIN.max());
/**
* Create a closed interval of unsigned longs
*
* @param min the lower bound
* @param max the upper bound
* @return the span
* @throws IllegalArgumentException if {@code max < min}
*/
public static ULongSpan span(long min, long max) {
return DOMAIN.closed(min, max);
}
/**
* Create a closed interval of unsigned longs having a given length
*
* @param min the lower bound
* @param length the length
* @return the span
* @throws IllegalArgumentException if the upper endpoint would exceed {@link Domain#max()}
*/
public static ULongSpan extent(long min, long length) {
return DOMAIN.closed(min, min + length - 1);
}
/**
* Create a closed interval of unsigned longs having the given (unsigned) length
*
* <p>
* This operates the same as {@link #extent(long, int)}, but ensures the given length is treated
* as an unsigned integer.
*
* @param min
* @param length
* @return the span
* @throws IllegalArgumentException if the upper endpoint would exceed {@link Domain#max()}
*/
public static ULongSpan extent(long min, int length) {
return extent(min, Integer.toUnsignedLong(length));
}
/**
* The domain of unsigned longs
*/
public enum Domain implements Span.Domain<Long, ULongSpan> {
INSTANCE;
@Override
public ULongSpan newSpan(Long min, Long max) {
return new Impl(min, max);
}
@Override
public ULongSpan all() {
return ALL;
}
@Override
public ULongSpan empty() {
return EMPTY;
}
@Override
public String toString(Long n) {
return Long.toUnsignedString(n);
}
@Override
public int compare(Long n1, Long n2) {
return Long.compareUnsigned(n1, n2);
}
@Override
public Long min() {
return 0L;
}
@Override
public Long max() {
return -1L;
}
@Override
public Long inc(Long n) {
return n + 1;
}
@Override
public Long dec(Long n) {
return n - 1;
}
}
/**
* The singleton empty span of unsigned longs
*/
class Empty implements ULongSpan, Span.Empty<Long, ULongSpan> {
private static final ULongSpan.Empty INSTANCE = new ULongSpan.Empty();
private Empty() {
}
@Override
public String toString() {
return doToString();
}
@Override
public ULongSpan.Domain domain() {
return DOMAIN;
}
@Override
public long length() {
return 0;
}
}
/**
* A non-empty span of unsigned longs
*/
record Impl(Long min, Long max) implements ULongSpan {
@Override
public String toString() {
return doToString();
}
@Override
public Span.Domain<Long, ULongSpan> domain() {
return DOMAIN;
}
}
/**
* A map of unsigned long spans to values
*
* @param <V> the type of values
*/
interface ULongSpanMap<V> extends SpanMap<Long, ULongSpan, V> {
}
/**
* A mutable map of unsigned long spans to values
*
* @param <V> the type of values
*/
interface MutableULongSpanMap<V> extends ULongSpanMap<V>, MutableSpanMap<Long, ULongSpan, V> {
}
/**
* An interval tree implementing {@link MutableULongSpanMap}
*
* @param <V> the type of values
*/
class DefaultULongSpanMap<V> extends DefaultSpanMap<Long, ULongSpan, V>
implements MutableULongSpanMap<V> {
public DefaultULongSpanMap() {
super(DOMAIN);
}
}
/**
* A set of unsigned long spans
*/
interface ULongSpanSet extends SpanSet<Long, ULongSpan> {
static ULongSpanSet of(ULongSpan... spans) {
MutableULongSpanSet result = new DefaultULongSpanSet();
for (ULongSpan s : spans) {
result.add(s);
}
return result;
}
}
/**
* A mutable set of unsigned long spans
*/
interface MutableULongSpanSet extends ULongSpanSet, MutableSpanSet<Long, ULongSpan> {
}
/**
* An interval tree implementing {@link MutableULongSpanSet}
*/
class DefaultULongSpanSet extends DefaultSpanSet<Long, ULongSpan>
implements MutableULongSpanSet {
public DefaultULongSpanSet() {
super(DOMAIN);
}
}
/**
* Get the length of the span
*
* @return the length
*/
default long length() {
return max() - min() + 1;
}
}

View file

@ -0,0 +1,93 @@
/* ###
* 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 generic;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Some utilities for when singleton collections are expected
*/
public interface Unique {
static <T> T assertAtMostOne(T[] arr) {
if (arr.length == 0) {
return null;
}
if (arr.length == 1) {
return arr[0];
}
throw new AssertionError("Expected at most one. Got many: " + List.of(arr));
}
/**
* Assert that exactly one element is in an iterable and get that element
*
* @param <T> the type of element
* @param col the iterable
* @return the element
* @throws AssertionError if no element or many elements exist in the iterable
*/
static <T> T assertOne(Iterable<T> col) {
Iterator<T> it = col.iterator();
if (!it.hasNext()) {
throw new AssertionError("Expected exactly one. Got none.");
}
T result = it.next();
if (it.hasNext()) {
List<T> all = new ArrayList<>();
all.add(result);
while (it.hasNext()) {
all.add(it.next());
}
throw new AssertionError("Expected exactly one. Got many: " + all);
}
return result;
}
/**
* Assert that exactly one element is in a stream and get that element
*
* @param <T> the type of element
* @param st the stream
* @return the element
* @throws AssertionError if no element or many elements exist in the stream
*/
static <T> T assertOne(Stream<T> st) {
return assertOne(st.collect(Collectors.toList()));
}
/**
* Assert that at most one element is in an iterable and get that element or {@code null}
*
* @param <T> the type of element
* @param col the iterable
* @return the element or {@code null} if empty
* @throws AssertionError if many elements exist in the iterable
*/
static <T> T assertAtMostOne(Iterable<T> col) {
Iterator<T> it = col.iterator();
if (!it.hasNext()) {
return null;
}
T result = it.next();
if (it.hasNext()) {
throw new AssertionError("Expected at most one. Got many.");
}
return result;
}
}

View file

@ -0,0 +1,467 @@
/* ###
* 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.app.emulator;
import java.util.*;
import ghidra.app.emulator.memory.*;
import ghidra.app.emulator.state.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emulate.*;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.memstate.*;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class Emulator {
private final MemoryFaultHandler faultHandler;
private SleighLanguage language;
private AddressFactory addrFactory;
private CompositeLoadImage loadImage = new CompositeLoadImage();
private RegisterState mstate;
private MemoryPageBank registerState;
private FilteredMemoryState memState;
private ghidra.pcode.emulate.BreakTableCallBack breakTable;
private Emulate emulator;
private boolean emuHalt = true;
private boolean isExecuting = false;
private boolean writeBack = false;
private int pageSize; // The preferred page size for a paged memory state
private String pcName;
private long initialPC;
private int instExecuted = 0;
public Emulator(EmulatorConfiguration cfg) {
this.faultHandler = cfg.getMemoryFaultHandler();
pcName = cfg.getProgramCounterName();
writeBack = cfg.isWriteBackEnabled();
pageSize = cfg.getPreferredMemoryPageSize();
Language lang = cfg.getLanguage();
if (!(lang instanceof SleighLanguage)) {
throw new IllegalArgumentException("Invalid configuartion language [" +
lang.getLanguageID() + "]: only Sleigh languages are supported by emulator");
}
// TODO: The way this is currently done, we are unable to emulate within overlay spaces
// The addrFactory should be obtained memState which is a reversal
// When a program load image is used the addrFactory should come from the program and
// not the language. Things may also get complex in terms of handling loads/stores and
// flow associated with overlays.
language = (SleighLanguage) lang;
addrFactory = lang.getAddressFactory();
EmulatorLoadData load = cfg.getLoadData();
loadImage.addProvider(load.getMemoryLoadImage(), load.getView());
mstate = load.getInitialRegisterState();
initMemState(mstate);
breakTable = new BreakTableCallBack(language);
emulator = new Emulate(language, memState, breakTable);
try {
setExecuteAddress(initialPC);
}
catch (LowlevelError lle) {
Msg.warn(this, "pc is unmappable -- no execution possible");
}
}
/**
* Get the page size to use with a specific AddressSpace. The page containers (MemoryBank)
* assume page size is always power of 2. Any address space is assigned at least 8-bits of
* addressable locations, so at the very least, the size is divisible by 256. Starting with this
* minimum, this method finds the power of 2 that is closest to the preferred page size (pageSize)
* but that still divides the size of the space.
* @param space is the specific AddressSpace
* @return the page size to use
*/
private int getValidPageSize(AddressSpace space) {
int ps = 256; // Minimum page size supported
long spaceSize = space.getMaxAddress().getOffset() + 1; // Number of bytes in the space (0 if 2^64 bytes)
if ((spaceSize & 0xff) != 0) {
Msg.warn(this, "Emulator using page size of 256 bytes for " + space.getName() +
" which is NOT a multiple of 256");
return ps;
}
spaceSize >>>= 8; // Divide required size by 256 (evenly)
while (ps < pageSize) { // If current page size is smaller than preferred page size
if ((spaceSize & 1) != 0) {
break; // a bigger page size does not divide the space size evenly, so use current size
}
ps <<= 1; // Bump up current page size to next power of 2
spaceSize >>>= 1; // Divide (evenly) by 2
}
return ps;
}
private void initMemState(RegisterState rstate) {
memState = new FilteredMemoryState(language);
for (AddressSpace space : addrFactory.getPhysicalSpaces()) {
if (!space.isLoadedMemorySpace()) {
continue;
}
FilteredMemoryPageOverlay ramBank = getMemoryBank(space, getValidPageSize(space));
memState.setMemoryBank(ramBank);
}
AddressSpace registerSpace = addrFactory.getRegisterSpace();
registerState = new FilteredRegisterBank(registerSpace, pageSize, rstate, language,
writeBack, faultHandler);
memState.setMemoryBank(registerState);
initRegisters(false);
}
public MemoryState cloneMemory() {
MemoryState newMemState = new FilteredMemoryState(language);
for (AddressSpace space : addrFactory.getPhysicalSpaces()) {
if (!space.isLoadedMemorySpace()) {
continue;
}
FilteredMemoryPageOverlay ramBank = getMemoryBank(space, getValidPageSize(space));
newMemState.setMemoryBank(ramBank);
}
return newMemState;
}
public FilteredMemoryPageOverlay getMemoryBank(AddressSpace space, int ps) {
MemoryImage image =
new MemoryImage(space, language.isBigEndian(), ps, loadImage, faultHandler);
return new FilteredMemoryPageOverlay(space, image, writeBack);
}
/**
* Initialize memory state using the initial register state. If restore is true,
* only those registers within the register space which have been modified will
* be reported and restored to their initial state.
* @param restore if true restore modified registers within the register space only
*/
private void initRegisters(boolean restore) {
DataConverter conv = DataConverter.getInstance(language.isBigEndian());
Set<String> keys = mstate.getKeys();
for (String key : keys) {
List<byte[]> vals = mstate.getVals(key);
List<Boolean> initiailizedVals = mstate.isInitialized(key);
for (int i = 0; i < vals.size(); i++) {
String useKey = "";
if (key.equals("GDTR") || key.equals("IDTR") || key.equals("LDTR")) {
if (i == 0) {
useKey = key + "_Limit";
}
if (i == 1) {
useKey = key + "_Address";
}
}
else if (key.equals("S.base")) {
Integer lval = conv.getInt(vals.get(i));
if (lval != 0 && i < vals.size() - 1) {
useKey = "FS_OFFSET"; // Colossal hack
memState.setValue("FS", (i + 2) * 0x8);
}
}
else {
useKey = (vals.size() > 1) ? key + i : key;
}
Register register = language.getRegister(useKey);
if (register == null) {
useKey = useKey.toUpperCase();
register = language.getRegister(useKey);
}
if (register != null) {
if (restore && !register.getAddress().isRegisterAddress()) {
continue; // only restore registers within register space
}
byte[] valBytes = vals.get(i);
boolean initializedValue = initiailizedVals.get(i);
Address regAddr = register.getAddress();
if (restore) {
byte[] curVal = new byte[valBytes.length];
memState.getChunk(curVal, regAddr.getAddressSpace(), regAddr.getOffset(),
register.getMinimumByteSize(), false);
if (Arrays.equals(curVal, valBytes)) {
continue;
}
System.out.println(
"resetRegisters : " + useKey + "=" + dumpBytesAsSingleValue(valBytes) +
"->" + dumpBytesAsSingleValue(curVal));
}
memState.setChunk(valBytes, regAddr.getAddressSpace(), regAddr.getOffset(),
register.getMinimumByteSize());
if (!initializedValue) {
memState.setInitialized(false, regAddr.getAddressSpace(),
regAddr.getOffset(), register.getMinimumByteSize());
}
if (register.isProgramCounter() ||
register.getName().equalsIgnoreCase(pcName)) {
initialPC = conv.getValue(valBytes, valBytes.length);
}
}
}
}
}
private String dumpBytesAsSingleValue(byte[] bytes) {
StringBuffer buf = new StringBuffer("0x");
if (language.isBigEndian()) {
for (byte b : bytes) {
String byteStr = Integer.toHexString(b & 0xff);
if (byteStr.length() == 1) {
buf.append('0');
}
buf.append(byteStr);
}
}
else {
for (int i = bytes.length - 1; i >= 0; i--) {
String byteStr = Integer.toHexString(bytes[i] & 0xff);
if (byteStr.length() == 1) {
buf.append('0');
}
buf.append(byteStr);
}
}
return buf.toString();
}
public void dispose() {
emuHalt = true;
emulator.dispose();
if (writeBack) {
initRegisters(true);
mstate.dispose();
}
loadImage.dispose();
}
public Address genAddress(String addr) {
return addrFactory.getDefaultAddressSpace().getAddress(NumericUtilities.parseHexLong(addr));
}
public long getPC() {
return memState.getValue(pcName);
}
public String getPCRegisterName() {
return pcName;
}
public MemoryState getMemState() {
return memState;
}
public FilteredMemoryState getFilteredMemState() {
return memState;
}
public void addMemoryAccessFilter(MemoryAccessFilter filter) {
filter.addFilter(this);
}
public BreakTableCallBack getBreakTable() {
return breakTable;
}
public void setExecuteAddress(long addressableWordOffset) {
AddressSpace space = addrFactory.getDefaultAddressSpace();
Address address = space.getTruncatedAddress(addressableWordOffset, true);
emulator.setExecuteAddress(address);
}
public Address getExecuteAddress() {
return emulator.getExecuteAddress();
}
public Address getLastExecuteAddress() {
return emulator.getLastExecuteAddress();
}
public Set<String> getDefaultContext() {
return mstate.getKeys();
}
public void setHalt(boolean halt) {
emuHalt = halt;
}
public boolean getHalt() {
return emuHalt;
}
public void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
throws CancelledException, LowlevelError, InstructionDecodeException {
isExecuting = true;
try {
emulator.executeInstruction(stopAtBreakpoint, monitor);
instExecuted++;
}
finally {
isExecuting = false;
}
}
/**
* @return true if halted at a breakpoint
*/
public boolean isAtBreakpoint() {
return getHalt() && emulator.getExecutionState() == EmulateExecutionState.BREAKPOINT;
}
/**
* @return emulator execution state. This can be useful within a memory fault handler to
* determine if a memory read was associated with instruction parsing (i.e., PCODE_EMIT) or
* normal an actual emulated read (i.e., EXECUTE).
*/
public EmulateExecutionState getEmulateExecutionState() {
return emulator.getExecutionState();
}
/**
* @return true if emulator is busy executing an instruction
*/
public boolean isExecuting() {
return isExecuting;
}
public SleighLanguage getLanguage() {
return language;
}
/**
* Disassemble from the current execute address
* @param count number of contiguous instructions to disassemble
* @return list of instructions
*/
public List<String> disassemble(Integer count) {
if (!emuHalt || isExecuting) {
throw new IllegalStateException("disassembly not allowed while emulator is executing");
}
// TODO: This can provide bad disassembly if reliant on future context state (e.g., end of loop)
List<String> disassembly = new ArrayList<>();
EmulateDisassemblerContext disassemblerContext = emulator.getNewDisassemblerContext();
Address addr = getExecuteAddress();
EmulateMemoryStateBuffer memBuffer = new EmulateMemoryStateBuffer(memState, addr);
Disassembler disassembler = Disassembler.getDisassembler(language, addrFactory,
TaskMonitor.DUMMY, null);
boolean stopOnError = false;
while (count > 0 && !stopOnError) {
memBuffer.setAddress(addr);
disassemblerContext.setCurrentAddress(addr);
InstructionBlock block = disassembler.pseudoDisassembleBlock(memBuffer,
disassemblerContext.getCurrentContextRegisterValue(), count);
if (block.hasInstructionError() && count > block.getInstructionCount()) {
InstructionError instructionError = block.getInstructionConflict();
Msg.error(this,
"Target disassembler error at " + instructionError.getConflictAddress() + ": " +
instructionError.getConflictMessage());
stopOnError = true;
}
Instruction lastInstr = null;
Iterator<Instruction> iterator = block.iterator();
while (iterator.hasNext() && count != 0) {
Instruction instr = iterator.next();
disassembly.add(instr.getAddressString(false, true) + " " + instr.toString());
lastInstr = instr;
--count;
}
try {
addr = lastInstr.getAddress().addNoWrap(lastInstr.getLength());
}
catch (Exception e) {
count = 0;
}
}
return disassembly;
}
public int getTickCount() {
return instExecuted;
}
/**
* Returns the current context register value. The context value returned reflects
* its state when the previously executed instruction was
* parsed/executed. The context value returned will feed into the next
* instruction to be parsed with its non-flowing bits cleared and
* any future context state merged in.
* @return context as a RegisterValue object
*/
public RegisterValue getContextRegisterValue() {
return emulator.getContextRegisterValue();
}
/**
* Sets the context register value at the current execute address.
* The Emulator should not be running when this method is invoked.
* Only flowing context bits should be set, as non-flowing bits
* will be cleared prior to parsing on instruction. In addition,
* any future context state set by the pcode emitter will
* take precedence over context set using this method. This method
* is primarily intended to be used to establish the initial
* context state.
* @param regValue is the value to set context to
*/
public void setContextRegisterValue(RegisterValue regValue) {
emulator.setContextRegisterValue(regValue);
}
/**
* Add memory load image provider
* @param provider memory load image provider
* @param view memory region which corresponds to provider
*/
public void addProvider(MemoryLoadImage provider, AddressSetView view) {
loadImage.addProvider(provider, view);
}
}

View file

@ -0,0 +1,48 @@
/* ###
* 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.app.emulator;
import ghidra.app.emulator.memory.EmulatorLoadData;
import ghidra.pcode.memstate.MemoryFaultHandler;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
public interface EmulatorConfiguration {
Language getLanguage();
EmulatorLoadData getLoadData();
MemoryFaultHandler getMemoryFaultHandler();
default boolean isWriteBackEnabled() {
return false;
}
default int getPreferredMemoryPageSize() {
return 0x1000;
}
default String getProgramCounterName() {
Language lang = getLanguage();
Register pcReg = lang.getProgramCounter();
if (pcReg == null) {
throw new IllegalStateException(
"Language has not defined Program Counter Register: " + lang.getLanguageID());
}
return pcReg.getName();
}
}

View file

@ -0,0 +1,718 @@
/* ###
* 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.app.emulator;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import ghidra.app.emulator.memory.*;
import ghidra.app.emulator.state.DumpMiscState;
import ghidra.app.emulator.state.RegisterState;
import ghidra.framework.store.LockException;
import ghidra.pcode.emulate.BreakCallBack;
import ghidra.pcode.emulate.EmulateExecutionState;
import ghidra.pcode.memstate.MemoryFaultHandler;
import ghidra.pcode.memstate.MemoryState;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.mem.MemoryConflictException;
import ghidra.util.DataConverter;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration {
private final Program program;
private final Emulator emulator;
private Register stackPtrReg;
private AddressSpace stackMemorySpace;
private String lastError;
private MemoryWriteTracker memoryWriteTracker;
private MemoryFaultHandler faultHandler;
private DataConverter converter;
private BreakCallBack addressBreak = new BreakCallBack() {
@Override
public boolean addressCallback(Address addr) {
emulator.setHalt(true);
return true;
}
};
public EmulatorHelper(Program program) {
this.program = program;
stackPtrReg = program.getCompilerSpec().getStackPointer();
stackMemorySpace = program.getCompilerSpec().getStackBaseSpace();
emulator = new Emulator(this);
converter = DataConverter.getInstance(program.getMemory().isBigEndian());
}
public void dispose() {
emulator.dispose();
if (memoryWriteTracker != null) {
memoryWriteTracker.dispose();
memoryWriteTracker = null;
}
}
@Override
public MemoryFaultHandler getMemoryFaultHandler() {
return this;
}
@Override
public EmulatorLoadData getLoadData() {
return new EmulatorLoadData() {
@Override
public MemoryLoadImage getMemoryLoadImage() {
return new ProgramMappedLoadImage(
new ProgramMappedMemory(program, EmulatorHelper.this));
}
@Override
public RegisterState getInitialRegisterState() {
return new DumpMiscState(getLanguage());
}
};
}
@Override
public Language getLanguage() {
return program.getLanguage();
}
public Program getProgram() {
return program;
}
/**
* Get Program Counter (PC) register defined by applicable processor specification
* @return Program Counter register
*/
public Register getPCRegister() {
return program.getLanguage().getProgramCounter();
}
/**
* Get Stack Pointer register defined by applicable compiler specification
* @return Stack Pointer register
*/
public Register getStackPointerRegister() {
return stackPtrReg;
}
/**
* Provides ability to install a low-level memory fault handler.
* The handler methods should generally return 'false' to allow
* the default handler to generate the appropriate target error.
* Within the fault handler, the EmulateExecutionState can be used
* to distinguish the pcode-emit state and the actual execution state
* since an attempt to execute an instruction at an uninitialized
* memory location will cause an uninitializedRead during the PCODE_EMIT
* state.
* @param handler memory fault handler.
*/
public void setMemoryFaultHandler(MemoryFaultHandler handler) {
faultHandler = handler;
}
/**
* @return the low-level emulator execution state
*/
public EmulateExecutionState getEmulateExecutionState() {
return emulator.getEmulateExecutionState();
}
private Register getRegister(String regName) throws IllegalArgumentException {
Register reg = program.getRegister(regName);
if (reg == null) {
throw new IllegalArgumentException("Undefined register: " + regName);
}
return reg;
}
public BigInteger readRegister(Register reg) {
if (reg.isProcessorContext()) {
RegisterValue contextRegisterValue = emulator.getContextRegisterValue();
if (!reg.equals(contextRegisterValue.getRegister())) {
contextRegisterValue = contextRegisterValue.getRegisterValue(reg);
}
return contextRegisterValue.getSignedValueIgnoreMask();
}
if (reg.getName().equals(emulator.getPCRegisterName())) {
return BigInteger.valueOf(emulator.getPC());
}
return emulator.getMemState().getBigInteger(reg);
}
public BigInteger readRegister(String regName) {
Register reg = getRegister(regName);
if (reg == null) {
throw new IllegalArgumentException("Undefined register: " + regName);
}
return readRegister(reg);
}
public void writeRegister(Register reg, long value) {
writeRegister(reg, BigInteger.valueOf(value));
}
public void writeRegister(String regName, long value) {
writeRegister(regName, BigInteger.valueOf(value));
}
public void writeRegister(Register reg, BigInteger value) {
if (reg.isProcessorContext()) {
RegisterValue contextRegisterValue = new RegisterValue(reg, value);
RegisterValue existingRegisterValue = emulator.getContextRegisterValue();
if (!reg.equals(existingRegisterValue.getRegister())) {
contextRegisterValue = existingRegisterValue.combineValues(contextRegisterValue);
}
emulator.setContextRegisterValue(contextRegisterValue);
return;
}
emulator.getMemState().setValue(reg, value);
if (reg.getName().equals(emulator.getPCRegisterName())) {
emulator.setExecuteAddress(value.longValue());
}
}
public void writeRegister(String regName, BigInteger value) {
Register reg = getRegister(regName);
if (reg == null) {
throw new IllegalArgumentException("Undefined register: " + regName);
}
writeRegister(reg, value);
}
/**
* Read string from memory state.
* @param addr memory address
* @param maxLength limit string read to this length. If return string is
* truncated, "..." will be appended.
* @return string read from memory state
*/
public String readNullTerminatedString(Address addr, int maxLength) {
int len = 0;
byte[] bytes = new byte[maxLength];
byte b = 0;
while (len < maxLength && (b = readMemoryByte(addr)) != 0) {
bytes[len++] = b;
addr = addr.next();
}
String str = new String(bytes, 0, len);
if (b != 0) {
str += "..."; // indicate string truncation
}
return str;
}
public byte readMemoryByte(Address addr) {
byte[] value = readMemory(addr, 1);
return value[0];
}
public byte[] readMemory(Address addr, int length) {
byte[] res = new byte[length];
int len = emulator.getMemState().getChunk(res, addr.getAddressSpace(), addr.getOffset(),
length, false);
if (len == 0) {
Msg.error(this, "Failed to read memory from Emulator at: " + addr);
return null;
}
else if (len < length) {
Msg.error(this,
"Only " + len + " of " + length + " bytes read memory from Emulator at: " + addr);
}
return res;
}
public void writeMemory(Address addr, byte[] bytes) {
emulator.getMemState().setChunk(bytes, addr.getAddressSpace(), addr.getOffset(),
bytes.length);
}
public void writeMemoryValue(Address addr, int size, long value) {
emulator.getMemState().setValue(addr.getAddressSpace(), addr.getOffset(), size, value);
}
/**
* Read a stack value from the memory state.
* @param relativeOffset offset relative to current stack pointer
* @param size data size in bytes
* @param signed true if value read is signed, false if unsigned
* @return value
* @throws Exception error occurs reading stack pointer
*/
public BigInteger readStackValue(int relativeOffset, int size, boolean signed)
throws Exception {
long offset = readRegister(stackPtrReg).longValue() + relativeOffset;
byte[] bytes = readMemory(stackMemorySpace.getAddress(offset), size);
return converter.getBigInteger(bytes, size, signed);
}
/**
* Write a value onto the stack
* @param relativeOffset offset relative to current stack pointer
* @param size data size in bytes
* @param value
* @throws Exception error occurs reading stack pointer
*/
public void writeStackValue(int relativeOffset, int size, long value) throws Exception {
long offset = readRegister(stackPtrReg).longValue() + relativeOffset;
byte[] bytes = new byte[size];
converter.getBytes(value, size, bytes, 0);
writeMemory(stackMemorySpace.getAddress(offset), bytes);
}
/**
* Write a value onto the stack
* @param relativeOffset offset relative to current stack pointer
* @param size data size in bytes
* @param value
* @throws Exception error occurs reading stack pointer
*/
public void writeStackValue(int relativeOffset, int size, BigInteger value) throws Exception {
// TODO: verify that sign byte is not added to size of bytes
long offset = readRegister(stackPtrReg).longValue() + relativeOffset;
byte[] bytes = converter.getBytes(value, size);
writeMemory(stackMemorySpace.getAddress(offset), bytes);
}
/**
* Establish breakpoint
* @param addr memory address for new breakpoint
*/
public void setBreakpoint(Address addr) {
emulator.getBreakTable().registerAddressCallback(addr, addressBreak);
}
/**
* Clear breakpoint
* @param addr memory address for breakpoint to be cleared
*/
public void clearBreakpoint(Address addr) {
emulator.getBreakTable().unregisterAddressCallback(addr);
}
/**
* Set current context register value.
* Keep in mind that any non-flowing context values will be stripped.
* @param ctxRegValue
*/
public void setContextRegister(RegisterValue ctxRegValue) {
emulator.setContextRegisterValue(ctxRegValue);
}
/**
* Set current context register value.
* Keep in mind that any non-flowing context values will be stripped.
* @param ctxReg context register
* @param value context value
*/
public void setContextRegister(Register ctxReg, BigInteger value) {
emulator.setContextRegisterValue(new RegisterValue(ctxReg, value));
}
/**
* Get the current context register value
* @return context register value or null if not set or unknown
*/
public RegisterValue getContextRegister() {
return emulator.getContextRegisterValue();
}
/**
* Register callback for language defined pcodeop (call other).
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
* when supplied by the Processor module.
* @param pcodeOpName the name of the pcode op
* @param callback the callback to register
*/
public void registerCallOtherCallback(String pcodeOpName, BreakCallBack callback) {
emulator.getBreakTable().registerPcodeCallback(pcodeOpName, callback);
}
/**
* Register default callback for language defined pcodeops (call other).
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
* when supplied by the Processor module.
* @param callback the default callback to register
*/
public void registerDefaultCallOtherCallback(BreakCallBack callback) {
emulator.getBreakTable().registerPcodeCallback("*", callback);
}
/**
* Unregister callback for language defined pcodeop (call other).
* @param pcodeOpName the name of the pcode op
*/
public void unregisterCallOtherCallback(String pcodeOpName) {
emulator.getBreakTable().unregisterPcodeCallback(pcodeOpName);
}
/**
* Unregister default callback for language defined pcodeops (call other).
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
* when supplied by the Processor module.
*/
public void unregisterDefaultCallOtherCallback() {
emulator.getBreakTable().unregisterPcodeCallback("*");
}
/**
* Get current execution address
* @return current execution address
*/
public Address getExecutionAddress() {
return emulator.getExecuteAddress();
}
/**
* Start execution at the specified address using the initial context specified.
* Method will block until execution stops. This method will initialize context
* register based upon the program stored context if not already done. In addition,
* both general register value and the context register may be further modified
* via the context parameter if specified.
* @param addr initial program address
* @param context optional context settings which override current program context
* @param monitor
* @return true if execution completes without error (i.e., is at breakpoint)
* @throws CancelledException if execution cancelled via monitor
*/
public boolean run(Address addr, ProcessorContext context, TaskMonitor monitor)
throws CancelledException {
if (emulator.isExecuting()) {
throw new IllegalStateException("Emulator is already running");
}
// Initialize context
ProgramContext programContext = program.getProgramContext();
Register baseContextRegister = programContext.getBaseContextRegister();
RegisterValue contextRegValue = null;
boolean mustSetContextReg = false;
if (baseContextRegister != null) {
contextRegValue = getContextRegister();
if (contextRegValue == null) {
contextRegValue = programContext.getRegisterValue(baseContextRegister, addr);
mustSetContextReg = (contextRegValue != null);
}
}
if (context != null) {
for (Register reg : context.getRegisters()) {
// skip non-base registers
if (reg.isBaseRegister() && context.hasValue(reg)) {
RegisterValue registerValue = context.getRegisterValue(reg);
if (reg.isProcessorContext()) {
if (contextRegValue != null) {
contextRegValue = contextRegValue.combineValues(registerValue);
}
else {
contextRegValue = registerValue;
}
mustSetContextReg = true;
}
else {
BigInteger value = registerValue.getUnsignedValueIgnoreMask();
writeRegister(reg, value);
}
}
}
}
long pcValue = addr.getAddressableWordOffset();
emulator.setExecuteAddress(pcValue);
if (mustSetContextReg) {
setContextRegister(contextRegValue);
}
continueExecution(monitor);
return emulator.isAtBreakpoint();
}
/**
* Continue execution from the current execution address.
* No adjustment will be made to the context beyond the normal
* context flow behavior defined by the language.
* Method will block until execution stops.
* @param monitor
* @return true if execution completes without error (i.e., is at breakpoint)
* @throws CancelledException if execution cancelled via monitor
*/
public synchronized boolean run(TaskMonitor monitor) throws CancelledException {
if (emulator.isExecuting()) {
throw new IllegalStateException("Emulator is already running");
}
continueExecution(monitor);
return emulator.isAtBreakpoint();
}
/**
* Continue execution and block until either a breakpoint hits or error occurs.
* @throws CancelledException if execution was cancelled
*/
private void continueExecution(TaskMonitor monitor) throws CancelledException {
emulator.setHalt(false);
do {
executeInstruction(true, monitor);
}
while (!emulator.getHalt());
}
/**
* Execute instruction at current address
* @param stopAtBreakpoint if true and breakpoint hits at current execution address
* execution will halt without executing instruction.
* @throws CancelledException if execution was cancelled
*/
private void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
throws CancelledException {
lastError = null;
try {
if (emulator.getLastExecuteAddress() == null) {
setProcessorContext();
}
emulator.executeInstruction(stopAtBreakpoint, monitor);
}
catch (Throwable t) {
// TODO: need to enumerate errors better !!
lastError = t.getMessage();
if (lastError == null) {
lastError = t.toString();
}
emulator.setHalt(true); // force execution to stop
if (t instanceof CancelledException) {
throw (CancelledException) t;
}
}
}
/**
* Used when the emulator has had the execution address changed to
* make sure it has a context consistent with the program context
* if there is one.
*/
private void setProcessorContext() {
// this assumes you have set the emulation address
// the emu will have cleared the context for the new address
RegisterValue contextRegisterValue = emulator.getContextRegisterValue();
if (contextRegisterValue != null) {
return;
}
Address executeAddress = emulator.getExecuteAddress();
Instruction instructionAt = program.getListing().getInstructionAt(executeAddress);
if (instructionAt != null) {
RegisterValue disassemblyContext =
instructionAt.getRegisterValue(instructionAt.getBaseContextRegister());
emulator.setContextRegisterValue(disassemblyContext);
}
}
/**
* @return last error message associated with execution failure
*/
public String getLastError() {
return lastError;
}
/**
* Step execution one instruction which may consist of multiple
* pcode operations. No adjustment will be made to the context beyond the normal
* context flow behavior defined by the language.
* Method will block until execution stops.
* @return true if execution completes without error
* @throws CancelledException if execution cancelled via monitor
*/
public synchronized boolean step(TaskMonitor monitor) throws CancelledException {
executeInstruction(true, monitor);
return lastError == null;
}
/**
* Create a new initialized memory block using the current emulator memory state
* @param name block name
* @param start start address of the block
* @param length the size of the block
* @param overlay if true, the block will be created as an OVERLAY which means that a new
* overlay address space will be created and the block will have a starting address at the same
* offset as the given start address parameter, but in the new address space.
* @param monitor
* @return new memory block
* @throws LockException if exclusive lock not in place (see haveLock())
* @throws MemoryConflictException if the new block overlaps with a
* previous block
* @throws AddressOverflowException if the start is beyond the
* address space
* @throws CancelledException user cancelled operation
* @throws DuplicateNameException
*/
public MemoryBlock createMemoryBlockFromMemoryState(String name, final Address start,
final int length, boolean overlay, TaskMonitor monitor) throws MemoryConflictException,
AddressOverflowException, CancelledException, LockException, DuplicateNameException {
if (emulator.isExecuting()) {
throw new IllegalStateException("Emulator must be paused to access memory state");
}
InputStream memStateStream = new InputStream() {
private MemoryState memState = emulator.getMemState();
private byte[] buffer = new byte[1024];
private long nextBufferOffset = start.getOffset();
private int bytesRemaining = length;
private int bufferPos = buffer.length;
@Override
public int read() throws IOException {
if (bytesRemaining <= 0) {
return -1;
}
if (bufferPos == buffer.length) {
int size = Math.min(buffer.length, bytesRemaining);
memState.getChunk(buffer, start.getAddressSpace(), nextBufferOffset, size,
false);
nextBufferOffset += buffer.length;
bufferPos = 0;
}
byte b = buffer[bufferPos++];
--bytesRemaining;
return b;
}
};
MemoryBlock block;
boolean success = false;
int txId = program.startTransaction("Create Memory Block");
try {
block = program.getMemory().createInitializedBlock(name, start, memStateStream, length,
monitor, overlay);
success = true;
}
finally {
program.endTransaction(txId, success);
}
return block;
}
/**
* Enable/Disable tracking of memory writes in the form of an
* address set.
* @param enable
*/
public void enableMemoryWriteTracking(boolean enable) {
if (!enable) {
if (memoryWriteTracker != null) {
memoryWriteTracker.dispose();
memoryWriteTracker = null;
}
return;
}
memoryWriteTracker = new MemoryWriteTracker();
emulator.addMemoryAccessFilter(memoryWriteTracker);
}
/**
* @return address set of memory locations written by the emulator
* if memory write tracking is enabled, otherwise null is returned.
* The address set returned will continue to be updated unless
* memory write tracking becomes disabled.
*/
public AddressSetView getTrackedMemoryWriteSet() {
if (memoryWriteTracker != null) {
return memoryWriteTracker.writeSet;
}
return null;
}
private class MemoryWriteTracker extends MemoryAccessFilter {
AddressSet writeSet = new AddressSet();
@Override
protected void processRead(AddressSpace spc, long off, int size, byte[] values) {
// do nothing
}
@Override
protected void processWrite(AddressSpace spc, long off, int size, byte[] values) {
AddressRange range =
new AddressRangeImpl(spc.getAddress(off), spc.getAddress(off + size - 1));
writeSet.add(range);
}
}
@Override
public boolean unknownAddress(Address address, boolean write) {
if (faultHandler != null) {
return faultHandler.unknownAddress(address, write);
}
Address pc = emulator.getExecuteAddress();
String access = write ? "written" : "read";
Msg.warn(this, "Unknown address " + access + " at " + pc + ": " + address);
return false;
}
@Override
public boolean uninitializedRead(Address address, int size, byte[] buf, int bufOffset) {
if (faultHandler != null) {
return faultHandler.uninitializedRead(address, size, buf, bufOffset);
}
if (emulator.getEmulateExecutionState() == EmulateExecutionState.INSTRUCTION_DECODE) {
return false;
}
Address pc = emulator.getExecuteAddress();
Register reg = program.getRegister(address, size);
if (reg != null) {
Msg.warn(this, "Uninitialized register read at " + pc + ": " + reg);
return true;
}
Msg.warn(this,
"Uninitialized memory read at " + pc + ": " + address.toString(true) + ":" + size);
return true;
}
public Emulator getEmulator() {
return emulator;
}
}

View file

@ -0,0 +1,66 @@
/* ###
* 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.app.emulator;
import ghidra.pcode.memstate.MemoryState;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
class FilteredMemoryState extends MemoryState {
private MemoryAccessFilter filter;
private boolean filterEnabled = true; // used to prevent filtering filter queries
FilteredMemoryState(Language lang) {
super(lang);
}
@Override
public int getChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) {
int readLen = super.getChunk(res, spc, off, size, stopOnUnintialized);
if (filterEnabled && filter != null) {
filterEnabled = false;
try {
filter.filterRead(spc, off, readLen, res);
}
finally {
filterEnabled = true;
}
}
return readLen;
}
@Override
public void setChunk(byte[] res, AddressSpace spc, long off, int size) {
super.setChunk(res, spc, off, size);
if (filterEnabled && filter != null) {
filterEnabled = false;
try {
filter.filterWrite(spc, off, size, res);
}
finally {
filterEnabled = true;
}
}
}
MemoryAccessFilter setFilter(MemoryAccessFilter filter) {
MemoryAccessFilter oldFilter = this.filter;
this.filter = filter;
return oldFilter;
}
}

View file

@ -0,0 +1,85 @@
/* ###
* 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.app.emulator;
import ghidra.program.model.address.AddressSpace;
public abstract class MemoryAccessFilter {
private MemoryAccessFilter prevFilter;
private MemoryAccessFilter nextFilter;
protected Emulator emu;
private boolean filterOnExecutionOnly = true;
final void filterRead(AddressSpace spc, long off, int size, byte [] values) {
if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries
processRead(spc, off, size, values);
if (nextFilter != null) {
nextFilter.filterRead(spc, off, size, values);
}
}
protected abstract void processRead(AddressSpace spc, long off, int size, byte[] values);
final void filterWrite(AddressSpace spc, long off, int size, byte [] values) {
if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries
processWrite(spc, off, size, values);
if (nextFilter != null) {
nextFilter.filterWrite(spc, off, size, values);
}
}
protected abstract void processWrite(AddressSpace spc, long off, int size, byte[] values);
final void addFilter(Emulator emu) {
this.emu = emu;
nextFilter = emu.getFilteredMemState().setFilter(this);
if (nextFilter != null) {
nextFilter.prevFilter = this;
}
}
/**
* Dispose this filter which will cause it to be removed from the memory state.
* If overriden, be sure to invoke super.dispose().
*/
public void dispose() {
if (nextFilter != null) {
nextFilter.prevFilter = prevFilter;
}
if (prevFilter != null) {
prevFilter.nextFilter = nextFilter;
}
else {
emu.getFilteredMemState().setFilter(nextFilter);
}
}
public boolean filterOnExecutionOnly() {
return filterOnExecutionOnly;
}
public void setFilterOnExecutionOnly(boolean filterOnExecutionOnly) {
this.filterOnExecutionOnly = filterOnExecutionOnly;
}
// public void compare(String id);
// public void clear();
// public void updateFlags(String id);
}

View file

@ -0,0 +1,75 @@
/* ###
* 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.app.emulator.memory;
import java.util.*;
import ghidra.pcode.memstate.MemoryPage;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
public class CompositeLoadImage implements MemoryLoadImage {
private List<MemoryLoadImage> providers = new ArrayList<MemoryLoadImage>();
private HashMap<MemoryLoadImage, AddressSetView> addrSets =
new HashMap<MemoryLoadImage, AddressSetView>();
public void addProvider(MemoryLoadImage provider, AddressSetView view) {
if (view == null) {
providers.add(providers.size(), provider);
}
else {
providers.add(0, provider);
}
addrSets.put(provider, view);
}
@Override
public byte[] loadFill(byte[] buf, int size, Address addr, int bufOffset,
boolean generateInitializedMask) {
// Warning: this implementation assumes that the memory page (specified by addr and size)
// will only correspond to a single program image.
Address endAddr = addr.add(size - 1);
for (MemoryLoadImage provider : providers) {
AddressSetView view = addrSets.get(provider);
if (view == null || view.intersects(addr, endAddr)) {
return provider.loadFill(buf, size, addr, bufOffset, generateInitializedMask);
}
}
return generateInitializedMask ? MemoryPage.getInitializedMask(size, false) : null;
}
@Override
public void writeBack(byte[] bytes, int size, Address addr, int offset) {
// Warning: this implementation assumes that the memory page (specified by addr and size)
// will only correspond to a single program image.
Address endAddr = addr.add(size - 1);
for (MemoryLoadImage provider : providers) {
AddressSetView view = addrSets.get(provider);
if (view == null || view.intersects(addr, endAddr)) {
provider.writeBack(bytes, size, addr, offset);
}
}
}
@Override
public void dispose() {
for (MemoryLoadImage provider : providers) {
provider.dispose();
}
}
}

View file

@ -0,0 +1,31 @@
/* ###
* 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.app.emulator.memory;
import ghidra.app.emulator.state.RegisterState;
import ghidra.program.model.address.AddressSetView;
public interface EmulatorLoadData {
public MemoryLoadImage getMemoryLoadImage();
public RegisterState getInitialRegisterState();
public default AddressSetView getView() {
return null;
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.app.emulator.memory;
import ghidra.pcode.memstate.*;
import ghidra.program.model.address.AddressSpace;
/// A kind of MemoryBank which retrieves its data from an underlying LoadImage
///
/// Any bytes requested on the bank which lie in the LoadImage are retrieved from
/// the LoadImage. Other addresses in the space are filled in with zero.
/// This bank cannot be written to.
public class MemoryImage extends MemoryBank {
private MemoryLoadImage loader; // The underlying LoadImage
/// A MemoryImage needs everything a basic memory bank needs and is needs to know
/// the underlying LoadImage object to forward read requests to.
/// \param spc is the address space associated with the memory bank
/// \param ws is the number of bytes in the preferred wordsize (must be power of 2)
/// \param ps is the number of bytes in a page (must be power of 2)
/// \param ld is the underlying LoadImage
public MemoryImage(AddressSpace spc, boolean isBigEndian, int ps, MemoryLoadImage ld,
MemoryFaultHandler faultHandler) {
super(spc, isBigEndian, ps, faultHandler);
loader = ld;
}
/// Retrieve an aligned page from the bank. First an attempt is made to retrieve the
/// page from the LoadImage, which may do its own zero filling. If the attempt fails, the
/// page is entirely filled in with zeros.
@Override
public MemoryPage getPage(long addr) {
MemoryPage page = new MemoryPage(getPageSize());
// Assume that -addr- is page aligned
AddressSpace spc = getSpace();
byte[] maskUpdate =
loader.loadFill(page.data, getPageSize(), spc.getAddress(addr), 0, true);
page.setInitialized(0, getPageSize(), maskUpdate);
return page;
}
@Override
protected void setPage(long addr, byte[] val, int skip, int size, int bufOffset) {
AddressSpace spc = getSpace();
loader.writeBack(val, size, spc.getAddress(addr), bufOffset);
}
@Override
protected void setPageInitialized(long addr, boolean initialized, int skip, int size,
int bufOffset) {
// unsupported
}
}

View file

@ -0,0 +1,27 @@
/* ###
* 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.app.emulator.memory;
import ghidra.pcode.loadimage.LoadImage;
import ghidra.program.model.address.Address;
public interface MemoryLoadImage extends LoadImage {
public void writeBack(byte[] bytes, int size, Address addr, int offset);
public void dispose();
}

View file

@ -0,0 +1,264 @@
/* ###
* 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.app.emulator.memory;
import java.util.Arrays;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.memstate.MemoryFaultHandler;
import ghidra.pcode.memstate.MemoryPage;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
// Derived from ProgramMappedMemory
public class ProgramLoadImage {
private Program program;
private AddressSetView initializedAddressSet;
private MemoryFaultHandler faultHandler;
public ProgramLoadImage(Program program, MemoryFaultHandler faultHandler) {
this.program = program;
Memory memory = program.getMemory();
initializedAddressSet = memory.getLoadedAndInitializedAddressSet();
for (MemoryBlock block : memory.getBlocks()) {
if (!block.isInitialized() && block.isMapped()) {
initializedAddressSet = addMappedInitializedMemory(block);
}
}
this.faultHandler = faultHandler;
// TODO: consider adding program consumer (would require proper dispose)
}
private AddressSetView addMappedInitializedMemory(MemoryBlock mappedBlock) {
MemoryBlockSourceInfo sourceInfo = mappedBlock.getSourceInfos().get(0); // mapped block has exactly 1 mapped source
if (!sourceInfo.getMappedRange().isPresent()) {
throw new AssertException("Mapped block did not have mapped range!");
}
AddressRange mappedRange = sourceInfo.getMappedRange().get();
Address mapStart = mappedRange.getMinAddress();
Address mapEnd = mappedRange.getMaxAddress();
AddressSet modifiedSet = new AddressSet(initializedAddressSet);
AddressSet mappedAreas = initializedAddressSet.intersectRange(mapStart, mapEnd);
for (AddressRange range : mappedAreas) {
Address start = mappedBlock.getStart().add(range.getMinAddress().subtract(mapStart));
Address end = mappedBlock.getStart().add(range.getMaxAddress().subtract(mapStart));
modifiedSet.add(start, end);
}
return modifiedSet;
}
public void dispose() {
// do nothing
}
// TODO: Need to investigate program write-back transaction issues -
// it could also be very expensive writing memory without some form of write-back cache
public void write(byte[] bytes, int size, Address addr, int offset) {
Memory memory = program.getMemory();
int currentOffset = offset;
int remaining = size;
Address nextAddr = addr;
Address endAddr;
try {
endAddr = addr.addNoWrap(size - 1);
}
catch (AddressOverflowException e) {
throw new LowlevelError(
"Illegal memory write request: " + addr + ", length=" + size + " bytes");
}
while (true) {
int chunkSize = remaining;
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
AddressRange range = it.hasNext() ? it.next() : null;
///
/// Begin change for addressSet changes - wcb
///
if (range == null) {
// nextAddr not in memory and is bigger that any initialized memory
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
break;
}
else if (range.contains(nextAddr)) {
// nextAddr is in memory
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
}
try {
memory.setBytes(nextAddr, bytes, currentOffset, chunkSize);
}
catch (MemoryAccessException e) {
throw new LowlevelError("Unexpected memory write error: " + e.getMessage());
}
}
else {
// nextAddr not in initialized memory, but is less than some initialized range
Address rangeAddr = range.getMinAddress();
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
break;
}
long gapSize = rangeAddr.subtract(nextAddr);
chunkSize = (int) Math.min(gapSize, remaining);
handleWriteFault(bytes, currentOffset, chunkSize, nextAddr);
}
///
/// End change for addressSet changes - wcb
///
if (chunkSize == remaining) {
break; // done
}
// prepare for next chunk
try {
nextAddr = nextAddr.addNoWrap(chunkSize);
}
catch (AddressOverflowException e) {
throw new LowlevelError("Unexpected error: " + e.getMessage());
}
currentOffset += chunkSize;
remaining -= chunkSize;
}
}
private void handleWriteFault(byte[] bytes, int currentOffset, int remaining,
Address nextAddr) {
// TODO: Should we create blocks or convert to initialized as needed ?
}
public byte[] read(byte[] bytes, int size, Address addr, int offset,
boolean generateInitializedMask) {
Memory memory = program.getMemory();
int currentOffset = offset;
int remaining = size;
Address nextAddr = addr;
Address endAddr;
byte[] initializedMask = null;
try {
endAddr = addr.addNoWrap(size - 1);
}
catch (AddressOverflowException e) {
throw new LowlevelError(
"Illegal memory read request: " + addr + ", length=" + size + " bytes");
}
while (true) {
int chunkSize = remaining;
///
/// Begin change for addressSet changes - wcb
///
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
AddressRange range = it.hasNext() ? it.next() : null;
if (range == null) {
if (generateInitializedMask) {
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
remaining, initializedMask);
}
else {
handleReadFault(bytes, currentOffset, remaining, nextAddr);
}
break;
}
else if (range.contains(nextAddr)) {
// nextAddr found in initialized memory
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
}
try {
memory.getBytes(nextAddr, bytes, currentOffset, chunkSize);
}
catch (MemoryAccessException e) {
//throw new LowlevelError("Unexpected memory read error: " + e.getMessage());
Msg.warn(this, "Unexpected memory read error: " + e.getMessage());
}
}
else {
Address rangeAddr = range.getMinAddress();
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
if (generateInitializedMask) {
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
remaining, initializedMask);
}
else {
handleReadFault(bytes, currentOffset, remaining, nextAddr);
}
break;
}
long gapSize = rangeAddr.subtract(nextAddr);
chunkSize = (gapSize > 0) ? (int) Math.min(gapSize, remaining) : remaining;
if (generateInitializedMask) {
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
chunkSize, initializedMask);
}
else {
handleReadFault(bytes, currentOffset, chunkSize, nextAddr);
}
}
///
/// End change for addressSet changes - wcb
///
if (chunkSize == remaining) {
break; // done
}
// prepare for next chunk
try {
nextAddr = nextAddr.addNoWrap(chunkSize);
}
catch (AddressOverflowException e) {
throw new LowlevelError("Unexpected error: " + e.getMessage());
}
currentOffset += chunkSize;
remaining -= chunkSize;
}
return initializedMask;
}
private static byte[] getInitializedMask(int bufsize, int initialOffset,
int uninitializedOffset, int uninitializedSize, byte[] initializedMask) {
if (initializedMask == null) {
initializedMask = MemoryPage.getInitializedMask(bufsize, 0, initialOffset, false);
}
MemoryPage.setUninitialized(initializedMask, uninitializedOffset, uninitializedSize);
return initializedMask;
}
private void handleReadFault(byte[] bytes, int offset, int size, Address addr) {
// NOTE: This can trigger a load from a different external library depending upon the specific fault handler installed
Arrays.fill(bytes, offset, offset + size, (byte) 0);
if (faultHandler != null) {
faultHandler.uninitializedRead(addr, size, bytes, size);
}
}
public AddressSetView getInitializedAddressSet() {
return initializedAddressSet;
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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.app.emulator.memory;
import ghidra.program.model.address.Address;
public class ProgramMappedLoadImage implements MemoryLoadImage {
private ProgramMappedMemory pmm;
//private Language lang;
public ProgramMappedLoadImage(ProgramMappedMemory memory) {
this.pmm = memory;
//this.lang = memory.getProgram().getLanguage();
}
@Override
public byte[] loadFill(byte[] bytes, int size, Address addr, int offset, boolean generateInitializedMask) {
return pmm.read(bytes, size, addr, offset, generateInitializedMask);
// boolean initialized = false;
// for (byte b : bytes) {
// if (b != 0) {
// initialized = true;
// break;
// }
// }
// return generateInitializedMask ? MemoryPage.getInitializedMask(size, initialized) : null;
}
@Override
public void writeBack(byte[] bytes, int size, Address addr, int offset) {
pmm.write(bytes, size, addr, offset);
}
@Override
public void dispose() {
pmm.dispose();
}
}

View file

@ -0,0 +1,274 @@
/* ###
* 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.app.emulator.memory;
import java.util.Arrays;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.memstate.MemoryFaultHandler;
import ghidra.pcode.memstate.MemoryPage;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
public class ProgramMappedMemory {
private Program program;
private AddressSetView initializedAddressSet;
private MemoryFaultHandler faultHandler;
public ProgramMappedMemory(Program program, MemoryFaultHandler faultHandler) {
this.program = program;
Memory memory = program.getMemory();
initializedAddressSet = memory.getLoadedAndInitializedAddressSet();
for (MemoryBlock block : memory.getBlocks()) {
if (!block.isInitialized() && block.isMapped()) {
initializedAddressSet = addMappedInitializedMemory(block);
}
}
program.addConsumer(this);
this.faultHandler = faultHandler;
}
private AddressSetView addMappedInitializedMemory(MemoryBlock mappedBlock) {
AddressSet modifiedSet = new AddressSet(initializedAddressSet);
MemoryBlockSourceInfo sourceInfo = mappedBlock.getSourceInfos().get(0); // mapped block has exactly 1 mapped source
if (!sourceInfo.getMappedRange().isPresent()) {
throw new AssertException("Mapped block did not have mapped range!");
}
AddressRange mappedRange = sourceInfo.getMappedRange().get();
Address mapStart = mappedRange.getMinAddress();
Address mapEnd = mappedRange.getMaxAddress();
AddressSet mappedAreas = initializedAddressSet.intersectRange(mapStart, mapEnd);
for (AddressRange range : mappedAreas) {
Address start = mappedBlock.getStart().add(range.getMinAddress().subtract(mapStart));
Address end = mappedBlock.getStart().add(range.getMaxAddress().subtract(mapStart));
modifiedSet.add(start, end);
}
return modifiedSet;
}
public Program getProgram() {
return program;
}
public void dispose() {
if (program != null) {
program.release(this);
program = null;
}
}
// TODO: Need to investigate program write-back transaction issues -
// it could also be very expensive writing memory without some form of write-back cache
public void write(byte[] bytes, int size, Address addr, int offset) {
Memory memory = program.getMemory();
int currentOffset = offset;
int remaining = size;
Address nextAddr = addr;
Address endAddr;
try {
endAddr = addr.addNoWrap(size - 1);
}
catch (AddressOverflowException e) {
throw new LowlevelError(
"Illegal memory write request: " + addr + ", length=" + size + " bytes");
}
while (true) {
int chunkSize = remaining;
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
AddressRange range = it.hasNext() ? it.next() : null;
///
/// Begin change for addressSet changes - wcb
///
if (range == null) {
// nextAddr not in memory and is bigger that any initialized memory
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
break;
}
else if (range.contains(nextAddr)) {
// nextAddr is in memory
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
}
try {
memory.setBytes(nextAddr, bytes, currentOffset, chunkSize);
}
catch (MemoryAccessException e) {
throw new LowlevelError("Unexpected memory write error: " + e.getMessage());
}
}
else {
// nextAddr not in initialized memory, but is less than some initialized range
Address rangeAddr = range.getMinAddress();
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
break;
}
long gapSize = rangeAddr.subtract(nextAddr);
chunkSize = (int) Math.min(gapSize, remaining);
handleWriteFault(bytes, currentOffset, chunkSize, nextAddr);
}
///
/// End change for addressSet changes - wcb
///
if (chunkSize == remaining) {
break; // done
}
// prepare for next chunk
try {
nextAddr = nextAddr.addNoWrap(chunkSize);
}
catch (AddressOverflowException e) {
throw new LowlevelError("Unexpected error: " + e.getMessage());
}
currentOffset += chunkSize;
remaining -= chunkSize;
}
}
private void handleWriteFault(byte[] bytes, int currentOffset, int remaining,
Address nextAddr) {
// TODO: Should we create blocks or convert to initialized as needed ?
}
public byte[] read(byte[] bytes, int size, Address addr, int offset,
boolean generateInitializedMask) {
Memory memory = program.getMemory();
int currentOffset = offset;
int remaining = size;
Address nextAddr = addr;
Address endAddr;
byte[] initializedMask = null;
try {
endAddr = addr.addNoWrap(size - 1);
}
catch (AddressOverflowException e) {
throw new LowlevelError(
"Illegal memory read request: " + addr + ", length=" + size + " bytes");
}
while (true) {
int chunkSize = remaining;
///
/// Begin change for addressSet changes - wcb
///
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
AddressRange range = it.hasNext() ? it.next() : null;
if (range == null) {
if (generateInitializedMask) {
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
remaining, initializedMask);
}
else {
handleReadFault(bytes, currentOffset, remaining, nextAddr);
}
break;
}
else if (range.contains(nextAddr)) {
// nextAddr found in initialized memory
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
}
try {
memory.getBytes(nextAddr, bytes, currentOffset, chunkSize);
}
catch (MemoryAccessException e) {
//throw new LowlevelError("Unexpected memory read error: " + e.getMessage());
Msg.warn(this, "Unexpected memory read error: " + e.getMessage());
}
}
else {
Address rangeAddr = range.getMinAddress();
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
if (generateInitializedMask) {
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
remaining, initializedMask);
}
else {
handleReadFault(bytes, currentOffset, remaining, nextAddr);
}
break;
}
long gapSize = rangeAddr.subtract(nextAddr);
chunkSize = (gapSize > 0) ? (int) Math.min(gapSize, remaining) : remaining;
if (generateInitializedMask) {
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
chunkSize, initializedMask);
}
else {
handleReadFault(bytes, currentOffset, chunkSize, nextAddr);
}
}
///
/// End change for addressSet changes - wcb
///
if (chunkSize == remaining) {
break; // done
}
// prepare for next chunk
try {
nextAddr = nextAddr.addNoWrap(chunkSize);
}
catch (AddressOverflowException e) {
throw new LowlevelError("Unexpected error: " + e.getMessage());
}
currentOffset += chunkSize;
remaining -= chunkSize;
}
return initializedMask;
}
private static byte[] getInitializedMask(int bufsize, int initialOffset,
int uninitializedOffset, int uninitializedSize, byte[] initializedMask) {
if (initializedMask == null) {
initializedMask = MemoryPage.getInitializedMask(bufsize, 0, initialOffset, false);
}
MemoryPage.setUninitialized(initializedMask, uninitializedOffset, uninitializedSize);
return initializedMask;
}
private void handleReadFault(byte[] bytes, int offset, int size, Address addr) {
// NOTE: This can trigger a load from a different external library depending upon the specific fault handler installed
Arrays.fill(bytes, offset, offset + size, (byte) 0);
if (faultHandler != null) {
faultHandler.uninitializedRead(addr, size, bytes, size);
}
}
public AddressSetView getInitializedAddressSet() {
return initializedAddressSet;
}
}

View file

@ -0,0 +1,83 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.emulator.state;
import generic.stl.Pair;
import ghidra.program.model.lang.Language;
import ghidra.util.*;
import java.util.*;
public class DumpMiscState implements RegisterState {
private Map<String, Pair<Boolean, byte[]>> context =
new HashMap<String, Pair<Boolean, byte[]>>();
private DataConverter dc;
public DumpMiscState(Language lang) {
dc =
lang.isBigEndian() ? BigEndianDataConverter.INSTANCE
: LittleEndianDataConverter.INSTANCE;
}
@Override
public void dispose() {
context.clear();
}
@Override
public Set<String> getKeys() {
return context.keySet();
}
@Override
public List<byte[]> getVals(String key) {
List<byte[]> list = new ArrayList<byte[]>();
Pair<Boolean, byte[]> pair = context.get(key);
if (pair != null && pair.second != null) {
list.add(pair.second);
}
return list;
}
@Override
public List<Boolean> isInitialized(String key) {
List<Boolean> list = new ArrayList<Boolean>();
Pair<Boolean, byte[]> pair = context.get(key);
if (pair != null && pair.first != null) {
list.add(pair.first);
}
else {
list.add(Boolean.FALSE);
}
return list;
}
@Override
public void setVals(String key, byte[] vals, boolean setInitiailized) {
Pair<Boolean, byte[]> pair = new Pair<Boolean, byte[]>(setInitiailized, vals);
context.put(key, pair);
}
@Override
public void setVals(String key, long val, int size, boolean setInitiailized) {
byte[] bytes = new byte[size];
dc.getBytes(val, size, bytes, 0);
setVals(key, bytes, setInitiailized);
}
}

View file

@ -0,0 +1,39 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.emulator.state;
import ghidra.pcode.memstate.MemoryBank;
import ghidra.pcode.memstate.MemoryPageOverlay;
import ghidra.program.model.address.AddressSpace;
public class FilteredMemoryPageOverlay extends MemoryPageOverlay {
private boolean writeBack;
public FilteredMemoryPageOverlay(AddressSpace spc, MemoryBank ul, boolean writeBack) {
super(spc, ul, ul.getMemoryFaultHandler());
this.writeBack = writeBack;
}
@Override
public void setChunk(long offset, int size, byte[] val) {
super.setChunk(offset, size, val);
if (writeBack) {
underlie.setChunk(offset, size, val);
}
}
}

View file

@ -0,0 +1,34 @@
/* ###
* 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.app.emulator.state;
import ghidra.pcode.memstate.MemoryFaultHandler;
import ghidra.pcode.memstate.MemoryPageBank;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
public class FilteredRegisterBank extends MemoryPageBank {
//private final RegisterState regState;
//private final boolean writeBack;
public FilteredRegisterBank(AddressSpace spc, int ps, RegisterState initState, Language lang, boolean writeBack, MemoryFaultHandler faultHandler) {
super(spc, lang.isBigEndian(), ps, faultHandler);
//regState = initState;
//this.writeBack = writeBack;
}
}

View file

@ -0,0 +1,35 @@
/* ###
* 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.app.emulator.state;
import java.util.List;
import java.util.Set;
public interface RegisterState {
public Set<String> getKeys();
public List<byte[]> getVals(String key);
public List<Boolean> isInitialized(String key);
public void setVals(String key, byte[] vals, boolean setInitiailized);
public void setVals(String key, long val, int size, boolean setInitiailized);
public void dispose();
}

View file

@ -0,0 +1,258 @@
/* ###
* 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.app.util.pcode;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
/**
* A base implementation of {@link Appender} suitable for most cases.
*
* @param <T> the type of output of the formatter
*/
public abstract class AbstractAppender<T> implements Appender<T> {
protected final Language language;
protected final boolean indent;
/**
* Create a new appender.
*
* @param language the language of the p-code ops to format
* @param indent whether or not to indent
*/
public AbstractAppender(Language language, boolean indent) {
this.language = language;
this.indent = indent;
}
@Override
public Language getLanguage() {
return language;
}
@Override
public void appendAddressWordOffcut(long wordOffset, long offcut) {
appendString(stringifyWordOffcut(wordOffset, offcut));
}
@Override
public void appendCharacter(char c) {
if (c == '=') {
appendString(" = "); // HACK
}
else {
appendString(Character.toString(c));
}
}
@Override
public void appendIndent() {
if (indent) {
appendCharacter(' ');
appendCharacter(' ');
}
}
@Override
public void appendLabel(String label) {
appendString(label);
}
@Override
public void appendLineLabelRef(long label) {
appendString(stringifyLineLabel(label));
}
@Override
public void appendMnemonic(int opcode) {
appendString(stringifyOpMnemonic(opcode));
}
@Override
public void appendRawVarnode(AddressSpace space, long offset, long size) {
appendString(stringifyRawVarnode(space, offset, size));
}
@Override
public void appendRegister(Register register) {
appendString(stringifyRegister(register));
}
@Override
public void appendScalar(long value) {
appendString(stringifyScalarValue(value));
}
@Override
public void appendSpace(AddressSpace space) {
appendString(stringifySpace(space));
}
/**
* Append a plain string.
*
* <p>
* By default, all append method delegate to this, so either it must be implemented, or every
* other append method must be overridden to avoid ever invoking this method. The default
* implementation throws an assertion error.
*
* @param string the string to append
*/
protected void appendString(String string) {
throw new AssertionError(
"Either this shouldn't happen, or you should accept the string");
}
@Override
public void appendUnique(long offset) {
appendString(stringifyUnique(offset));
}
@Override
public void appendUserop(int id) {
appendString(stringifyUserop(language, id));
}
/**
* Covert the given line label to a string as it should be conventionally displayed.
*
* @param label the label number
* @return the display string, e.g., {@code <L1>}
*/
protected String stringifyLineLabel(long label) {
return "<" + label + ">";
}
/**
* Convert the given opcode to a string as it should be conventionally displayed.
*
* @param opcode the opcode
* @return the display string, i.e., its mnemonic
*/
protected String stringifyOpMnemonic(int opcode) {
return PcodeOp.getMnemonic(opcode);
}
/**
* Convert the given varnode to its raw conventional form.
*
* @param space the address space
* @param offset the offset in the space
* @param size the size in bytes
* @return the raw display string
*/
protected String stringifyRawVarnode(AddressSpace space, long offset, long size) {
return "(" + space.getName() + ", 0x" + Long.toHexString(offset) + ", " + size + ")";
}
/**
* Convert the given register to a string as it should be conventionally displayed.
*
* @param register the register
* @return the display string, i.e., its name
*/
protected String stringifyRegister(Register register) {
return register.getName();
}
/**
* Convert the given scalar to a string as it should be conventionally displayed.
*
* @param value the value
* @return the display string, i.e., its decimal value if small, or hex value is large
*/
protected String stringifyScalarValue(long value) {
if (value >= -64 && value <= 64) {
return Long.toString(value);
}
else {
return "0x" + Long.toHexString(value);
}
}
/**
* Convert the given address space to a string as it should be conventionally displayed.
*
* @param space the address space
* @return the display string, i.e., its name
*/
protected String stringifySpace(AddressSpace space) {
if (space == null) {
return "unknown";
}
return space.getName();
}
/**
* Convert a given unique variable to a string as it should be conventionally displayed.
*
* @param offset the variable's offset
* @return the display string, e.g., {@code $U1234}
*/
protected String stringifyUnique(long offset) {
return "$U" + Long.toHexString(offset);
}
/**
* Lookup a given userop name
*
* @param language the language containing the userop
* @param id the userop id
* @return the display string, i.e., its name, or null if it doesn't exist
*/
protected String stringifyUseropUnchecked(Language language, int id) {
if (!(language instanceof SleighLanguage)) {
throw new RuntimeException("Expected Sleigh language for CALLOTHER op");
}
return ((SleighLanguage) language).getUserDefinedOpName(id);
}
/**
* Convert a given userop to a string as it should be conventionally displayed.
*
* @param language the langauge containing the userop
* @param id the userop id
* @return the display string, i.e., its name or "unknown"
*/
protected String stringifyUserop(Language language, int id) {
String pseudoOp = stringifyUseropUnchecked(language, id);
if (pseudoOp == null) {
Msg.error(this, "Pseudo-op index not found: " + id);
pseudoOp = "unknown";
}
return pseudoOp;
}
/**
* Convert a given word-offcut style address to a string as it should be conventionally
* displayed.
*
* @param wordOffset the offset of the word in memory
* @param offcut the byte "offcut" within the word
* @return the display string, e.g., {@code 0x1234.1}
*/
protected String stringifyWordOffcut(long wordOffset, long offcut) {
String str = "0x" + Long.toHexString(wordOffset);
if (offcut != 0) {
str += "." + offcut;
}
return str;
}
}

View file

@ -0,0 +1,412 @@
/* ###
* 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.app.util.pcode;
import java.util.List;
import ghidra.app.plugin.processors.sleigh.template.*;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
/**
* An abstract p-code formatter which can take a list of p-code ops or op templates and consistently
* format them. The general pattern is to extend this class and specify another class which extends
* an {@link AbstractAppender}. In most cases, it is only necessary to override
* {@link #formatOpTemplate(Appender, OpTpl)}. Otherwise, most formatting logic is implemented by
* the appender.
*
* @see {@link StringPcodeFormatter} for an example
* @see {@link AbstractAppender}
* @param <T> the type of this formatter's output, e.g., {@link String}
* @param <A> the type of the appender
*/
public abstract class AbstractPcodeFormatter<T, A extends Appender<T>>
implements PcodeFormatter<T> {
/**
* A result instructing the formatter whether or not to continue
*/
protected enum FormatResult {
CONTINUE, TERMINATE;
}
/**
* Create the appender for a formatting invocation
*
* @param language the language of the p-code to format
* @param indent indicates whether each line should be indented to accommodate line labels
* @return the new appender
*/
protected abstract A createAppender(Language language, boolean indent);
/**
* Check if this formatter is configured to display raw p-code
*
* @return true if displaying raw, false otherwise
*/
protected boolean isFormatRaw() {
return false;
}
@Override
public T formatTemplates(Language language, List<OpTpl> pcodeOpTemplates) {
boolean indent = hasLabel(pcodeOpTemplates);
A appender = createAppender(language, indent);
for (OpTpl template : pcodeOpTemplates) {
if (FormatResult.TERMINATE == formatOpTemplate(appender, template)) {
break;
}
}
return appender.finish();
}
/**
* Format a single op template
*
* @param appender the appender to receive the formatted text
* @param op the template to format
* @return instructions to continue or terminate. The loop in
* {@link #formatTemplates(Language, List)} is terminated if this method returns
* {@link FormatResult#TERMINATE}.
*/
protected FormatResult formatOpTemplate(A appender, OpTpl op) {
int opcode = op.getOpcode();
if (PcodeOp.PTRADD == opcode) {
appender.appendLineLabel(op.getInput()[0].getOffset().getReal());
return FormatResult.CONTINUE;
}
appender.appendIndent();
if (opcode >= PcodeOp.PCODE_MAX) {
throw new RuntimeException("Unsupported opcode encountered: " + opcode);
}
VarnodeTpl output = op.getOutput();
if (output != null) {
formatOutput(appender, opcode, output);
appender.appendCharacter('=');
}
appender.appendMnemonic(opcode);
VarnodeTpl[] inputs = op.getInput();
for (int i = 0; i < inputs.length; i++) {
if (i > 0) {
appender.appendCharacter(',');
}
appender.appendCharacter(' ');
if (i == 0) {
if (!isFormatRaw()) {
if (opcode == PcodeOp.LOAD || opcode == PcodeOp.STORE) {
formatMemoryInput(appender, inputs[0], inputs[1]);
++i;
continue;
}
if (opcode == PcodeOp.CALLOTHER) {
formatCallOtherName(appender, inputs[0]);
continue;
}
}
if (opcode == PcodeOp.BRANCH || opcode == PcodeOp.CBRANCH) {
if (formatLabelInput(appender, inputs[i])) {
continue;
}
}
}
formatInput(appender, opcode, i, inputs[i]);
}
return FormatResult.CONTINUE;
}
/**
* Format an output varnode
*
* @param appender the appender to receive the formatted text
* @param opcode the op's opcode
* @param output the varnode to format
*/
protected void formatOutput(A appender, int opcode, VarnodeTpl output) {
formatVarnode(appender, opcode, -1, output);
}
/**
* Format an input varnode
*
* @param appender the appender to receive the formatted text
* @param opcode the op's opcode
* @param opIndex the operand's index
* @param input the varnode to format
*/
protected void formatInput(A appender, int opcode, int opIndex, VarnodeTpl input) {
formatVarnode(appender, opcode, opIndex, input);
}
/**
* Format a varnode
*
* @param appender the appender to receive the formatted text
* @param opcode the op's opcode
* @param opIndex the operand's index (-1 is output, 0 is first input)
* @param vTpl the varnode to format
*/
protected void formatVarnode(A appender, int opcode, int opIndex, VarnodeTpl vTpl) {
ConstTpl space = vTpl.getSpace();
ConstTpl offset = vTpl.getOffset();
ConstTpl size = vTpl.getSize();
if (space.getType() == ConstTpl.J_CURSPACE) {
if (offset.getType() == ConstTpl.J_START) {
appender.appendLabel("inst_start");
}
else if (offset.getType() == ConstTpl.J_NEXT) {
appender.appendLabel("inst_next");
}
else if (offset.getType() == ConstTpl.J_NEXT2) {
appender.appendLabel("inst_next2");
}
else {
formatAddress(appender, null, offset, size);
}
}
else if (space.getType() == ConstTpl.SPACEID) {
if (isFormatRaw() && offset.getType() == ConstTpl.REAL &&
size.getType() == ConstTpl.REAL) {
formatVarnodeRaw(appender, space.getSpaceId(), offset, size);
}
else {
formatVarnodeNice(appender, space.getSpaceId(), offset, size);
}
}
else {
throw new RuntimeException("Unsupported space template type: " + space.getType());
}
}
/**
* Format a varnode in nice (non-raw) form
*
* @param appender the appender to receive the formatted text
* @param space the address space of the varnode
* @param offset the offset in the address space
* @param size the size in bytes
*/
protected void formatVarnodeNice(A appender, AddressSpace space, ConstTpl offset,
ConstTpl size) {
if (space.isConstantSpace()) {
formatConstant(appender, offset, size);
}
else if (space.isUniqueSpace()) {
formatUnique(appender, offset, size);
}
else {
formatAddress(appender, space, offset, size);
}
}
/**
* Format a varnode in raw form
*
* @param appender the appender to receive the formatted text
* @param space the address space of the varnode
* @param offset the offset in the address space
* @param size the size in bytes
*/
protected void formatVarnodeRaw(A appender, AddressSpace space, ConstTpl offset,
ConstTpl size) {
appender.appendRawVarnode(space, offset.getReal(), size.getReal());
}
/**
* Format a unique variable
*
* @param appender the appender to receive the formatted text
* @param offset the offset in unique space
* @param size the size in bytes
*/
protected void formatUnique(A appender, ConstTpl offset, ConstTpl size) {
if (offset.getType() != ConstTpl.REAL) {
throw new RuntimeException("Unsupported unique offset type: " + offset.getType());
}
if (size.getType() != ConstTpl.REAL) {
throw new RuntimeException("Unsupported unique size type: " + size.getType());
}
appender.appendUnique(offset.getReal());
formatSize(appender, size);
}
/**
* Format a memory variable
*
* @param appender the appender to receive the formatted text
* @param addrSpace the address space of the variable
* @param offset the offset in the address space
* @param size the size in bytes
*/
protected void formatAddress(A appender, AddressSpace addrSpace,
ConstTpl offset, ConstTpl size) {
if (offset.getType() != ConstTpl.REAL) {
throw new RuntimeException("Unsupported address offset type: " + offset.getType());
}
long offsetValue = offset.getReal();
if (addrSpace == null) {
appender.appendCharacter('*');
appender.appendAddressWordOffcut(offsetValue, 0);
if (size.getType() != ConstTpl.J_CURSPACE_SIZE) {
formatSize(appender, size);
}
return;
}
long sizeValue = size.getReal();
Register reg =
appender.getLanguage().getRegister(addrSpace.getAddress(offsetValue), (int) sizeValue);
if (reg != null) {
appender.appendRegister(reg);
if (reg.getMinimumByteSize() > sizeValue) {
appender.appendCharacter(':');
appender.appendScalar(sizeValue);
}
return;
}
appender.appendCharacter('*');
appender.appendCharacter('[');
appender.appendSpace(addrSpace);
appender.appendCharacter(']');
long wordOffset = offsetValue / addrSpace.getAddressableUnitSize();
long offcut = offsetValue % addrSpace.getAddressableUnitSize();
appender.appendAddressWordOffcut(wordOffset, offcut);
formatSize(appender, size);
return;
}
/**
* Format a constant
*
* @param appender the appender to receive the formatted text
* @param offset the value of the constant
* @param size the size in bytes
*/
protected void formatConstant(A appender, ConstTpl offset, ConstTpl size) {
if (offset.getType() != ConstTpl.REAL) {
throw new RuntimeException("Unsupported constant offset type: " + offset.getType());
}
long value = offset.getReal();
appender.appendScalar(value);
formatSize(appender, size);
}
/**
* Format a size indicator
*
* @param appender the appender to receive the formatted text
* @param size the size in bytes
*/
protected void formatSize(A appender, ConstTpl size) {
if (size.getType() != ConstTpl.REAL) {
throw new RuntimeException("Unsupported address size type: " + size.getType());
}
if (size.getReal() != 0) {
appender.appendCharacter(':');
appender.appendScalar(size.getReal());
}
}
/**
* Format a p-code userop name (CALLOTHER)
*
* @param appender the appender to receive the formatted text
* @param input0 the constant varnode giving the userop id
*/
protected void formatCallOtherName(A appender, VarnodeTpl input0) {
if (!input0.getSpace().isConstSpace() || input0.getOffset().getType() != ConstTpl.REAL) {
throw new RuntimeException("Expected constant input[0] for CALLOTHER pcode op");
}
int id = (int) input0.getOffset().getReal();
appender.appendCharacter('"');
appender.appendUserop(id);
appender.appendCharacter('"');
}
/**
* Try to format a local label (e.g., {@code instr_next})
*
* @param appender the appender to receive the formatted text
* @param input0 the relative jump varnode
* @return true if the varnode was formatted, false if not
*/
protected boolean formatLabelInput(A appender, VarnodeTpl input0) {
if (input0.getSpace().isConstSpace() &&
input0.getOffset().getType() == ConstTpl.J_RELATIVE) {
appender.appendLineLabelRef(input0.getOffset().getReal());
return true;
}
return false;
}
/**
* Format the memory location for a LOAD or STORE op
*
* @param appender the appender to receive the formatted text
* @param input0 the const varnode giving the address space id
* @param input1 the varnode giving the address offset
*/
protected void formatMemoryInput(A appender, VarnodeTpl input0, VarnodeTpl input1) {
if (!input0.getSpace().isConstSpace() || input0.getOffset().getType() != ConstTpl.REAL) {
throw new RuntimeException("Expected constant input[0] for LOAD/STORE pcode op");
}
int id = (int) input0.getOffset().getReal();
AddressSpace space = appender.getLanguage().getAddressFactory().getAddressSpace(id);
if (space == null) {
Msg.error(this, "Address space id not found: " + id);
}
appender.appendSpace(space);
appender.appendCharacter('(');
formatVarnode(appender, -1, 0, input1);
appender.appendCharacter(')');
}
private static boolean hasLabel(List<OpTpl> pcodeOpTemplates) {
for (OpTpl op : pcodeOpTemplates) {
if (isLineLabel(op)) {
return true;
}
}
return false;
}
/**
* Check if the given template represents a line label
*
* <p>
* The {@link PcodeOp#PTRADD} op is ordinarily only use in high p-code. We reuse (read "abuse")
* it to hold a display slot for line labels later referred to in {@link PcodeOp#BRANCH} and
* {@link PcodeOp#CBRANCH} ops. This method checks if the given op template is one of those
* placeholders.
*
* @param template the op template
* @return true if it's a line label
*/
protected static boolean isLineLabel(OpTpl template) {
// Overloaded: PTRADD is high p-code
return template.getOpcode() == PcodeOp.PTRADD;
}
}

View file

@ -0,0 +1,146 @@
/* ###
* 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.app.util.pcode;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
/**
* An appender to receive formatted p-code ops.
*
* <p>
* Using {@link AbstractAppender} is highly recommended, as it makes available methods for
* displaying elements according to established Ghidra conventions.
*
* @param <T> the type of the final formatted output
*/
interface Appender<T> {
/**
* Get the language of the p-code being formatted
*
* @return
*/
Language getLanguage();
/**
* Append a line label, usually meant to be on its own line
*
* @param label the label number
*/
default void appendLineLabel(long label) {
appendLineLabelRef(label);
}
/**
* Append indentation, usually meant for the beginning of a line
*/
default void appendIndent() {
appendCharacter(' ');
appendCharacter(' ');
}
/**
* Append a reference to the given line label
*
* @param label the label number
*/
void appendLineLabelRef(long label);
/**
* Append the given opcode
*
* @param opcode the op's opcode
*/
void appendMnemonic(int opcode);
/**
* Append the the given userop
*
* @param id the userop id
*/
void appendUserop(int id);
/**
* Append the given varnode in raw form
*
* @param space the address space
* @param offset the offset in the space
* @param size the size in bytes
*/
void appendRawVarnode(AddressSpace space, long offset, long size);
/**
* Append a character
*
* <p>
* <b>NOTE:</b> if extra spacing is desired, esp., surrounding the equals sign, it must be
* appended manually.
*
* @param c the character
*/
void appendCharacter(char c);
/**
* Append an address in word-offcut form
*
* @param wordOffset the word offset
* @param offcut the byte within the word
*/
void appendAddressWordOffcut(long wordOffset, long offcut);
/**
* Append a local label
*
* @param label the label name, e.g., {@code instr_next}
*/
void appendLabel(String label);
/**
* Append a register
*
* @param register the register
*/
void appendRegister(Register register);
/**
* Append a scalar value
*
* @param value the value
*/
void appendScalar(long value);
/**
* Append an address space
*
* @param space the space
*/
void appendSpace(AddressSpace space);
/**
* Append a unique variable
*
* @param offset the offset in unique space
*/
void appendUnique(long offset);
/**
* Finish formatting and return the final result
*
* @return the final result
*/
T finish();
}

View file

@ -0,0 +1,153 @@
/* ###
* 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.app.util.pcode;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.template.*;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
public interface PcodeFormatter<T> {
/**
* Format the p-code ops
*
* @param language the language generating the p-code
* @param pcodeOps the p-code ops
* @return the formatted result
*/
default T formatOps(Language language, List<PcodeOp> pcodeOps) {
return formatOps(language, language.getAddressFactory(), pcodeOps);
}
/**
* Format the pcode ops with a specified {@link AddressFactory}. For use when the
* pcode ops can reference program-specific address spaces.
*
* @param language the language generating the p-code
* @param addrFactory addressFactory to use when generating pcodeop templates
* @param pcodeOps p-code ops to format
* @return the formatted result
*
*/
default T formatOps(Language language, AddressFactory addrFactory, List<PcodeOp> pcodeOps) {
return formatTemplates(language, getPcodeOpTemplates(addrFactory, pcodeOps));
}
/**
* Format the p-code op templates
*
* @param language the language generating the p-code
* @param pcodeOpTemplates the templates
* @return the formatted result
*/
T formatTemplates(Language language, List<OpTpl> pcodeOpTemplates);
/**
* Convert flattened p-code ops into templates.
*
* @param addrFactory the language's address factory
* @param pcodeOps the p-code ops to convert
* @return p-code op templates
*/
public static List<OpTpl> getPcodeOpTemplates(AddressFactory addrFactory,
List<PcodeOp> pcodeOps) {
ArrayList<OpTpl> list = new ArrayList<OpTpl>();
HashMap<Integer, Integer> labelMap = new HashMap<Integer, Integer>(); // label offset to index map
for (PcodeOp pcodeOp : pcodeOps) {
int opcode = pcodeOp.getOpcode();
VarnodeTpl outputTpl = null;
Varnode v = pcodeOp.getOutput();
if (v != null) {
outputTpl = getVarnodeTpl(addrFactory, v);
}
Varnode[] inputs = pcodeOp.getInputs();
VarnodeTpl[] inputTpls = new VarnodeTpl[inputs.length];
for (int i = 0; i < inputs.length; i++) {
Varnode input = inputs[i];
if (i == 0 && (opcode == PcodeOp.BRANCH || opcode == PcodeOp.CBRANCH)) {
// Handle internal branch destination represented by constant destination
if (input.isConstant()) {
int labelOffset = pcodeOp.getSeqnum().getTime() + (int) input.getOffset();
int labelIndex;
if (labelMap.containsKey(labelOffset)) {
labelIndex = labelMap.get(labelOffset);
}
else {
labelIndex = labelMap.size();
labelMap.put(labelOffset, labelIndex);
}
ConstTpl offsetTpl = new ConstTpl(ConstTpl.J_RELATIVE, labelIndex);
ConstTpl spaceTpl = new ConstTpl(addrFactory.getConstantSpace());
ConstTpl sizeTpl = new ConstTpl(ConstTpl.REAL, 8);
inputTpls[i] = new VarnodeTpl(spaceTpl, offsetTpl, sizeTpl);
continue;
}
}
inputTpls[i] = getVarnodeTpl(addrFactory, input);
}
list.add(new OpTpl(opcode, outputTpl, inputTpls));
}
// Insert label templates from the bottom-up
ArrayList<Integer> offsetList = new ArrayList<Integer>(labelMap.keySet());
Collections.sort(offsetList);
for (int i = offsetList.size() - 1; i >= 0; i--) {
int labelOffset = offsetList.get(i);
int labelIndex = labelMap.get(labelOffset);
OpTpl labelTpl = getLabelOpTemplate(addrFactory, labelIndex);
list.add(labelOffset, labelTpl);
}
return list;
}
/**
* Create label OpTpl. Uses overloaded PcodeOp.PTRADD with input[0] = labelIndex
*
* @param addrFactory
* @param labelIndex
* @return label OpTpl
*/
private static OpTpl getLabelOpTemplate(AddressFactory addrFactory, int labelIndex) {
ConstTpl offsetTpl = new ConstTpl(ConstTpl.REAL, labelIndex);
ConstTpl spaceTpl = new ConstTpl(addrFactory.getConstantSpace());
ConstTpl sizeTpl = new ConstTpl(ConstTpl.REAL, 8);
VarnodeTpl input = new VarnodeTpl(spaceTpl, offsetTpl, sizeTpl);
return new OpTpl(PcodeOp.PTRADD, null, new VarnodeTpl[] { input });
}
private static VarnodeTpl getVarnodeTpl(AddressFactory addrFactory, Varnode v) {
ConstTpl offsetTpl = new ConstTpl(ConstTpl.REAL, v.getOffset());
AddressSpace addressSpace = addrFactory.getAddressSpace(v.getSpace());
if (addressSpace == null) {
throw new IllegalArgumentException("Unknown varnode space ID: " + v.getSpace());
}
ConstTpl spaceTpl = new ConstTpl(addressSpace);
ConstTpl sizeTpl = new ConstTpl(ConstTpl.REAL, v.getSize());
return new VarnodeTpl(spaceTpl, offsetTpl, sizeTpl);
}
}

View file

@ -0,0 +1,304 @@
/* ###
* 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.generic.util.datastruct;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import generic.ULongSpan;
import generic.ULongSpan.*;
import ghidra.util.MathUtilities;
/**
* A sparse byte array characterized by contiguous dense regions
*
* <p>
* Notionally, the array is 2 to the power 64 bytes in size. Only the initialized values are
* actually stored. Uninitialized indices are assumed to have the value 0. Naturally, this
* implementation works best when the array is largely uninitialized. For efficient use, isolated
* initialized values should be avoided. Rather, an entire range should be initialized at the same
* time.
*
* <p>
* On a number line, the initialized indices of a semisparse array might be depicted:
*
* <pre>
* ----- --------- - ------ ---
* </pre>
*
* <p>
* In contrast, the same for a sparse array might be depicted:
*
* <pre>
* - -- - - - --- -- - -
* </pre>
*
* <p>
* This implementation is well-suited for memory caches where the memory is accessed by reading
* ranges instead of individual bytes. Because consecutive reads and writes tend to occur in a
* common locality, caches using a semisparse array may perform well.
*
* <p>
* This implementation is also thread-safe. Any thread needing exclusive access for multiple reads
* and/or writes, e.g., to implement a compare-and-set operation, must apply additional
* synchronization.
*/
public class SemisparseByteArray {
/** The size of blocks used internally to store array values */
public static final int BLOCK_SIZE = 0x1000;
private final Map<Long, byte[]> blocks;
private final MutableULongSpanSet defined;
public SemisparseByteArray() {
this.blocks = new HashMap<>();
this.defined = new DefaultULongSpanSet();
}
protected SemisparseByteArray(Map<Long, byte[]> blocks, MutableULongSpanSet defined) {
this.blocks = blocks;
this.defined = defined;
}
static byte[] copyArr(Map.Entry<?, byte[]> ent) {
byte[] b = ent.getValue();
return Arrays.copyOf(b, b.length);
}
public synchronized SemisparseByteArray fork() {
// TODO Could use some copy-on-write optimization here and in parents
Map<Long, byte[]> copyBlocks = blocks.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, SemisparseByteArray::copyArr));
MutableULongSpanSet copyDefined = new DefaultULongSpanSet();
copyDefined.addAll(defined);
return new SemisparseByteArray(copyBlocks, copyDefined);
}
/**
* Clear the array
*
* <p>
* All indices will be uninitialized after this call, just as it was immediately after
* construction
*/
public synchronized void clear() {
defined.clear();
blocks.clear();
}
/**
* Copy a range of data from the semisparse array into the given byte array
*
* @see #getData(long, byte[], int, int)
* @param loc the index to begin copying data out
* @param data the array to copy data into
*/
public synchronized void getData(long loc, byte[] data) {
getData(loc, data, 0, data.length);
}
/**
* Copy a range of data from the semisparse array into a portion of the given byte array
*
* <p>
* Copies {@code length} bytes of data from the semisparse array starting at index {@code loc}
* into {@code data} starting at index {@code} offset. All initialized portions within the
* requested region are copied. The uninitialized portions may be treated as zeroes or not
* copied at all. Typically, the destination array has been initialized to zero by the caller,
* such that all uninitialized portions are zero. To avoid fetching uninitialized data, use
* {@link #contiguousAvailableAfter(long)} as an upper bound on the length.
*
* @param loc the index to begin copying data out
* @param data the array to copy data into
* @param offset the offset into the destination array
* @param length the length of data to read
*/
public synchronized void getData(final long loc, final byte[] data, final int offset,
final int length) {
if (length < 0) {
throw new IllegalArgumentException("length: " + length);
}
// Read in 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.get(blockNum);
int amt = Math.min(length, BLOCK_SIZE - blockOffset);
if (block != null) {
System.arraycopy(block, blockOffset, data, offset, amt);
}
// Read in each following block
int cur = amt;
while (cur < length) {
blockNum++;
if (blockNum == 0) {
throw new BufferUnderflowException();
}
block = blocks.get(blockNum);
amt = Math.min(length - cur, BLOCK_SIZE);
if (block != null) {
System.arraycopy(block, 0, data, cur + offset, amt);
}
cur += amt;
}
}
/**
* Enumerate the initialized ranges within the given range
*
* <p>
* The given range is interpreted as closed, i.e., [a, b].
*
* @param a the lower-bound, inclusive, of the range
* @param b the upper-bound, inclusive, of the range
* @return the set of initialized ranges
*/
public synchronized ULongSpanSet getInitialized(long a, long b) {
MutableULongSpanSet result = new DefaultULongSpanSet();
ULongSpan query = ULongSpan.span(a, b);
for (ULongSpan span : defined.intersecting(query)) {
result.add(query.intersect(span));
}
return result;
}
/**
* Check if a range is completely initialized
*
* <p>
* The given range is interpreted as closed, i.e., [a, b].
*
* @param a the lower-bound, inclusive, of the range
* @param b the upper-bound, inclusive, of the range
* @return true if all indices in the range are initialized, false otherwise
*/
public synchronized boolean isInitialized(long a, long b) {
return defined.encloses(ULongSpan.span(a, b));
}
/**
* Check if an index is initialized
*
* @param a the index to check
* @return true if the index is initialized, false otherwise
*/
public synchronized boolean isInitialized(long a) {
return defined.contains(a);
}
/**
* Enumerate the uninitialized ranges within the given range
*
* <p>
* The given range is interpreted as closed, i.e., [a, b].
*
* @param a the lower-bound, inclusive, of the range
* @param b the upper-bound, inclusive, of the range
* @return the set of uninitialized ranges
*/
public synchronized ULongSpanSet getUninitialized(long a, long b) {
MutableULongSpanSet result = new DefaultULongSpanSet();
for (ULongSpan s : defined.complement(ULongSpan.span(a, b))) {
result.add(s);
}
return result;
}
/**
* Initialize or modify a range of the array by copying from a given array
*
* @see #putData(long, byte[], int, int)
* @param loc the index of the semisparse array to begin copying into
* @param data the data to copy
*/
public synchronized void putData(long loc, byte[] data) {
putData(loc, data, 0, data.length);
}
/**
* Initialize or modify a range of the array by copying a portion from a given array
*
* @param loc the index of the semisparse array to begin copying into
* @param data the source array to copy from
* @param offset the offset of the source array to begin copying from
* @param length the length of data to copy
*/
public synchronized void putData(final long loc, final byte[] data, final int offset,
final int length) {
if (length == 0) {
return;
}
defined.add(ULongSpan.extent(loc, length));
// 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]);
int amt = Math.min(length, BLOCK_SIZE - blockOffset);
System.arraycopy(data, offset, block, blockOffset, amt);
// Write out each following block
int cur = amt;
while (cur < length) {
blockNum++;
if (blockNum == 0) {
throw new BufferOverflowException();
}
block = blocks.computeIfAbsent(blockNum, n -> new byte[BLOCK_SIZE]);
amt = Math.min(length - cur, BLOCK_SIZE);
System.arraycopy(data, cur + offset, block, 0, amt);
cur += amt;
}
}
/**
* Copy the contents on another semisparse array into this one
*
* @param from the source array
*/
public synchronized void putAll(SemisparseByteArray from) {
byte[] temp = new byte[4096];
for (ULongSpan span : from.defined.spans()) {
long lower = span.min();
long length = span.length();
for (long i = 0; Long.compareUnsigned(i, length) < 0;) {
int l = MathUtilities.unsignedMin(temp.length, length - i);
from.getData(lower + i, temp, 0, l);
this.putData(lower + i, temp, 0, l);
i += l;
}
}
}
/**
* Check how many contiguous bytes are available starting at the given address
*
* @param loc the starting offset
* @return the number of contiguous defined bytes following
*/
public synchronized int contiguousAvailableAfter(long loc) {
ULongSpan span = defined.spanContaining(loc);
if (span == null) {
return 0;
}
long diff = span.max() - loc + 1;
return MathUtilities.unsignedMin(Integer.MAX_VALUE, diff);
}
}

View file

@ -0,0 +1,31 @@
/* ###
* 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.lifecycle;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Target;
/**
* An annotation for experimental things
*
* <p>
* The items are intended to become part of the public API, but the interfaces are unstable, and
* there's no guarantee they will ever become public.
*/
@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE, PARAMETER })
public @interface Experimental {
}

View file

@ -0,0 +1,37 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lifecycle;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Target;
/**
* An annotation for things internal to an implementation
*
* For organization, some interfaces and classes exist in packages outside where they are used, and
* method access is required. Java allows those methods to be accessed from any package. This
* annotation is applied to public methods which should not be accessed outside the implementation.
*
* A decent way to manually verify this is to ensure that any method marked with this annotation is
* not listed in the exported interface. Generally, this means no method should have both
* {@link Override} and {@link Internal} applied.
*/
@Target({ TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE })
public @interface Internal {
// TODO: Is it possible to warn when used outside the jar?
// TODO: Is it possible to warn when also overrides an interface method?
}

View file

@ -0,0 +1,24 @@
/* ###
* 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.lifecycle;
/**
* The item is present for transitional purposes only and will soon be removed
*/
public @interface Transitional {
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.lifecycle;
/**
* This serves both as a marker interface for classes missing important methods and as container for
* the {@link #TODO(String, Object...))} method.
*
* <p>
* TODO: It'd be nice to optionally ignore TODO exceptions, but this seems to require a dependency
* on JUnit, which is a no-no within {@code src/main}. Maybe there's a way via the abstract test
* case, or an interface mixin....
*/
public interface Unfinished {
public class TODOException extends UnsupportedOperationException {
public TODOException(String message) {
super(message);
}
public TODOException() {
this("TODO");
}
}
/**
* Perhaps a little better than returning {@code null} or throwing
* {@link UnsupportedOperationException} yourself, as references can be found in most IDEs.
*
* @param message A message describing the task that is yet to be done
* @param ignore variables involved in the implementation so far
*/
static <T> T TODO(String message, Object... ignore) {
throw new TODOException(message);
}
/**
* Perhaps a little better than returning {@code null} or throwing
* {@link UnsupportedOperationException} yourself, as references can be found in most IDEs.
*/
static <T> T TODO() {
throw new TODOException();
}
}

View file

@ -0,0 +1,402 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.util.classfinder.ClassSearcher;
/**
* An abstract implementation of {@link PcodeMachine} suitable as a base for most implementations
*
* <p>
* A note regarding terminology: A p-code "machine" refers to any p-code-based machine simulator,
* whether or not it operates on abstract or concrete values. The term "emulator" is reserved for
* machines whose values always include a concrete piece. That piece doesn't necessarily have to be
* a (derivative of) {@link BytesPcodeExecutorStatePiece}, but it usually is. To be called an
* "emulator" implies that {@link PcodeArithmetic#toConcrete(Object, Purpose)} never throws
* {@link ConcretionError} for any value in its state.
*
* <p>
* For a complete example of a p-code emulator, see {@link PcodeEmulator}. For an alternative
* implementation incorporating an abstract piece, see the Taint Analyzer.
*/
public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
/**
* Check and cast the language to Sleigh
*
* <p>
* Sleigh is currently the only realization, but this should give a decent error should that
* ever change.
*
* @param language the language
* @return the same language, cast to Sleigh
*/
protected static SleighLanguage assertSleigh(Language language) {
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Emulation requires a sleigh language");
}
return (SleighLanguage) language;
}
protected final SleighLanguage language;
protected final PcodeArithmetic<T> arithmetic;
protected final PcodeUseropLibrary<T> library;
protected final PcodeUseropLibrary<T> stubLibrary;
protected SwiMode swiMode = SwiMode.ACTIVE;
/* for abstract thread access */ PcodeStateInitializer initializer;
private PcodeExecutorState<T> sharedState;
protected final Map<String, PcodeThread<T>> threads = new LinkedHashMap<>();
protected final Collection<PcodeThread<T>> threadsView =
Collections.unmodifiableCollection(threads.values());
protected volatile boolean suspended = false;
protected final Map<Address, PcodeProgram> injects = new HashMap<>();
protected final SparseAddressRangeMap<AccessKind> accessBreakpoints =
new SparseAddressRangeMap<>();
/**
* Construct a p-code machine with the given language and arithmetic
*
* @param language the processor language to be emulated
*/
public AbstractPcodeMachine(Language language) {
this.language = assertSleigh(language);
this.arithmetic = createArithmetic();
this.library = createUseropLibrary();
this.stubLibrary = createThreadStubLibrary().compose(library);
/**
* NOTE: Do not initialize memoryState here, since createMemoryState may depend on fields
* initialized in a sub-constructor
*/
this.initializer = getPluggableInitializer(language);
}
/**
* A factory method to create the arithmetic used by this machine
*
* @return the arithmetic
*/
protected abstract PcodeArithmetic<T> createArithmetic();
/**
* A factory method to create the userop library shared by all threads in this machine
*
* @return the library
*/
protected abstract PcodeUseropLibrary<T> createUseropLibrary();
@Override
public SleighLanguage getLanguage() {
return language;
}
@Override
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
@Override
public PcodeUseropLibrary<T> getUseropLibrary() {
return library;
}
@Override
public PcodeUseropLibrary<T> getStubUseropLibrary() {
return stubLibrary;
}
/**
* A factory method to create the (memory) state shared by all threads in this machine
*
* @return the shared state
*/
protected abstract PcodeExecutorState<T> createSharedState();
/**
* A factory method to create the (register) state local to the given thread
*
* @param thread the thread
* @return the thread-local state
*/
protected abstract PcodeExecutorState<T> createLocalState(PcodeThread<T> thread);
/**
* A factory method to create a stub library for compiling thread-local Sleigh source
*
* <p>
* Because threads may introduce p-code userops using libraries unique to that thread, it
* becomes necessary to at least export stub symbols, so that p-code programs can be compiled
* from Sleigh source before the thread has necessarily been created. A side effect of this
* strategy is that all threads, though they may have independent libraries, must export
* identically-named symbols.
*
* @return the stub library for all threads
*/
protected PcodeUseropLibrary<T> createThreadStubLibrary() {
return new DefaultPcodeThread.PcodeEmulationLibrary<T>(null);
}
@Override
public void setSoftwareInterruptMode(SwiMode mode) {
this.swiMode = mode;
}
@Override
public SwiMode getSoftwareInterruptMode() {
return swiMode;
}
/**
* A factory method to create a new thread in this machine
*
* @see #newThread(String)
* @param name the name of the new thread
* @return the new thread
*/
protected PcodeThread<T> createThread(String name) {
return new DefaultPcodeThread<>(name, this);
}
/**
* Search the classpath for an applicable state initializer
*
* <p>
* If found, the initializer is executed immediately upon creating this machine's shared state
* and upon creating each thread.
*
* <p>
* TODO: This isn't really being used. At one point in development it was used to initialize
* x86's FS_OFFSET and GS_OFFSET registers. Those only exist in p-code, not the real processor,
* and replace what might have been {@code segment(FS)}. There seems more utility in detecting
* when those registers are uninitialized, requiring the user to initialize them, than it is to
* silently initialize them to 0. Unless we find utility in this, it will likely be removed in
* the near future.
*
* @see #doPluggableInitialization()
* @see DefaultPcodeThread#doPluggableInitialization()
* @param language the language requiring pluggable initialization
* @return the initializer
*/
protected static PcodeStateInitializer getPluggableInitializer(Language language) {
for (PcodeStateInitializer init : ClassSearcher.getInstances(PcodeStateInitializer.class)) {
if (init.isApplicable(language)) {
return init;
}
}
return null;
}
/**
* Execute the initializer upon this machine, if applicable
*
* @see #getPluggableInitializer(Language)
*/
protected void doPluggableInitialization() {
if (initializer != null) {
initializer.initializeMachine(this);
}
}
@Override
public PcodeThread<T> newThread() {
return newThread("Thread " + threads.size());
}
@Override
public PcodeThread<T> newThread(String name) {
if (threads.containsKey(name)) {
throw new IllegalStateException("Thread with name '" + name + "' already exists");
}
PcodeThread<T> thread = createThread(name);
threads.put(name, thread);
return thread;
}
@Override
public PcodeThread<T> getThread(String name, boolean createIfAbsent) {
PcodeThread<T> thread = threads.get(name);
if (thread == null && createIfAbsent) {
thread = newThread(name);
}
return thread;
}
@Override
public Collection<? extends PcodeThread<T>> getAllThreads() {
return threadsView;
}
@Override
public PcodeExecutorState<T> getSharedState() {
if (sharedState == null) {
sharedState = createSharedState();
doPluggableInitialization();
}
return sharedState;
}
@Override
public void setSuspended(boolean suspended) {
this.suspended = suspended;
}
/**
* Check for a p-code injection (override) at the given address
*
* @param address the address, usually the program counter
* @return the injected program, most likely {@code null}
*/
protected PcodeProgram getInject(Address address) {
return injects.get(address);
}
@Override
public PcodeProgram compileSleigh(String sourceName, String source) {
return SleighProgramCompiler.compileProgram(language, sourceName, source, stubLibrary);
}
@Override
public void inject(Address address, String source) {
/**
* TODO: Can I compile the template and build as if the inject were a
* instruction:^instruction constructor? This would require me to delay that build until
* execution, or at least check for instruction modification, if I do want to cache the
* built p-code.
*/
PcodeProgram pcode = compileSleigh("machine_inject:" + address, source);
injects.put(address, pcode);
}
@Override
public void clearInject(Address address) {
injects.remove(address);
}
@Override
public void clearAllInjects() {
injects.clear();
}
@Override
public void addBreakpoint(Address address, String sleighCondition) {
/**
* TODO: The template build idea is probably more pertinent here. If a user places a
* breakpoint with the purpose of single-stepping the p-code of that instruction, it won't
* work, because that p-code is occluded by emu_exec_decoded(). I suppose this could also be
* addressed by formalizing and better exposing the notion of p-code stacks (of p-code
* frames)
*/
PcodeProgram pcode = compileSleigh("breakpoint:" + address, String.format("""
if (!(%s)) goto <nobreak>;
emu_swi();
<nobreak>
emu_exec_decoded();
""", sleighCondition));
injects.put(address, pcode);
}
@Override
public void addAccessBreakpoint(AddressRange range, AccessKind kind) {
accessBreakpoints.put(range, kind);
}
@Override
public void clearAccessBreakpoints() {
accessBreakpoints.clear();
}
/**
* Perform checks on a requested LOAD
*
* <p>
* Throw an exception if the LOAD should cause an interrupt.
*
* @param space the address space being accessed
* @param offset the offset being accessed
* @param size the size of the variable being accessed
*/
protected void checkLoad(AddressSpace space, T offset, int size) {
if (accessBreakpoints.isEmpty()) {
return;
}
try {
long concrete = arithmetic.toLong(offset, Purpose.LOAD);
if (accessBreakpoints.hasEntry(space.getAddress(concrete), AccessKind::trapsRead)) {
throw new InterruptPcodeExecutionException(null, null);
}
}
catch (ConcretionError e) {
// Consider this not hitting any breakpoint
}
}
/**
* Perform checks on a requested STORE
*
* <p>
* Throw an exception if the STORE should cause an interrupt.
*
* @param space the address space being accessed
* @param offset the offset being accessed
* @param size the size of the variable being accessed
*/
protected void checkStore(AddressSpace space, T offset, int size) {
if (accessBreakpoints.isEmpty()) {
return;
}
try {
long concrete = arithmetic.toLong(offset, Purpose.LOAD);
if (accessBreakpoints.hasEntry(space.getAddress(concrete), AccessKind::trapsWrite)) {
throw new InterruptPcodeExecutionException(null, null);
}
}
catch (ConcretionError e) {
// Consider this not hitting any breakpoint
}
}
/**
* Throw a software interrupt exception if those interrupts are active
*/
protected void swi() {
if (swiMode == SwiMode.ACTIVE) {
throw new InterruptPcodeExecutionException(null, null);
}
}
/**
* Notify the machine a thread has been stepped, so that it may re-enable software interrupts,
* if applicable
*/
protected void stepped() {
if (swiMode == SwiMode.IGNORE_STEP) {
swiMode = SwiMode.ACTIVE;
}
}
}

View file

@ -0,0 +1,36 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
/**
* A simple p-code thread that operates on concrete bytes
*
* <p>
* For a complete example of a p-code emulator, see {@link PcodeEmulator}. This is the default
* thread for that emulator.
*/
public class BytesPcodeThread extends ModifiedPcodeThread<byte[]> {
/**
* Construct a new thread
*
* @see PcodeMachine#newThread(String)
* @param name the thread's name
* @param machine the machine to which the thread belongs
*/
public BytesPcodeThread(String name, AbstractPcodeMachine<byte[]> machine) {
super(name, machine);
}
}

View file

@ -0,0 +1,683 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.math.BigInteger;
import java.util.*;
import ghidra.app.emulator.Emulator;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
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.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.util.ProgramContextImpl;
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
* {@link #beginInstructionOrInject()} and {@link #advanceAfterFinished()}.
*/
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.
*
* @param <T> no particular type, except to match the thread's
*/
public static class PcodeEmulationLibrary<T> extends AnnotatedPcodeUseropLibrary<T> {
private final DefaultPcodeThread<T> thread;
/**
* Construct a library to control the given thread
*
* @param thread the thread
*/
public PcodeEmulationLibrary(DefaultPcodeThread<T> thread) {
this.thread = thread;
}
/**
* Execute the actual machine instruction at the current program counter
*
* <p>
* Because "injects" override the machine instruction, injects which need to defer to the
* machine instruction must invoke this userop.
*
* @see #emu_skip_decoded()
*/
@PcodeUserop
public void emu_exec_decoded() {
/**
* TODO: This idea of "pushing" a frame could be formalized, and the full stack made
* accessible to the client. This would permit "stepping into", and provide continuation
* after an interrupt. The caveat however, is whatever Java code invoked the inner frame
* cannot be continued/resumed. Such code could provide nothing more than glue.
*/
PcodeFrame saved = thread.frame;
thread.dropInstruction();
thread.executeInstruction();
thread.frame = saved;
}
/**
* Advance the program counter beyond the current machine instruction
*
* <p>
* Because "injects" override the machine instruction, they must specify the effect on the
* program counter, lest the thread become caught in an infinite loop on the inject. To
* emulate fall-through without executing the machine instruction, the inject must invoke
* this userop.
*
* @see #emu_exec_decoded()
*/
@PcodeUserop
public void emu_skip_decoded() {
PcodeFrame saved = thread.frame;
thread.dropInstruction();
thread.skipInstruction();
thread.frame = saved;
}
/**
* Interrupt execution
*
* <p>
* This immediately throws an {@link InterruptPcodeExecutionException}. To implement
* out-of-band breakpoints, inject an invocation of this userop at the desired address.
*
* @see PcodeMachine#addBreakpoint(Address, String)
*/
@PcodeUserop
public void emu_swi() {
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
* calls to this p-code op. Then, only if and when an erroneous inject is encountered will
* the client be notified.
*/
@PcodeUserop
public 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.
*/
public static class PcodeThreadExecutor<T> extends PcodeExecutor<T> {
volatile boolean suspended = false;
protected final DefaultPcodeThread<T> thread;
/**
* Construct the executor
*
* @see DefaultPcodeThread#createExecutor()
* @param language the language of the containing machine
* @param arithmetic the arithmetic of the containing machine
* @param state the composite state assigned to the thread
*/
public PcodeThreadExecutor(DefaultPcodeThread<T> thread) {
super(thread.language, thread.arithmetic, thread.state, Reason.EXECUTE);
this.thread = thread;
}
@Override
public void executeSleigh(String source) {
PcodeProgram program =
SleighProgramCompiler.compileProgram(language, "exec", source, thread.library);
execute(program, thread.library);
}
@Override
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
if (suspended || thread.machine.suspended) {
throw new SuspendedPcodeExecutionException(frame, null);
}
super.stepOp(op, frame, library);
thread.stepped();
}
@Override
protected void checkLoad(AddressSpace space, T offset, int size) {
thread.checkLoad(space, offset, size);
}
@Override
protected void checkStore(AddressSpace space, T offset, int size) {
thread.checkStore(space, offset, size);
}
@Override
protected void branchToAddress(Address target) {
thread.branchToAddress(target);
}
@Override
protected void onMissingUseropDef(PcodeOp op, PcodeFrame frame, String opName,
PcodeUseropLibrary<T> library) {
if (!thread.onMissingUseropDef(op, opName)) {
super.onMissingUseropDef(op, frame, opName, library);
}
}
/**
* Get the thread owning this executor
*
* @return the thread
*/
public DefaultPcodeThread<T> getThread() {
return thread;
}
}
private final String name;
private final AbstractPcodeMachine<T> machine;
protected final SleighLanguage language;
protected final PcodeArithmetic<T> arithmetic;
protected final ThreadPcodeExecutorState<T> state;
protected final InstructionDecoder decoder;
protected final PcodeUseropLibrary<T> library;
protected final PcodeThreadExecutor<T> executor;
protected final Register pc;
protected final Register contextreg;
private Address counter;
private RegisterValue context;
protected Instruction instruction;
protected PcodeFrame frame;
protected final ProgramContextImpl defaultContext;
protected final Map<Address, PcodeProgram> injects = new HashMap<>();
/**
* Construct a new thread
*
* @see AbstractPcodeMachine#createThread(String)
* @param name the name of the thread
* @param machine the machine containing the thread
*/
public DefaultPcodeThread(String name, AbstractPcodeMachine<T> machine) {
this.name = name;
this.machine = machine;
this.language = machine.language;
this.arithmetic = machine.arithmetic;
PcodeExecutorState<T> sharedState = machine.getSharedState();
PcodeExecutorState<T> localState = machine.createLocalState(this);
this.state = new ThreadPcodeExecutorState<>(sharedState, localState);
this.decoder = createInstructionDecoder(sharedState);
this.library = createUseropLibrary();
this.executor = createExecutor();
this.pc =
Objects.requireNonNull(language.getProgramCounter(), "Language has no program counter");
this.contextreg = language.getContextBaseRegister();
if (contextreg != Register.NO_CONTEXT) {
defaultContext = new ProgramContextImpl(language);
language.applyContextSettings(defaultContext);
this.context = defaultContext.getDefaultDisassemblyContext();
}
else {
defaultContext = null;
}
this.reInitialize();
}
/**
* A factory method for the instruction decoder
*
* @param sharedState the machine's shared (memory state)
* @return
*/
protected SleighInstructionDecoder createInstructionDecoder(PcodeExecutorState<T> sharedState) {
return new SleighInstructionDecoder(language, sharedState);
}
/**
* A factory method to create the complete userop library for this thread
*
* <p>
* The returned library must compose the containing machine's shared userop library. See
* {@link PcodeUseropLibrary#compose(PcodeUseropLibrary)}.
*
* @return the thread's complete userop library
*/
protected PcodeUseropLibrary<T> createUseropLibrary() {
return new PcodeEmulationLibrary<>(this).compose(machine.library);
}
/**
* A factory method to create the executor for this thread
*
* @return the executor
*/
protected PcodeThreadExecutor<T> createExecutor() {
return new PcodeThreadExecutor<>(this);
}
@Override
public String getName() {
return name;
}
@Override
public AbstractPcodeMachine<T> getMachine() {
return machine;
}
@Override
public void setCounter(Address counter) {
this.counter = counter;
}
@Override
public Address getCounter() {
return counter;
}
protected void branchToAddress(Address target) {
overrideCounter(target);
decoder.branched(counter);
}
@Override
public void overrideCounter(Address counter) {
setCounter(counter);
state.setVar(pc,
arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize()));
}
@Override
public void assignContext(RegisterValue context) {
if (!context.getRegister().isProcessorContext()) {
throw new IllegalArgumentException("context must be the contextreg value");
}
this.context = this.context.assign(context.getRegister(), context);
}
@Override
public RegisterValue getContext() {
return context;
}
@Override
public void overrideContext(RegisterValue context) {
assignContext(context);
state.setVar(contextreg, arithmetic.fromConst(
this.context.getUnsignedValueIgnoreMask(),
contextreg.getMinimumByteSize(), true));
}
@Override
public void overrideContextWithDefault() {
if (contextreg != Register.NO_CONTEXT) {
overrideContext(defaultContext.getDefaultValue(contextreg, counter));
}
}
/**
* Execute the initializer upon this thread, if applicable
*
* @see AbstractPcodeMachine#getPluggableInitializer(Language)
*/
protected void doPluggableInitialization() {
if (machine.initializer != null) {
machine.initializer.initializeThread(this);
}
}
@Override
public void reInitialize() {
long offset = arithmetic.toLong(state.getVar(pc, executor.getReason()), Purpose.BRANCH);
setCounter(language.getDefaultSpace().getAddress(offset, true));
if (contextreg != Register.NO_CONTEXT) {
try {
BigInteger ctx = arithmetic.toBigInteger(state.getVar(
contextreg, executor.getReason()), Purpose.CONTEXT);
assignContext(new RegisterValue(contextreg, ctx));
}
catch (AccessPcodeExecutionException e) {
Msg.info(this, "contextreg not recorded in trace. This is pretty normal.");
}
}
doPluggableInitialization();
}
@Override
public void stepInstruction() {
PcodeProgram inj = getInject(counter);
if (inj != null) {
instruction = null;
try {
executor.execute(inj, library);
}
catch (PcodeExecutionException e) {
frame = e.getFrame();
throw e;
}
}
else {
executeInstruction();
}
}
@Override
public void stepPcodeOp() {
if (frame == null) {
beginInstructionOrInject();
}
else if (!frame.isFinished()) {
executor.step(frame, library);
}
else {
advanceAfterFinished();
}
}
@Override
public void skipPcodeOp() {
if (frame == null) {
beginInstructionOrInject();
}
else if (!frame.isFinished()) {
executor.skip(frame);
}
else {
advanceAfterFinished();
}
}
/**
* Start execution of the instruction or inject at the program counter
*/
protected void beginInstructionOrInject() {
PcodeProgram inj = getInject(counter);
if (inj != null) {
instruction = null;
frame = executor.begin(inj);
}
else {
instruction = decoder.decodeInstruction(counter, context);
PcodeProgram pcode = PcodeProgram.fromInstruction(instruction);
frame = executor.begin(pcode);
}
}
/**
* Resolve a finished instruction, advancing the program counter if necessary
*/
protected void advanceAfterFinished() {
if (instruction == null) { // Frame resulted from an inject
frame = null;
return;
}
if (frame.isFallThrough()) {
overrideCounter(counter.addWrap(decoder.getLastLengthWithDelays()));
}
if (contextreg != Register.NO_CONTEXT) {
overrideContext(defaultContext.getFlowValue(instruction.getRegisterValue(contextreg)));
}
postExecuteInstruction();
frame = null;
instruction = null;
}
@Override
public PcodeFrame getFrame() {
return frame;
}
@Override
public Instruction getInstruction() {
return instruction;
}
/**
* A sanity-checking measure: Cannot start a new instruction while one is still being executed
*/
protected void assertCompletedInstruction() {
if (frame != null) {
throw new IllegalStateException("The current instruction or inject has not finished.");
}
}
/**
* A sanity-checking measure: Cannot finish an instruction unless one is currently being
* executed
*/
protected void assertMidInstruction() {
if (frame == null) {
throw new IllegalStateException("There is no current instruction to finish.");
}
}
/**
* 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.
*/
protected void preExecuteInstruction() {
}
/**
* 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.
*/
protected void postExecuteInstruction() {
}
/**
* Extension point: Behavior when a p-code userop definition is not found
*
* @param op the op
* @param opName the name of the p-code userop
* @return true if handle, false if still undefined
*/
protected boolean onMissingUseropDef(PcodeOp op, String opName) {
return false;
}
@Override
public void executeInstruction() {
assertCompletedInstruction();
instruction = decoder.decodeInstruction(counter, context);
PcodeProgram insProg = PcodeProgram.fromInstruction(instruction);
preExecuteInstruction();
try {
frame = executor.execute(insProg, library);
}
catch (PcodeExecutionException e) {
frame = e.getFrame();
throw e;
}
advanceAfterFinished();
}
@Override
public void finishInstruction() {
assertMidInstruction();
executor.finish(frame, library);
advanceAfterFinished();
}
@Override
public void skipInstruction() {
assertCompletedInstruction();
instruction = decoder.decodeInstruction(counter, context);
overrideCounter(counter.addWrap(decoder.getLastLengthWithDelays()));
}
@Override
public void dropInstruction() {
frame = null;
}
@Override
public void run() {
executor.suspended = false;
if (frame != null) {
finishInstruction();
}
while (true) {
stepInstruction();
}
}
@Override
public void setSuspended(boolean suspended) {
executor.suspended = suspended;
}
@Override
public SleighLanguage getLanguage() {
return language;
}
@Override
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
@Override
public PcodeExecutor<T> getExecutor() {
return executor;
}
@Override
public PcodeUseropLibrary<T> getUseropLibrary() {
return library;
}
@Override
public ThreadPcodeExecutorState<T> getState() {
return state;
}
/**
* Check for a p-code injection (override) at the given address
*
* <p>
* This checks this thread's particular injects and then defers to the machine's injects.
*
* @param address the address, usually the program counter
* @return the injected program, most likely {@code null}
*/
protected PcodeProgram getInject(Address address) {
PcodeProgram inj = injects.get(address);
if (inj != null) {
return inj;
}
return machine.getInject(address);
}
@Override
public void inject(Address address, String source) {
PcodeProgram pcode = SleighProgramCompiler.compileProgram(
language, "thread_inject:" + address, source, library);
injects.put(address, pcode);
}
@Override
public void clearInject(Address address) {
injects.remove(address);
}
@Override
public void clearAllInjects() {
injects.clear();
}
/**
* Perform checks on a requested LOAD
*
* <p>
* Throw an exception if the LOAD should cause an interrupt.
*
* @param space the address space being accessed
* @param offset the offset being accessed
* @param size the size of the variable being accessed
*/
protected void checkLoad(AddressSpace space, T offset, int size) {
machine.checkLoad(space, offset, size);
}
/**
* Perform checks on a requested STORE
*
* <p>
* Throw an exception if the STORE should cause an interrupt.
*
* @param space the address space being accessed
* @param offset the offset being accessed
* @param size the size of the variable being accessed
*/
protected void checkStore(AddressSpace space, T offset, int size) {
machine.checkStore(space, offset, size);
}
/**
* Throw a software interrupt exception if those interrupts are active
*/
protected void swi() {
machine.swi();
}
/**
* Notify the machine a thread has been stepped, so that it may re-enable software interrupts,
* if applicable
*/
protected void stepped() {
machine.stepped();
}
}

View file

@ -0,0 +1,58 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction;
/**
* A means of decoding machine instructions from the bytes contained in the machine state
*/
public interface InstructionDecoder {
/**
* Decode the instruction starting at the given address using the given context
*
* <p>
* This method cannot return null. If a decode error occurs, it must throw an exception.
*
* @param address the address to start decoding
* @param context the disassembler/decode context
* @return the instruction
*/
Instruction decodeInstruction(Address address, RegisterValue context);
/**
* Inform the decoder that the emulator thread just branched
*
* @param address
*/
void branched(Address address);
/**
* Get the last instruction decoded
*
* @return the instruction
*/
Instruction getLastInstruction();
/**
* Get the length of the last decoded instruction, including delay slots
*
* @return the length
*/
int getLastLengthWithDelays();
}

View file

@ -0,0 +1,228 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.lang.reflect.Constructor;
import ghidra.app.emulator.Emulator;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emulate.*;
import ghidra.pcode.exec.ConcretionError;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.memstate.MemoryState;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
/**
* A p-code thread which incorporates per-architecture state modifiers
*
* <p>
* All machines that include a concrete state piece, i.e., all emulators, should use threads derived
* from this class. This implementation assumes that the modified state can be concretized. This
* doesn't necessarily require the machine to be a concrete emulator, but an abstract machine must
* avoid or handle {@link ConcretionError}s arising from state modifiers.
*
* <p>
* For a complete example of a p-code emulator, see {@link PcodeEmulator}.
*
* <p>
* TODO: "State modifiers" are a feature of the older {@link Emulator}. They are crudely
* incorporated into threads extended from this abstract class, so that they do not yet need to be
* ported to this emulator.
*/
public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
/**
* Glue for incorporating state modifiers
*
* <p>
* This allows the modifiers to change the context and counter of the thread.
*/
protected class GlueEmulate extends Emulate {
public GlueEmulate(SleighLanguage lang, MemoryState s, BreakTable b) {
super(lang, s, b);
}
@Override
public Language getLanguage() {
return language;
}
@Override
public void setExecuteAddress(Address addr) {
overrideCounter(addr);
}
@Override
public Address getExecuteAddress() {
return getCounter();
}
@Override
public void setContextRegisterValue(RegisterValue regValue) {
overrideContext(regValue);
}
@Override
public RegisterValue getContextRegisterValue() {
return getContext();
}
}
/**
* Glue for incorporating state modifiers
*
* <p>
* This allows the modifiers to access the thread's state (memory and registers).
*/
protected class GlueMemoryState extends MemoryState {
public GlueMemoryState(Language language) {
super(language);
}
@Override
public int getChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) {
return getBytesChunk(res, spc, off, size, stopOnUnintialized);
}
@Override
public void setChunk(byte[] val, AddressSpace spc, long off, int size) {
setBytesChunk(val, spc, off, size);
}
@Override
public void setInitialized(boolean initialized, AddressSpace spc, long off, int size) {
// Do nothing
}
}
/**
* Part of the glue that makes existing state modifiers work in new emulation framework
*
* <p>
* <b>NOTE:</b> These are instantiated one per thread, rather than sharing one across the
* machine, because some of the modifiers are stateful and assume a single-threaded model. The
* best way to mitigate that assumption is to ensure a modifier is responsible for only a single
* thread, even though a machine may have multiple threads.
*/
protected final EmulateInstructionStateModifier modifier;
protected final Emulate emulate;
protected Address savedCounter;
/**
* Construct a new thread with the given name belonging to the given machine
*
* @see PcodeMachine#newThread(String)
* @param name the name of the new thread
* @param machine the machine to which the new thread belongs
*/
public ModifiedPcodeThread(String name, AbstractPcodeMachine<T> machine) {
super(name, machine);
/**
* These two exist as a way to integrate the language-specific injects that are already
* written for {@link Emulator}.
*/
emulate = new GlueEmulate(language, new GlueMemoryState(language),
new BreakTableCallBack(language));
modifier = createModifier();
}
/**
* Construct a modifier for the given language
*
* @return the state modifier
*/
protected EmulateInstructionStateModifier createModifier() {
String classname = language
.getProperty(GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
if (classname == null) {
return null;
}
try {
Class<?> c = Class.forName(classname);
if (!EmulateInstructionStateModifier.class.isAssignableFrom(c)) {
Msg.error(this,
"Language " + language.getLanguageID() + " does not specify a valid " +
GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
throw new RuntimeException(classname + " does not implement interface " +
EmulateInstructionStateModifier.class.getName());
}
Constructor<?> constructor = c.getConstructor(Emulate.class);
return (EmulateInstructionStateModifier) constructor.newInstance(emulate);
}
catch (Exception e) {
Msg.error(this, "Language " + language.getLanguageID() + " does not specify a valid " +
GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
throw new RuntimeException(
"Failed to instantiate " + classname + " for language " + language.getLanguageID(),
e);
}
}
/**
* Called by a state modifier to read concrete bytes from the thread's state
*
* @see {@link MemoryState#getChunk(byte[], AddressSpace, long, int, boolean)}
*/
protected int getBytesChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) {
T t = state.getVar(spc, off, size, true, executor.getReason());
byte[] val = arithmetic.toConcrete(t, Purpose.OTHER);
System.arraycopy(val, 0, res, 0, val.length);
return val.length;
}
/**
* Called by a state modifier to write concrete bytes to the thread's state
*
* @see {@link MemoryState#setChunk(byte[], AddressSpace, long, int)}
*/
protected void setBytesChunk(byte[] val, AddressSpace spc, long off, int size) {
T t = arithmetic.fromConst(val);
state.setVar(spc, off, size, true, t);
}
@Override
public void reInitialize() {
super.reInitialize();
if (modifier != null) {
savedCounter = getCounter();
modifier.initialExecuteCallback(emulate, savedCounter, getContext());
}
}
@Override
protected void postExecuteInstruction() {
if (modifier != null) {
modifier.postExecuteCallback(emulate, savedCounter, frame.copyCode(),
frame.getBranched(), getCounter());
}
}
@Override
protected boolean onMissingUseropDef(PcodeOp op, String opName) {
if (modifier != null) {
return modifier.executeCallOther(op);
}
return super.onMissingUseropDef(op, opName);
}
}

View file

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

View file

@ -0,0 +1,315 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.util.Collection;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* A machine which execute p-code on state of an abstract type
*
* @param <T> the type of objects in the machine's state
*/
public interface PcodeMachine<T> {
/**
* Specifies whether or not to interrupt on p-code breakpoints
*/
enum SwiMode {
/**
* Heed {@link PcodeEmulationLibrary#emu_swi()} calls
*/
ACTIVE,
/**
* Ignore all {@link PcodeEmulationLibrary#emu_swi()} calls
*/
IGNORE_ALL,
/**
* Ignore {@link PcodeEmulationLibrary#emu_swi()} calls for one p-code step
*
* <p>
* The mode is reset to {@link #ACTIVE} after one p-code step, whether or not that step
* causes an SWI.
*/
IGNORE_STEP,
}
/**
* The kind of access breakpoint
*/
enum AccessKind {
/** A read access breakpoint */
R(true, false),
/** A write access breakpoint */
W(false, true),
/** A read/write access breakpoint */
RW(true, true);
private final boolean trapsRead;
private final boolean trapsWrite;
private AccessKind(boolean trapsRead, boolean trapsWrite) {
this.trapsRead = trapsRead;
this.trapsWrite = trapsWrite;
;
}
/**
* Check if this kind of breakpoint should trap a read, i.e., {@link PcodeOp#LOAD}
*
* @return true to interrupt
*/
public boolean trapsRead() {
return trapsRead;
}
/**
* Check if this kind of breakpoint should trap a write, i.e., {@link PcodeOp#STORE}
*
* @return true to interrupt
*/
public boolean trapsWrite() {
return trapsWrite;
}
}
/**
* Get the machine's Sleigh language (processor model)
*
* @return the language
*/
SleighLanguage getLanguage();
/**
* Get the arithmetic applied by the machine
*
* @return the arithmetic
*/
PcodeArithmetic<T> getArithmetic();
/**
* Change the efficacy of p-code breakpoints
*
* <p>
* This is used to prevent breakpoints from interrupting at inappropriate times, e.g., upon
* continuing from a breakpoint.
*
* @param mode the new mode
* @see #withSoftwareInterruptMode(SwiMode)
*/
void setSoftwareInterruptMode(SwiMode mode);
/**
* Get the current software interrupt mode
*
* @return the mode
*/
SwiMode getSoftwareInterruptMode();
/**
* Get the userop library common to all threads in the machine.
*
* <p>
* Note that threads may have larger libraries, but each contains all the userops in this
* library.
*
* @return the userop library
*/
PcodeUseropLibrary<T> getUseropLibrary();
/**
* Get a userop library which at least declares all userops available in each thread userop
* library.
*
* <p>
* Thread userop libraries may have more userops than are defined in the machine's userop
* library. However, to compile Sleigh programs linked to thread libraries, the thread's userops
* must be known to the compiler. The stub library will name all userops common among the
* threads, even if their definitions vary. <b>WARNING:</b> The stub library is not required to
* provide implementations of the userops. Often they will throw exceptions, so do not attempt
* to use the returned library in an executor.
*
* @return the stub library
*/
PcodeUseropLibrary<T> getStubUseropLibrary();
/**
* Create a new thread with a default name in this machine
*
* @return the new thread
*/
PcodeThread<T> newThread();
/**
* Create a new thread with the given name in this machine
*
* @param name the name
* @return the new thread
*/
PcodeThread<T> newThread(String name);
/**
* Get the thread, if present, with the given name
*
* @param name the name
* @param createIfAbsent create a new thread if the thread does not already exist
* @return the thread, or {@code null} if absent and not created
*/
PcodeThread<T> getThread(String name, boolean createIfAbsent);
/**
* Collect all threads present in the machine
*
* @return the collection of threads
*/
Collection<? extends PcodeThread<T>> getAllThreads();
/**
* Get the machine's shared (memory) state
*
* <p>
* The returned state will may throw {@link IllegalArgumentException} if the client requests
* register values of it. This state is shared among all threads in this machine.
*
* @return the memory state
*/
PcodeExecutorState<T> getSharedState();
/**
* Set the suspension state of the machine
*
* @see PcodeThread#setSuspended(boolean)
*/
void setSuspended(boolean suspended);
/**
* Compile the given Sleigh code for execution by a thread of this machine
*
* <p>
* This links in the userop library given at construction time and those defining the emulation
* userops, e.g., {@code emu_swi}.
*
* @param sourceName a user-defined source name for the resulting "program"
* @param lines the Sleigh source
* @return the compiled program
*/
PcodeProgram compileSleigh(String sourceName, String source);
/**
* Override the p-code at the given address with the given Sleigh source
*
* <p>
* This will attempt to compile the given source against this machine's userop library and then
* inject it at the given address. The resulting p-code <em>replaces</em> that which would be
* executed by decoding the instruction at the given address. The means the machine will not
* decode, nor advance its counter, unless the Sleigh causes it. In most cases, the Sleigh will
* call {@link PcodeEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and
* execute the overridden instruction.
*
* <p>
* Each address can have at most a single inject. If there is already one present, it is
* replaced and the old inject completely forgotten. The injector does not support chaining or
* double-wrapping, etc.
*
* <p>
* No synchronization is provided on the internal injection storage. Clients should ensure the
* machine is not executing when injecting p-code. Additionally, the client must ensure only one
* thread is injecting p-code to the machine at a time.
*
* @param address the address to inject at
* @param source the Sleigh source to compile and inject
*/
void inject(Address address, String source);
/**
* Remove the inject, if present, at the given address
*
* @param address the address to clear
*/
void clearInject(Address address);
/**
* Remove all injects from this machine
*
* <p>
* This will clear execution breakpoints, but not access breakpoints. See
* {@link #clearAccessBreakpoints()}.
*/
void clearAllInjects();
/**
* Add a conditional execution breakpoint at the given address
*
* <p>
* Breakpoints are implemented at the p-code level using an inject, without modification to the
* emulated image. As such, it cannot coexist with another inject. A client needing to break
* during an inject must use {@link PcodeEmulationLibrary#emu_swi()} in the injected Sleigh.
*
* <p>
* No synchronization is provided on the internal breakpoint storage. Clients should ensure the
* machine is not executing when adding breakpoints. Additionally, the client must ensure only
* one thread is adding breakpoints to the machine at a time.
*
* @param address the address at which to break
* @param sleighCondition a Sleigh expression which controls the breakpoint
*/
void addBreakpoint(Address address, String sleighCondition);
/**
* Add an access breakpoint over the given range
*
* <p>
* Access breakpoints are implemented out of band, without modification to the emulated image.
* The breakpoints are only effective for p-code {@link PcodeOp#LOAD} and {@link PcodeOp#STORE}
* operations with concrete offsets. Thus, an operation that refers directly to a memory
* address, e.g., a memory-mapped register, will not be trapped. Similarly, access breakpoints
* on registers or unique variables will not work. Access to an abstract offset that cannot be
* made concrete, i.e., via {@link PcodeArithmetic#toConcrete(Object, Purpose)} cannot be
* trapped. To interrupt on direct and/or abstract accesses, consider wrapping the relevant
* state and/or overriding {@link PcodeExecutorStatePiece#getVar(Varnode, Reason)} and related.
* For accesses to abstract offsets, consider overriding
* {@link AbstractPcodeMachine#checkLoad(AddressSpace, Object)} and/or
* {@link AbstractPcodeMachine#checkStore(AddressSpace, Object)} instead.
*
* <p>
* A breakpoint's range cannot cross more than one page boundary. Pages are 4096 bytes each.
* This allows implementations to optimize checking for breakpoints. If a breakpoint does not
* follow this rule, the behavior is undefined. Breakpoints may overlap, but currently no
* indication is given as to which breakpoint interrupted emulation.
*
* <p>
* No synchronization is provided on the internal breakpoint storage. Clients should ensure the
* machine is not executing when adding breakpoints. Additionally, the client must ensure only
* one thread is adding breakpoints to the machine at a time.
*
* @param range the address range to trap
* @param kind the kind of access to trap
*/
void addAccessBreakpoint(AddressRange range, AccessKind kind);
/**
* Remove all access breakpoints from this machine
*/
void clearAccessBreakpoints();
}

View file

@ -0,0 +1,69 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import ghidra.program.model.lang.Language;
import ghidra.util.classfinder.ExtensionPoint;
/**
* An extension for preparing execution state for sleigh emulation
*
* <p>
* As much as possible, it's highly-recommended to use Sleigh execution to perform any
* modifications. This will help it remain agnostic to various state types.
*
* <p>
* TODO: Implement annotation-based {@link #isApplicable(Language)}?
*/
public interface PcodeStateInitializer extends ExtensionPoint {
/**
* Check if this initializer applies to the given language
*
* @param language the language to check
* @return true if it applies, false otherwise
*/
boolean isApplicable(Language language);
/**
* The machine's memory state has just been initialized, and additional initialization is needed
* for Sleigh execution
*
* <p>
* There's probably not much preparation of memory
*
* @param <T> the type of values in the machine state
* @param machine the newly-initialized machine
*/
default <T> void initializeMachine(PcodeMachine<T> machine) {
}
/**
* The thread's register state has just been initialized, and additional initialization is
* needed for Sleigh execution
*
* <p>
* Initialization generally consists of setting "virtual" registers using data from the real
* ones. Virtual registers are those specified in the Sleigh, but which don't actually exist on
* the target processor. Often, they exist to simplify static analysis, but unfortunately cause
* a minor headache for dynamic execution.
*
* @param <T> the type of values in the machine state
* @param thread the newly-initialized thread
*/
default <T> void initializeThread(PcodeThread<T> thread) {
}
}

View file

@ -0,0 +1,366 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.util.List;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
import ghidra.pcode.emu.PcodeMachine.SwiMode;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Instruction;
/**
* An emulated thread of execution
*
* @param <T> the type of values in the emulated machine state
*/
public interface PcodeThread<T> {
/**
* Get the name of this thread
*
* @return the name
*/
String getName();
/**
* Get the machine within which this thread executes
*
* @return the containing machine
*/
PcodeMachine<T> getMachine();
/**
* Set the thread's program counter without writing to its executor state
*
* @see #overrideCounter(Address)
* @param counter the new target address
*/
void setCounter(Address counter);
/**
* Get the value of the program counter of this thread
*
* @return the value
*/
Address getCounter();
/**
* Set the thread's program counter and write the pc register of its executor state
*
* <p>
* <b>Warning:</b> Setting the counter into the middle of group constructs, e.g., parallel
* instructions or delay-slotted instructions, may cause undefined behavior.
*
* @see #setCounter(Address)
* @param counter the new target address
*/
void overrideCounter(Address counter);
/**
* Adjust the thread's decoding context without writing to its executor state
*
* <p>
* As in {@link RegisterValue#assign(Register, RegisterValue)}, only those bits having a value
* in the given context are applied to the current context.
*
* @see #overrideContext(RegisterValue)
* @param context the new context
*/
void assignContext(RegisterValue context);
/**
* Get the thread's decoding context
*
* @return the context
*/
RegisterValue getContext();
/**
* Adjust the thread's decoding context and write the contextreg of its executor state
*
* @see #assignContext(RegisterValue)
* @param context the new context
*/
void overrideContext(RegisterValue context);
/**
* Set the context at the current counter to the default given by the language
*
* <p>
* This also writes the context to the thread's state. For languages without context, this call
* does nothing.
*/
void overrideContextWithDefault();
/**
* Re-sync the decode context and counter address from the machine state
*/
void reInitialize();
/**
* Step emulation a single instruction
*
* <p>
* Note because of the way Ghidra and Sleigh handle delay slots, the execution of an instruction
* with delay slots cannot be separated from the instructions filling those slots. It and its
* slotted instructions are executed in a single "step." However, stepping the individual p-code
* ops is still possible using {@link #stepPcodeOp()}.
*/
void stepInstruction();
/**
* Repeat {@link #stepInstruction()} count times
*
* @param count the number of instructions to step
*/
default void stepInstruction(long count) {
for (long i = 0; i < count; i++) {
stepInstruction();
}
}
/**
* Step emulation a single p-code operation
*
* <p>
* Execution of the current instruction begins if there is no current frame: A new frame is
* constructed and its counter is initialized. If a frame is present, and it has not been
* completed, its next operation is executed and its counter is stepped. If the current frame is
* completed, the machine's program counter is advanced and the current frame is removed.
*
* <p>
* Consider the case of a fall-through instruction: The first p-code step decodes the
* instruction and sets up the p-code frame. The second p-code step executes the first p-code op
* of the frame. Each subsequent p-code step executes the next p-code op until no ops remain.
* The final p-code step detects the fall-through result, advances the counter, and disposes the
* frame. The next p-code step is actually the first p-code step of the next instruction.
*
* <p>
* Consider the case of a branching instruction: The first p-code step decodes the instruction
* and sets up the p-code frame. The second p-code step executes the first p-code op of the
* frame. Each subsequent p-code step executes the next p-code op until an (external) branch is
* executed. That branch itself sets the program counter appropriately. The final p-code step
* detects the branch result and simply disposes the frame. The next p-code step is actually the
* first p-code step of the next instruction.
*
* <p>
* The decode step in both examples is subject to p-code injections. In order to provide the
* most flexibility, there is no enforcement of various emulation state on this method. Expect
* strange behavior for strange call sequences.
*
* <p>
* While this method heeds injects, such injects will obscure the p-code of the instruction
* itself. If the inject executes the instruction, the entire instruction will be executed when
* stepping the {@link PcodeEmulationLibrary#emu_exec_decoded()} userop, since there is not
* (currently) any way to "step into" a userop.
*/
void stepPcodeOp();
/**
* Repeat {@link #stepPcodeOp()} count times
*
* @param count the number of p-code operations to step
*/
default void stepPcodeOp(long count) {
for (long i = 0; i < count; i++) {
stepPcodeOp();
}
}
/**
* Skip emulation of a single p-code operation
*
* <p>
* If there is no current frame, this behaves as in {@link #stepPcodeOp()}. Otherwise, this
* skips the current pcode op, advancing as if a fall-through op. If no ops remain in the frame,
* this behaves as in {@link #stepPcodeOp()}. Please note to skip an extranal branch, the op
* itself must be skipped. "Skipping" the following op, which disposes the frame, cannot prevent
* the branch.
*/
void skipPcodeOp();
/**
* Get the current frame, if present
*
* <p>
* If the client only calls {@link #stepInstruction()} and execution completes normally, this
* method will always return {@code null}. If interrupted, the frame marks where execution of an
* instruction or inject should resume. Depending on the case, the frame may need to be stepped
* back in order to retry the failed p-code operation. If this frame is present, it means that
* the instruction has not been executed completed. Even if the frame
* {@link PcodeFrame#isFinished()},
*
* @return the current frame
*/
PcodeFrame getFrame();
/**
* Get the current decoded instruction, if applicable
*
* @return the instruction
*/
Instruction getInstruction();
/**
* Execute the next instruction, ignoring injects
*
* <p>
* <b>WARNING:</b> This method should likely only be used internally. It steps the current
* instruction, but without any consideration for user injects, e.g., breakpoints. Most clients
* should call {@link #stepInstruction()} instead.
*
* @throws IllegalStateException if the emulator is still in the middle of an instruction. That
* can happen if the machine is interrupted, or if the client has called
* {@link #stepPcodeOp()}.
*/
void executeInstruction();
/**
* Finish execution of the current instruction or inject
*
* <p>
* In general, this method is only used after an interrupt or fault in order to complete the
* p-code of the faulting instruction. Depending on the nature of the interrupt, this behavior
* may not be desired.
*
* @throws IllegalStateException if there is no current instruction, i.e., the emulator has not
* started executing the next instruction, yet.
*/
void finishInstruction();
/**
* Decode, but skip the next instruction
*/
void skipInstruction();
/**
* If there is a current instruction, drop its frame of execution
*
* <p>
* <b>WARNING:</b> This does not revert any state changes caused by a partially-executed
* instruction. It is up to the client to revert the underlying machine state if desired. Note
* the thread's program counter will not be advanced. Likely, the next call to
* {@link #stepInstruction()} will re-start the same instruction. If there is no current
* instruction, this method has no effect.
*/
void dropInstruction();
/**
* Emulate indefinitely
*
* <p>
* This begins or resumes execution of the emulator. If there is a current instruction, that
* instruction is finished. By calling this method, you are "donating" the current Java thread
* to the emulator. This method will not likely return, but instead only terminates via
* exception, e.g., hitting a user breakpoint or becoming suspended. Depending on the use case,
* this method might be invoked from a Java thread dedicated to this emulated thread.
*/
void run();
/**
* Set the suspension state of the thread's executor
*
* <p>
* When {@link #run()} is invoked by a dedicated thread, suspending the pcode thread is the most
* reliable way to halt execution. Note the emulator may halt mid instruction. If this is not
* desired, then upon catching the exception, un-suspend the p-code thread and call
* {@link #finishInstruction()} or {@link #dropInstruction()}.
*/
void setSuspended(boolean suspended);
/**
* Get the thread's Sleigh language (processor model)
*
* @return the language
*/
SleighLanguage getLanguage();
/**
* Get the thread's p-code arithmetic
*
* @return the arithmetic
*/
PcodeArithmetic<T> getArithmetic();
/**
* Get the thread's p-code executor
*
* <p>
* This can be used to execute injected p-code, e.g., as part of implementing a userop, or as
* part of testing, outside the thread's usual control flow. Any new frame generated by the
* executor is ignored by the thread. It retains the instruction frame, if any. Note that
* suspension is implemented by the executor, so if this p-code thread is suspended, the
* executor cannot execute any code.
*
* @return the executor
*/
PcodeExecutor<T> getExecutor();
/**
* Get the complete userop library for this thread
*
* @return the library
*/
PcodeUseropLibrary<T> getUseropLibrary();
/**
* Get the thread's memory and register state
*
* <p>
* The memory part of this state is shared among all threads in the same machine. See
* {@link PcodeMachine#getSharedState()}.
*
*/
ThreadPcodeExecutorState<T> getState();
/**
* Override the p-code at the given address with the given Sleigh source for only this thread
*
* <p>
* This works the same {@link PcodeMachine#inject(Address, List)} but on a per-thread basis.
* Where there is both a machine-level and thread-level inject the thread inject takes
* precedence. Furthermore, the machine-level inject cannot be accessed by the thread-level
* inject.
*
* @param address the address to inject at
* @param source the Sleigh source to compile and inject
*/
void inject(Address address, String source);
/**
* Remove the per-thread inject, if present, at the given address
*
* <p>
* This has no effect on machine-level injects. If there is one present, it will still override
* this thread's p-code if execution reaches the address.
*
* @param address the address to clear
*/
void clearInject(Address address);
/**
* Remove all per-thread injects from this thread
*
* <p>
* All machine-level injects are still effective after this call.
*/
void clearAllInjects();
}

View file

@ -0,0 +1,143 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import ghidra.app.util.PseudoInstruction;
import ghidra.pcode.emulate.InstructionDecodeException;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.disassemble.DisassemblerMessageListener;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* The default instruction decoder, based on Sleigh
*
* <p>
* This simply uses a {@link Disassembler} on the machine's memory state.
*/
public class SleighInstructionDecoder implements InstructionDecoder {
// TODO: Some sort of instruction decode caching?
// Not as important for stepping small distances
// Could become important when dealing with "full system emulation," if we get there.
private static final String DEFAULT_ERROR = "Unknown disassembly error";
protected final Language language;
protected final PcodeExecutorState<?> state;
protected final AddressFactory addrFactory;
protected final Disassembler disassembler;
protected String lastMsg = DEFAULT_ERROR;
protected InstructionBlock block;
protected int lengthWithDelays;
private PseudoInstruction instruction;
/**
* Construct a Sleigh instruction decoder
*
* @see {@link DefaultPcodeThread#createInstructionDecoder(PcodeExecutorState)}
* @param language the language to decoder
* @param state the state containing the target program, probably the shared state of the p-code
* machine. It must be possible to obtain concrete buffers on this state.
*/
public SleighInstructionDecoder(Language language, PcodeExecutorState<?> state) {
this.language = language;
this.state = state;
addrFactory = language.getAddressFactory();
DisassemblerMessageListener listener = msg -> {
Msg.warn(this, msg);
lastMsg = msg;
};
disassembler =
Disassembler.getDisassembler(language, addrFactory, TaskMonitor.DUMMY, listener);
}
@Override
public Instruction decodeInstruction(Address address, RegisterValue context) {
lastMsg = DEFAULT_ERROR;
if (block != null &&
(instruction = (PseudoInstruction) block.getInstructionAt(address)) != null) {
return instruction;
}
/*
* Parse as few instructions as possible. If more are returned, it's because they form a
* parallel instruction group. In that case, I should not have to worry self-modifying code
* within that group, so no need to re-disassemble after each is executed.
*/
block = disassembler.pseudoDisassembleBlock(
state.getConcreteBuffer(address, Purpose.DECODE), context, 1);
if (block == null || block.isEmpty()) {
throw new InstructionDecodeException(lastMsg, address);
}
instruction = (PseudoInstruction) block.getInstructionAt(address);
lengthWithDelays = computeLength();
return instruction;
}
@Override
public void branched(Address address) {
/*
* This shouldn't happen in the middle of a parallel instruction group, but in case the
* group modifies itself and jumps back to itself, this will ensure it is re-disassembled.
*/
block = null;
}
/**
* Compute the "length" of an instruction, including any delay-slotted instructions that follow
*
* @return the length
*/
protected int computeLength() {
int length = instruction.getLength();
int slots = instruction.getDelaySlotDepth();
Instruction ins = instruction;
for (int i = 0; i < slots; i++) {
try {
Address next = ins.getAddress().addNoWrap(ins.getLength());
Instruction ni = block.getInstructionAt(next);
if (ni == null) {
throw new InstructionDecodeException("Failed to parse delay slot instruction",
next);
}
ins = ni;
length += ins.getLength();
}
catch (AddressOverflowException e) {
throw new InstructionDecodeException("Delay slot would exceed address space",
ins.getAddress());
}
}
return length;
}
@Override
public int getLastLengthWithDelays() {
return lengthWithDelays;
}
@Override
public Instruction getLastInstruction() {
return instruction;
}
}

View file

@ -0,0 +1,118 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import ghidra.program.model.address.*;
public class SparseAddressRangeMap<V> {
public static final long PAGE_BITS = 12;
public static final long PAGE_MASK = -1L << PAGE_BITS;
public static final long OFF_MASK = ~PAGE_MASK;
private static class Space<V> {
private final Map<Long, Page<V>> pages = new HashMap<>();
private static long getPageIndex(Address addr) {
return addr.getOffset() >> PAGE_BITS;
}
Entry<AddressRange, V> put(Entry<AddressRange, V> entry) {
AddressRange range = entry.getKey();
long indexMin = getPageIndex(range.getMinAddress());
Page<V> pageMin = pages.computeIfAbsent(indexMin, o -> new Page<>());
pageMin.put(entry);
long indexMax = getPageIndex(range.getMaxAddress());
if (indexMax == indexMin) {
return entry;
}
Page<V> pageMax = pages.computeIfAbsent(indexMax, o -> new Page<>());
return pageMax.put(entry);
}
boolean hasEntry(Address address, Predicate<V> predicate) {
Page<V> page = pages.get(getPageIndex(address));
if (page == null) {
return false;
}
return page.hasEntry(address, predicate);
}
}
private static class Page<V> {
static final Comparator<Entry<AddressRange, ?>> ENTRY_COMPARATOR = Page::compareEntries;
private final List<Entry<AddressRange, V>> entries = new ArrayList<>();
private static int compareEntries(Entry<AddressRange, ?> e1, Entry<AddressRange, ?> e2) {
return e1.getKey().getMinAddress().compareTo(e2.getKey().getMinAddress());
}
Entry<AddressRange, V> put(Entry<AddressRange, V> entry) {
int index = Collections.binarySearch(entries, entry, ENTRY_COMPARATOR);
if (index < 0) {
index = -index - 1;
}
entries.add(index, entry);
return entry;
}
boolean hasEntry(Address address, Predicate<V> predicate) {
for (Entry<AddressRange, V> ent : entries) {
AddressRange range = ent.getKey();
if (range.contains(address)) {
if (predicate.test(ent.getValue())) {
return true;
}
continue;
}
if (address.compareTo(range.getMinAddress()) < 0) {
return false;
}
}
return false;
}
}
private final Map<AddressSpace, Space<V>> spaces = new HashMap<>();
private boolean isEmpty = true;
public Entry<AddressRange, V> put(AddressRange range, V value) {
Space<V> space = spaces.computeIfAbsent(range.getAddressSpace(), s -> new Space<>());
Entry<AddressRange, V> entry = space.put(Map.entry(range, value));
isEmpty = false;
return entry;
}
public boolean hasEntry(Address address, Predicate<V> predicate) {
Space<V> space = spaces.get(address.getAddressSpace());
if (space == null) {
return false;
}
return space.hasEntry(address, predicate);
}
public void clear() {
spaces.clear();
isEmpty = true;
}
public boolean isEmpty() {
return isEmpty;
}
}

View file

@ -0,0 +1,142 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.util.*;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
* A p-code executor state that multiplexes shared and thread-local states for use in a machine that
* models multi-threading
*
* @param <T> the type of values stored in the states
*/
public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
protected final PcodeExecutorState<T> sharedState;
protected final PcodeExecutorState<T> localState;
protected final PcodeArithmetic<T> arithmetic;
/**
* Create a multiplexed state
*
* @see {@link DefaultPcodeThread#DefaultPcodeThread(String, AbstractPcodeMachine)}
* @param sharedState the shared part of the state
* @param localState the thread-local part of the state
*/
public ThreadPcodeExecutorState(PcodeExecutorState<T> sharedState,
PcodeExecutorState<T> localState) {
assert Objects.equals(sharedState.getLanguage(), localState.getLanguage());
assert Objects.equals(sharedState.getArithmetic(), localState.getArithmetic());
this.sharedState = sharedState;
this.localState = localState;
this.arithmetic = sharedState.getArithmetic();
}
@Override
public Language getLanguage() {
return sharedState.getLanguage();
}
@Override
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
@Override
public ThreadPcodeExecutorState<T> fork() {
return new ThreadPcodeExecutorState<>(sharedState.fork(), localState.fork());
}
/**
* Decide whether or not access to the given space is directed to thread-local state
*
* @param space the space
* @return true for thread-local state, false for shared state
*/
protected boolean isThreadLocalSpace(AddressSpace space) {
return space.isRegisterSpace() || space.isUniqueSpace();
}
@Override
public void setVar(AddressSpace space, T offset, int size, boolean quantize, T val) {
if (isThreadLocalSpace(space)) {
localState.setVar(space, offset, size, quantize, val);
return;
}
sharedState.setVar(space, offset, size, quantize, val);
}
@Override
public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) {
if (isThreadLocalSpace(space)) {
return localState.getVar(space, offset, size, quantize, reason);
}
return sharedState.getVar(space, offset, size, quantize, reason);
}
@Override
public Map<Register, T> getRegisterValues() {
Map<Register, T> result = new HashMap<>();
result.putAll(localState.getRegisterValues());
result.putAll(sharedState.getRegisterValues());
return result;
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
assert !isThreadLocalSpace(address.getAddressSpace());
return sharedState.getConcreteBuffer(address, purpose);
}
/**
* Get the shared state
*
* @return the shared state
*/
public PcodeExecutorState<T> getSharedState() {
return sharedState;
}
/**
* Get the thread-local state
*
* @return the thread-local state
*/
public PcodeExecutorState<T> getLocalState() {
return localState;
}
/**
* {@inheritDoc}
*
* <p>
* This will only clear the thread's local state, lest we invoke clear on the shared state for
* every thread. Instead, if necessary, the machine should clear its local state then clear each
* thread's local state.
*/
@Override
public void clear() {
localState.clear();
}
}

View file

@ -0,0 +1,143 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu.auxiliary;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.emu.*;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor;
import ghidra.pcode.exec.*;
import ghidra.program.model.lang.Language;
/**
* An auxiliary emulator parts factory for stand-alone emulation
*
* <p>
* This can manufacture all the parts needed for a stand-alone emulator with concrete and some
* implementation-defined auxiliary state. More capable emulators may also use many of these parts.
* Usually, the additional capabilities deal with how state is loaded and stored or otherwise made
* available to the user. The pattern of use for a stand-alone emulator is usually in a script:
* Create an emulator, initialize its state, write instructions to its memory, create and initialize
* a thread, point its counter at the instructions, instrument, step/run, inspect, and finally
* terminate.
*
* <p>
* This "parts factory" pattern aims to flatten the extension points of the
* {@link AbstractPcodeMachine} and its components into a single class. Its use is not required, but
* may make things easier. It also encapsulates some "special knowledge," that might not otherwise
* be obvious to a developer, e.g., it creates the concrete state pieces, so the developer need not
* guess (or keep up to date) the concrete state piece classes to instantiate.
*
* <p>
* The factory itself should be a singleton object. See the Taint Analyzer for a complete example
* solution using this interface.
*
* @param <U> the type of auxiliary values
*/
public interface AuxEmulatorPartsFactory<U> {
/**
* Get the arithmetic for the emulator given a target langauge
*
* @param language the language
* @return the arithmetic
*/
PcodeArithmetic<U> getArithmetic(Language language);
/**
* Create the userop library for the emulator (used by all threads)
*
* @param emulator the emulator
* @return the userop library
*/
PcodeUseropLibrary<Pair<byte[], U>> createSharedUseropLibrary(AuxPcodeEmulator<U> emulator);
/**
* Create a stub userop library for the emulator's threads
*
* @param emulator the emulator
* @return the library of stubs
*/
PcodeUseropLibrary<Pair<byte[], U>> createLocalUseropStub(AuxPcodeEmulator<U> emulator);
/**
* Create a userop library for a given thread
*
* @param emulator the emulator
* @param thread the thread
* @return the userop library
*/
PcodeUseropLibrary<Pair<byte[], U>> createLocalUseropLibrary(AuxPcodeEmulator<U> emulator,
PcodeThread<Pair<byte[], U>> thread);
/**
* Create an executor for the given thread
*
* <p>
* This allows the implementor to override or intercept the logic for individual p-code
* operations that would not otherwise be possible in the arithmetic, e.g., to print diagnostics
* on a conditional branch.
*
* @param emulator the emulator
* @param thread the thread
* @return the executor
*/
default PcodeThreadExecutor<Pair<byte[], U>> createExecutor(
AuxPcodeEmulator<U> emulator, DefaultPcodeThread<Pair<byte[], U>> thread) {
return new PcodeThreadExecutor<>(thread);
}
/**
* Create a thread with the given name
*
* @param emulator the emulator
* @param name the thread's name
* @return the thread
*/
default PcodeThread<Pair<byte[], U>> createThread(AuxPcodeEmulator<U> emulator, String name) {
return new AuxPcodeThread<>(name, emulator);
}
/**
* Create the shared (memory) state of a new stand-alone emulator
*
* <p>
* This is usually composed of pieces using {@link PairedPcodeExecutorStatePiece}, but it does
* not have to be. It must incorporate the concrete piece provided. It should be self contained
* and relatively fast.
*
* @param emulator the emulator
* @param concrete the concrete piece
* @return the composed state
*/
PcodeExecutorState<Pair<byte[], U>> createSharedState(AuxPcodeEmulator<U> emulator,
BytesPcodeExecutorStatePiece concrete);
/**
* Create the local (register) state of a new stand-alone emulator
*
* <p>
* This is usually composed of pieces using {@link PairedPcodeExecutorStatePiece}, but it does
* not have to be. It must incorporate the concrete piece provided. It should be self contained
* and relatively fast.
*
* @param emulator the emulator
* @param thread the thread
* @param concrete the concrete piece
* @return the composed state
*/
PcodeExecutorState<Pair<byte[], U>> createLocalState(AuxPcodeEmulator<U> emulator,
PcodeThread<Pair<byte[], U>> thread, BytesPcodeExecutorStatePiece concrete);
}

View file

@ -0,0 +1,90 @@
/* ###
* 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.auxiliary;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.emu.AbstractPcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.program.model.lang.Language;
/**
* A stand-alone emulator whose parts are manufactured by a {@link AuxEmulatorPartsFactory}
*
* <p>
* See the parts factory interface: {@link AuxEmulatorPartsFactory}. Also see the Taint Analyzer for
* a complete solution based on this class.
*
* @param <U> the type of auxiliary values
*/
public abstract class AuxPcodeEmulator<U> extends AbstractPcodeMachine<Pair<byte[], U>> {
/**
* Create a new emulator
*
* @param language the language (processor model)
*/
public AuxPcodeEmulator(Language language) {
super(language);
}
/**
* Get the factory that manufactures parts for this emulator
*
* @implNote This should just return a singleton, since it is called repeatedly (without
* caching) during emulator and thread construction. If, for some reason, a singleton
* is not suitable, then this should instantiate it just once and cache the factory
* itself. If cached, it should be done in a thread-safe manner.
*
* @return the factory
*/
protected abstract AuxEmulatorPartsFactory<U> getPartsFactory();
@Override
protected PcodeArithmetic<Pair<byte[], U>> createArithmetic() {
return new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language),
getPartsFactory().getArithmetic(language));
}
@Override
protected PcodeUseropLibrary<Pair<byte[], U>> createUseropLibrary() {
return getPartsFactory().createSharedUseropLibrary(this);
}
@Override
protected PcodeUseropLibrary<Pair<byte[], U>> createThreadStubLibrary() {
return getPartsFactory().createLocalUseropStub(this);
}
@Override
protected PcodeExecutorState<Pair<byte[], U>> createSharedState() {
return getPartsFactory().createSharedState(this,
new BytesPcodeExecutorStatePiece(language));
}
@Override
protected PcodeExecutorState<Pair<byte[], U>> createLocalState(
PcodeThread<Pair<byte[], U>> thread) {
return getPartsFactory().createLocalState(this, thread,
new BytesPcodeExecutorStatePiece(language));
}
@Override
protected PcodeThread<Pair<byte[], U>> createThread(String name) {
return getPartsFactory().createThread(this, name);
}
}

View file

@ -0,0 +1,57 @@
/* ###
* 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.auxiliary;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.emu.ModifiedPcodeThread;
import ghidra.pcode.exec.PcodeUseropLibrary;
/**
* The default thread for {@link AuxPcodeEmulator}
*
* <p>
* Generally, extending this class should not be necessary, as it already defers to the emulator's
* parts factory
*
* @param <U> the type of auxiliary values
*/
public class AuxPcodeThread<U> extends ModifiedPcodeThread<Pair<byte[], U>> {
public AuxPcodeThread(String name, AuxPcodeEmulator<U> emulator) {
super(name, emulator);
}
@Override
public AuxPcodeEmulator<U> getMachine() {
return (AuxPcodeEmulator<U>) super.getMachine();
}
protected AuxEmulatorPartsFactory<U> getPartsFactory() {
return getMachine().getPartsFactory();
}
@Override
protected PcodeUseropLibrary<Pair<byte[], U>> createUseropLibrary() {
return super.createUseropLibrary().compose(
getPartsFactory().createLocalUseropLibrary(getMachine(), this));
}
@Override
protected PcodeThreadExecutor<Pair<byte[], U>> createExecutor() {
return getPartsFactory().createExecutor(getMachine(), this);
}
}

View file

@ -0,0 +1,172 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
/**
* An abstract p-code executor state piece for storing and retrieving bytes as arrays
*
* @param <S> the type of an executor state space, internally associated with an address space
*/
public abstract class AbstractBytesPcodeExecutorStatePiece<S extends BytesPcodeExecutorStateSpace<?>>
extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], byte[], S> {
/**
* A memory buffer bound to a given space in this state
*/
protected class StateMemBuffer implements MemBufferAdapter {
protected final Address address;
protected final BytesPcodeExecutorStateSpace<?> source;
/**
* Construct a buffer bound to the given space, at the given address
*
* @param address the address
* @param source the space
*/
public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace<?> source) {
this.address = address;
this.source = source;
}
@Override
public Address getAddress() {
return address;
}
@Override
public Memory getMemory() {
throw new UnsupportedOperationException();
}
@Override
public boolean isBigEndian() {
return language.isBigEndian();
}
@Override
public int getBytes(ByteBuffer buffer, int addressOffset) {
byte[] data = source.read(address.getOffset() + addressOffset, buffer.remaining(),
Reason.EXECUTE);
buffer.put(data);
return data.length;
}
}
protected final AbstractSpaceMap<S> spaceMap;
/**
* Construct a state for the given language
*
* @param language the language, used for its memory model and arithmetic
*/
public AbstractBytesPcodeExecutorStatePiece(Language language) {
this(language, BytesPcodeArithmetic.forLanguage(language));
}
protected AbstractBytesPcodeExecutorStatePiece(Language language,
AbstractSpaceMap<S> spaceMap) {
this(language, BytesPcodeArithmetic.forLanguage(language), spaceMap);
}
/**
* Construct a state for the given language
*
* @param language the language, used for its memory model
* @param arithmetic the arithmetic
*/
public AbstractBytesPcodeExecutorStatePiece(Language language,
PcodeArithmetic<byte[]> arithmetic) {
super(language, arithmetic, arithmetic);
spaceMap = newSpaceMap();
}
protected AbstractBytesPcodeExecutorStatePiece(Language language,
PcodeArithmetic<byte[]> arithmetic, AbstractSpaceMap<S> spaceMap) {
super(language, arithmetic, arithmetic);
this.spaceMap = spaceMap;
}
/**
* A factory method for this state's space map.
*
* <p>
* Because most of the special logic for extensions is placed in the "state space," i.e., an
* object assigned to a particular address space in the state's language, this factory method
* must provide the map to create and maintain those spaces. That map will in turn be the
* factory of the spaces themselves, allowing extensions to provide additional read/write logic.
*
* @return the new space map
*/
protected abstract AbstractSpaceMap<S> newSpaceMap();
@Override
protected S getForSpace(AddressSpace space, boolean toWrite) {
return spaceMap.getForSpace(space, toWrite);
}
@Override
protected void setInSpace(S space, long offset, int size, byte[] val) {
if (val.length > size) {
throw new IllegalArgumentException(
"Value is larger than variable: " + val.length + " > " + size);
}
if (val.length < size) {
Msg.warn(this, "Value is smaller than variable: " + val.length + " < " + size +
". Zero extending");
val = arithmetic.unaryOp(PcodeOp.INT_ZEXT, size, val.length, val);
}
space.write(offset, val, 0, size);
}
@Override
protected byte[] getFromSpace(S space, long offset, int size, Reason reason) {
byte[] read = space.read(offset, size, reason);
if (read.length != size) {
throw new AccessPcodeExecutionException("Incomplete read (" + read.length +
" of " + size + " bytes)");
}
return read;
}
@Override
protected Map<Register, byte[]> getRegisterValuesFromSpace(S s, List<Register> registers) {
return s.getRegisterValues(registers);
}
@Override
public MemBuffer getConcreteBuffer(Address address, PcodeArithmetic.Purpose purpose) {
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));
}
@Override
public void clear() {
for (S space : spaceMap.values()) {
space.clear();
}
}
}

View file

@ -0,0 +1,347 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
/**
* An abstract executor state piece which internally uses {@code long} to address contents
*
* <p>
* This also provides an internal mechanism for breaking the piece down into the spaces defined by a
* language. It also provides for the special treatment of the {@code unique} space.
*
* @param <A> the type used to address contents, convertible to and from {@code long}
* @param <T> the type of values stored
* @param <S> the type of an execute state space, internally associated with an address space
*/
public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
implements PcodeExecutorStatePiece<A, T> {
/**
* A map of address spaces to objects which store or cache state for that space
*
* @param <S> the type of object for each address space
*/
public abstract static class AbstractSpaceMap<S> {
protected final Map<AddressSpace, S> spaces;
public AbstractSpaceMap() {
this.spaces = new HashMap<>();
}
protected AbstractSpaceMap(Map<AddressSpace, S> spaces) {
this.spaces = spaces;
}
public abstract S getForSpace(AddressSpace space, boolean toWrite);
public Collection<S> values() {
return spaces.values();
}
/**
* Deep copy this map, for use in a forked state (or piece)
*
* @return the copy
*/
public abstract AbstractSpaceMap<S> fork();
/**
* Deep copy the given space
*
* @param s the space
* @return the copy
*/
public abstract S fork(S s);
/**
* Produce a deep copy of the given map
*
* @param spaces the map to copy
* @return the copy
*/
public Map<AddressSpace, S> fork(Map<AddressSpace, S> spaces) {
return spaces.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> fork(e.getValue())));
}
}
/**
* Use this when each S contains the complete state for the address space
*
* @param <S> the type of object for each address space
*/
public abstract static class SimpleSpaceMap<S> extends AbstractSpaceMap<S> {
public SimpleSpaceMap() {
super();
}
protected SimpleSpaceMap(Map<AddressSpace, S> spaces) {
super(spaces);
}
/**
* Construct a new space internally associated with the given address space
*
* <p>
* As the name implies, this often simply wraps {@code S}'s constructor
*
* @param space the address space
* @return the new space
*/
protected abstract S newSpace(AddressSpace space);
@Override
public synchronized S getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space, s -> newSpace(s));
}
}
/**
* Use this when each S is possibly a cache to some other state (backing) object
*
* @param <B> the type of the object backing the cache for each address space
* @param <S> the type of cache for each address space
*/
public abstract static class CacheingSpaceMap<B, S> extends AbstractSpaceMap<S> {
public CacheingSpaceMap() {
super();
}
protected CacheingSpaceMap(Map<AddressSpace, S> spaces) {
super(spaces);
}
/**
* Get the object backing the cache for the given address space
*
* @param space the space
* @return the backing object
*/
protected abstract B getBacking(AddressSpace space);
/**
* Construct a new space internally associated with the given address space, having the
* given backing
*
* <p>
* As the name implies, this often simply wraps {@code S}'s constructor
*
* @param space the address space
* @param backing the backing, if applicable. null for the unique space
* @return the new space
*/
protected abstract S newSpace(AddressSpace space, B backing);
@Override
public synchronized S getForSpace(AddressSpace space, boolean toWrite) {
return spaces.computeIfAbsent(space,
s -> newSpace(s, s.isUniqueSpace() ? null : getBacking(s)));
}
}
protected final Language language;
protected final PcodeArithmetic<A> addressArithmetic;
protected final PcodeArithmetic<T> arithmetic;
protected final AddressSpace uniqueSpace;
/**
* Construct a state piece for the given language and arithmetic
*
* @param language the language (used for its memory model)
* @param arithmetic an arithmetic used to generate default values of {@code T}
*/
public AbstractLongOffsetPcodeExecutorStatePiece(Language language,
PcodeArithmetic<A> addressArithmetic, PcodeArithmetic<T> arithmetic) {
this.language = language;
this.addressArithmetic = addressArithmetic;
this.arithmetic = arithmetic;
uniqueSpace = language.getAddressFactory().getUniqueSpace();
}
@Override
public Language getLanguage() {
return language;
}
@Override
public PcodeArithmetic<A> getAddressArithmetic() {
return addressArithmetic;
}
@Override
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
/**
* Set a value in the unique space
*
* <p>
* Some state pieces treat unique values in a way that merits a separate implementation. This
* permits the standard path to be overridden.
*
* @param offset the offset in unique space to store the value
* @param size the number of bytes to write (the size of the value)
* @param val the value to store
*/
protected void setUnique(long offset, int size, T val) {
S s = getForSpace(uniqueSpace, true);
setInSpace(s, offset, size, val);
}
/**
* Get a value from the unique space
*
* Some state pieces treat unique values in a way that merits a separate implementation. This
* permits the standard path to be overridden.
*
* @param offset the offset in unique space to get the value
* @param size the number of bytes to read (the size of the value)
* @param reason the reason for reading state
* @return the read value
*/
protected T getUnique(long offset, int size, Reason reason) {
S s = getForSpace(uniqueSpace, false);
return getFromSpace(s, offset, size, reason);
}
/**
* Get the internal space for the given address space
*
* @param space the address space
* @param toWrite in case internal spaces are generated lazily, this indicates the space must be
* present, because it is going to be written to.
* @return the space, or {@code null}
* @see AbstractSpaceMap
*/
protected abstract S getForSpace(AddressSpace space, boolean toWrite);
/**
* Set a value in the given space
*
* @param space the address space
* @param offset the offset within the space
* @param size the number of bytes to write (the size of the value)
* @param val the value to store
*/
protected abstract void setInSpace(S space, long offset, int size, T val);
/**
* Get a value from the given space
*
* @param space the address space
* @param offset the offset within the space
* @param size the number of bytes to read (the size of the value)
* @param reason the reason for reading state
* @return the read value
*/
protected abstract T getFromSpace(S space, long offset, int size, Reason reason);
/**
* In case spaces are generated lazily, and we're reading from a space that doesn't yet exist,
* "read" a default value.
*
* <p>
* By default, the returned value is 0, which should be reasonable for all implementations.
*
* @param size the number of bytes to read (the size of the value)
* @param reason the reason for reading state
* @return the default value
*/
protected T getFromNullSpace(int size, Reason reason) {
return arithmetic.fromConst(0, size);
}
@Override
public void setVar(AddressSpace space, A offset, int size, boolean quantize, T val) {
long lOffset = addressArithmetic.toLong(offset, Purpose.STORE);
setVar(space, lOffset, size, quantize, val);
}
@Override
public void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) {
checkRange(space, offset, size);
if (space.isConstantSpace()) {
throw new IllegalArgumentException("Cannot write to constant space");
}
if (space.isUniqueSpace()) {
setUnique(offset, size, val);
return;
}
S s = getForSpace(space, true);
offset = quantizeOffset(space, offset);
setInSpace(s, offset, size, val);
}
@Override
public T getVar(AddressSpace space, A offset, int size, boolean quantize, Reason reason) {
long lOffset = addressArithmetic.toLong(offset, Purpose.LOAD);
return getVar(space, lOffset, size, quantize, reason);
}
@Override
public T getVar(AddressSpace space, long offset, int size, boolean quantize,
Reason reason) {
checkRange(space, offset, size);
if (space.isConstantSpace()) {
return arithmetic.fromConst(offset, size);
}
if (space.isUniqueSpace()) {
return getUnique(offset, size, reason);
}
S s = getForSpace(space, false);
if (s == null) {
return getFromNullSpace(size, reason);
}
offset = quantizeOffset(space, offset);
return getFromSpace(s, offset, size, reason);
}
/**
* Can the given space for register values, as in {@link #getRegisterValues()}
*
* @param s the space to scan
* @param registers the registers known to be in the corresponding address space
* @return the map of registers to values
*/
protected abstract Map<Register, T> getRegisterValuesFromSpace(S s, List<Register> registers);
@Override
public Map<Register, T> getRegisterValues() {
Map<AddressSpace, List<Register>> regsBySpace = language.getRegisters()
.stream()
.collect(Collectors.groupingBy(Register::getAddressSpace));
Map<Register, T> result = new HashMap<>();
for (Map.Entry<AddressSpace, List<Register>> ent : regsBySpace.entrySet()) {
S s = getForSpace(ent.getKey(), false);
if (s == null) {
continue;
}
result.putAll(getRegisterValuesFromSpace(s, ent.getValue()));
}
return result;
}
}

View file

@ -0,0 +1,33 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
/**
* There was an issue accessing the executor's state, i.e., memory or register values
*/
public class AccessPcodeExecutionException extends PcodeExecutionException {
public AccessPcodeExecutionException(String message, PcodeFrame frame, Throwable cause) {
super(message, frame, cause);
}
public AccessPcodeExecutionException(String message, Exception cause) {
super(message, cause);
}
public AccessPcodeExecutionException(String message) {
super(message);
}
}

View file

@ -0,0 +1,91 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import javax.help.UnsupportedOperationException;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.Endian;
/**
* An auxilliary arithmetic that reports the union of all addresses read, typically during the
* evaluation of an expression.
*/
public enum AddressesReadPcodeArithmetic implements PcodeArithmetic<AddressSetView> {
/** The singleton instance */
INSTANCE;
@Override
public Endian getEndian() {
return null;
}
@Override
public AddressSetView unaryOp(int opcode, int sizeout, int sizein1, AddressSetView in1) {
return in1;
}
@Override
public AddressSetView binaryOp(int opcode, int sizeout, int sizein1, AddressSetView in1,
int sizein2, AddressSetView in2) {
return in1.union(in2);
}
@Override
public AddressSetView modBeforeStore(int sizeout, int sizeinAddress, AddressSetView inAddress,
int sizeinValue, AddressSetView inValue) {
return inValue;
}
@Override
public AddressSetView modAfterLoad(int sizeout, int sizeinAddress, AddressSetView inAddress,
int sizeinValue, AddressSetView inValue) {
return inValue.union(inAddress);
}
@Override
public AddressSetView fromConst(byte[] value) {
return new AddressSet();
}
@Override
public AddressSetView fromConst(BigInteger value, int size, boolean isContextreg) {
return new AddressSet();
}
@Override
public AddressSetView fromConst(BigInteger value, int size) {
return new AddressSet();
}
@Override
public AddressSetView fromConst(long value, int size) {
return new AddressSet();
}
@Override
public byte[] toConcrete(AddressSetView value, Purpose purpose) {
throw new ConcretionError("Cannot make 'addresses read' concrete", purpose);
}
@Override
public long sizeOf(AddressSetView value) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,589 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.lang.annotation.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.reflect.TypeUtils;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.pcode.Varnode;
import utilities.util.AnnotationUtilities;
/**
* A userop library wherein Java methods are exported via a special annotation
*
* <p>
* See {@code StandAloneEmuExampleScript} for an example of implementing a userop library.
*
* @param <T> the type of data processed by the library
*/
public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibrary<T> {
private static final Map<Class<?>, Set<Method>> CACHE_BY_CLASS = new HashMap<>();
private static Set<Method> collectDefinitions(
Class<? extends AnnotatedPcodeUseropLibrary<?>> cls) {
return AnnotationUtilities.collectAnnotatedMethods(PcodeUserop.class, cls);
}
private enum ParamAnnotProc {
EXECUTOR(OpExecutor.class, PcodeExecutor.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posExecutor;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posExecutor = pos;
}
},
STATE(OpState.class, PcodeExecutorState.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posState;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posState = pos;
}
},
LIBRARY(OpLibrary.class, PcodeUseropLibrary.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posLib;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posLib = pos;
}
},
OUTPUT(OpOutput.class, Varnode.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posOut;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posOut = pos;
}
};
static boolean processParameter(AnnotatedPcodeUseropDefinition<?> opdef, Type declClsOpType,
int i, Parameter p) {
ParamAnnotProc only = null;
for (ParamAnnotProc proc : ParamAnnotProc.values()) {
if (proc.hasAnnot(p)) {
if (only != null) {
throw new IllegalArgumentException("Parameter can have at most one of " +
Stream.of(ParamAnnotProc.values())
.map(pr -> "@" + pr.annotCls.getSimpleName())
.collect(Collectors.toList()));
}
only = proc;
}
}
if (only == null) {
return false;
}
only.processParameterPerAnnot(opdef, declClsOpType, i, p);
return true;
}
private final Class<? extends Annotation> annotCls;
private final Class<?> paramCls;
private ParamAnnotProc(Class<? extends Annotation> annotCls, Class<?> paramCls) {
this.annotCls = annotCls;
this.paramCls = paramCls;
}
abstract int getPos(AnnotatedPcodeUseropDefinition<?> opdef);
abstract void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos);
boolean hasAnnot(Parameter p) {
return p.getAnnotation(annotCls) != null;
}
Type getArgumentType(Type opType) {
TypeVariable<?>[] typeParams = paramCls.getTypeParameters();
if (typeParams.length == 0) {
return paramCls;
}
if (typeParams.length == 1) {
return TypeUtils.parameterize(paramCls, opType);
}
throw new AssertionError();
}
void processParameterPerAnnot(AnnotatedPcodeUseropDefinition<?> opdef, Type declClsOpType,
int i, Parameter p) {
if (getPos(opdef) != -1) {
throw new IllegalArgumentException(
"Can only have one parameter with @" + annotCls.getSimpleName());
}
Type pType = p.getParameterizedType();
Map<TypeVariable<?>, Type> typeArgs = TypeUtils.getTypeArguments(pType, paramCls);
if (typeArgs == null) {
throw new IllegalArgumentException("Parameter " + p.getName() + " with @" +
annotCls.getSimpleName() + " must acccept " + getArgumentType(declClsOpType));
}
if (typeArgs.isEmpty()) {
// Nothing
}
else if (typeArgs.size() == 1) {
Type declMthOpType = typeArgs.get(paramCls.getTypeParameters()[0]);
if (!Objects.equals(declClsOpType, declMthOpType)) {
throw new IllegalArgumentException("Parameter " + p.getName() + " with @" +
annotCls.getSimpleName() + " must acccept " +
getArgumentType(declClsOpType));
}
}
else {
throw new AssertionError("Internal: paramCls for @" + annotCls.getSimpleName() +
"should only have one type parameter <T>");
}
setPos(opdef, i);
}
}
/**
* A wrapped, annotated Java method, exported as a userop definition
*
* @param <T> the type of data processed by the userop
*/
protected static abstract class AnnotatedPcodeUseropDefinition<T>
implements PcodeUseropDefinition<T> {
protected static <T> AnnotatedPcodeUseropDefinition<T> create(PcodeUserop annot,
AnnotatedPcodeUseropLibrary<T> library, Type opType, Lookup lookup, Method method) {
if (annot.variadic()) {
return new VariadicAnnotatedPcodeUseropDefinition<>(library, opType, lookup,
method);
}
else {
return new FixedArgsAnnotatedPcodeUseropDefinition<>(library, opType, lookup,
method);
}
}
protected final Method method;
private final MethodHandle handle;
private int posExecutor = -1;
private int posState = -1;
private int posLib = -1;
private int posOut = -1;
public AnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary<T> library, Type opType,
Lookup lookup, Method method) {
initStarting();
this.method = method;
try {
this.handle = lookup.unreflect(method).bindTo(library);
}
catch (IllegalAccessException e) {
throw new IllegalArgumentException(
"Cannot access " + method + " having @" +
PcodeUserop.class.getSimpleName() +
" annotation. Override getMethodLookup()");
}
Type declClsOpType = PcodeUseropLibrary.getOperandType(method.getDeclaringClass());
Type rType = method.getGenericReturnType();
if (rType != void.class && !TypeUtils.isAssignable(rType, declClsOpType)) {
throw new IllegalArgumentException(
"Method " + method.getName() + " with @" +
PcodeUserop.class.getSimpleName() +
" annotation must return void or a type assignable to " + declClsOpType);
}
Parameter[] params = method.getParameters();
for (int i = 0; i < params.length; i++) {
Parameter p = params[i];
boolean processed = ParamAnnotProc.processParameter(this, declClsOpType, i, p);
if (!processed) {
processNonAnnotatedParameter(declClsOpType, opType, i, p);
}
}
initFinished();
}
@Override
public String getName() {
return method.getName();
}
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> inVars) {
validateInputs(inVars);
PcodeExecutorStatePiece<T, T> state = executor.getState();
List<Object> args = Arrays.asList(new Object[method.getParameterCount()]);
if (posExecutor != -1) {
args.set(posExecutor, executor);
}
if (posState != -1) {
args.set(posState, state);
}
if (posLib != -1) {
args.set(posLib, library);
}
if (posOut != -1) {
args.set(posOut, outVar);
}
placeInputs(executor, args, inVars);
try {
@SuppressWarnings("unchecked")
T result = (T) handle.invokeWithArguments(args);
if (result != null && outVar != null) {
state.setVar(outVar, result);
}
}
catch (PcodeExecutionException e) {
throw e;
}
catch (Throwable e) {
throw new PcodeExecutionException("Error executing userop", null, e);
}
}
protected void initStarting() {
// Optional override
}
protected abstract void processNonAnnotatedParameter(Type declClsOpType, Type opType, int i,
Parameter p);
protected void initFinished() {
// Optional override
}
protected void validateInputs(List<Varnode> inVars) throws PcodeExecutionException {
// Optional override
}
protected abstract void placeInputs(PcodeExecutor<T> executor, List<Object> args,
List<Varnode> inVars);
}
/**
* An annotated userop with a fixed number of arguments
*
* @param <T> the type of data processed by the userop
*/
protected static class FixedArgsAnnotatedPcodeUseropDefinition<T>
extends AnnotatedPcodeUseropDefinition<T> {
private List<Integer> posIns;
private Set<Integer> posTs;
public FixedArgsAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary<T> library,
Type opType, Lookup lookup, Method method) {
super(library, opType, lookup, method);
}
@Override
protected void initStarting() {
posIns = new ArrayList<>();
posTs = new HashSet<>();
}
@Override
protected void processNonAnnotatedParameter(Type declClsOpType, Type opType, int i,
Parameter p) {
Type pType = p.getParameterizedType();
if (TypeUtils.isAssignable(Varnode.class, pType)) {
// Just use the Varnode by default
}
else if (TypeUtils.isAssignable(declClsOpType, pType)) {
posTs.add(i);
}
else {
throw new IllegalArgumentException("Input parameter " + p.getName() +
" of userop " + method.getName() + " must be " +
Varnode.class.getSimpleName() + " or accept " + declClsOpType);
}
posIns.add(i);
}
@Override
protected void validateInputs(List<Varnode> inVars)
throws PcodeExecutionException {
if (inVars.size() != posIns.size()) {
throw new PcodeExecutionException(
"Incorrect input parameter count for userop " +
method.getName() + ". Expected " + posIns.size() + " but got " +
inVars.size());
}
}
@Override
protected void placeInputs(PcodeExecutor<T> executor, List<Object> args,
List<Varnode> inVars) {
PcodeExecutorStatePiece<T, T> state = executor.getState();
for (int i = 0; i < posIns.size(); i++) {
int pos = posIns.get(i);
if (posTs.contains(pos)) {
args.set(pos, state.getVar(inVars.get(i), executor.getReason()));
}
else {
args.set(pos, inVars.get(i));
}
}
}
@Override
public int getInputCount() {
return posIns.size();
}
}
/**
* An annotated userop with a variable number of arguments
*
* @param <T> the type of data processed by the userop
*/
protected static class VariadicAnnotatedPcodeUseropDefinition<T>
extends AnnotatedPcodeUseropDefinition<T> {
private int posIns;
private Class<?> opRawType;
public VariadicAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary<T> library,
Type opType, Lookup lookup, Method method) {
super(library, opType, lookup, method);
}
@Override
protected void initStarting() {
posIns = -1;
opRawType = null;
}
@Override
protected void processNonAnnotatedParameter(Type declClsOpType, Type opType, int i,
Parameter p) {
if (posIns != -1) {
throw new IllegalArgumentException(
"Only one non-annotated parameter is allowed to receive the inputs");
}
Type pType = p.getParameterizedType();
Type eType = TypeUtils.getArrayComponentType(pType);
if (eType == null) {
throw new IllegalArgumentException(
"Variadic userop must receive inputs as " + declClsOpType + "[] or " +
Varnode.class.getSimpleName() + "[]");
}
if (pType.equals(Varnode[].class)) {
// Just pass inVars as is
}
else if (TypeUtils.isAssignable(declClsOpType, eType)) {
this.opRawType = TypeUtils.getRawType(opType, getClass());
}
else {
throw new IllegalArgumentException(
"Variadic userop must receive inputs as " + declClsOpType + "[] or " +
Varnode.class.getSimpleName() + "[]");
}
posIns = i;
}
@Override
protected void initFinished() {
if (posIns == -1) {
throw new IllegalArgumentException(
"Variadic userop must have a parameter for the inputs");
}
}
protected Object[] readVars(PcodeExecutorState<T> state, List<Varnode> vars,
Reason reason) {
Object[] vals = (Object[]) Array.newInstance(opRawType, vars.size());
for (int i = 0; i < vals.length; i++) {
vals[i] = state.getVar(vars.get(i), reason);
}
return vals;
}
@Override
protected void placeInputs(PcodeExecutor<T> executor, List<Object> args,
List<Varnode> inVars) {
if (opRawType != null) {
args.set(posIns, readVars(executor.getState(), inVars, executor.getReason()));
}
else {
args.set(posIns, inVars.toArray(Varnode[]::new));
}
}
@Override
public int getInputCount() {
return -1;
}
}
/**
* An annotation to export a Java method as a userop in the library.
*
* <p>
* Ordinarily, each parameter receives an input to the userop. Each parameter may be annotated
* with at most one of {@link OpExecutor}, {@link OpState}, {@link OpLibrary}, or
* {@link OpOutput} to change what it receives. If {@link #variadic()} is false, non-annotated
* parameters receive the inputs to the userop in matching order. Conventionally, annotated
* parameters should be placed first or last. Parameters accepting inputs must have type either
* {@link Varnode} or assignable from {@code T}. A parameter of type {@link Varnode} will
* receive the input {@link Varnode}. A parameter that is assignable from {@code T} will receive
* the input value. If it so happens that {@code T} is assignable from {@link Varnode}, the
* parameter will receive the {@link Varnode}, not the value. <b>NOTE:</b> Receiving a value
* instead of a variable may lose its size. Depending on the type of the value, that size may or
* may not be recoverable.
*
* <p>
* If {@link #variadic()} is true, then a single non-annotated parameter receives all inputs in
* order. This parameter must have a type {@link Varnode}{@code []} to receive variables or have
* type assignable from {@code T[]} to receive values.
*
* <p>
* Note that there is no annotation to receive the "thread," because threads are not a concept
* known to the p-code executor or userop libraries, in general. In most cases, receiving the
* executor and/or state (which are usually bound to a specific thread) is sufficient. The
* preferred means of exposing thread-specific userops is to construct a library bound to that
* specific thread. That strategy should preserve compile-time type safety. Alternatively, you
* can receive the executor or state, cast it to your specific type, and use an accessor to get
* its thread.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PcodeUserop {
/**
* Set to true to receive all inputs in an array
*/
boolean variadic() default false;
}
/**
* An annotation to receive the executor itself into a parameter
*
* <p>
* The annotated parameter must have type {@link PcodeExecutor} with the same {@code <T>} as the
* class declaring the method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpExecutor {
}
/**
* An annotation to receive the executor's state into a parameter
*
* <p>
* The annotated parameter must have type {@link PcodeExecutorState} with the same {@code <T>}
* as the class declaring the method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpState {
}
/**
* An annotation to receive the complete library into a parameter
*
* <p>
* Because the library defining the userop may be composed with other libraries, it is not
* sufficient to use the "{@code this}" reference to obtain the library. If the library being
* used for execution needs to be passed to a dependent component of execution, it must be the
* complete library, not just the one defining the userop. This annotation allows a userop
* definition to receive the complete library.
*
* <p>
* The annotated parameter must have type {@link PcodeUseropLibrary} with the same {@code <T>}
* as the class declaring the method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpLibrary {
}
/**
* An annotation to receive the output varnode into a parameter
*
* <p>
* The annotated parameter must have type {@link Varnode}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpOutput {
}
protected Map<String, PcodeUseropDefinition<T>> ops = new HashMap<>();
private Map<String, PcodeUseropDefinition<T>> unmodifiableOps =
Collections.unmodifiableMap(ops);
/**
* Default constructor, usually invoked implicitly
*/
public AnnotatedPcodeUseropLibrary() {
Lookup lookup = getMethodLookup();
Type opType = getOperandType();
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<? extends AnnotatedPcodeUseropLibrary<T>> cls = (Class) this.getClass();
Set<Method> methods;
synchronized (CACHE_BY_CLASS) {
methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls));
}
for (Method m : methods) {
ops.put(m.getName(), AnnotatedPcodeUseropDefinition
.create(m.getAnnotation(PcodeUserop.class), this, opType, lookup, m));
}
}
/**
* Determine the operand type by examining the type substituted for {@code T}
*
* @return the type of data processed by the userop
*/
protected Type getOperandType() {
return PcodeUseropLibrary.getOperandType(getClass());
}
/**
* An override to provide method access, if any non-public method is exported as a userop.
*
* @return a lookup that can access all {@link PcodeUserop}-annotated methods.
*/
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@Override
public Map<String, PcodeUseropDefinition<T>> getUserops() {
return unmodifiableOps;
}
}

View file

@ -0,0 +1,130 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import ghidra.pcode.opbehavior.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.lang.Language;
/**
* A p-code arithmetic that operates on concrete byte array values
*
* <p>
* The arithmetic interprets the arrays as big- or little-endian values, then performs the
* arithmetic as specified by the p-code operation. The implementation defers to {@link OpBehavior}.
*/
public enum BytesPcodeArithmetic implements PcodeArithmetic<byte[]> {
/**
* The instance which interprets arrays as big-endian values
*/
BIG_ENDIAN(Endian.BIG),
/**
* The instance which interprets arrays as little-endian values
*/
LITTLE_ENDIAN(Endian.LITTLE);
/**
* Obtain the instance for the given endianness
*
* @param bigEndian true for {@link #BIG_ENDIAN}, false of {@link #LITTLE_ENDIAN}
* @return the arithmetic
*/
public static BytesPcodeArithmetic forEndian(boolean bigEndian) {
return bigEndian ? BIG_ENDIAN : LITTLE_ENDIAN;
}
/**
* Obtain the instance for the given language's endianness
*
* @param language the language
* @return the arithmetic
*/
public static BytesPcodeArithmetic forLanguage(Language language) {
return forEndian(language.isBigEndian());
}
private final Endian endian;
private BytesPcodeArithmetic(Endian endian) {
this.endian = endian;
}
@Override
public Endian getEndian() {
return endian;
}
@Override
public byte[] unaryOp(int opcode, int sizeout, int sizein1, byte[] in1) {
UnaryOpBehavior b = (UnaryOpBehavior) OpBehaviorFactory.getOpBehavior(opcode);
boolean isBigEndian = endian.isBigEndian();
if (sizein1 > 8 || sizeout > 8) {
BigInteger in1Val = Utils.bytesToBigInteger(in1, sizein1, isBigEndian, false);
BigInteger outVal = b.evaluateUnary(sizeout, sizein1, in1Val);
return Utils.bigIntegerToBytes(outVal, sizeout, isBigEndian);
}
long in1Val = Utils.bytesToLong(in1, sizein1, isBigEndian);
long outVal = b.evaluateUnary(sizeout, sizein1, in1Val);
return Utils.longToBytes(outVal, sizeout, isBigEndian);
}
@Override
public byte[] binaryOp(int opcode, int sizeout, int sizein1, byte[] in1, int sizein2,
byte[] in2) {
BinaryOpBehavior b = (BinaryOpBehavior) OpBehaviorFactory.getOpBehavior(opcode);
boolean isBigEndian = endian.isBigEndian();
if (sizein1 > 8 || sizein2 > 8 || sizeout > 8) {
BigInteger in1Val = Utils.bytesToBigInteger(in1, sizein1, isBigEndian, false);
BigInteger in2Val = Utils.bytesToBigInteger(in2, sizein2, isBigEndian, false);
BigInteger outVal = b.evaluateBinary(sizeout, sizein1, in1Val, in2Val);
return Utils.bigIntegerToBytes(outVal, sizeout, isBigEndian);
}
long in1Val = Utils.bytesToLong(in1, sizein1, isBigEndian);
long in2Val = Utils.bytesToLong(in2, sizein2, isBigEndian);
long outVal = b.evaluateBinary(sizeout, sizein1, in1Val, in2Val);
return Utils.longToBytes(outVal, sizeout, isBigEndian);
}
@Override
public byte[] modBeforeStore(int sizeout, int sizeinAddress, byte[] inAddress, int sizeinValue,
byte[] inValue) {
return inValue;
}
@Override
public byte[] modAfterLoad(int sizeout, int sizeinAddress, byte[] inAddress, int sizeinValue,
byte[] inValue) {
return inValue;
}
@Override
public byte[] fromConst(byte[] value) {
return value;
}
@Override
public byte[] toConcrete(byte[] value, Purpose purpose) {
return value;
}
@Override
public long sizeOf(byte[] value) {
return value.length;
}
}

View file

@ -0,0 +1,42 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import ghidra.program.model.lang.Language;
/**
* A state composing a single {@link BytesPcodeExecutorStatePiece}
*/
public class BytesPcodeExecutorState extends DefaultPcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param language the language (processor model)
*/
public BytesPcodeExecutorState(Language language) {
super(new BytesPcodeExecutorStatePiece(language),
BytesPcodeArithmetic.forLanguage(language));
}
protected BytesPcodeExecutorState(PcodeExecutorStatePiece<byte[], byte[]> piece) {
super(piece);
}
@Override
public BytesPcodeExecutorState fork() {
return new BytesPcodeExecutorState(piece.fork());
}
}

View file

@ -0,0 +1,77 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.Map;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
/**
* A plain concrete state piece without any backing objects
*/
public class BytesPcodeExecutorStatePiece
extends AbstractBytesPcodeExecutorStatePiece<BytesPcodeExecutorStateSpace<Void>> {
/**
* Construct a state for the given language
*
* @param langauge the language (used for its memory model)
*/
public BytesPcodeExecutorStatePiece(Language language) {
super(language);
}
protected BytesPcodeExecutorStatePiece(Language language,
AbstractSpaceMap<BytesPcodeExecutorStateSpace<Void>> spaceMap) {
super(language, spaceMap);
}
@Override
public BytesPcodeExecutorStatePiece fork() {
return new BytesPcodeExecutorStatePiece(language, spaceMap.fork());
}
class BytesSpaceMap extends SimpleSpaceMap<BytesPcodeExecutorStateSpace<Void>> {
BytesSpaceMap() {
super();
}
BytesSpaceMap(Map<AddressSpace, BytesPcodeExecutorStateSpace<Void>> spaces) {
super(spaces);
}
@Override
protected BytesPcodeExecutorStateSpace<Void> newSpace(AddressSpace space) {
return new BytesPcodeExecutorStateSpace<>(language, space, null);
}
@Override
public AbstractSpaceMap<BytesPcodeExecutorStateSpace<Void>> fork() {
return new BytesSpaceMap(fork(spaces));
}
@Override
public BytesPcodeExecutorStateSpace<Void> fork(BytesPcodeExecutorStateSpace<Void> s) {
return s.fork();
}
}
@Override
protected AbstractSpaceMap<BytesPcodeExecutorStateSpace<Void>> newSpaceMap() {
return new BytesSpaceMap();
}
}

View file

@ -0,0 +1,182 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.*;
import generic.ULongSpan;
import generic.ULongSpan.ULongSpanSet;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.util.Msg;
/**
* A p-code executor state space for storing and retrieving bytes as arrays
*
* @param <B> if this space is a cache, the type of object backing this space
*/
public class BytesPcodeExecutorStateSpace<B> {
protected final SemisparseByteArray bytes;
protected final Language language; // for logging diagnostics
protected final AddressSpace space;
protected final B backing;
/**
* Construct an internal space for the given address space
*
* @param language the language, for logging diagnostics
* @param space the address space
* @param backing the backing object, possibly {@code null}
*/
public BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing) {
this.language = language;
this.space = space;
this.backing = backing;
this.bytes = new SemisparseByteArray();
}
protected BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing,
SemisparseByteArray bytes) {
this.language = language;
this.space = space;
this.backing = backing;
this.bytes = bytes;
}
public BytesPcodeExecutorStateSpace<B> fork() {
return new BytesPcodeExecutorStateSpace<>(language, space, backing, bytes.fork());
}
/**
* Write a value at the given offset
*
* @param offset the offset
* @param val the value
*/
public void write(long offset, byte[] val, int srcOffset, int length) {
bytes.putData(offset, val, srcOffset, length);
}
/**
* Extension point: Read from backing into this space, when acting as a cache.
*
* @param uninitialized the ranges which need to be read.
*/
protected void readUninitializedFromBacking(ULongSpanSet uninitialized) {
}
/**
* Read a value from cache (or raw space if not acting as a cache) at the given offset
*
* @param offset the offset
* @param size the number of bytes to read (the size of the value)
* @return the bytes read
*/
protected byte[] readBytes(long offset, int size, Reason reason) {
byte[] data = new byte[size];
bytes.getData(offset, data);
return data;
}
protected AddressRange addrRng(ULongSpan span) {
Address start = space.getAddress(span.min());
Address end = space.getAddress(span.max());
return new AddressRangeImpl(start, end);
}
protected AddressSet addrSet(ULongSpanSet set) {
AddressSet result = new AddressSet();
for (ULongSpan span : set.spans()) {
result.add(addrRng(span));
}
return result;
}
protected Set<Register> getRegs(AddressSetView set) {
Set<Register> regs = new TreeSet<>();
for (AddressRange rng : set) {
Register r = language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (r != null) {
regs.add(r);
}
else {
regs.addAll(Arrays.asList(language.getRegisters(rng.getMinAddress())));
}
}
return regs;
}
protected void warnAddressSet(String message, AddressSetView set) {
Set<Register> regs = getRegs(set);
if (regs.isEmpty()) {
Msg.warn(this, message + ": " + set);
}
else {
Msg.warn(this, message + ": " + set + " (registers " + regs + ")");
}
}
protected void warnUninit(ULongSpanSet uninit) {
AddressSet uninitialized = addrSet(uninit);
warnAddressSet("Emulator read from uninitialized state", uninitialized);
}
/**
* Read a value from the space at the given offset
*
* <p>
* If this space is not acting as a cache, this simply delegates to
* {@link #readBytes(long, int)}. Otherwise, it will first ensure the cache covers the requested
* value.
*
* @param offset the offset
* @param size the number of bytes to read (the size of the value)
* @param reason the reason for reading state
* @return the bytes read
*/
public byte[] read(long offset, int size, Reason reason) {
if (backing != null) {
readUninitializedFromBacking(bytes.getUninitialized(offset, offset + size - 1));
}
ULongSpanSet stillUninit = bytes.getUninitialized(offset, offset + size - 1);
if (!stillUninit.isEmpty() && reason == Reason.EXECUTE) {
warnUninit(stillUninit);
}
return readBytes(offset, size, reason);
}
public Map<Register, byte[]> getRegisterValues(List<Register> registers) {
Map<Register, byte[]> result = new HashMap<>();
for (Register reg : registers) {
long min = reg.getAddress().getOffset();
long max = min + reg.getNumBytes();
if (!bytes.isInitialized(min, max)) {
continue;
}
byte[] data = new byte[reg.getNumBytes()];
bytes.getData(min, data);
result.put(reg, data);
}
return result;
}
public void clear() {
bytes.clear();
}
}

View file

@ -0,0 +1,70 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.*;
/**
* A p-code userop library composed of other libraries
*
* @param <T> the type of values processed by the library
*/
public class ComposedPcodeUseropLibrary<T> implements PcodeUseropLibrary<T> {
/**
* Obtain a map representing the composition of userops from all the given libraries
*
* <p>
* Name collisions are not allowed. If any two libraries export the same symbol, even if the
* definitions happen to do the same thing, it is an error.
*
* @param <T> the type of values processed by the libraries
* @param libraries the libraries whose userops to collect
* @return the resulting map
*/
public static <T> Map<String, PcodeUseropDefinition<T>> composeUserops(
Collection<PcodeUseropLibrary<T>> libraries) {
Map<String, PcodeUseropDefinition<T>> userops = new HashMap<>();
for (PcodeUseropLibrary<T> lib : libraries) {
for (PcodeUseropDefinition<T> def : lib.getUserops().values()) {
if (userops.put(def.getName(), def) != null) {
throw new IllegalArgumentException(
"Cannot compose libraries with conflicting definitions on " +
def.getName());
}
}
}
return userops;
}
private final Map<String, PcodeUseropDefinition<T>> userops;
/**
* Construct a composed userop library from the given libraries
*
* <p>
* This uses {@link #composeUserops(Collection)}, so its restrictions apply here, too.
*
* @param libraries the libraries
*/
public ComposedPcodeUseropLibrary(Collection<PcodeUseropLibrary<T>> libraries) {
this.userops = composeUserops(libraries);
}
@Override
public Map<String, PcodeUseropDefinition<T>> getUserops() {
return userops;
}
}

View file

@ -0,0 +1,45 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
/**
* The emulator or a client attempted to concretize an abstract value
*/
public class ConcretionError extends PcodeExecutionException {
private final Purpose purpose;
/**
* Create the exception
*
* @param message a message for the client
* @param purpose the reason why the emulator needs a concrete value
*/
public ConcretionError(String message, Purpose purpose) {
super(message);
this.purpose = purpose;
}
/**
* Get the reason why the emulator needs a concrete value
*
* @return the purpose
*/
public Purpose getPurpose() {
return purpose;
}
}

View file

@ -0,0 +1,91 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.Map;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
* A p-code executor state formed from a piece whose address and value types are the same
*
* <p>
* This class will also wire in the arithmetic's
* {@link PcodeArithmetic#modBeforeStore(int, int, Object, int, Object)} and
* {@link PcodeArithmetic#modAfterLoad(int, int, Object, int, Object)}, which is only possible when
* the address and value type are guaranteed to match.
*
* @param <T> the type of values and addresses in the state
*/
public class DefaultPcodeExecutorState<T> implements PcodeExecutorState<T> {
protected final PcodeExecutorStatePiece<T, T> piece;
protected final PcodeArithmetic<T> arithmetic;
public DefaultPcodeExecutorState(PcodeExecutorStatePiece<T, T> piece,
PcodeArithmetic<T> arithmetic) {
this.piece = piece;
this.arithmetic = arithmetic;
}
public DefaultPcodeExecutorState(PcodeExecutorStatePiece<T, T> piece) {
this(piece, piece.getArithmetic());
}
@Override
public Language getLanguage() {
return piece.getLanguage();
}
@Override
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
@Override
public DefaultPcodeExecutorState<T> fork() {
return new DefaultPcodeExecutorState<>(piece.fork(), arithmetic);
}
@Override
public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) {
return piece.getVar(space, offset, size, quantize, reason);
}
@Override
public void setVar(AddressSpace space, T offset, int size, boolean quantize, T val) {
piece.setVar(space, offset, size, quantize, val);
}
@Override
public Map<Register, T> getRegisterValues() {
return piece.getRegisterValues();
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return piece.getConcreteBuffer(address, purpose);
}
@Override
public void clear() {
piece.clear();
}
}

View file

@ -0,0 +1,26 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
/**
* Exception thrown by {@link PcodeEmulationLibrary#emu_injection_err(), a p-code userop invoked
* when client-provided Sleigh code in an injection could not be compiled.
*/
public class InjectionErrorPcodeExecutionException extends PcodeExecutionException {
public InjectionErrorPcodeExecutionException(PcodeFrame frame, Throwable cause) {
super("Error compiling injected Sleigh source", frame, cause);
}
}

View file

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
/**
* Exception thrown by {@link PcodeEmulationLibrary#emu_swi()}, a p-code userop exported by
* emulators for implementing breakpoints.
*/
public class InterruptPcodeExecutionException extends PcodeExecutionException {
public InterruptPcodeExecutionException(PcodeFrame frame, Throwable cause) {
super("Execution hit breakpoint", frame, cause);
}
}

View file

@ -0,0 +1,123 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.pcode.PcodeOp;
/**
* An auxiliary arithmetic that reports the location the control value
*
* <p>
* This is intended for use as the right side of a {@link PairedPcodeArithmetic}. Note that constant
* and unique spaces are never returned. Furthermore, any computation performed on a value,
* producing a temporary value, philosophically does not exist at any location in the state. Thus,
* most operations in this arithmetic result in {@code null}. The accompanying state piece
* {@link LocationPcodeExecutorStatePiece} generates the actual locations.
*/
public enum LocationPcodeArithmetic implements PcodeArithmetic<ValueLocation> {
BIG_ENDIAN(Endian.BIG), LITTLE_ENDIAN(Endian.LITTLE);
public static LocationPcodeArithmetic forEndian(boolean bigEndian) {
return bigEndian ? BIG_ENDIAN : LITTLE_ENDIAN;
}
private final Endian endian;
private LocationPcodeArithmetic(Endian endian) {
this.endian = endian;
}
@Override
public Endian getEndian() {
return endian;
}
@Override
public ValueLocation unaryOp(int opcode, int sizeout, int sizein1, ValueLocation in1) {
switch (opcode) {
case PcodeOp.COPY:
case PcodeOp.INT_ZEXT:
case PcodeOp.INT_SEXT:
return in1;
default:
return null;
}
}
@Override
public ValueLocation binaryOp(int opcode, int sizeout, int sizein1, ValueLocation in1,
int sizein2, ValueLocation in2) {
switch (opcode) {
case PcodeOp.INT_LEFT:
BigInteger amount = in2 == null ? null : in2.getConst();
if (in2 == null) {
return null;
}
return in1.shiftLeft(amount.intValue());
case PcodeOp.INT_OR:
return in1 == null || in2 == null ? null : in1.intOr(in2);
default:
return null;
}
}
@Override
public ValueLocation modBeforeStore(int sizeout, int sizeinAddress, ValueLocation inAddress,
int sizeinValue, ValueLocation inValue) {
return inValue;
}
@Override
public ValueLocation modAfterLoad(int sizeout, int sizeinAddress, ValueLocation inAddress,
int sizeinValue, ValueLocation inValue) {
return inValue;
}
@Override
public ValueLocation fromConst(byte[] value) {
return ValueLocation.fromConst(Utils.bytesToLong(value, value.length, endian.isBigEndian()),
value.length);
}
@Override
public ValueLocation fromConst(BigInteger value, int size, boolean isContextreg) {
return ValueLocation.fromConst(value.longValueExact(), size);
}
@Override
public ValueLocation fromConst(BigInteger value, int size) {
return ValueLocation.fromConst(value.longValueExact(), size);
}
@Override
public ValueLocation fromConst(long value, int size) {
return ValueLocation.fromConst(value, size);
}
@Override
public byte[] toConcrete(ValueLocation value, Purpose purpose) {
throw new ConcretionError("Cannot make 'location' concrete", purpose);
}
@Override
public long sizeOf(ValueLocation value) {
return value == null ? 0 : value.size();
}
}

View file

@ -0,0 +1,122 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.HashMap;
import java.util.Map;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
* An auxiliary state piece that reports the location of the control value
*
* <p>
* This is intended for use as the right side of a {@link PairedPcodeExecutorState} or
* {@link PairedPcodeExecutorStatePiece}. Except for unique spaces, sets are ignored, and gets
* simply echo back the location of the requested read. In unique spaces, the "location" is treated
* as the value, so that values transiting unique space can correctly have their source locations
* reported.
*/
public class LocationPcodeExecutorStatePiece
implements PcodeExecutorStatePiece<byte[], ValueLocation> {
private final Language language;
private final LocationPcodeArithmetic arithmetic;
private final BytesPcodeArithmetic addressArithmetic;
private final Map<Long, ValueLocation> unique;
/**
* Construct a "location" state piece
*
* @param isBigEndian true if the control language is big endian
*/
public LocationPcodeExecutorStatePiece(Language language) {
this.language = language;
boolean isBigEndian = language.isBigEndian();
this.arithmetic = LocationPcodeArithmetic.forEndian(isBigEndian);
this.addressArithmetic = BytesPcodeArithmetic.forEndian(isBigEndian);
this.unique = new HashMap<>();
}
protected LocationPcodeExecutorStatePiece(Language language,
BytesPcodeArithmetic addressArithmetic, Map<Long, ValueLocation> unique) {
this.language = language;
this.arithmetic = LocationPcodeArithmetic.forEndian(language.isBigEndian());
this.addressArithmetic = addressArithmetic;
this.unique = unique;
}
@Override
public Language getLanguage() {
return language;
}
@Override
public PcodeArithmetic<byte[]> getAddressArithmetic() {
return addressArithmetic;
}
@Override
public PcodeArithmetic<ValueLocation> getArithmetic() {
return arithmetic;
}
@Override
public LocationPcodeExecutorStatePiece fork() {
return new LocationPcodeExecutorStatePiece(language, addressArithmetic,
new HashMap<>(unique));
}
@Override
public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize,
ValueLocation val) {
if (!space.isUniqueSpace()) {
return;
}
// TODO: size is not considered
long lOffset = addressArithmetic.toLong(offset, Purpose.STORE);
unique.put(lOffset, val);
}
@Override
public ValueLocation getVar(AddressSpace space, byte[] offset, int size, boolean quantize,
Reason reason) {
long lOffset = addressArithmetic.toLong(offset, Purpose.LOAD);
if (!space.isUniqueSpace()) {
return ValueLocation.fromVarnode(space.getAddress(lOffset), size);
}
return unique.get(lOffset);
}
@Override
public Map<Register, ValueLocation> getRegisterValues() {
return Map.of();
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
throw new ConcretionError("Cannot make 'location' concrete buffers", purpose);
}
@Override
public void clear() {
unique.clear();
}
}

View file

@ -0,0 +1,170 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.Objects;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.pcode.PcodeOp;
/**
* An arithmetic composed from two.
*
* <p>
* The new arithmetic operates on tuples where each is subject to its respective arithmetic. One
* exception is {@link #toConcrete(Pair, Purpose)}. This arithmetic defers to left ("control")
* arithmetic. Thus, conventionally, when part of the pair represents the concrete value, it should
* be the left.
*
* <p>
* See {@link PairedPcodeExecutorStatePiece} regarding composing three or more elements. Generally,
* it's recommended the client provide its own "record" type and the corresponding arithmetic and
* state piece to manipulate it. Nesting pairs would work, but is not recommended.
*
* @param <L> the type of the left ("control") element
* @param <R> the type of the right ("auxiliary") element
*/
public class PairedPcodeArithmetic<L, R> implements PcodeArithmetic<Pair<L, R>> {
private final PcodeArithmetic<L> leftArith;
private final PcodeArithmetic<R> rightArith;
private final Endian endian;
/**
* Construct a composed arithmetic from the given two
*
* @param leftArith the left ("control") arithmetic
* @param rightArith the right ("rider") arithmetic
*/
public PairedPcodeArithmetic(PcodeArithmetic<L> leftArith, PcodeArithmetic<R> rightArith) {
Endian lend = leftArith.getEndian();
Endian rend = rightArith.getEndian();
if (lend != null && rend != null && lend != rend) {
throw new IllegalArgumentException("Arithmetics must agree in endianness");
}
this.endian = lend != null ? lend : rend;
this.leftArith = leftArith;
this.rightArith = rightArith;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
PairedPcodeArithmetic<?, ?> that = (PairedPcodeArithmetic<?, ?>) obj;
if (!Objects.equals(this.leftArith, that.leftArith)) {
return false;
}
if (!Objects.equals(this.rightArith, that.rightArith)) {
return false;
}
return true;
}
@Override
public Endian getEndian() {
return endian;
}
@Override
public Pair<L, R> unaryOp(int opcode, int sizeout, int sizein1,
Pair<L, R> in1) {
return Pair.of(
leftArith.unaryOp(opcode, sizeout, sizein1, in1.getLeft()),
rightArith.unaryOp(opcode, sizeout, sizein1, in1.getRight()));
}
@Override
public Pair<L, R> unaryOp(PcodeOp op, Pair<L, R> in1) {
return Pair.of(
leftArith.unaryOp(op, in1.getLeft()),
rightArith.unaryOp(op, in1.getRight()));
}
@Override
public Pair<L, R> binaryOp(int opcode, int sizeout, int sizein1,
Pair<L, R> in1, int sizein2, Pair<L, R> in2) {
return Pair.of(
leftArith.binaryOp(opcode, sizeout, sizein1, in1.getLeft(), sizein2, in2.getLeft()),
rightArith.binaryOp(opcode, sizeout, sizein1, in1.getRight(), sizein2, in2.getRight()));
}
@Override
public Pair<L, R> binaryOp(PcodeOp op, Pair<L, R> in1, Pair<L, R> in2) {
return Pair.of(
leftArith.binaryOp(op, in1.getLeft(), in2.getLeft()),
rightArith.binaryOp(op, in1.getRight(), in2.getRight()));
}
@Override
public Pair<L, R> modBeforeStore(int sizeout, int sizeinAddress, Pair<L, R> inAddress,
int sizeinValue, Pair<L, R> inValue) {
return Pair.of(
leftArith.modBeforeStore(sizeout, sizeinAddress, inAddress.getLeft(), sizeinValue,
inValue.getLeft()),
rightArith.modBeforeStore(sizeout, sizeinAddress, inAddress.getRight(), sizeinValue,
inValue.getRight()));
}
@Override
public Pair<L, R> modAfterLoad(int sizeout, int sizeinAddress, Pair<L, R> inAddress,
int sizeinValue, Pair<L, R> inValue) {
return Pair.of(
leftArith.modAfterLoad(sizeout, sizeinAddress, inAddress.getLeft(), sizeinValue,
inValue.getLeft()),
rightArith.modAfterLoad(sizeout, sizeinAddress, inAddress.getRight(), sizeinValue,
inValue.getRight()));
}
@Override
public Pair<L, R> fromConst(byte[] value) {
return Pair.of(leftArith.fromConst(value), rightArith.fromConst(value));
}
@Override
public byte[] toConcrete(Pair<L, R> value, Purpose purpose) {
return leftArith.toConcrete(value.getLeft(), purpose);
}
@Override
public long sizeOf(Pair<L, R> value) {
return leftArith.sizeOf(value.getLeft());
// TODO: Assert that the right agrees? Nah. Some aux types have no size.
}
/**
* Get the left ("control") arithmetic
*
* @return the arithmetic
*/
public PcodeArithmetic<L> getLeft() {
return leftArith;
}
/**
* Get the right ("rider") arithmetic
*
* @return the arithmetic
*/
public PcodeArithmetic<R> getRight() {
return rightArith;
}
}

View file

@ -0,0 +1,137 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
* A paired executor state
*
* <p>
* This composes a delegate state and piece "left" and "write" creating a single state which instead
* stores pairs of values, where the left component has the value type of the left state, and the
* right component has the value type of the right state. Note that both states are addressed using
* only the left "control" component. Otherwise, every operation on this state is decomposed into
* operations upon the delegate states, and the final result composed from the results of those
* operations.
*
* <p>
* Where a response cannot be composed of both states, the paired state defers to the left. In this
* way, the left state controls the machine, while the right is computed in tandem. The right never
* directly controls the machine
*
* <p>
* See {@link PairedPcodeExecutorStatePiece} regarding the composition of three or more pieces.
*
* @param <L> the type of values for the "left" state
* @param <R> the type of values for the "right" state
*/
public class PairedPcodeExecutorState<L, R> implements PcodeExecutorState<Pair<L, R>> {
private final PairedPcodeExecutorStatePiece<L, L, R> piece;
private final PcodeArithmetic<Pair<L, R>> arithmetic;
public PairedPcodeExecutorState(PairedPcodeExecutorStatePiece<L, L, R> piece) {
this.piece = piece;
this.arithmetic = piece.getArithmetic();
}
/**
* Compose a paired state from the given left and right states
*
* @param left the state backing the left side of paired values ("control")
* @param right the state backing the right side of paired values ("auxiliary")
*/
public PairedPcodeExecutorState(PcodeExecutorState<L> left,
PcodeExecutorStatePiece<L, R> right, PcodeArithmetic<Pair<L, R>> arithmetic) {
this.piece =
new PairedPcodeExecutorStatePiece<>(left, right, left.getArithmetic(), arithmetic);
this.arithmetic = arithmetic;
}
public PairedPcodeExecutorState(PcodeExecutorState<L> left,
PcodeExecutorStatePiece<L, R> right) {
this(left, right, new PairedPcodeArithmetic<>(left.getArithmetic(), right.getArithmetic()));
}
@Override
public Language getLanguage() {
return piece.getLanguage();
}
@Override
public PcodeArithmetic<Pair<L, R>> getArithmetic() {
return arithmetic;
}
@Override
public Map<Register, Pair<L, R>> getRegisterValues() {
return piece.getRegisterValues();
}
@Override
public PairedPcodeExecutorState<L, R> fork() {
return new PairedPcodeExecutorState<>(piece.fork());
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return piece.getConcreteBuffer(address, purpose);
}
/**
* Get the delegate backing the left side of paired values
*
* @return the left state
*/
public PcodeExecutorStatePiece<L, L> getLeft() {
return piece.getLeft();
}
/**
* Get the delegate backing the right side of paired values
*
* @return the right state
*/
public PcodeExecutorStatePiece<L, R> getRight() {
return piece.getRight();
}
@Override
public void setVar(AddressSpace space, Pair<L, R> offset, int size, boolean quantize,
Pair<L, R> val) {
piece.setVar(space, offset.getLeft(), size, quantize, val);
}
@Override
public Pair<L, R> getVar(AddressSpace space, Pair<L, R> offset, int size, boolean quantize,
Reason reason) {
return piece.getVar(space, offset.getLeft(), size, quantize, reason);
}
@Override
public void clear() {
piece.clear();
}
}

View file

@ -0,0 +1,156 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
/**
* A paired executor state piece
*
* <p>
* This composes two delegate pieces "left" and "right" creating a single piece which stores pairs
* of values, where the left component has the value type of the left piece, and the right component
* has the value type of the right piece. Both pieces must have the same address type. Every
* operation on this piece is decomposed into operations upon the delegate pieces, and the final
* result composed from the results of those operations.
*
* <p>
* To compose three or more states, first ask if it is really necessary. Second, consider
* implementing the {@link PcodeExecutorStatePiece} interface for a record type. Third, use the
* Church-style triple. In that third case, it is recommended to compose the nested pair on the
* right of the top pair: Compose the two right pieces into a single piece, then use
* {@link PairedPcodeExecutorState} to compose a concrete state with the composed piece, yielding a
* state of triples. This can be applied ad nauseam to compose arbitrarily large tuples; however, at
* a certain point clients should consider creating a record and implementing the state piece and/or
* state interface. It's helpful to use this implementation as a reference. Alternatively, the
* {@code Debugger} module has a {@code WatchValuePcodeExecutorState} which follows this
* recommendation.
*
* @see PairedPcodeExecutorState
* @param <A> the type of offset, usually the type of a controlling state
* @param <L> the type of the "left" state
* @param <R> the type of the "right" state
*/
public class PairedPcodeExecutorStatePiece<A, L, R>
implements PcodeExecutorStatePiece<A, Pair<L, R>> {
private final PcodeExecutorStatePiece<A, L> left;
private final PcodeExecutorStatePiece<A, R> right;
private final PcodeArithmetic<A> addressArithmetic;
private final PcodeArithmetic<Pair<L, R>> arithmetic;
public PairedPcodeExecutorStatePiece(PcodeExecutorStatePiece<A, L> left,
PcodeExecutorStatePiece<A, R> right, PcodeArithmetic<A> addressArithmetic,
PcodeArithmetic<Pair<L, R>> arithmetic) {
this.left = left;
this.right = right;
this.addressArithmetic = addressArithmetic;
this.arithmetic = arithmetic;
}
public PairedPcodeExecutorStatePiece(PcodeExecutorStatePiece<A, L> left,
PcodeExecutorStatePiece<A, R> right) {
this(left, right, left.getAddressArithmetic(),
new PairedPcodeArithmetic<>(left.getArithmetic(), right.getArithmetic()));
}
@Override
public Language getLanguage() {
return left.getLanguage();
}
@Override
public PcodeArithmetic<A> getAddressArithmetic() {
return addressArithmetic;
}
@Override
public PcodeArithmetic<Pair<L, R>> getArithmetic() {
return arithmetic;
}
@Override
public Map<Register, Pair<L, R>> getRegisterValues() {
Map<Register, L> leftRVs = left.getRegisterValues();
Map<Register, R> rightRVs = right.getRegisterValues();
Set<Register> union = new HashSet<>();
union.addAll(leftRVs.keySet());
union.addAll(rightRVs.keySet());
Map<Register, Pair<L, R>> result = new HashMap<>();
for (Register k : union) {
result.put(k, Pair.of(leftRVs.get(k), rightRVs.get(k)));
}
return result;
}
@Override
public PairedPcodeExecutorStatePiece<A, L, R> fork() {
return new PairedPcodeExecutorStatePiece<>(left.fork(), right.fork(), addressArithmetic,
arithmetic);
}
@Override
public void setVar(AddressSpace space, A offset, int size, boolean quantize, Pair<L, R> val) {
left.setVar(space, offset, size, quantize, val.getLeft());
right.setVar(space, offset, size, quantize, val.getRight());
}
@Override
public Pair<L, R> getVar(AddressSpace space, A offset, int size, boolean quantize,
Reason reason) {
return Pair.of(
left.getVar(space, offset, size, quantize, reason),
right.getVar(space, offset, size, quantize, reason));
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return left.getConcreteBuffer(address, purpose);
}
/**
* Get the delegate backing the left side of paired values
*
* @return the left piece
*/
public PcodeExecutorStatePiece<A, L> getLeft() {
return left;
}
/**
* Get the delegate backing the right side of paired values
*
* @return the right piece
*/
public PcodeExecutorStatePiece<A, R> getRight() {
return right;
}
@Override
public void clear() {
left.clear();
right.clear();
}
}

View file

@ -0,0 +1,400 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import ghidra.pcode.opbehavior.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.pcode.PcodeOp;
/**
* An interface that defines arithmetic p-code operations on values of type {@code T}.
*
* <p>
* See {@link BytesPcodeArithmetic} for the typical pattern when implementing an arithmetic. There
* are generally two cases: 1) Where endianness matters, 2) Where endianness does not matter. The
* first is typical. The implementation should be an {@link Enum} with two constants, one for the
* big endian implementation, and one for the little endian implementation. The class should also
* provide static methods: {@code forEndian(boolean isBigEndian)} for getting the correct one based
* on endianness, and {@code forLanguage(Language language)} for getting the correct one given a
* language. If endianness does not matter, then the implementation should follow a singleton
* pattern. See notes on {@link #getEndian()} for the endian-agnostic case.
*
* @param <T> the type of values operated on
*/
public interface PcodeArithmetic<T> {
/**
* The number of bytes needed to encode the size (in bytes) of any value
*/
int SIZEOF_SIZEOF = 8;
/**
* Reasons for requiring a concrete value
*/
enum Purpose {
/** The value is needed to parse an instruction */
DECODE,
/** The value is needed for disassembly context */
CONTEXT,
/** The value is needed to decide a conditional branch */
CONDITION,
/** The value will be used as the address of an indirect branch */
BRANCH,
/** The value will be used as the address of a value to load */
LOAD,
/** The value will be used as the address of a value to store */
STORE,
/** Some other reason, perhaps for userop library use */
OTHER,
/** The user or a tool is inspecting the value */
INSPECT
}
/**
* Get the endianness of this arithmetic
*
* <p>
* Often T is a byte array, or at least represents one abstractly. Ideally, it is an array where
* each element is an abstraction of a byte. If that is the case, then the arithmetic likely has
* to interpret those bytes as integral values according to an endianness. This should return
* that endianness.
*
* <p>
* If the abstraction has no notion of endianness, return null. In that case, the both
* {@link #fromConst(BigInteger, int, boolean)} and {@link #fromConst(long, int)} must be
* overridden. Furthermore, unless {@link #toConcrete(Object, Purpose)} is guaranteed to throw
* an exception, then {@link #toBigInteger(Object, Purpose)} and
* {@link #toLong(Object, Purpose)} must also be overridden.
*
* @return the endianness or null
*/
Endian getEndian();
/**
* Apply a unary operator to the given input
*
* <p>
* Note the sizes of variables are given, because values don't necessarily have an intrinsic
* size. For example, a {@link BigInteger} may have a minimum encoding size, but that does not
* necessarily reflect the size of the variable from which is was read.
*
* @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to
* return a derivative of {@link UnaryOpBehavior}.
*
* @param opcode the p-code opcode
* @param sizeout the size (in bytes) of the output variable
* @param sizein1 the size (in bytes) of the input variable
* @param in1 the input value
* @return the output value
*/
T unaryOp(int opcode, int sizeout, int sizein1, T in1);
/**
* Apply a unary operator to the given input
*
* <p>
* This provides the full p-code op, allowing deeper inspection of the code. For example, an
* arithmetic may wish to distinguish immediate (constant) values from variables. By default,
* this unpacks the details and defers to {@link #unaryOp(int, int, int, Object)}.
*
* @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to
* return a derivative of {@link UnaryOpBehavior}.
*
* @param op the operation
* @param in1 the input value
* @return the output value
*/
default T unaryOp(PcodeOp op, T in1) {
return unaryOp(op.getOpcode(), op.getOutput().getSize(), op.getInput(0).getSize(), in1);
}
/**
* Apply a binary operator to the given inputs
*
* <p>
* Note the sizes of variables are given, because values don't necessarily have an intrinsic
* size. For example, a {@link BigInteger} may have a minimum encoding size, but that does not
* necessarily reflect the size of the variable from which is was read.
*
* @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to
* return a derivative of {@link BinaryOpBehavior}.
*
* @param op the operation
* @param b the behavior of the operator
* @param sizeout the size (in bytes) of the output variable
* @param sizein1 the size (in bytes) of the first (left) input variable
* @param in1 the first (left) input value
* @param sizein2 the size (in bytes) of the second (right) input variable
* @param in2 the second (right) input value
* @return the output value
*/
T binaryOp(int opcode, int sizeout, int sizein1, T in1, int sizein2, T in2);
/**
* Apply a binary operator to the given input
*
* <p>
* This provides the full p-code op, allowing deeper inspection of the code. For example, an
* arithmetic may wish to distinguish immediate (constant) values from variables. By default,
* this unpacks the details and defers to {@link #binaryOp(int, int, int, Object, int, Object)}.
*
* @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to
* return a derivative of {@link BinaryOpBehavior}.
*
* @param op
* @param in1
* @param in2
* @return the output value
*/
default T binaryOp(PcodeOp op, T in1, T in2) {
return binaryOp(op.getOpcode(), op.getOutput().getSize(), op.getInput(0).getSize(), in1,
op.getInput(1).getSize(), in2);
}
/**
* Apply the {@link PcodeOp#PTRADD} operator to the given inputs
*
* <p>
* The "pointer add" op takes three operands: base, index, size; and is used as a more compact
* representation of array index address computation. The {@code size} operand must be constant.
* Suppose {@code arr} is an array whose elements are {@code size} bytes each, and the address
* of its first element is {@code base}. The decompiler would likely render the
* {@link PcodeOp#PTRADD} op as {@code &arr[index]}. An equivalent SLEIGH expression is
* {@code base + index*size}.
*
* <p>
* NOTE: This op is always a result of decompiler simplification, not low p-code generation, and
* so are not ordinarily used by a {@link PcodeExecutor}.
*
* @param sizeout the size (in bytes) of the output variable
* @param sizeinBase the size (in bytes) of the variable used for the array's base address
* @param inBase the value used as the array's base address
* @param sizeinIndex the size (in bytes) of the variable used for the index
* @param inIndex the value used as the index
* @param inSize the size of each array element in bytes
* @return the output value
*/
default T ptrAdd(int sizeout, int sizeinBase, T inBase, int sizeinIndex, T inIndex,
int inSize) {
T indexSized = binaryOp(PcodeOp.INT_MULT, sizeout,
sizeinIndex, inIndex, 4, fromConst(inSize, 4));
return binaryOp(PcodeOp.INT_ADD, sizeout,
sizeinBase, inBase, sizeout, indexSized);
}
/**
* Apply the {@link PcodeOp#PTRSUB} operator to the given inputs
*
* <p>
* The "pointer subfield" op takes two operands: base, offset; and is used as a more specific
* representation of structure field address computation. Its behavior is exactly equivalent to
* {@link PcodeOp#INT_ADD}. Suppose {@code st} is a structure pointer with a field {@code f}
* located {@link offset} bytes into the structure, and {@code st} has the value {@code base}.
* The decompiler would likely render the {@link PcodeOp#PTRSUB} op as {@code &st->f}. An
* equivalent SLEIGH expression is {@code base + offset}.
*
* <p>
* NOTE: This op is always a result of decompiler simplification, not low p-code generation, and
* so are not ordinarily used by a {@link PcodeExecutor}.
*
* @param sizeout the size (in bytes) of the output variable
* @param sizeinBase the size (in bytes) of the variable used for the structure's base address
* @param inBase the value used as the structure's base address
* @param sizeinOffset the size (in bytes) of the variable used for the offset
* @param inOffset the value used as the offset
* @return the output value
*/
default T ptrSub(int sizeout, int sizeinBase, T inBase, int sizeinOffset, T inOffset) {
return binaryOp(PcodeOp.INT_ADD, sizeout, sizeinBase, inBase, sizeinOffset, inOffset);
}
/**
* Apply any modifications before a value is stored
*
* <p>
* This implements any abstractions associated with {@link PcodeOp#STORE}. This is called on the
* address/offset and the value before the value is actually stored into the state.
*
* @param sizeout the size (in bytes) of the output variable
* @param sizeinAddress the size (in bytes) of the variable used for indirection
* @param inAddress the value used as the address (or offset)
* @param sizeinValue the size (in bytes) of the variable to store
* @param inValue the value to store
* @return the modified value to store
*/
T modBeforeStore(int sizeout, int sizeinAddress, T inAddress, int sizeinValue, T inValue);
/**
* Apply any modifications after a value is loaded
*
* <p>
* This implements any abstractions associated with {@link PcodeOp#LOAD}. This is called on the
* address/offset and the value after the value is actually loaded from the state.
*
* @param sizeout the size (in bytes) of the output variable
* @param sizeinAddress the size (in bytes) of the variable used for indirection
* @param inAddress the value used as the address (or offset)
* @param sizeinValue the size (in bytes) of the variable loaded
* @param inValue the value loaded
* @return the modified value loaded
*/
T modAfterLoad(int sizeout, int sizeinAddress, T inAddress, int sizeinValue, T inValue);
/**
* Convert the given constant concrete value to type {@code T} having the same size.
*
* @param value the constant value
* @return the value as a {@code T}
*/
T fromConst(byte[] value);
/**
* Convert the given constant concrete value to type {@code T} having the given size.
*
* <p>
* Note that the size may not be applicable to {@code T}. It is given to ensure the value can be
* held in a variable of that size when passed to downstream operators or stored in the executor
* state.
*
* @param value the constant value
* @param size the size (in bytes) of the variable into which the value is to be stored
* @return the value as a {@code T}
*/
default T fromConst(long value, int size) {
return fromConst(Utils.longToBytes(value, size, getEndian().isBigEndian()));
}
/**
* Convert the given constant concrete value to type {@code T} having the given size.
*
* <p>
* Note that the size may not be applicable to {@code T}. It is given to ensure the value can be
* held in a variable of that size when passed to downstream operators or stored in the executor
* state.
*
* @param value the constant value
* @param size the size (in bytes) of the variable into which the value is to be stored
* @param isContextreg true to indicate the value is from the disassembly context register. If
* {@code T} represents bytes, and the value is the contextreg, then the bytes are in
* big endian, no matter the machine language's endianness.
* @return the value as a {@code T}
*/
default T fromConst(BigInteger value, int size, boolean isContextreg) {
return fromConst(
Utils.bigIntegerToBytes(value, size, isContextreg || getEndian().isBigEndian()));
}
/**
* Convert the given constant concrete value to type {@code T} having the given size.
*
* <p>
* The value is assumed <em>not</em> to be for the disassembly context register.
*
* @see #fromConst(BigInteger, int, boolean)
*/
default T fromConst(BigInteger value, int size) {
return fromConst(value, size, false);
}
/**
* Convert, if possible, the given abstract value to a concrete byte array
*
* @param value the abstract value
* @param size the expected size (in bytes) of the array
* @param the reason why the emulator needs a concrete value
* @return the array
* @throws ConcretionError if the value cannot be made concrete
*/
byte[] toConcrete(T value, Purpose purpose);
/**
* Convert, if possible, the given abstract condition to a concrete boolean value
*
* @param cond the abstract condition
* @param purpose probably {@link Purpose#CONDITION}
* @return the boolean value
*/
default boolean isTrue(T cond, Purpose purpose) {
byte[] concrete = toConcrete(cond, purpose);
for (byte b : concrete) {
if (b != 0) {
return true;
}
}
return false;
}
/**
* Convert, if possible, the given abstract value to a concrete big integer
*
* <p>
* If the conversion is not possible, throw an exception.
*
* @param value the abstract value
* @param purpose the reason why the emulator needs a concrete value
* @return the concrete value
* @throws ConcretionError if the value cannot be made concrete
*/
default BigInteger toBigInteger(T value, Purpose purpose) {
byte[] concrete = toConcrete(value, purpose);
return Utils.bytesToBigInteger(concrete, concrete.length,
purpose == Purpose.CONTEXT || getEndian().isBigEndian(), false);
}
/**
* Convert, if possible, the given abstract value to a concrete long
*
* <p>
* If the conversion is not possible, throw an exception.
*
* @param value the abstract value
* @param purpose the reason why the emulator needs a concrete value
* @return the concrete value
* @throws ConcretionError if the value cannot be made concrete
*/
default long toLong(T value, Purpose purpose) {
byte[] concrete = toConcrete(value, purpose);
return Utils.bytesToLong(concrete, concrete.length,
purpose == Purpose.CONTEXT || getEndian().isBigEndian());
}
/**
* Get the size in bytes, if possible, of the given abstract value
*
* <p>
* If the abstract value does not conceptually have a size, throw an exception.
*
* @param value the abstract value
* @return the size in bytes
*/
long sizeOf(T value);
/**
* Get the size in bytes, if possible, of the given abstract value, as an abstract value
*
* <p>
* The returned size should itself has a size of {@link #SIZEOF_SIZEOF}.
*
* @param value the abstract value
* @return the size in bytes, as an abstract value
*/
default T sizeOfAbstract(T value) {
return fromConst(sizeOf(value), SIZEOF_SIZEOF);
}
}

View file

@ -0,0 +1,77 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
/**
* The base exception for all p-code execution errors
*
* <p>
* Exceptions caught by the executor that are not of this type are typically caught and wrapped, so
* that the frame can be recovered. The frame is important for diagnosing the error, because it
* records what the executor was doing. It essentially serves as the "line number" of the p-code
* program within the greater Java stack. Additionally, if execution of p-code is to resume, the
* frame must be recovered, and possibly stepped back one.
*/
public class PcodeExecutionException extends RuntimeException {
/*package*/ PcodeFrame frame;
/**
* Construct an execution exception
*
* <p>
* The frame is often omitted at the throw site. The executor should catch the exception, fill
* in the frame, and re-throw it.
*
* @param message the message
* @param frame if known, the frame at the time of the exception
* @param cause the exception that caused this one
*/
public PcodeExecutionException(String message, PcodeFrame frame, Throwable cause) {
super(message, cause);
this.frame = frame;
}
public PcodeExecutionException(String message, PcodeFrame frame) {
this(message, frame, null);
}
public PcodeExecutionException(String message, Throwable cause) {
this(message, null, cause);
}
public PcodeExecutionException(String message) {
this(message, null, null);
}
/**
* Get the frame at the time of the exception
*
* <p>
* Note that the frame counter is advanced <em>before</em> execution of the p-code op. Thus, the
* counter often points to the op following the one which caused the exception. For a frame to
* be present and meaningful, the executor must intervene between the throw and the catch. In
* other words, if you're invoking the executor, you should always expect to see a frame. If you
* are implementing, e.g., a userop, then it is possible to catch an exception without frame
* information populated. You might instead retrieve the frame from the executor, if you have a
* handle to it.
*
* @return the frame, possibly {@code null}
*/
public PcodeFrame getFrame() {
return frame;
}
}

View file

@ -0,0 +1,589 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.List;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.pcode.opbehavior.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* An executor of p-code programs
*
* <p>
* This is the kernel of Sleigh expression evaluation and p-code emulation. For a complete example
* of a p-code emulator, see {@link PcodeEmulator}.
*
* @param <T> the type of values processed by the executor
*/
public class PcodeExecutor<T> {
protected final SleighLanguage language;
protected final PcodeArithmetic<T> arithmetic;
protected final PcodeExecutorState<T> state;
protected final Reason reason;
protected final Register pc;
protected final int pointerSize;
/**
* Construct an executor with the given bindings
*
* @param language the processor language
* @param arithmetic an implementation of arithmetic p-code ops
* @param state an implementation of load/store p-code ops
* @param reason a reason for reading the state with this executor
*/
public PcodeExecutor(SleighLanguage language, PcodeArithmetic<T> arithmetic,
PcodeExecutorState<T> state, Reason reason) {
this.language = language;
this.arithmetic = arithmetic;
this.state = state;
this.reason = reason;
this.pc = language.getProgramCounter();
this.pointerSize = language.getDefaultSpace().getPointerSize();
}
/**
* Get the executor's Sleigh language (processor model)
*
* @return the language
*/
public SleighLanguage getLanguage() {
return language;
}
/**
* Get the arithmetic applied by the executor
*
* @return the arithmetic
*/
public PcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
/**
* Get the state bound to this executor
*
* @return the state
*/
public PcodeExecutorState<T> getState() {
return state;
}
/**
* Get the reason for reading state with this executor
*
* @return the reason
*/
public Reason getReason() {
return reason;
}
/**
* Compile and execute a block of Sleigh
*
* @param source the Sleigh source
*/
public void executeSleigh(String source) {
PcodeProgram program =
SleighProgramCompiler.compileProgram(language, "exec", source, PcodeUseropLibrary.NIL);
execute(program, PcodeUseropLibrary.nil());
}
/**
* Begin execution of the given program
*
* @param program the program, e.g., from an injection, or a decoded instruction
* @return the frame
*/
public PcodeFrame begin(PcodeProgram program) {
return begin(program.code, program.useropNames);
}
/**
* Execute a program using the given library
*
* @param program the program, e.g., from an injection, or a decoded instruction
* @param library the library
* @return the frame
*/
public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary<T> library) {
return execute(program.code, program.useropNames, library);
}
/**
* Begin execution of a list of p-code ops
*
* @param code the ops
* @param useropNames the map of userop numbers to names
* @return the frame
*/
public PcodeFrame begin(List<PcodeOp> code, Map<Integer, String> useropNames) {
return new PcodeFrame(language, code, useropNames);
}
/**
* Execute a list of p-code ops
*
* @param code the ops
* @param useropNames the map of userop numbers to names
* @param library the library of userops
* @return the frame
*/
public PcodeFrame execute(List<PcodeOp> code, Map<Integer, String> useropNames,
PcodeUseropLibrary<T> library) {
PcodeFrame frame = begin(code, useropNames);
finish(frame, library);
return frame;
}
/**
* Finish execution of a frame
*
* <p>
* TODO: This is not really sufficient for continuation after a break, esp. if that break occurs
* within a nested call back into the executor. This would likely become common when using pCode
* injection.
*
* @param frame the incomplete frame
* @param library the library of userops to use
*/
public void finish(PcodeFrame frame, PcodeUseropLibrary<T> library) {
try {
while (!frame.isFinished()) {
step(frame, library);
}
}
catch (PcodeExecutionException e) {
if (e.frame == null) {
e.frame = frame;
}
throw e;
}
}
/**
* Handle an unrecognized or unimplemented p-code op
*
* @param op the op
*/
protected void badOp(PcodeOp op) {
switch (op.getOpcode()) {
case PcodeOp.UNIMPLEMENTED:
throw new LowlevelError(
"Encountered an unimplemented instruction at " + op.getSeqnum().getTarget());
default:
throw new LowlevelError(
"Unsupported p-code op at " + op.getSeqnum().getTarget() + ": " + op);
}
}
/**
* Step on p-code op
*
* @param op the op
* @param frame the current frame
* @param library the library, invoked in case of {@link PcodeOp#CALLOTHER}
*/
public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
OpBehavior b = OpBehaviorFactory.getOpBehavior(op.getOpcode());
if (b == null) {
badOp(op);
return;
}
if (b instanceof UnaryOpBehavior unOp) {
executeUnaryOp(op, unOp);
return;
}
if (b instanceof BinaryOpBehavior binOp) {
executeBinaryOp(op, binOp);
return;
}
switch (op.getOpcode()) {
case PcodeOp.LOAD:
executeLoad(op);
return;
case PcodeOp.STORE:
executeStore(op);
return;
case PcodeOp.BRANCH:
executeBranch(op, frame);
return;
case PcodeOp.CBRANCH:
executeConditionalBranch(op, frame);
return;
case PcodeOp.BRANCHIND:
executeIndirectBranch(op, frame);
return;
case PcodeOp.CALL:
executeCall(op, frame, library);
return;
case PcodeOp.CALLIND:
executeIndirectCall(op, frame);
return;
case PcodeOp.CALLOTHER:
executeCallother(op, frame, library);
return;
case PcodeOp.RETURN:
executeReturn(op, frame);
return;
default:
badOp(op);
return;
}
}
/**
* Step a single p-code op
*
* @param frame the frame whose next op to execute
* @param library the userop library
*/
public void step(PcodeFrame frame, PcodeUseropLibrary<T> library) {
try {
stepOp(frame.nextOp(), frame, library);
}
catch (PcodeExecutionException e) {
e.frame = frame;
throw e;
}
catch (Exception e) {
throw new PcodeExecutionException(e.getMessage(), frame, e);
}
}
/**
* Skip a single p-code op
*
* @param frame the frame whose next op to skip
*/
public void skip(PcodeFrame frame) {
frame.nextOp();
}
/**
* Assert that a varnode is constant and get its value as an integer.
*
* <p>
* Here "constant" means a literal or immediate value. It does not read from the state.
*
* @param vn the varnode
* @return the value
*/
protected int getIntConst(Varnode vn) {
assert vn.getAddress().getAddressSpace().isConstantSpace();
return (int) vn.getAddress().getOffset();
}
/**
* Execute the given unary op
*
* @param op the op
* @param b the op behavior
*/
public void executeUnaryOp(PcodeOp op, UnaryOpBehavior b) {
Varnode in1Var = op.getInput(0);
Varnode outVar = op.getOutput();
T in1 = state.getVar(in1Var, reason);
T out = arithmetic.unaryOp(op, in1);
state.setVar(outVar, out);
}
/**
* Execute the given binary op
*
* @param op the op
* @param b the op behavior
*/
public void executeBinaryOp(PcodeOp op, BinaryOpBehavior b) {
Varnode in1Var = op.getInput(0);
Varnode in2Var = op.getInput(1);
Varnode outVar = op.getOutput();
T in1 = state.getVar(in1Var, reason);
T in2 = state.getVar(in2Var, reason);
T out = arithmetic.binaryOp(op, in1, in2);
state.setVar(outVar, out);
}
/**
* Extension point: logic preceding a load
*
* @param space the address space to be loaded from
* @param offset the offset about to be loaded from
* @param size the size in bytes to be loaded
*/
protected void checkLoad(AddressSpace space, T offset, int size) {
}
/**
* Execute a load
*
* @param op the op
*/
public void executeLoad(PcodeOp op) {
int spaceID = getIntConst(op.getInput(0));
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
Varnode inOffset = op.getInput(1);
T offset = state.getVar(inOffset, reason);
Varnode outVar = op.getOutput();
checkLoad(space, offset, outVar.getSize());
T out = state.getVar(space, offset, outVar.getSize(), true, reason);
T mod = arithmetic.modAfterLoad(outVar.getSize(), inOffset.getSize(), offset,
outVar.getSize(), out);
state.setVar(outVar, mod);
}
/**
* Extension point: logic preceding a store
*
* @param space the address space to be stored to
* @param offset the offset about to be stored to
* @param size the size in bytes to be stored
*/
protected void checkStore(AddressSpace space, T offset, int size) {
}
/**
* Execute a store
*
* @param op the op
*/
public void executeStore(PcodeOp op) {
int spaceID = getIntConst(op.getInput(0));
AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID);
Varnode inOffset = op.getInput(1);
T offset = state.getVar(inOffset, reason);
Varnode valVar = op.getInput(2);
checkStore(space, offset, valVar.getSize());
T val = state.getVar(valVar, reason);
T mod = arithmetic.modBeforeStore(valVar.getSize(), inOffset.getSize(), offset,
valVar.getSize(), val);
state.setVar(space, offset, valVar.getSize(), true, mod);
}
/**
* Extension point: Called when execution branches to a target address
*
* <p>
* NOTE: This is <em>not</em> called for the fall-through case
*
* @param target the target address
*/
protected void branchToAddress(Address target) {
}
/**
* Set the state's pc to the given offset and finish the frame
*
* <p>
* This implements only part of the p-code control flow semantics. An emulator must also
* override {@link #branchToAddress(Address)}, so that it can update its internal program
* counter. The emulator could just read the program counter from the state after <em>every</em>
* completed frame, but receiving it "out of band" is faster.
*
* @param offset the offset (the new value of the program counter)
* @param frame the frame to finish
*/
protected void branchToOffset(T offset, PcodeFrame frame) {
state.setVar(pc, offset);
frame.finishAsBranch();
}
/**
* Perform the actual logic of a branch p-code op
*
* <p>
* This is a separate method, so that overriding {@link #executeBranch(PcodeOp, PcodeFrame)}
* does not implicitly modify {@link #executeConditionalBranch(PcodeOp, PcodeFrame)}.
*
* @param op the op
* @param frame the frame
*/
protected void doExecuteBranch(PcodeOp op, PcodeFrame frame) {
Address target = op.getInput(0).getAddress();
if (target.isConstantAddress()) {
frame.branch((int) target.getOffset());
}
else {
branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame);
branchToAddress(target);
}
}
/**
* Execute a branch
*
* <p>
* This merely defers to {@link #doExecuteBranch(PcodeOp, PcodeFrame)}. To instrument the
* operation, override this. To modify or instrument branching in general, override
* {@link #doExecuteBranch(PcodeOp, PcodeFrame)}, {@link #branchToOffset(Object, PcodeFrame)},
* and/or {@link #branchToAddress(Address)}.
*
* @param op the op
* @param frame the frame
*/
public void executeBranch(PcodeOp op, PcodeFrame frame) {
doExecuteBranch(op, frame);
}
/**
* Execute a conditional branch
*
* @param op the op
* @param frame the frame
*/
public void executeConditionalBranch(PcodeOp op, PcodeFrame frame) {
Varnode condVar = op.getInput(1);
T cond = state.getVar(condVar, reason);
if (arithmetic.isTrue(cond, Purpose.CONDITION)) {
doExecuteBranch(op, frame);
}
}
/**
* Perform the actual logic of an indirect branch p-code op
*
* <p>
* This is a separate method, so that overriding
* {@link #executeIndirectBranch(PcodeOp, PcodeFrame)} does not implicitly modify
* {@link #executeIndirectCall(PcodeOp, PcodeFrame)} and
* {@link #executeReturn(PcodeOp, PcodeFrame)}.
*
* @param op the op
* @param frame the frame
*/
protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) {
T offset = state.getVar(op.getInput(0), reason);
branchToOffset(offset, frame);
long concrete = arithmetic.toLong(offset, Purpose.BRANCH);
Address target = op.getSeqnum().getTarget().getNewAddress(concrete, true);
branchToAddress(target);
}
/**
* Execute an indirect branch
*
* <p>
* This merely defers to {@link #doExecuteIndirectBranch(PcodeOp, PcodeFrame)}. To instrument
* the operation, override this. To modify or instrument indirect branching in general, override
* {@link #doExecuteIndirectBranch(PcodeOp, PcodeFrame)}.
*
* @param op the op
* @param frame the frame
*/
public void executeIndirectBranch(PcodeOp op, PcodeFrame frame) {
doExecuteIndirectBranch(op, frame);
}
/**
* Execute a call
*
* @param op the op
* @param frame the frame
*/
public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
Address target = op.getInput(0).getAddress();
branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame);
branchToAddress(target);
}
/**
* Execute an indirect call
*
* @param op the op
* @param frame the frame
*/
public void executeIndirectCall(PcodeOp op, PcodeFrame frame) {
doExecuteIndirectBranch(op, frame);
}
/**
* Get the name of a userop
*
* @param opNo the userop number
* @param frame the frame
* @return the name, or null if it is not defined
*/
public String getUseropName(int opNo, PcodeFrame frame) {
if (opNo < language.getNumberOfUserDefinedOpNames()) {
return language.getUserDefinedOpName(opNo);
}
return frame.getUseropName(opNo);
}
/**
* Execute a userop call
*
* @param op the op
* @param frame the frame
* @param library the library of userops
*/
public void executeCallother(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
int opNo = getIntConst(op.getInput(0));
String opName = getUseropName(opNo, frame);
if (opName == null) {
throw new AssertionError("Pcode userop " + opNo + " is not defined");
}
PcodeUseropDefinition<T> opDef = library.getUserops().get(opName);
if (opDef != null) {
opDef.execute(this, library, op.getOutput(),
List.of(op.getInputs()).subList(1, op.getNumInputs()));
return;
}
onMissingUseropDef(op, frame, opName, library);
}
/**
* Extension point: Behavior when a userop definition was not found in the library
*
* <p>
* The default behavior is to throw a {@link SleighLinkException}.
*
* @param op the op
* @param frame the frame
* @param opName the name of the p-code userop
* @param library the library
*/
protected void onMissingUseropDef(PcodeOp op, PcodeFrame frame, String opName,
PcodeUseropLibrary<T> library) {
throw new SleighLinkException(
"Sleigh userop '" + opName + "' is not in the library " + library);
}
/**
* Execute a return
*
* @param op the op
* @param frame the frame
*/
public void executeReturn(PcodeOp op, PcodeFrame frame) {
doExecuteIndirectBranch(op, frame);
}
}

View file

@ -0,0 +1,61 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.program.model.address.AddressSpace;
/**
* An interface that provides storage for values of type {@code T}
*
* <p>
* This is not much more than a stricter form of {@link PcodeExecutorStatePiece}, in that it
* requires the value and address offset types to agree, so that a p-code executor or emulator can
* perform loads and stores using indirect addresses. The typical pattern for implementing a state
* is to compose it from pieces. See {@link PcodeExecutorStatePiece}.
*
* @param <T> the type of offsets and values
*/
public interface PcodeExecutorState<T> extends PcodeExecutorStatePiece<T, T> {
@Override
default PcodeArithmetic<T> getAddressArithmetic() {
return getArithmetic();
}
@Override
PcodeExecutorState<T> fork();
/**
* Use this state as the control, paired with the given auxiliary state.
*
* <p>
* <b>CAUTION:</b> Often, the default paired state is not quite sufficient. Consider
* {@link #getVar(AddressSpace, Object, int, boolean)}. The rider on the offset may offer
* information that must be incorporated into the rider of the value just read. This is the
* case, for example, with taint propagation. In those cases, an anonymous inner class extending
* {@link PairedPcodeExecutorState} is sufficient.
*
* @param <U> the type of values and offsets stored by the rider
* @param right the rider state
* @return the paired state
*/
default <U> PcodeExecutorState<Pair<T, U>> paired(
PcodeExecutorStatePiece<T, U> right) {
return new PairedPcodeExecutorState<>(this, right);
}
}

View file

@ -0,0 +1,270 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.Map;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.pcode.Varnode;
/**
* An interface that provides storage for values of type {@code T}, addressed by offsets of type
* {@code A}
*
* <p>
* The typical pattern for implementing a state is to compose it from one or more state pieces. Each
* piece must use the same address type and arithmetic. If more than one piece is needed, they are
* composed using {@link PairedPcodeExecutorStatePiece}. Once all the pieces are composed, the root
* piece can be wrapped to make a state using {@link DefaultPcodeExecutorState} or
* {@link PairedPcodeExecutorState}. The latter corrects the address type to be a pair so it matches
* the type of values.
*
* @param <A> the type of address offsets
* @param <T> the type of values
*/
public interface PcodeExecutorStatePiece<A, T> {
/**
* Reasons for reading state
*/
enum Reason {
/** The value is needed by the emulator in the course of execution */
EXECUTE,
/** The value is being inspected */
INSPECT
}
/**
* Construct a range, if only to verify the range is valid
*
* @param space the address space
* @param offset the starting offset
* @param size the length (in bytes) of the range
*/
default void checkRange(AddressSpace space, long offset, int size) {
// TODO: Perhaps get/setVar should just take an AddressRange?
try {
new AddressRangeImpl(space.getAddress(offset), size);
}
catch (AddressOverflowException | AddressOutOfBoundsException e) {
throw new IllegalArgumentException("Given offset and length exceeds address space");
}
}
/**
* Get the language defining the address spaces of this state piece
*
* @return the language
*/
Language getLanguage();
/**
* Get the arithmetic used to manipulate addresses of the type used by this state
*
* @return the address (or offset) arithmetic
*/
PcodeArithmetic<A> getAddressArithmetic();
/**
* Get the arithmetic used to manipulate values of the type stored by this state
*
* @return the arithmetic
*/
PcodeArithmetic<T> getArithmetic();
/**
* Create a deep copy of this state
*
* @return the copy
*/
PcodeExecutorStatePiece<A, T> fork();
/**
* Set the value of a register variable
*
* @param reg the register
* @param val the value
*/
default void setVar(Register reg, T val) {
Address address = reg.getAddress();
setVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(), true, val);
}
/**
* Set the value of a variable
*
* @param var the variable
* @param val the value
*/
default void setVar(Varnode var, T val) {
Address address = var.getAddress();
setVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true, val);
}
/**
* Set the value of a variable
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param quantize true to quantize to the language's "addressable unit"
* @param val the value
*/
void setVar(AddressSpace space, A offset, int size, boolean quantize, T val);
/**
* Set the value of a variable
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param quantize true to quantize to the language's "addressable unit"
* @param val the value
*/
default void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) {
checkRange(space, offset, size);
A aOffset = getAddressArithmetic().fromConst(offset, space.getPointerSize());
setVar(space, aOffset, size, quantize, val);
}
/**
* Set the value of a variable
*
* @param address the address in memory
* @param size the size of the variable
* @param quantize true to quantize to the language's "addressable unit"
* @param val the value
*/
default void setVar(Address address, int size, boolean quantize, T val) {
setVar(address.getAddressSpace(), address.getOffset(), size, quantize, val);
}
/**
* Get the value of a register variable
*
* @param reg the register
* @param reason the reason for reading the register
* @return the value
*/
default T getVar(Register reg, Reason reason) {
Address address = reg.getAddress();
return getVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(),
true, reason);
}
/**
* Get the value of a variable
*
* @param var the variable
* @param reason the reason for reading the variable
* @return the value
*/
default T getVar(Varnode var, Reason reason) {
Address address = var.getAddress();
return getVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true, reason);
}
/**
* Get the value of a variable
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param quantize true to quantize to the language's "addressable unit"
* @param reason the reason for reading the variable
* @return the value
*/
T getVar(AddressSpace space, A offset, int size, boolean quantize, Reason reason);
/**
* Get the value of a variable
*
* <p>
* This method is typically used for reading memory variables.
*
* @param space the address space
* @param offset the offset within the space
* @param size the size of the variable
* @param quantize true to quantize to the language's "addressable unit"
* @param reason the reason for reading the variable
* @return the value
*/
default T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) {
checkRange(space, offset, size);
A aOffset = getAddressArithmetic().fromConst(offset, space.getPointerSize());
return getVar(space, aOffset, size, quantize, reason);
}
/**
* Get the value of a variable
*
* <p>
* This method is typically used for reading memory variables.
*
* @param address the address of the variable
* @param size the size of the variable
* @param quantize true to quantize to the language's "addressable unit"
* @param reason the reason for reading the variable
* @return the value
*/
default T getVar(Address address, int size, boolean quantize, Reason reason) {
return getVar(address.getAddressSpace(), address.getOffset(), size, quantize, reason);
}
/**
* Get all register values known to this state
*
* <p>
* When the state acts as a cache, it should only return those cached.
*
* @return a map of registers and their values
*/
Map<Register, T> getRegisterValues();
/**
* Bind a buffer of concrete bytes at the given start address
*
* @param address the start address
* @param purpose the reason why the emulator needs a concrete value
* @return a buffer
*/
MemBuffer getConcreteBuffer(Address address, Purpose purpose);
/**
* Quantize the given offset to the language's "addressable unit"
*
* @param space the space where the offset applies
* @param offset the offset
* @return the quantized offset
*/
default long quantizeOffset(AddressSpace space, long offset) {
return space.truncateAddressableWordOffset(offset) * space.getAddressableUnitSize();
}
/**
* Erase the entire state or piece
*
* <p>
* This is generally only useful when the state is itself a cache to another object. This will
* ensure the state is reading from that object rather than a stale cache. If this is not a
* cache, this could in fact clear the whole state, and the machine using it will be left in the
* dark.
*/
void clear();
}

View file

@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.List;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.pcode.PcodeOp;
/**
* A p-code program that evaluates a Sleigh expression
*/
public class PcodeExpression extends PcodeProgram {
public static final String RESULT_NAME = "___result";
protected static final PcodeUseropLibrary<?> CAPTURING =
new ValueCapturingPcodeUseropLibrary<>();
/**
* A clever means of capturing the result of the expression.
*
* @implNote The compiled source is actually {@code ___result(<expression>);} which allows us to
* capture the value (and size) of arbitrary expressions. Assigning the value to a
* temp variable instead of a userop does not quite suffice, since it requires a fixed
* size, which cannot be known ahead of time.
*
* @param <T> no type in particular, except to match the executor
*/
protected static class ValueCapturingPcodeUseropLibrary<T>
extends AnnotatedPcodeUseropLibrary<T> {
T result;
@PcodeUserop
public void ___result(T result) {
this.result = result;
}
}
/**
* Construct a p-code program from source already compiled into p-code ops
*
* @param language the language that generated the p-code
* @param code the list of p-code ops
* @param useropSymbols a map of expected userop symbols
*/
protected PcodeExpression(SleighLanguage language, List<PcodeOp> code,
Map<Integer, UserOpSymbol> useropSymbols) {
super(language, code, useropSymbols);
}
// TODO: One that can take a library, and compose the result into it
/**
* Evaluate the expression using the given executor
*
* @param <T> the type of the result
* @param executor the executor
* @return the result
*/
public <T> T evaluate(PcodeExecutor<T> executor) {
ValueCapturingPcodeUseropLibrary<T> library =
new ValueCapturingPcodeUseropLibrary<>();
execute(executor, library);
return library.result;
}
}

View file

@ -0,0 +1,337 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.List;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.util.pcode.AbstractAppender;
import ghidra.app.util.pcode.AbstractPcodeFormatter;
import ghidra.pcode.emulate.EmulateInstructionStateModifier;
import ghidra.pcode.error.LowlevelError;
import ghidra.program.model.lang.Language;
import ghidra.program.model.pcode.PcodeOp;
/**
* The executor's internal counter
*
* <p>
* To distinguish the program counter of a p-code program from the program counter of the machine it
* models, we address p-code ops by "index." When derived from an instruction, the address and index
* together form the "sequence number." Because the executor care's not about the derivation of a
* p-code program, it counts through indices. The frame carries with it the p-code ops comprising
* its current p-code program.
*
* <p>
* A p-code emulator feeds p-code to an executor by decoding one instruction at a time. Thus, the
* "current p-code program" comprises only those ops generated by a single instruction. Or else, it
* is a user-supplied p-code program, e.g., to evaluate a Sleigh expression. The frame completes the
* program by falling-through, i.e., stepping past the final op, or by branching externally, i.e.,
* to a different machine instruction. The emulator must then update its program counter accordingly
* and proceed to the next instruction.
*/
public class PcodeFrame {
protected class MyAppender extends AbstractAppender<String> {
protected final StringBuffer buf = new StringBuffer();
protected boolean isLineLabel = false;
protected int i = 0;
public MyAppender(Language language) {
super(language, true);
buf.append("<p-code frame: index=" + index);
if (branched != -1) {
buf.append(" branched=" + branched);
}
buf.append(" {\n");
}
@Override
protected void appendString(String string) {
buf.append(string);
}
@Override
public void appendLineLabel(long label) {
buf.append(" " + stringifyLineLabel(label));
}
@Override
public void appendIndent() {
if (isLineLabel) {
buf.append(" ");
}
if (i == branched) {
buf.append(" *> ");
}
else if (i == index) {
buf.append(" -> ");
}
else {
buf.append(" ");
}
}
protected void endLine() {
if (!isLineLabel) {
i++;
}
buf.append("\n");
}
@Override
protected String stringifyUseropUnchecked(Language language, int id) {
String name = super.stringifyUseropUnchecked(language, id);
if (name != null) {
return name;
}
return useropNames.get(id);
}
@Override
public String finish() {
if (index == code.size()) {
buf.append(" *> fall-through\n");
}
buf.append("}>");
return buf.toString();
}
}
protected class MyFormatter extends AbstractPcodeFormatter<String, MyAppender> {
@Override
protected MyAppender createAppender(Language language, boolean indent) {
return new MyAppender(language);
}
@Override
protected FormatResult formatOpTemplate(MyAppender appender, OpTpl op) {
appender.isLineLabel = isLineLabel(op);
FormatResult result = super.formatOpTemplate(appender, op);
appender.endLine();
return result;
}
}
private final Language language;
private final List<PcodeOp> code;
private final Map<Integer, String> useropNames;
private int count = 0;
private int index = 0;
private int branched = -1;
/**
* Construct a frame of p-code execution
*
* <p>
* The passed in code should be an immutable list. It is returned directly by
* {@link #getCode()}, which would otherwise allow mutation. The frame does not create its own
* immutable copy as a matter of efficiency. Instead, the provider of the code should create an
* immutable copy, probably once, e.g., when compiling a {@link PcodeProgram}.
*
* @param language the language to which the program applies
* @param code the program's p-code
* @param useropNames a map of additional sleigh/p-code userops linked to the program
*/
public PcodeFrame(Language language, List<PcodeOp> code, Map<Integer, String> useropNames) {
this.language = language;
this.code = code;
this.useropNames = useropNames;
}
@Override
public String toString() {
return new MyFormatter().formatOps(language, code);
}
/**
* The number of p-code ops executed
*
* <p>
* Contrast this to {@link #index()}, which marks the next op to be executed. This counts the
* number of ops executed, which will differ from index when an internal branch is taken.
*
* @return the count
*/
public int count() {
return count;
}
/**
* The index of the <em>next</em> p-code op to be executed
*
* <p>
* If the last p-code op resulted in a branch, this will instead return -1.
*
* @see #isBranch()
* @see #isFallThrough()
* @see #isFinished()
* @return the index, i.e, p-code "program counter."
*/
public int index() {
return index;
}
/**
* Get the op at the current index, and then advance that index
*
* <p>
* This is used in the execution loop to retrieve each op to execute
*
* @return the op to execute
*/
public PcodeOp nextOp() {
return code.get(advance());
}
/**
* Advance the index
*
* @return the value of the index <em>before</em> it was advanced
*/
public int advance() {
count++;
return index++;
}
/**
* Step the index back one
*
* @return the value of the index <em>before</em> it was stepped back
*/
public int stepBack() {
count--;
return index--;
}
/**
* Get the name of the userop for the given number
*
* @param userop the userop number, as encoded in the first operand of {@link PcodeOp#CALLOTHER}
* @return the name of the userop, as expressed in the Sleigh source
*/
public String getUseropName(int userop) {
return useropNames.get(userop);
}
/**
* Get the map of userop numbers to names
*
* @return the map
*/
public Map<Integer, String> getUseropNames() {
return useropNames;
}
/**
* Check if the index has advanced past the end of the p-code program
*
* <p>
* If the index has advanced beyond the program, it implies the program has finished executing.
* In the case of instruction emulation, no branch was encountered. The machine should advance
* to the fall-through instruction.
*
* @see #isBranch()
* @see #isFinished()
* @return true if the program completed without branching
*/
public boolean isFallThrough() {
return index == code.size();
}
/**
* Check if the p-code program has executed a branch
*
* <p>
* Branches can be internal, i.e., within the current program, or external, i.e., to another
* machine instructions. This refers strictly to the latter.
*
* @see #isFallThrough()
* @see #isFinished()
* @return true if the program completed with an external branch
*/
public boolean isBranch() {
return index == -1;
}
/**
* Check if the p-code program is completely executed
*
* @see #isFallThrough()
* @see #isBranch()
* @return true if execution finished, either in fall-through or an external branch
*/
public boolean isFinished() {
return !(0 <= index && index < code.size());
}
/**
* Perform an internal branch, relative to the <em>current op</em>.
*
* <p>
* Because index advances before execution of each op, the index is adjusted by an extra -1.
*
* @param rel the adjustment to the index
*/
public void branch(int rel) {
index += rel - 1; // -1 because we already advanced
if (!(0 <= index && index <= code.size())) {
throw new LowlevelError("Bad p-code branch");
}
}
/**
* Complete the p-code program, indicating an external branch
*/
public void finishAsBranch() {
branched = index - 1; // -1 because we already advanced
index = -1;
}
/**
* Get all the ops in the current p-code program.
*
* @return the list of ops
*/
public List<PcodeOp> getCode() {
return code;
}
/**
* Copy the frame's code (shallow copy) into a new array
*
* @return the array of ops
*/
public PcodeOp[] copyCode() {
return code.toArray(PcodeOp[]::new);
}
/**
* Get the index of the last (branch) op executed
*
* <p>
* The behavior here is a bit strange for compatibility with
* {@link EmulateInstructionStateModifier}. If the p-code program (likely derived from a machine
* instruction) completed with fall-through, then this will return -1. If it completed on a
* branch, then this will return the index of that branch.
*
* @return
*/
public int getBranched() {
return branched;
}
}

View file

@ -0,0 +1,208 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.io.IOException;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.template.OpTpl;
import ghidra.app.util.pcode.AbstractAppender;
import ghidra.app.util.pcode.AbstractPcodeFormatter;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.exception.NotFoundException;
/**
* A p-code program to be executed by a {@link PcodeExecutor}
*
* <p>
* This is a list of p-code operations together with a map of expected userops.
*/
public class PcodeProgram {
protected static class MyAppender extends AbstractAppender<String> {
protected final PcodeProgram program;
protected final StringBuffer buf = new StringBuffer();
public MyAppender(PcodeProgram program, Language language) {
super(language, true);
this.program = program;
buf.append("<" + program.getHead() + ":\n");
}
@Override
protected void appendString(String string) {
buf.append(string);
}
protected void endLine() {
buf.append("\n");
}
@Override
protected String stringifyUseropUnchecked(Language language, int id) {
String name = super.stringifyUseropUnchecked(language, id);
if (name != null) {
return name;
}
return program.useropNames.get(id);
}
@Override
public String finish() {
buf.append(">");
return buf.toString();
}
}
protected static class MyFormatter extends AbstractPcodeFormatter<String, MyAppender> {
protected final PcodeProgram program;
public MyFormatter(PcodeProgram program) {
this.program = program;
}
@Override
protected MyAppender createAppender(Language language, boolean indent) {
return new MyAppender(program, language);
}
@Override
protected FormatResult formatOpTemplate(MyAppender appender, OpTpl op) {
FormatResult result = super.formatOpTemplate(appender, op);
appender.endLine();
return result;
}
}
/**
* Generate a p-code program from the given instruction, without overrides
*
* @param instruction the instruction
* @return the p-code program
*/
public static PcodeProgram fromInstruction(Instruction instruction) {
return fromInstruction(instruction, false);
}
/**
* Generate a p-code program from the given instruction
*
* @param instruction the instruction
* @param includeOverrides as in {@link Instruction#getPcode(boolean)}
* @return the p-code program
*/
public static PcodeProgram fromInstruction(Instruction instruction, boolean includeOverrides) {
Language language = instruction.getPrototype().getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Instruction must be parsed using Sleigh");
}
PcodeOp[] pcode = instruction.getPcode(includeOverrides);
return new PcodeProgram((SleighLanguage) language, List.of(pcode),
Map.of());
}
/**
* Generate a p-code program from a given program's inject library
*
* @param program the program
* @param name the name of the snippet
* @param type the type of the snippet
* @return the p-code program
* @throws MemoryAccessException for problems establishing the injection context
* @throws IOException for problems while emitting the injection p-code
* @throws UnknownInstructionException if there is no underlying instruction being injected
* @throws NotFoundException if an expected aspect of the injection is not present in context
*/
public static PcodeProgram fromInject(Program program, String name, int type)
throws MemoryAccessException, UnknownInstructionException, NotFoundException,
IOException {
PcodeInjectLibrary library = program.getCompilerSpec().getPcodeInjectLibrary();
InjectContext ctx = library.buildInjectContext();
InjectPayload payload = library.getPayload(type, name);
PcodeOp[] pcode = payload.getPcode(program, ctx);
return new PcodeProgram((SleighLanguage) program.getLanguage(), List.of(pcode), Map.of());
}
protected final SleighLanguage language;
protected final List<PcodeOp> code;
protected final Map<Integer, String> useropNames = new HashMap<>();
/**
* Construct a p-code program with the given bindings
*
* @param language the language that generated the p-code
* @param code the list of p-code ops
* @param useropSymbols a map of expected userop symbols
*/
protected PcodeProgram(SleighLanguage language, List<PcodeOp> code,
Map<Integer, UserOpSymbol> useropSymbols) {
this.language = language;
this.code = code;
int langOpCount = language.getNumberOfUserDefinedOpNames();
for (Map.Entry<Integer, UserOpSymbol> ent : useropSymbols.entrySet()) {
int index = ent.getKey();
if (index < langOpCount) {
useropNames.put(index, language.getUserDefinedOpName(index));
}
else {
useropNames.put(index, ent.getValue().getName());
}
}
}
/**
* Get the language generating this program
*
* @return the language
*/
public SleighLanguage getLanguage() {
return language;
}
public List<PcodeOp> getCode() {
return code;
}
/**
* Execute this program using the given executor and library
*
* @param <T> the type of values to be operated on
* @param executor the executor
* @param library the library
*/
public <T> void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library) {
executor.execute(this, library);
}
/**
* For display purposes, get the header above the frame, usually the class name
*
* @return the frame's display header
*/
protected String getHead() {
return getClass().getSimpleName();
}
@Override
public String toString() {
return new MyFormatter(this).formatOps(language, code);
}
}

View file

@ -0,0 +1,187 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.*;
import org.apache.commons.lang3.reflect.TypeUtils;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.pcode.Varnode;
import ghidra.sleigh.grammar.Location;
/**
* A "library" of p-code userops available to a p-code executor
*
* <p>
* The library can provide definitions of p-code userops already declared by the executor's language
* as well as completely new userops accessible to Sleigh/p-code later compiled for the executor.
* The recommended way to implement a library is to extend {@link AnnotatedPcodeUseropLibrary}.
*
* @param <T> the type of values accepted by the p-code userops.
*/
public interface PcodeUseropLibrary<T> {
/**
* The class of the empty userop library.
*
* @see {@link PcodeUseropLibrary#nil()}
*/
final class EmptyPcodeUseropLibrary implements PcodeUseropLibrary<Object> {
@Override
public Map<String, PcodeUseropDefinition<Object>> getUserops() {
return Map.of();
}
}
/**
* Get the type {@code T} for the given class
*
* <p>
* If the class does not implement {@link PcodeUseropLibrary}, this returns null. If it does,
* but no arguments are given (i.e., it implements the raw type), this return {@link Object}.
*
* @param cls the class
* @return the type, or null
*/
static Type getOperandType(Class<?> cls) {
Map<TypeVariable<?>, Type> args =
TypeUtils.getTypeArguments(cls, PcodeUseropLibrary.class);
if (args == null) {
return null;
}
if (args.isEmpty()) {
return Object.class;
}
return args.get(PcodeUseropLibrary.class.getTypeParameters()[0]);
}
/**
* The empty userop library.
*
* <p>
* Executors cannot accept {@code null} libraries. Instead, give it this empty library. To
* satisfy Java's type checker, you may use {@link #nil()} instead.
*/
PcodeUseropLibrary<?> NIL = new EmptyPcodeUseropLibrary();
/**
* The empty userop library, cast to match parameter types.
*
* @param <T> the type required by the executor
* @return the empty userop library
*/
@SuppressWarnings("unchecked")
public static <T> PcodeUseropLibrary<T> nil() {
return (PcodeUseropLibrary<T>) NIL;
}
/**
* The definition of a p-code userop.
*
* @param <T> the type of parameter accepted (and possibly returned) by the userop.
*/
interface PcodeUseropDefinition<T> {
/**
* Get the name of the userop.
*
* <p>
* This is the symbol assigned to the userop when compiling new Sleigh code. It cannot
* conflict with existing userops (except those declared, but not defined, by the executor's
* language) or other symbols of the executor's language. If this userop is to be used
* generically across many languages, choose an unlikely name. Conventionally, these start
* with two underscores {@code __}.
*
* @return the name of the userop
*/
String getName();
/**
* Get the number of <em>input</em> operands acccepted by the userop.
*
* @return the count or -1 if the userop is variadic
*/
int getInputCount();
/**
* Invoke/execute the userop.
*
* @param executor the executor invoking this userop.
* @param library the complete library for this execution. Note the library may have been
* composed from more than the one defining this userop.
* @param outVar if invoked as an rval, the destination varnode for the userop's output.
* Otherwise, {@code null}.
* @param inVars the input varnodes as ordered in the source.
* @see AnnotatedPcodeUseropLibrary.AnnotatedPcodeUseropDefinition
*/
void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library, Varnode outVar,
List<Varnode> inVars);
}
/**
* Get all the userops defined in this library, keyed by (symbol) name.
*
* @return the map of names to defined userops
*/
Map<String, PcodeUseropDefinition<T>> getUserops();
/**
* Compose this and the given library into a new library.
*
* @param lib the other library
* @return a new library having all userops defined between the two
*/
default PcodeUseropLibrary<T> compose(PcodeUseropLibrary<T> lib) {
if (lib == null) {
return this;
}
return new ComposedPcodeUseropLibrary<>(List.of(this, lib));
}
/**
* Get named symbols defined by this library that are not already declared in the language
*
* @param language the language whose existing symbols to consider
* @return a map of new userop indices to extra userop symbols
*/
default Map<Integer, UserOpSymbol> getSymbols(SleighLanguage language) {
//Set<String> langDefedNames = new HashSet<>();
Map<Integer, UserOpSymbol> symbols = new HashMap<>();
Set<String> allNames = new HashSet<>();
int langOpCount = language.getNumberOfUserDefinedOpNames();
for (int i = 0; i < langOpCount; i++) {
String name = language.getUserDefinedOpName(i);
allNames.add(name);
}
int nextOpNo = langOpCount;
for (PcodeUseropDefinition<?> uop : new TreeMap<>(getUserops()).values()) {
String opName = uop.getName();
if (!allNames.add(opName)) {
// Real duplicates will cause a warning during execution
continue;
}
int opNo = nextOpNo++;
Location loc = new Location(getClass().getName() + ":" + opName, 0);
UserOpSymbol sym = new UserOpSymbol(loc, opName);
sym.setIndex(opNo);
symbols.put(opNo, sym);
}
return symbols;
}
}

View file

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import ghidra.program.model.pcode.PcodeOp;
/**
* An exception thrown by
* {@link PcodeExecutor#executeCallother(PcodeOp, PcodeFrame, PcodeUseropLibrary) when a p-code
* userop turns up missing.
*/
public class SleighLinkException extends RuntimeException {
public SleighLinkException(String message) {
super(message);
}
}

View file

@ -0,0 +1,191 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.program.model.pcode.Varnode;
/**
* A p-code userop defined using Sleigh source
*
* @param <T> no type in particular, except to match any executor
*/
public class SleighPcodeUseropDefinition<T> implements PcodeUseropDefinition<T> {
public static final String OUT_SYMBOL_NAME = "__op_output";
/**
* A factory for building {@link SleighPcodeUseropDefinition}s.
*/
public static class Factory {
private final SleighLanguage language;
/**
* Construct a factory for the given language
*
* @param language the language
*/
public Factory(SleighLanguage language) {
this.language = language;
}
/**
* Begin building the definition for a userop with the given name
*
* @param name the name of the new userop
* @return a builder for the userop
*/
public Builder define(String name) {
return new Builder(this, name);
}
}
/**
* A builder for a particular userop
*
* @see Factory
*/
public static class Builder {
private final Factory factory;
private final String name;
private final List<String> params = new ArrayList<>();
private final StringBuffer body = new StringBuffer();
protected Builder(Factory factory, String name) {
this.factory = factory;
this.name = name;
params(OUT_SYMBOL_NAME);
}
/**
* Add parameters with the given names (to the end)
*
* @param additionalParams the additional parameter names
* @return this builder
*/
public Builder params(Collection<String> additionalParams) {
this.params.addAll(additionalParams);
return this;
}
/**
* @see #params(Collection)
*/
public Builder params(String... additionalParams) {
return this.params(Arrays.asList(additionalParams));
}
/**
* Add Sleigh source to the body
*
* @param additionalBody the additional source
*/
public Builder body(CharSequence additionalBody) {
body.append(additionalBody);
return this;
}
/**
* Build the actual definition
*
* <p>
* NOTE: Compilation of the sleigh source is delayed until the first invocation, since the
* compiler must know about the varnodes used as parameters. TODO: There may be some way to
* template it at the p-code level instead of the Sleigh source level.
*
* @param <T> no particular type, except to match the executor
* @return the definition
*/
public <T> SleighPcodeUseropDefinition<T> build() {
return new SleighPcodeUseropDefinition<>(factory.language, name, List.copyOf(params),
body.toString());
}
}
private final SleighLanguage language;
private final String name;
private final List<String> params;
private final String body;
private final Map<List<Varnode>, PcodeProgram> cacheByArgs = new HashMap<>();
protected SleighPcodeUseropDefinition(SleighLanguage language, String name, List<String> params,
String body) {
this.language = language;
this.name = name;
this.params = params;
this.body = body;
}
/**
* Get the p-code program implementing this userop for the given arguments and library.
*
* <p>
* This will compile and cache a program for each new combination of arguments seen.
*
* @param outArg the output operand, if applicable
* @param inArgs the input operands
* @param library the complete userop library
* @return the p-code program to be fed to the same executor as invoked this userop, but in a
* new frame
*/
public PcodeProgram programFor(Varnode outArg, List<Varnode> inArgs,
PcodeUseropLibrary<?> library) {
List<Varnode> args = new ArrayList<>(inArgs.size() + 1);
args.add(outArg);
args.addAll(inArgs);
return cacheByArgs.computeIfAbsent(args,
a -> SleighProgramCompiler.compileUserop(language, name, params, body, library, a));
}
@Override
public String getName() {
return name;
}
@Override
public int getInputCount() {
return params.size() - 1; // account for __op_output
}
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outArg, List<Varnode> inArgs) {
PcodeProgram program = programFor(outArg, inArgs, library);
executor.execute(program, library);
}
/**
* Get the names of the inputs in order
*
* @return the input names
*/
public List<String> getInputs() {
return params;
}
/**
* Get the Sleigh source that defines this userop
*
* @return the lines
*/
public String getBody() {
return body;
}
}

View file

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

View file

@ -0,0 +1,776 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.io.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.antlr.runtime.*;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.Tree;
import ghidra.sleigh.grammar.*;
/**
* A collection of utilities for parsing and manipulating Sleigh semantic source
*/
public enum SleighUtils {
;
public static final String CONDITION_ALWAYS = "1:1";
public static final String UNCONDITIONAL_BREAK = """
emu_swi();
emu_exec_decoded();
""";
/**
* A Sleigh parsing error
*/
public record SleighParseErrorEntry(String header, String message, int start, int stop) {
public String fullMessage() {
return header + " " + message;
}
}
/**
* An exception carrying one or more Sleigh parsing errors
*/
public static class SleighParseError extends RuntimeException {
private final List<SleighParseErrorEntry> errors;
public SleighParseError(Collection<SleighParseErrorEntry> errors) {
super(errors.stream().map(e -> e.fullMessage()).collect(Collectors.joining("\n")));
this.errors = List.copyOf(errors);
}
/**
* Get the actual errors
*
* @return the list of entries
*/
public List<SleighParseErrorEntry> getErrors() {
return errors;
}
}
/**
* A function representing a non-terminal in the Sleigh semantic grammar
*
* @param <T> the return type
*/
public interface ParseFunction<T> {
T apply(SleighParser parser) throws RecognitionException;
}
/**
* Parse a non-terminal symbol from the Sleigh semantic grammar
*
* <p>
* Because the ANTLR parsing function for the non-terminal symbol depends on the "follows" set
* to determine when it has finished, we can't just invoke the function in isolation without
* some hacking. If EOF is not in the non-terminal's follows set, then it won't recognize EOF as
* completing the non-terminal. Instead, we have to present some token that it will recognize.
* Furthermore, regardless of the follow token, we have to check that all of the given input was
* consumed by the parser.
*
* @param <T> the type of result from parsing
* @param nt the function from the parser implementing the non-terminal symbol
* @param text the text to parse
* @param follow a token that would ordinarily follow the non-terminal symbol, or empty for EOF
* @return the parsed result
*/
public static <T extends RuleReturnScope> T parseSleigh(ParseFunction<T> nt,
String text, String follow) {
LineArrayListWriter writer = new LineArrayListWriter();
ParsingEnvironment env = new ParsingEnvironment(writer);
// inject pcode statement lines into writer (needed for error reporting)
BufferedReader r = new BufferedReader(new StringReader(text));
String line;
try {
while ((line = r.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
catch (IOException e) {
throw new AssertionError(e);
}
String inputText = writer.toString().stripTrailing();
CharStream input = new ANTLRStringStream(inputText + follow);
env.getLocator().registerLocation(0, new Location("sleigh", 0));
SleighLexer lex = new SleighLexer(input);
lex.setEnv(env);
UnbufferedTokenStream tokens = new UnbufferedTokenStream(lex);
List<SleighParseErrorEntry> errors = new ArrayList<>();
SleighParser parser = new SleighParser(tokens) {
private void collectError(String[] tokenNames, RecognitionException e) {
String hdr = getErrorHeader(e);
String msg = getErrorMessage(e, tokenNames);
CommonToken ct = (CommonToken) e.token;
errors.add(new SleighParseErrorEntry(hdr, msg, ct.getStartIndex(),
ct.getStopIndex()));
}
{
this.gSemanticParser = new SleighParser_SemanticParser(input, state, this) {
@Override
public void displayRecognitionError(String[] tokenNames,
RecognitionException e) {
collectError(tokenNames, e);
}
@Override
public void emitErrorMessage(String msg) {
throw new AssertionError();
}
};
}
@Override
public void displayRecognitionError(String[] tokenNames, RecognitionException e) {
collectError(tokenNames, e);
}
@Override
public void emitErrorMessage(String msg) {
throw new AssertionError();
}
};
parser.setEnv(env);
parser.setLexer(lex);
lex.pushMode(SleighRecognizerConstants.SEMANTIC);
T t;
try {
t = nt.apply(parser);
}
catch (RecognitionException e) {
parser.reportError(e);
return null;
}
lex.popMode();
CommonToken lastTok = (CommonToken) tokens.elementAt(0);
if (follow.isEmpty()) {
if (!tokens.isEOF(lastTok)) {
parser.reportError(new UnwantedTokenException(Token.EOF, tokens));
}
}
else {
if (inputText.length() != lastTok.getStartIndex()) {
parser.reportError(new UnwantedTokenException(Token.EOF, tokens));
}
}
if (!errors.isEmpty()) {
throw new SleighParseError(errors);
}
return t;
}
/**
* Parse a semantic block, that is a list of Sleigh semantic statements
*
* @param sleigh the source
* @return the parse tree
*/
public static Tree parseSleighSemantic(String sleigh) {
return parseSleigh(SleighParser::semantic, sleigh, "").getTree();
}
/**
* Parse a semantic expression
*
* @param expression the expression as a string
* @return the parse tree
*/
public static Tree parseSleighExpression(String expression) {
return parseSleigh(SleighParser::expr, expression, ";").getTree();
}
/**
* An exception indicating the parse tree did not match a pattern
*/
public static class MismatchException extends RuntimeException {
}
private static String getIdentifier(Tree tree) {
if (tree.getType() != SleighParser.IDENTIFIER) {
throw new MismatchException();
}
return tree.getText();
}
private static boolean isIdentifier(Tree tree, String id) {
return id.equals(getIdentifier(tree));
}
private static void matchIdentifier(Tree tree, String id) {
if (!isIdentifier(tree, id)) {
throw new MismatchException();
}
}
/**
* Get the children of a parse tree node
*
* @param tree the node
* @return the list of children
*/
public static List<Tree> getChildren(Tree tree) {
final int count = tree.getChildCount();
List<Tree> children = Arrays.asList(new Tree[count]);
for (int i = 0; i < count; i++) {
children.set(i, tree.getChild(i));
}
return children;
}
/**
* Match the given tree to a given pattern
*
* @param tree the (sub-)tree to match, actually its root node
* @param type the expected type of the given node
* @param onChildren actions (usually sub-matching) to perform on the children
*/
public static void matchTree(Tree tree, int type, Consumer<List<Tree>> onChildren) {
if (tree.getType() != type) {
throw new MismatchException();
}
onChildren.accept(getChildren(tree));
}
/**
* Require (as part of pattern matching) that the given list of children has a particular size
*
* @param count the required size
* @param list the list of children
*/
public static void requireCount(int count, List<?> list) {
if (count != list.size()) {
throw new MismatchException();
}
}
/**
* Match the given tree to a given pattern with per-child actions
*
* @param tree the (sub-)tree to match, actually its root node
* @param type the expected type of the given node
* @param onChild a list of actions (usually sub-matching) to perform on each corresponding
* child. The matcher will verify the number of children matches the number of
* actions.
*/
@SafeVarargs
public static void match(Tree tree, int type, Consumer<Tree>... onChild) {
matchTree(tree, type, children -> {
requireCount(onChild.length, children);
for (int i = 0; i < onChild.length; i++) {
onChild[i].accept(children.get(i));
}
});
}
/**
* Check if the given tree represents an unconditional breakpoint in the emulator
*
* @param tree the result of parsing a semantic block
* @return true if an unconditional breakpoint, false otherwise
*/
public static boolean isUnconditionalBreakpoint(Tree tree) {
try {
match(tree, SleighParser.OP_SEMANTIC, wantApplyEmuSwi -> {
match(wantApplyEmuSwi, SleighParser.OP_APPLY, wantId -> {
match(wantId, SleighParser.OP_IDENTIFIER, id -> {
matchIdentifier(id, "emu_swi");
});
});
}, wantApplyEmuExecDecoded -> {
match(wantApplyEmuExecDecoded, SleighParser.OP_APPLY, wantId -> {
match(wantId, SleighParser.OP_IDENTIFIER, id -> {
matchIdentifier(id, "emu_exec_decoded");
});
});
});
return true;
}
catch (MismatchException e) {
return false;
}
}
/**
* Check if the given tree represents a conditional breakpoint, and recover that condition
*
* @param tree the result of parsing a semantic block
* @return the condition if matched, null otherwise
*/
public static String recoverConditionFromBreakpoint(Tree tree) {
try {
var l = new Object() {
Tree cond;
String labelId;
};
match(tree, SleighParser.OP_SEMANTIC, wantIf -> {
match(wantIf, SleighParser.OP_IF, cond -> {
l.cond = cond;
}, wantGotoLabel -> {
match(wantGotoLabel, SleighParser.OP_GOTO, wantJumpDest -> {
match(wantJumpDest, SleighParser.OP_JUMPDEST_LABEL, wantLabel -> {
match(wantLabel, SleighParser.OP_LABEL, wantId -> {
match(wantId, SleighParser.OP_IDENTIFIER, id -> {
l.labelId = getIdentifier(id);
});
});
});
});
});
}, wantApplyEmuSwi -> {
match(wantApplyEmuSwi, SleighParser.OP_APPLY, wantId -> {
match(wantId, SleighParser.OP_IDENTIFIER, id -> {
matchIdentifier(id, "emu_swi");
});
});
}, wantLabel -> {
match(wantLabel, SleighParser.OP_LABEL, wantId -> {
match(wantId, SleighParser.OP_IDENTIFIER, id -> {
matchIdentifier(id, l.labelId);
});
});
}, wantApplyEmuExecDecoded -> {
match(wantApplyEmuExecDecoded, SleighParser.OP_APPLY, wantId -> {
match(wantId, SleighParser.OP_IDENTIFIER, id -> {
matchIdentifier(id, "emu_exec_decoded");
});
});
});
return generateSleighExpression(notTree(l.cond));
}
catch (MismatchException e) {
return null;
}
}
/**
* Check if the given Sleigh semantic block implements a conditional breakpoint, and recover
* that condition
*
* @param sleigh the source for a Sleigh semantic block
* @return the condition if matched, null otherwise
*/
public static String recoverConditionFromBreakpoint(String sleigh) {
try {
Tree tree = parseSleighSemantic(sleigh);
if (isUnconditionalBreakpoint(tree)) {
return CONDITION_ALWAYS;
}
return recoverConditionFromBreakpoint(tree);
}
catch (SleighParseError e) {
return null;
}
}
/**
* Synthesize a tree (node)
*
* @param type the type of the node
* @param text the "text" of the node
* @param children the children
* @return the new node
*/
public static Tree makeTree(int type, String text, List<Tree> children) {
CommonTree tree = new CommonTree(new CommonToken(type, text));
tree.addChildren(children);
return tree;
}
private static void catChildrenWithSep(Tree tree, String sep, int chopFront, int chopBack,
StringBuilder sb) {
int count = tree.getChildCount() - chopFront - chopBack;
for (int i = 0; i < count; i++) {
if (i != 0) {
sb.append(sep);
}
generateSleighExpression(tree.getChild(i + chopFront), sb);
}
}
private static void generateSleighExpression(Tree tree, StringBuilder sb) {
switch (tree.getType()) {
case SleighParser.BIN_INT:
case SleighParser.DEC_INT:
case SleighParser.HEX_INT:
case SleighParser.IDENTIFIER:
sb.append(tree.getText());
break;
case SleighParser.OP_BIN_CONSTANT:
case SleighParser.OP_DEC_CONSTANT:
case SleighParser.OP_HEX_CONSTANT:
case SleighParser.OP_IDENTIFIER:
generateSleighExpression(tree.getChild(0), sb);
break;
case SleighParser.OP_NOT:
sb.append("!");
generateSleighExpression(tree.getChild(0), sb);
break;
case SleighParser.OP_INVERT:
sb.append("~");
generateSleighExpression(tree.getChild(0), sb);
break;
case SleighParser.OP_NEGATE:
sb.append("-");
generateSleighExpression(tree.getChild(0), sb);
break;
case SleighParser.OP_FNEGATE:
sb.append("f-");
generateSleighExpression(tree.getChild(0), sb);
break;
case SleighParser.OP_ADD:
catChildrenWithSep(tree, " + ", 0, 0, sb);
break;
case SleighParser.OP_SUB:
catChildrenWithSep(tree, " - ", 0, 0, sb);
break;
case SleighParser.OP_MULT:
catChildrenWithSep(tree, " * ", 0, 0, sb);
break;
case SleighParser.OP_DIV:
catChildrenWithSep(tree, " / ", 0, 0, sb);
break;
case SleighParser.OP_REM:
catChildrenWithSep(tree, " % ", 0, 0, sb);
break;
case SleighParser.OP_SDIV:
catChildrenWithSep(tree, " s/ ", 0, 0, sb);
break;
case SleighParser.OP_SREM:
catChildrenWithSep(tree, " s% ", 0, 0, sb);
break;
case SleighParser.OP_FADD:
catChildrenWithSep(tree, " f+ ", 0, 0, sb);
break;
case SleighParser.OP_FSUB:
catChildrenWithSep(tree, " f- ", 0, 0, sb);
break;
case SleighParser.OP_FMULT:
catChildrenWithSep(tree, " f* ", 0, 0, sb);
break;
case SleighParser.OP_FDIV:
catChildrenWithSep(tree, " f/ ", 0, 0, sb);
break;
case SleighParser.OP_LEFT:
catChildrenWithSep(tree, " << ", 0, 0, sb);
break;
case SleighParser.OP_RIGHT:
catChildrenWithSep(tree, " >> ", 0, 0, sb);
break;
case SleighParser.OP_SRIGHT:
catChildrenWithSep(tree, " s>> ", 0, 0, sb);
break;
case SleighParser.OP_AND:
catChildrenWithSep(tree, " & ", 0, 0, sb);
break;
case SleighParser.OP_OR:
catChildrenWithSep(tree, " | ", 0, 0, sb);
break;
case SleighParser.OP_XOR:
catChildrenWithSep(tree, " ^ ", 0, 0, sb);
break;
case SleighParser.OP_BOOL_AND:
catChildrenWithSep(tree, " && ", 0, 0, sb);
break;
case SleighParser.OP_BOOL_OR:
catChildrenWithSep(tree, " || ", 0, 0, sb);
break;
case SleighParser.OP_BOOL_XOR:
catChildrenWithSep(tree, " ^^ ", 0, 0, sb);
break;
case SleighParser.OP_EQUAL:
catChildrenWithSep(tree, " == ", 0, 0, sb);
break;
case SleighParser.OP_NOTEQUAL:
catChildrenWithSep(tree, " != ", 0, 0, sb);
break;
case SleighParser.OP_FEQUAL:
catChildrenWithSep(tree, " f== ", 0, 0, sb);
break;
case SleighParser.OP_FNOTEQUAL:
catChildrenWithSep(tree, " f!= ", 0, 0, sb);
break;
case SleighParser.OP_LESS:
catChildrenWithSep(tree, " < ", 0, 0, sb);
break;
case SleighParser.OP_LESSEQUAL:
catChildrenWithSep(tree, " <= ", 0, 0, sb);
break;
case SleighParser.OP_GREATEQUAL:
catChildrenWithSep(tree, " >= ", 0, 0, sb);
break;
case SleighParser.OP_GREAT:
catChildrenWithSep(tree, " > ", 0, 0, sb);
break;
case SleighParser.OP_SLESS:
catChildrenWithSep(tree, " s< ", 0, 0, sb);
break;
case SleighParser.OP_SLESSEQUAL:
catChildrenWithSep(tree, " s<= ", 0, 0, sb);
break;
case SleighParser.OP_SGREATEQUAL:
catChildrenWithSep(tree, " s>= ", 0, 0, sb);
break;
case SleighParser.OP_SGREAT:
catChildrenWithSep(tree, " s> ", 0, 0, sb);
break;
case SleighParser.OP_FLESS:
catChildrenWithSep(tree, " f< ", 0, 0, sb);
break;
case SleighParser.OP_FLESSEQUAL:
catChildrenWithSep(tree, " f<= ", 0, 0, sb);
break;
case SleighParser.OP_FGREATEQUAL:
catChildrenWithSep(tree, " f>= ", 0, 0, sb);
break;
case SleighParser.OP_FGREAT:
catChildrenWithSep(tree, " f> ", 0, 0, sb);
break;
case SleighParser.OP_DEREFERENCE:
if (tree.getChildCount() == 3) {
sb.append("*[");
generateSleighExpression(tree.getChild(0), sb);
sb.append("]:");
generateSleighExpression(tree.getChild(1), sb);
sb.append(" ");
generateSleighExpression(tree.getChild(2), sb);
}
else if (tree.getChildCount() == 2) {
Tree child0 = tree.getChild(0);
switch (child0.getType()) {
case SleighParser.OP_IDENTIFIER:
sb.append("*[");
generateSleighExpression(child0, sb);
sb.append("] ");
generateSleighExpression(tree.getChild(1), sb);
break;
case SleighParser.OP_BIN_CONSTANT:
case SleighParser.OP_DEC_CONSTANT:
case SleighParser.OP_HEX_CONSTANT:
sb.append("*:");
generateSleighExpression(child0, sb);
sb.append(" ");
generateSleighExpression(tree.getChild(1), sb);
break;
default:
throw new AssertionError(
"OP_DEREFERENCE with 2 children where child[0] is " +
SleighParser.tokenNames[child0.getType()]);
}
}
else if (tree.getChildCount() == 1) {
sb.append("*");
generateSleighExpression(tree.getChild(0), sb);
}
else {
throw new AssertionError(
"OP_DEREFERENCE with " + tree.getChildCount() + " children");
}
break;
case SleighParser.OP_ADDRESS_OF:
if (tree.getChildCount() == 2) {
sb.append("&");
generateSleighExpression(tree.getChild(0), sb);
sb.append(" ");
generateSleighExpression(tree.getChild(1), sb);
}
else if (tree.getChildCount() == 1) {
sb.append("&");
generateSleighExpression(tree.getChild(0), sb);
}
else {
throw new AssertionError(
"OP_ADDRESS_OF with " + tree.getChildCount() + " children");
}
break;
case SleighParser.OP_SIZING_SIZE:
sb.append(":");
generateSleighExpression(tree.getChild(0), sb);
break;
case SleighParser.OP_APPLY:
generateSleighExpression(tree.getChild(0), sb);
sb.append("(");
catChildrenWithSep(tree, ", ", 1, 0, sb);
sb.append(")");
break;
case SleighParser.OP_TRUNCATION_SIZE:
generateSleighExpression(tree.getChild(0), sb);
sb.append(":");
generateSleighExpression(tree.getChild(1), sb);
break;
case SleighParser.OP_BITRANGE:
generateSleighExpression(tree.getChild(0), sb);
sb.append("[");
generateSleighExpression(tree.getChild(1), sb);
sb.append(",");
generateSleighExpression(tree.getChild(2), sb);
sb.append("]");
break;
case SleighParser.OP_BITRANGE2:
generateSleighExpression(tree.getChild(0), sb);
sb.append(":");
generateSleighExpression(tree.getChild(1), sb);
break;
case SleighParser.OP_ARGUMENTS:
catChildrenWithSep(tree, ", ", 0, 0, sb);
break;
case SleighParser.OP_PARENTHESIZED:
sb.append("(");
generateSleighExpression(tree.getChild(0), sb);
sb.append(")");
break;
default:
throw new AssertionError("type = " + SleighParser.tokenNames[tree.getType()]);
}
}
/**
* Generate source for the given Sleigh parse tree
*
* <p>
* Currently, only nodes that could appear in a Sleigh expression are supported.
*
* @param tree the expression tree
* @return the generated string
*/
public static String generateSleighExpression(Tree tree) {
StringBuilder sb = new StringBuilder();
generateSleighExpression(tree, sb);
return sb.toString();
}
/**
* Remove parenthesis from the root of the given tree
*
* <p>
* If the root is parenthesis, this simply gets the child. This is applied recursively until a
* non-parenthesis child is encountered.
*
* @param tree the result of parsing a Sleigh expression
* @return the same or sub-tree
*/
public static Tree removeParenthesisTree(Tree tree) {
if (tree.getType() == SleighParser.OP_PARENTHESIZED) {
return removeParenthesisTree(tree.getChild(0));
}
return tree;
}
/**
* Apply the boolean "not" operator to a Sleigh expression
*
* <p>
* This will attempt to invert the expression when possible, e.g., by changing a top-level
* "equals" to "not equals." If that is not possible, the this adds parenthesis and applies the
* actual Sleigh boolean "not" operator.
*
* @param boolExpr the result of parsing a Sleigh expression
* @return the tree for the inverted expression
*/
public static Tree notTree(Tree boolExpr) {
boolExpr = removeParenthesisTree(boolExpr);
switch (boolExpr.getType()) {
case SleighParser.OP_EQUAL:
return makeTree(SleighParser.OP_NOTEQUAL, "!=", getChildren(boolExpr));
case SleighParser.OP_NOTEQUAL:
return makeTree(SleighParser.OP_EQUAL, "==", getChildren(boolExpr));
case SleighParser.OP_FEQUAL:
return makeTree(SleighParser.OP_FNOTEQUAL, "f!=", getChildren(boolExpr));
case SleighParser.OP_FNOTEQUAL:
return makeTree(SleighParser.OP_FEQUAL, "f==", getChildren(boolExpr));
case SleighParser.OP_LESS:
return makeTree(SleighParser.OP_GREATEQUAL, ">=", getChildren(boolExpr));
case SleighParser.OP_LESSEQUAL:
return makeTree(SleighParser.OP_GREAT, ">", getChildren(boolExpr));
case SleighParser.OP_GREATEQUAL:
return makeTree(SleighParser.OP_LESS, "<", getChildren(boolExpr));
case SleighParser.OP_GREAT:
return makeTree(SleighParser.OP_LESSEQUAL, "<=", getChildren(boolExpr));
case SleighParser.OP_SLESS:
return makeTree(SleighParser.OP_SGREATEQUAL, "s>=", getChildren(boolExpr));
case SleighParser.OP_SLESSEQUAL:
return makeTree(SleighParser.OP_SGREAT, "s>", getChildren(boolExpr));
case SleighParser.OP_SGREATEQUAL:
return makeTree(SleighParser.OP_SLESS, "s<", getChildren(boolExpr));
case SleighParser.OP_SGREAT:
return makeTree(SleighParser.OP_SLESSEQUAL, "s<=", getChildren(boolExpr));
case SleighParser.OP_FLESS:
return makeTree(SleighParser.OP_FGREATEQUAL, "f>=", getChildren(boolExpr));
case SleighParser.OP_FLESSEQUAL:
return makeTree(SleighParser.OP_FGREAT, "f>", getChildren(boolExpr));
case SleighParser.OP_FGREATEQUAL:
return makeTree(SleighParser.OP_FLESS, "f<", getChildren(boolExpr));
case SleighParser.OP_FGREAT:
return makeTree(SleighParser.OP_FLESSEQUAL, "f<=", getChildren(boolExpr));
case SleighParser.OP_NOT:
return removeParenthesisTree(boolExpr.getChild(0));
default:
return makeTree(SleighParser.OP_NOT, "!", List.of(
makeTree(SleighParser.OP_PARENTHESIZED, "(...)", List.of(
boolExpr))));
}
}
/**
* Generate Sleigh source for a breakpoint predicated on the given condition
*
* @param condition a Sleigh expression
* @return the Sleigh source
*/
public static String sleighForConditionalBreak(String condition) {
if (CONDITION_ALWAYS.equals(condition)) {
return UNCONDITIONAL_BREAK;
}
Tree tree = parseSleighExpression(condition);
String negCond = generateSleighExpression(notTree(tree));
return String.format("""
if %s goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
""", negCond);
}
}

View file

@ -0,0 +1,28 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import ghidra.pcode.emu.PcodeThread;
/**
* An exception thrown during execution if {@link PcodeThread#setSuspended(boolean)} is invoked with
* {@code true}.
*/
public class SuspendedPcodeExecutionException extends PcodeExecutionException {
public SuspendedPcodeExecutionException(PcodeFrame frame, Throwable cause) {
super("Execution suspended by user", frame, cause);
}
}

View file

@ -0,0 +1,258 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* The location of a value
*
* <p>
* This is an analog to {@link VariableStorage}, except that this records the actual storage
* location of the evaluated variable or expression. This does not incorporate storage of
* intermediate dereferenced values. For example, suppose {@code R0 = 0xdeadbeef}, and we want to
* evaluate {@code *:4 R0}. The storage would be {@code ram:deadbeef:4}, not
* {@code R0,ram:deadbeef:4}.
*/
public class ValueLocation {
private static final AddressSpace CONST =
new GenericAddressSpace("const", 64, AddressSpace.TYPE_CONSTANT, 0);
public static String vnToString(Varnode vn, Language language) {
Register register =
language == null ? null : language.getRegister(vn.getAddress(), vn.getSize());
if (register != null) {
return String.format("%s:%d", register.getName(), vn.getSize());
}
return String.format("%s:%d", vn.getAddress(), vn.getSize());
}
private static boolean isZero(Varnode vn) {
return vn.isConstant() && vn.getOffset() == 0;
}
private static List<Varnode> removeLeading0s(List<Varnode> nodes) {
for (int i = 0; i < nodes.size(); i++) {
if (!isZero(nodes.get(i))) {
return nodes.subList(i, nodes.size());
}
}
return List.of();
}
/**
* Generate the "location" of a constant
*
* @param value the value
* @param size the size of the constant in bytes
* @return the "location"
*/
public static ValueLocation fromConst(long value, int size) {
return new ValueLocation(new Varnode(CONST.getAddress(value), size));
}
/**
* Generate a location from a varnode
*
* @param address the dynamic address of the variable
* @param size the size of the variable in bytes
* @return the location
*/
public static ValueLocation fromVarnode(Address address, int size) {
return new ValueLocation(new Varnode(address, size));
}
private final List<Varnode> nodes;
/**
* Construct a location from a list of varnodes
*
* <p>
* Any leading varnodes which are constant 0s are removed.
*
* @param nodes the varnodes
*/
public ValueLocation(Varnode... nodes) {
this.nodes = removeLeading0s(List.of(nodes));
}
/**
* Construct a location from a list of varnodes
*
* <p>
* Any leading varnodes which are constant 0s are removed.
*
* @param nodes the varnodes
*/
public ValueLocation(List<Varnode> nodes) {
this.nodes = removeLeading0s(List.copyOf(nodes));
}
/**
* Get the number of varnodes for this location
*
* @return the count
*/
public int nodeCount() {
return nodes.size();
}
/**
* Get the address of the first varnode
*
* @return the address, or null if this location has no varnodes
*/
public Address getAddress() {
return nodes.isEmpty() ? null : nodes.get(0).getAddress();
}
/**
* Render this location as a string, substituting registers where applicable
*
* @param language the optional language for register substitution
* @return the string
*/
public String toString(Language language) {
return nodes.stream().map(vn -> vnToString(vn, language)).collect(Collectors.joining(","));
}
@Override
public String toString() {
return toString(null);
}
/**
* Apply a {@link PcodeOp#INT_OR} operator
*
* <p>
* There is a very restrictive set of constraints for which this yields a non-null location. If
* either this or that is empty, the other is returned. Otherwise, the varnodes are arranged in
* pairs by taking one from each storage starting at the right, or least-significant varnode.
* Each pair must match in length, and one of the pair must be a constant zero. The non-zero
* varnode is taken. The unpaired varnodes to the left, if any, are all taken. If any pair does
* not match in length, or if neither is zero, the resulting location is null. This logic is to
* ensure location information is accrued during concatenation.
*
* @param that the other location
* @return the location
*/
public ValueLocation intOr(ValueLocation that) {
if (this.isEmpty()) {
return that;
}
if (that.isEmpty()) {
return this;
}
ListIterator<Varnode> itA = this.nodes.listIterator(this.nodeCount());
ListIterator<Varnode> itB = that.nodes.listIterator(that.nodeCount());
Varnode[] result = new Varnode[Math.max(this.nodeCount(), that.nodeCount())];
int i = result.length;
while (itA.hasNext() && itB.hasPrevious()) {
Varnode vnA = itA.previous();
Varnode vnB = itB.previous();
if (vnA.getSize() != vnB.getSize()) {
return null;
}
if (isZero(vnA)) {
result[--i] = vnB;
}
else if (isZero(vnB)) {
result[--i] = vnA;
}
}
while (itA.hasPrevious()) {
result[--i] = itA.previous();
}
while (itB.hasPrevious()) {
result[--i] = itB.previous();
}
return new ValueLocation(result);
}
/**
* If the location represents a constant, get its value
*
* @return the constant value
*/
public BigInteger getConst() {
BigInteger result = BigInteger.ZERO;
for (Varnode vn : nodes) {
if (!vn.isConstant()) {
return null;
}
result = result.shiftLeft(vn.getSize() * 8);
result = result.or(vn.getAddress().getOffsetAsBigInteger());
}
return result;
}
/**
* Apply a {@link PcodeOp#INT_LEFT} operator
*
* <p>
* This requires the shift amount to represent an integral number of bytes. Otherwise, the
* result is null. This simply inserts a constant zero to the right, having the number of bytes
* indicated by the shift amount. This logic is to ensure location information is accrued during
* concatenation.
*
* @param amount the number of bits to shift
* @return the location.
*/
public ValueLocation shiftLeft(int amount) {
if (amount % 8 != 0) {
return null;
}
List<Varnode> result = new ArrayList<>(nodes);
result.add(new Varnode(CONST.getAddress(0), amount / 8));
return new ValueLocation(result);
}
/**
* Get the total size of this location in bytes
*
* @return the size in bytes
*/
public int size() {
int result = 0;
for (Varnode vn : nodes) {
result += vn.getSize();
}
return result;
}
/**
* Check if this location includes any varnodes
*
* <p>
* Note that a location cannot consist entirely of constant zeros and be non-empty. The
* constructor will have removed them all.
*
* @return true if empty
*/
public boolean isEmpty() {
return nodes.isEmpty();
}
}

View file

@ -0,0 +1,70 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package utilities.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
/**
* Some utilities for reflection using annotations
*/
public enum AnnotationUtilities {
;
/**
* Collect from among the given class, its superclasses, and its interfaces all methods
* annotated with the given annotation type.
*
* @param annotCls the annotation type
* @param cls the class whose methods to examine
* @return the set of all methods having the given annotation type
*/
public static Set<Method> collectAnnotatedMethods(Class<? extends Annotation> annotCls,
Class<?> cls) {
Set<Method> defs = new HashSet<>();
collectAnnotatedMethods(annotCls, cls, defs, new HashSet<>());
return defs;
}
private static void collectAnnotatedMethods(Class<? extends Annotation> annotCls, Class<?> cls,
Set<Method> defs,
Set<Class<?>> visited) {
if (!visited.add(cls)) {
return;
}
Class<?> superCls = cls.getSuperclass();
if (superCls != null) {
collectAnnotatedMethods(annotCls, superCls, defs, visited);
}
for (Class<?> superIf : cls.getInterfaces()) {
collectAnnotatedMethods(annotCls, superIf, defs, visited);
}
collectAnnotatedMethodsForClass(annotCls, cls, defs);
}
private static void collectAnnotatedMethodsForClass(Class<? extends Annotation> annotCls,
Class<?> cls, Set<Method> defs) {
for (Method method : cls.getDeclaredMethods()) {
Annotation annot = method.getAnnotation(annotCls);
if (annot == null) {
continue;
}
defs.add(method);
}
}
}

View file

@ -0,0 +1,35 @@
/* ###
* 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 generic;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import generic.ULongSpan.DefaultULongSpanSet;
import generic.ULongSpan.MutableULongSpanSet;
public class ULongSpanTest {
@Test
public void testULongSpanSet() {
MutableULongSpanSet set = new DefaultULongSpanSet();
set.add(ULongSpan.extent(0, 50));
set.add(ULongSpan.extent(50,50));
assertEquals(ULongSpan.extent(0, 100), Unique.assertOne(set.spans()));
}
}

View file

@ -0,0 +1,63 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.processors.sleigh;
import java.io.IOException;
import java.util.*;
import org.xml.sax.SAXException;
import generic.jar.ResourceFile;
import ghidra.program.model.lang.*;
import resources.ResourceManager;
public class SleighLanguageHelper {
public static SleighLanguage getMockBE64Language()
throws UnknownInstructionException, SAXException, IOException {
ResourceFile cSpecFile =
new ResourceFile(ResourceManager.getResource("mock.cspec").getPath());
CompilerSpecDescription cSpecDesc =
new SleighCompilerSpecDescription(new CompilerSpecID("default"), "default", cSpecFile);
ResourceFile lDefsFile =
new ResourceFile(ResourceManager.getResource("mock.ldefs").getPath());
ResourceFile pSpecFile =
new ResourceFile(ResourceManager.getResource("mock.pspec").getPath());
ResourceFile slaFile =
new ResourceFile(ResourceManager.getResource("mock.sla").getPath());
SleighLanguageDescription langDesc = new SleighLanguageDescription(
new LanguageID("Mock:BE:64:default"),
"Mock language (64-bit BE)",
Processor.findOrPossiblyCreateProcessor("Mock"),
Endian.BIG, // endian
Endian.BIG, // instructionEndian
64,
"default", // variant
0, // major version
0, // minor version
false, // deprecated
new HashMap<>(), // truncatedSpaceMap
new ArrayList<>(List.of(cSpecDesc)),
new HashMap<>() // externalNames
);
langDesc.setDefsFile(lDefsFile);
langDesc.setSpecFile(pSpecFile);
langDesc.setSlaFile(slaFile);
return new SleighLanguage(langDesc);
}
}

View file

@ -0,0 +1,145 @@
/* ###
* 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.generic.util.datastruct;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Test;
import generic.ULongSpan;
public class SemisparseByteArrayTest {
private static final String HELLO_WORLD = "Hello, World!";
protected static final byte[] HW = HELLO_WORLD.getBytes();
protected static <T> Set<T> toSet(Iterable<T> it) {
Set<T> result = new HashSet<>();
for (T e : it) {
result.add(e);
}
return result;
}
@Test
public void testSingles() {
SemisparseByteArray cache = new SemisparseByteArray();
cache.putData(0, HW, 0, 1);
assertEquals(Set.of(
ULongSpan.span(0, 0)),
toSet(cache.getInitialized(0, HW.length + 7).spans()));
assertEquals(Set.of(
ULongSpan.span(1, HW.length - 1)),
toSet(cache.getUninitialized(0, HW.length - 1).spans()));
cache.putData(2, HW, 2, 1);
assertEquals(Set.of(
ULongSpan.span(1, 1),
ULongSpan.span(3, HW.length - 1)),
toSet(cache.getUninitialized(0, HW.length - 1).spans()));
cache.putData(11, HW, 11, 2);
byte[] read = new byte[HW.length + 5]; // 5 extra
cache.getData(0, read, 2, HW.length - 1); // Intentionally miss the '!'
// ..................(offset of 2) H e l l o , W o r l d ! (5 extra - 2)
byte[] expRead = new byte[] { 0, 0, 'H', 0, 'l', 0, 0, 0, 0, 0, 0, 0, 0, 'd', 0, 0, 0, 0 };
assertTrue(Arrays.equals(expRead, read));
}
@Test
public void testBoundary() {
SemisparseByteArray cache = new SemisparseByteArray();
cache.putData(SemisparseByteArray.BLOCK_SIZE - 6, HW);
byte[] data = new byte[HW.length];
cache.getData(SemisparseByteArray.BLOCK_SIZE - 6, data);
assertEquals(HELLO_WORLD, new String(data));
}
@Test
public void testBoundaryAtSignedOverflow() {
SemisparseByteArray cache = new SemisparseByteArray();
cache.putData(0x7ffffffffffffff8L, HW);
byte[] data = new byte[HW.length];
cache.getData(0x7ffffffffffffff8L, data);
assertEquals(HELLO_WORLD, new String(data));
}
@Test
public void testBoundaryAtUnsignedMax() {
SemisparseByteArray cache = new SemisparseByteArray();
cache.putData(-HW.length, HW);
byte[] data = new byte[HW.length];
cache.getData(-HW.length, data);
assertEquals(HELLO_WORLD, new String(data));
}
@Test
public void testLarge() {
Random rand = new Random();
byte[] chunk = new byte[SemisparseByteArray.BLOCK_SIZE * 10];
rand.nextBytes(chunk);
SemisparseByteArray cache = new SemisparseByteArray();
cache.putData(191, chunk);
cache.putData(191 + chunk.length, HW);
byte[] read = new byte[1 + chunk.length + HW.length];
cache.getData(191, read, 1, chunk.length);
assertEquals(0, read[0]); // Test the offset of 1
for (int i = 0; i < chunk.length; i++) {
assertEquals(chunk[i], read[1 + i]); // Test the actual copy
}
for (int i = 0; i < HW.length; i++) {
assertEquals(0, read[1 + chunk.length + i]); // Test length param. Should not see HW.
}
}
@Test
public void testPutAll() {
SemisparseByteArray first = new SemisparseByteArray();
SemisparseByteArray second = new SemisparseByteArray();
second.putData(0, new byte[] { 1, 2, 3, 4 });
second.putData(-HW.length, HW);
first.putData(2, new byte[] { 1, 2, 3, 4 });
first.putData(-HW.length - 1, new byte[] { 10, 11 });
first.putAll(second);
assertEquals(Set.of(
ULongSpan.span(0, 5),
ULongSpan.span(-HW.length - 1, -1)),
toSet(first.getInitialized(0, -1).spans()));
byte[] read = new byte[6];
first.getData(0, read);
assertArrayEquals(new byte[] { 1, 2, 3, 4, 3, 4 }, read);
read = new byte[HW.length];
first.getData(-HW.length, read);
assertArrayEquals(HW, read);
read = new byte[2];
first.getData(-HW.length - 1, read);
assertArrayEquals(new byte[] { 10, 'H' }, read);
}
}

View file

@ -0,0 +1,80 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import ghidra.program.model.address.*;
public class SparseAddressRangeMapTest {
AddressSpace space = new GenericAddressSpace("test", 64, AddressSpace.TYPE_RAM, 0);
Address addr(long off) {
return space.getAddress(off);
}
AddressRange range(long min, long max) {
return new AddressRangeImpl(addr(min), addr(max));
}
@Test
public void testIsEmpty() {
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
assertTrue(map.isEmpty());
map.put(range(0x0, 0xff), "Hello!");
assertFalse(map.isEmpty());
map.clear();
assertTrue(map.isEmpty());
}
@Test
public void testHasEntry() {
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
assertFalse(map.hasEntry(addr(0x0f), "Hello!"::equals));
map.put(range(0x0, 0xff), "Hello!");
assertTrue(map.hasEntry(addr(0x0f), "Hello!"::equals));
assertFalse(map.hasEntry(addr(0x100), "Hello!"::equals));
assertFalse(map.hasEntry(addr(0x0f), "Good bye!"::equals));
map.clear();
assertFalse(map.hasEntry(addr(0x0f), "Hello!"::equals));
}
@Test
public void testHasEntrySpansPages() {
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
map.put(range(0x100, 0x1100), "Hello!");
assertTrue(map.hasEntry(addr(0x0fff), "Hello!"::equals));
assertTrue(map.hasEntry(addr(0x1000), "Hello!"::equals));
}
@Test
public void testHasEntryOverlapping() {
SparseAddressRangeMap<String> map = new SparseAddressRangeMap<>();
map.put(range(0x0, 0xff), "Hello!");
map.put(range(0x10, 0x10f), "Good bye!");
assertTrue(map.hasEntry(addr(0x0f), "Hello!"::equals));
assertTrue(map.hasEntry(addr(0x20), "Hello!"::equals));
assertTrue(map.hasEntry(addr(0x20), "Good bye!"::equals));
assertTrue(map.hasEntry(addr(0x100), "Good bye!"::equals));
}
}

View file

@ -0,0 +1,459 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.*;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.GhidraTestApplicationLayout;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.Varnode;
public class AnnotatedPcodeUseropLibraryTest extends AbstractGTest {
private class TestUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]> {
@Override
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
}
@Before
public void setUp() throws IOException {
if (!Application.isInitialized()) {
Application.initializeApplication(
new GhidraTestApplicationLayout(new File(getTestDirectoryPath())),
new ApplicationConfiguration());
}
}
protected PcodeExecutor<byte[]> createBytesExecutor() throws Exception {
return createBytesExecutor(SleighLanguageHelper.getMockBE64Language());
}
protected PcodeExecutor<byte[]> createBytesExecutor(SleighLanguage language) throws Exception {
PcodeExecutorState<byte[]> state = new BytesPcodeExecutorState(language);
PcodeArithmetic<byte[]> arithmetic = BytesPcodeArithmetic.forLanguage(language);
return new PcodeExecutor<>(language, arithmetic, state, Reason.EXECUTE);
}
protected <T> void executeSleigh(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
String source) {
PcodeProgram program =
SleighProgramCompiler.compileProgram(executor.getLanguage(), getName(),
source, library);
executor.execute(program, library);
}
protected void executeSleigh(PcodeUseropLibrary<byte[]> library, String source)
throws Exception {
executeSleigh(createBytesExecutor(), library, source);
}
protected static void assertBytes(long expectedVal, int expectedSize, byte[] actual) {
assertEquals(expectedSize, actual.length);
assertEquals(expectedVal, Utils.bytesToLong(actual, expectedSize, true));
}
protected static void assertConstVarnode(long expectedVal, int expectedSize, Varnode actual) {
assertTrue(actual.getAddress().isConstantAddress());
assertEquals(expectedVal, actual.getOffset());
assertEquals(expectedSize, actual.getSize());
}
protected static void assertRegVarnode(Register expected, Varnode actual) {
assertEquals(expected.getAddress(), actual.getAddress());
assertEquals(expected.getNumBytes(), actual.getSize());
}
@Test
public void testNoParams() throws Exception {
var library = new TestUseropLibrary() {
boolean invoked = false;
@PcodeUserop
private void __testop() {
invoked = true;
}
};
executeSleigh(library, "__testop();");
assertTrue(library.invoked);
}
@Test
public void testOneInputFixedT() throws Exception {
var library = new TestUseropLibrary() {
byte[] input;
@PcodeUserop
private void __testop(byte[] input) {
this.input = input;
}
};
executeSleigh(library, "__testop(1234:4);");
assertBytes(1234, 4, library.input);
}
@Test
public void testVariadicInputT() throws Exception {
var library = new TestUseropLibrary() {
byte[][] inputs;
@PcodeUserop(variadic = true)
private void __testop(byte[][] inputs) {
this.inputs = inputs;
}
};
executeSleigh(library, "__testop(1234:4, 4567:2);");
assertBytes(1234, 4, library.inputs[0]);
assertBytes(4567, 2, library.inputs[1]);
}
@Test
public void testVariadicInputVars() throws Exception {
var library = new TestUseropLibrary() {
Varnode[] inputs;
@PcodeUserop(variadic = true)
private void __testop(Varnode[] inputs) {
this.inputs = inputs;
}
};
executeSleigh(library, "__testop(1234:4, 4567:2);");
assertConstVarnode(1234, 4, library.inputs[0]);
assertConstVarnode(4567, 2, library.inputs[1]);
}
@Test
public void testReturnedOutput() throws Exception {
var library = new TestUseropLibrary() {
@PcodeUserop
private byte[] __testop() {
return Utils.longToBytes(1234, 8, true);
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
Register r0 = executor.getLanguage().getRegister("r0");
executeSleigh(executor, library, "r0 = __testop();");
assertBytes(1234, 8, executor.getState().getVar(r0, Reason.INSPECT));
}
@Test
public void testReturnedOutputBinaryFunc() throws Exception {
var library = new TestUseropLibrary() {
@PcodeUserop
private byte[] __testop(byte[] aBytes, byte[] bBytes) {
long a = Utils.bytesToLong(aBytes, 8, true);
long b = Utils.bytesToLong(bBytes, 8, true);
return Utils.longToBytes(a * a + b, 8, true);
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
Register r0 = executor.getLanguage().getRegister("r0");
Register r1 = executor.getLanguage().getRegister("r1");
executor.getState().setVar(r0, Utils.longToBytes(10, 8, true));
executeSleigh(executor, library, "r1 = __testop(r0, 59:8);");
assertBytes(159, 8, executor.getState().getVar(r1, Reason.INSPECT));
}
@Test
public void testOpExecutor() throws Exception {
var library = new TestUseropLibrary() {
PcodeExecutor<byte[]> executor;
@PcodeUserop
private void __testop(@OpExecutor PcodeExecutor<byte[]> executor) {
this.executor = executor;
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
executeSleigh(executor, library, "__testop();");
assertEquals(executor, library.executor);
}
@Test
public void testOpState() throws Exception {
var library = new TestUseropLibrary() {
PcodeExecutorState<byte[]> state;
@PcodeUserop
private void __testop(@OpState PcodeExecutorState<byte[]> state) {
this.state = state;
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
executeSleigh(executor, library, "__testop();");
assertEquals(executor.getState(), library.state);
}
@Test
public void testOpLibrary() throws Exception {
var library = new TestUseropLibrary() {
PcodeUseropLibrary<byte[]> lib;
@PcodeUserop
private void __testop(@OpLibrary PcodeUseropLibrary<byte[]> lib) {
this.lib = lib;
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
executeSleigh(executor, library, "__testop();");
assertEquals(library, library.lib);
}
@Test
public void testOpOutput() throws Exception {
var library = new TestUseropLibrary() {
Varnode outVar;
@PcodeUserop
private void __testop(@OpOutput Varnode outVar) {
this.outVar = outVar;
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
Register r0 = executor.getLanguage().getRegister("r0");
executeSleigh(executor, library, "r0 = __testop();");
assertRegVarnode(r0, library.outVar);
}
@Test
public void testKitchenSink() throws Exception {
var library = new TestUseropLibrary() {
PcodeExecutor<byte[]> executor;
PcodeExecutorState<byte[]> state;
PcodeUseropLibrary<byte[]> lib;
Varnode outVar;
Varnode inVar0;
byte[] inVal1;
@PcodeUserop
private byte[] __testop(
@OpOutput Varnode outVar,
@OpLibrary PcodeUseropLibrary<byte[]> lib,
@OpExecutor PcodeExecutor<byte[]> executor,
Varnode inVar0,
@OpState PcodeExecutorState<byte[]> state,
byte[] inVal1) {
this.executor = executor;
this.state = state;
this.lib = lib;
this.outVar = outVar;
this.inVar0 = inVar0;
this.inVal1 = inVal1;
return inVal1;
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
Register r0 = executor.getLanguage().getRegister("r0");
Register r1 = executor.getLanguage().getRegister("r1");
executeSleigh(executor, library, "r0 = __testop(r1, 1234:8);");
assertEquals(executor, library.executor);
assertEquals(executor.getState(), library.state);
assertEquals(library, library.lib);
assertRegVarnode(r0, library.outVar);
assertRegVarnode(r1, library.inVar0);
assertBytes(1234, 8, library.inVal1);
assertBytes(1234, 8, executor.getState().getVar(r0, Reason.INSPECT));
}
@Test(expected = SleighException.class)
public void testErrNotExported() throws Exception {
var library = new TestUseropLibrary() {
@SuppressWarnings("unused")
private void __testop() {
}
};
executeSleigh(library, "r0 = __testop();");
}
@Test(expected = PcodeExecutionException.class)
public void testErrParameterCountMismatch() throws Exception {
var library = new TestUseropLibrary() {
@PcodeUserop
private void __testop(Varnode in0) {
}
};
executeSleigh(library, "r0 = __testop();");
}
@Test(expected = IllegalArgumentException.class)
public void testErrAccess() throws Exception {
new AnnotatedPcodeUseropLibrary<byte[]>() {
@PcodeUserop
private void __testop(Varnode in0) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrReturnType() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private int __testop() {
return 0;
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrInputType() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(int in0) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrExecutorType() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpExecutor int executor) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrExecutorTypeParam() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpExecutor PcodeExecutor<Object> executor) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrStateType() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpState int state) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrStateTypeParam() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpState PcodeExecutorState<Object> state) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrOutputType() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpOutput int out) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrVariadicInputsType() throws Exception {
new TestUseropLibrary() {
@PcodeUserop(variadic = true)
private void __testop(int[] ins) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrConflictingAnnotations() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpExecutor @OpState int in0) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrDuplicateExecutor() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpExecutor PcodeExecutor<byte[]> executor0,
@OpExecutor PcodeExecutor<byte[]> executor1) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrDuplicateState() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpState PcodeExecutorState<byte[]> state0,
@OpState PcodeExecutorState<byte[]> state1) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrDuplicateOutput() throws Exception {
new TestUseropLibrary() {
@PcodeUserop
private void __testop(@OpOutput Varnode out0, @OpOutput Varnode out1) {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrMissingVariadicInputs() throws Exception {
new TestUseropLibrary() {
@PcodeUserop(variadic = true)
private void __testop() {
}
};
}
@Test(expected = IllegalArgumentException.class)
public void testErrDuplicateVariadicInputs() throws Exception {
new TestUseropLibrary() {
@PcodeUserop(variadic = true)
private void __testop(Varnode[] ins0, Varnode[] ins1) {
}
};
}
}

View file

@ -0,0 +1,261 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import static org.junit.Assert.assertEquals;
import java.io.File;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.GhidraTestApplicationLayout;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
public class PcodeFrameTest extends AbstractGTest {
static final String SAMPLE_ADD = """
r0 = r0 + r1;
""";
static final String SAMPLE_ADD2 = """
r0 = r0 + r1 + r2;
""";
static final String SAMPLE_IF = """
if (r0 == r1) goto <skip>;
r2 = r2 + 1;
<skip>
""";
static final String SAMPLE_LOOP = """
<loop>
r0 = r0 + 1;
if (r0 == r1) goto <loop>;
""";
static final String SAMPLE_BRANCH = """
goto 0x1234;
""";
static final String SAMPLE_LOAD = """
r0 = *:8 r1;
""";
static final String SAMPLE_LANG_USEROP = """
pcodeop_one(r0);
""";
static final String SAMPLE_LIB_USEROP = """
__lib_userop(r0);
""";
static class MyLib extends AnnotatedPcodeUseropLibrary<Void> {
@PcodeUserop
void __lib_userop() {
}
}
SleighLanguage language;
MyLib library = new MyLib();
@Before
public void setUp() throws Exception {
if (!Application.isInitialized()) {
Application.initializeApplication(
new GhidraTestApplicationLayout(new File(getTestDirectoryPath())),
new ApplicationConfiguration());
}
language = SleighLanguageHelper.getMockBE64Language();
}
private PcodeProgram compile(String sample) {
return SleighProgramCompiler.compileProgram(language, "test", sample, library);
}
private PcodeFrame frame(String sample) {
PcodeProgram program = compile(sample);
return new PcodeFrame(language, program.code, program.useropNames);
}
@Test
public void testProgramToStringAdd() throws Exception {
PcodeProgram program = compile(SAMPLE_ADD);
assertEquals("""
<PcodeProgram:
r0 = INT_ADD r0, r1
>""",
program.toString());
}
@Test
public void testProgramToStringAdd2() throws Exception {
PcodeProgram program = compile(SAMPLE_ADD2);
assertEquals("""
<PcodeProgram:
$U200:8 = INT_ADD r0, r1
r0 = INT_ADD $U200:8, r2
>""",
program.toString());
}
@Test
public void testProgramToStringIf() throws Exception {
PcodeProgram program = compile(SAMPLE_IF);
assertEquals("""
<PcodeProgram:
$U200:1 = INT_EQUAL r0, r1
CBRANCH <0>, $U200:1
r2 = INT_ADD r2, 1:8
<0>
>""",
program.toString());
}
@Test
public void testProgramToStringLoop() throws Exception {
PcodeProgram program = compile(SAMPLE_LOOP);
assertEquals("""
<PcodeProgram:
<0>
r0 = INT_ADD r0, 1:8
$U280:1 = INT_EQUAL r0, r1
CBRANCH <0>, $U280:1
>""",
program.toString());
}
@Test
public void testProgramToStringLoad() throws Exception {
PcodeProgram program = compile(SAMPLE_LOAD);
assertEquals("""
<PcodeProgram:
r0 = LOAD ram(r1)
>""",
program.toString());
}
@Test
public void testProgramToStringLangUserop() throws Exception {
PcodeProgram program = compile(SAMPLE_LANG_USEROP);
assertEquals("""
<PcodeProgram:
CALLOTHER "pcodeop_one", r0
>""",
program.toString());
}
@Test
public void testProgramToStringLibUserop() throws Exception {
PcodeProgram program = compile(SAMPLE_LIB_USEROP);
assertEquals("""
<PcodeProgram:
CALLOTHER "__lib_userop", r0
>""",
program.toString());
}
@Test
public void testFrameToStringAdd() throws Exception {
PcodeFrame frame = frame(SAMPLE_ADD);
assertEquals("""
<p-code frame: index=0 {
-> r0 = INT_ADD r0, r1
}>""",
frame.toString());
frame.advance();
assertEquals("""
<p-code frame: index=1 {
r0 = INT_ADD r0, r1
*> fall-through
}>""",
frame.toString());
}
@Test
public void testFrameToStringIf() throws Exception {
PcodeFrame frame = frame(SAMPLE_IF);
assertEquals("""
<p-code frame: index=0 {
-> $U200:1 = INT_EQUAL r0, r1
CBRANCH <0>, $U200:1
r2 = INT_ADD r2, 1:8
<0>
}>""",
frame.toString());
frame.advance();
frame.advance();
frame.advance();
assertEquals("""
<p-code frame: index=3 {
$U200:1 = INT_EQUAL r0, r1
CBRANCH <0>, $U200:1
r2 = INT_ADD r2, 1:8
<0>
*> fall-through
}>""",
frame.toString());
}
@Test
public void testFrameToStringLoop() throws Exception {
PcodeFrame frame = frame(SAMPLE_LOOP);
assertEquals("""
<p-code frame: index=0 {
<0>
-> r0 = INT_ADD r0, 1:8
$U280:1 = INT_EQUAL r0, r1
CBRANCH <0>, $U280:1
}>""",
frame.toString());
}
@Test
public void testFrameToStringBranch() throws Exception {
PcodeFrame frame = frame(SAMPLE_BRANCH);
assertEquals("""
<p-code frame: index=0 {
-> BRANCH *[ram]0x1234:8
}>""",
frame.toString());
frame.advance();
frame.finishAsBranch();
assertEquals("""
<p-code frame: index=-1 branched=0 {
*> BRANCH *[ram]0x1234:8
}>""",
frame.toString());
}
@Test
public void testFrameToStringLangUserop() throws Exception {
PcodeFrame frame = frame(SAMPLE_LANG_USEROP);
assertEquals("""
<p-code frame: index=0 {
-> CALLOTHER \"pcodeop_one\", r0
}>""",
frame.toString());
}
@Test
public void testFrameToStringLibUserop() throws Exception {
PcodeFrame frame = frame(SAMPLE_LIB_USEROP);
assertEquals("""
<p-code frame: index=0 {
-> CALLOTHER \"__lib_userop\", r0
}>""",
frame.toString());
}
}

View file

@ -0,0 +1,300 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.Tree;
import org.junit.Test;
import ghidra.pcode.exec.SleighUtils.SleighParseError;
public class SleighUtilsTest {
@Test
public void testParseSleighSemantic() throws RecognitionException {
String mySleigh = """
if !(RAX == 0) goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
""";
Tree tree = SleighUtils.parseSleighSemantic(mySleigh);
assertEquals(
"(OP_SEMANTIC (if (! ((...) (== (IDENTIFIER RAX) (DEC_INT 0)))) (goto " +
"(OP_JUMPDEST_LABEL (< (IDENTIFIER L1))))) (OP_APPLY (IDENTIFIER emu_swi)) " +
"(< (IDENTIFIER L1)) (OP_APPLY (IDENTIFIER emu_exec_decoded)))",
tree.toStringTree());
}
@Test
public void testParseSleighSemanticErr() throws RecognitionException {
String mySleigh = """
if (!(RAX == 0)) goto <L1>;
emu_swi();
<L1>
emu_exec_decoded
""";
try {
SleighUtils.parseSleighSemantic(mySleigh);
fail();
}
catch (SleighParseError e) {
assertEquals("""
sleigh line 4: no viable alternative on EOF (missing semi-colon after this?):
emu_exec_decoded
----------------^
""", e.getMessage());
}
}
@Test
public void testRecoverConditionEqDec() {
assertEquals("RAX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if !(RAX == 0) goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionNeqHex() {
assertEquals("RAX != 0x3",
SleighUtils.recoverConditionFromBreakpoint("""
if RAX == 0x3 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionUserop() {
assertEquals("userop(a, b, c)",
SleighUtils.recoverConditionFromBreakpoint("""
if !(userop(a,b,c)) goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionAddition() {
assertEquals("RAX + RBX + RCX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if RAX + RBX + RCX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionDeref() {
assertEquals("*RAX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if *RAX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionSizedDeref() {
assertEquals("*:4 RAX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if *:4 RAX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionSpacedSizedDeref() {
assertEquals("*[ram]:4 RAX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if *[ram]:4 RAX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionSpacedDeref() {
assertEquals("*[ram] RAX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if *[ram] RAX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionSizedAddressOf() {
assertEquals("&:8 RAX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if &:8 RAX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionAddressOf() {
assertEquals("&RAX == 0",
SleighUtils.recoverConditionFromBreakpoint("""
if &RAX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionSizedConst() {
assertEquals("!(1:1)",
SleighUtils.recoverConditionFromBreakpoint("""
if 1:1 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionNotSizedConst() {
assertEquals("1:1",
SleighUtils.recoverConditionFromBreakpoint("""
if !(1:1) goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionBitRange() {
assertEquals("RAX[0,1]",
SleighUtils.recoverConditionFromBreakpoint("""
if !(RAX[0,1]) goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionBitRange2() {
assertEquals("RAX:4",
SleighUtils.recoverConditionFromBreakpoint("""
if !(RAX:4) goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
"""));
}
@Test
public void testRecoverConditionAlways() {
assertEquals("1:1",
SleighUtils.recoverConditionFromBreakpoint("""
emu_swi();
emu_exec_decoded();
"""));
}
@Test
public void testParseSleighExpression() throws RecognitionException {
assertEquals("(|| (== (IDENTIFIER RAX) (DEC_INT 0)) (== (IDENTIFIER RBX) (DEC_INT 7)))",
SleighUtils.parseSleighExpression("RAX == 0 || RBX == 7").toStringTree());
}
@Test
public void testParseSleighExpressionErr() throws RecognitionException {
try {
SleighUtils.parseSleighExpression("RAX RBX RCX");
fail();
}
catch (SleighParseError e) {
assertEquals("""
sleigh line 1: no viable alternative on IDENTIFIER: 'RBX':
RAX RBX RCX
----^
""", e.getMessage());
}
}
@Test
public void testParseSleighExpressionTooMuch() throws RecognitionException {
try {
SleighUtils.parseSleighExpression("RAX == 0;");
fail();
}
catch (SleighParseError e) {
assertEquals("""
sleigh line 1: extraneous input ';' expecting EOF:
RAX == 0;
--------^
""", e.getMessage());
}
}
@Test
public void testParseSleighExpressionTooLittle() throws RecognitionException {
try {
SleighUtils.parseSleighExpression("RAX ==");
fail();
}
catch (SleighParseError e) {
assertEquals("""
sleigh line 1: no viable alternative on SEMI: ';':
RAX ==
------^
""", e.getMessage());
}
}
@Test
public void testSleighForConditionalBreakpointAlways() throws RecognitionException {
assertEquals("""
emu_swi();
emu_exec_decoded();
""", SleighUtils.sleighForConditionalBreak("1:1"));
}
@Test
public void testSleighForConditionalBreakpoint() throws RecognitionException {
assertEquals("""
if RAX != 0 goto <L1>;
emu_swi();
<L1>
emu_exec_decoded();
""", SleighUtils.sleighForConditionalBreak("RAX == 0"));
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<processor_spec>
</processor_spec>

View file

@ -0,0 +1,37 @@
@define ENDIAN "big"
@define RAMSIZE "8"
define endian = $(ENDIAN);
define alignment = 1;
define space ram type=ram_space size=8 default;
define space register type=register_space size=4;
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# AT LEAST ONE REGISTER, AND STACK POINTER ARE REQUIRED
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
define register offset=0x0 size=8 [ sp r0 r1 r2 r3 r4 r5 r6 r7 ];
# Define context bits
define register offset=0x100 size=4 contextreg;
define context contextreg
test=(0,0)
;
define pcodeop pcodeop_one;
define pcodeop pcodeop_two;
define pcodeop pcodeop_three;
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# AT LEAST ONE INSTRUCTION IS REQUIRED
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
:nop is test=1 unimpl
# # # # # # # # # # # # # # # # # # # # # # # # # # # #