Implementing native breakpoint interface

This commit is contained in:
Guy Perfect 2020-10-16 19:25:36 -05:00
parent 2d048a74c0
commit 826e921dac
7 changed files with 365 additions and 65 deletions

View File

@ -10,7 +10,7 @@ default:
@echo $(include_linux)
@echo "Planet Virtual Boy Emulator"
@echo " https://www.planetvb.com/"
@echo " October 13, 2020"
@echo " October 16, 2020"
@echo
@echo "Intended build environment: Debian i386 or amd64"
@echo " gcc-multilib"

View File

@ -51,7 +51,7 @@ public class Main {
// Begin application operations
new App(useNative);
/*
var brk = new Breakpoint(null);
String exp =
@ -74,6 +74,7 @@ public class Main {
brk.getErrorPosition(Breakpoint.ADDRESS) + ":" +
brk.getErrorText (Breakpoint.ADDRESS)
);
*/
}

View File

@ -612,13 +612,13 @@ static void evalUnary(Vue *vue, int32_t id, int32_t *stack, int32_t size) {
int32_t evaluate(Vue *vue, Breakpoint *brk, int32_t *stack) {
// The condition is empty
if (brk->numCondition == 0)
if (brk->numTokens == 0)
return brk->enabled;
// Process tokens
int size = 0;
for (int x = 0; x < brk->numCondition; x++) {
Token *tok = &brk->condition[x];
for (int x = 0; x < brk->numTokens; x++) {
Token *tok = &brk->tokens[x];
switch (tok->type) {
case BINARY: size =
evalBinary( tok->value, stack, size); continue;

View File

@ -7,15 +7,16 @@ import java.util.*;
public class Breakpoint {
// Instance fields
private int[][] addresses; // Applicable address ranges
private String addressText; // Un-processed address ranges
private String addresses; // Un-processed address ranges
private String condition; // Un-processed condition
private int[] errCode; // Error codes
private int[] errPosition; // Character position of errors
private String[] errText; // Offending error text
private int hooks; // Applied breakpoint types
private boolean isEnabled; // Breakpoint is active
private String name; // Display name
private Token[] tokens; // Evaluation tokens
private int[][] ranges; // Applicable address ranges
private Token[] tokens; // Condition tokens
private Vue vue; // Containing emulation context
@ -39,6 +40,12 @@ public class Breakpoint {
public static final int ADDRESS = 0;
public static final int CONDITION = 1;
// Breakpoint hooks
private static final int EXCEPTION = 0x00000001;
private static final int EXECUTE = 0x00000002;
private static final int READ = 0x00000004;
private static final int WRITE = 0x00000008;
// Token types
private static final int BINARY = 4;
private static final int CLOSE = 6;
@ -296,6 +303,11 @@ public class Breakpoint {
this.type = type;
}
// Retrieve the applicable serialized "value"
int flatten() {
return type == FLOAT || type == WORD ? value : id;
}
}
@ -306,13 +318,13 @@ public class Breakpoint {
// Default constructor
public Breakpoint(Vue vue) {
addresses = new int[0][];
addressText = "";
addresses = "";
condition = "";
errCode = new int [] { NONE, NONE };
errPosition = new int [] { 0, 0 };
errText = new String[] { "", "" };
name = "";
ranges = new int[0][];
tokens = new Token[0];
this.vue = vue;
}
@ -378,8 +390,8 @@ public class Breakpoint {
// Produce a string representation of the internal address ranges
public String test() {
var ret = new StringBuilder();
for (int x = 0; x < addresses.length; x++) {
var range = addresses[x];
for (int x = 0; x < ranges.length; x++) {
var range = ranges[x];
if (x > 0)
ret.append("\n");
ret.append(String.format("%08X", range[0]));
@ -396,7 +408,7 @@ public class Breakpoint {
// Retrieve the most recent input addresses
public String getAddresses() {
return addressText;
return addresses;
}
// Retrieve the most recent input condition
@ -419,6 +431,26 @@ public class Breakpoint {
return type < 0 || type > 1 ? null : errText[type];
}
// Retrieve whether the breakpoint hooks exceptions
public boolean getException() {
return (hooks & EXCEPTION) != 0;
}
// Retrieve whether the breakpoint hooks executions
public boolean getExecute() {
return (hooks & EXECUTE) != 0;
}
// Retrieve whether the breakpoint hooks reads
public boolean getRead() {
return (hooks & READ) != 0;
}
// Retrieve whether the breakpoint hooks writes
public boolean getWrite() {
return (hooks & WRITE) != 0;
}
// Retrieve the display name
public String getName() {
return name;
@ -438,7 +470,7 @@ public class Breakpoint {
int x = 0; // Input position
// Configure instance fields
addressText = addresses == null ? addresses = "" : addresses;
this.addresses = addresses == null ? addresses = "" : addresses;
// Parse the input
var chars = (addresses + " ").toCharArray();
@ -485,7 +517,7 @@ public class Breakpoint {
errPosition[ADDRESS] = start + 1;
errText [ADDRESS] = text;
if (vue != null)
vue.update(this);
vue.updateRanges(this);
return false;
}
@ -535,7 +567,7 @@ public class Breakpoint {
errPosition[ADDRESS] = x + 1;
errText [ADDRESS] = Character.toString(c);
if (vue != null)
vue.update(this);
vue.updateRanges(this);
return false;
} // x
@ -545,7 +577,7 @@ public class Breakpoint {
errPosition[ADDRESS] = x + 1;
errText [ADDRESS] = "";
if (vue != null)
vue.update(this);
vue.updateRanges(this);
return false;
}
@ -554,13 +586,12 @@ public class Breakpoint {
ranges.add(new int[] { first, first });
// Parsing was successful
this.addresses = ranges.toArray(new int[ranges.size()][]);
this.addressText = addresses;
this.ranges = ranges.toArray(new int[ranges.size()][]);
errCode [ADDRESS] = NONE;
errPosition[ADDRESS] = 0;
errText [ADDRESS] = "";
if (vue != null)
vue.update(this);
vue.updateRanges(this);
return true;
}
@ -578,7 +609,7 @@ public class Breakpoint {
var tokens = parse();
if (tokens == null || !validate(tokens)) {
if (vue != null)
vue.update(this);
vue.updateTokens(this);
return false;
}
tree(tokens);
@ -586,7 +617,7 @@ public class Breakpoint {
// The expression is empty
if (tokens.size() == 0) {
if (vue != null)
vue.update(this);
vue.updateTokens(this);
return true;
}
@ -616,15 +647,48 @@ public class Breakpoint {
// The expression was successfully parsed
if (vue != null)
vue.update(this);
vue.updateTokens(this);
return true;
}
// Specify whether the breakpoint is enabled
public void setEnabled(boolean enabled) {
isEnabled = enabled;
if (vue != null)
vue.updateState(this);
}
// Specify whether the breakpoint hooks exceptions
public void setException(boolean hook) {
hooks = hook ? hooks | EXCEPTION : hooks & ~EXCEPTION;
if (vue != null)
vue.updateState(this);
}
// Specify whether the breakpoint hooks executions
public void setExecute(boolean hook) {
hooks = hook ? hooks | EXECUTE : hooks & ~EXECUTE;
if (vue != null)
vue.updateState(this);
}
// Specify the display name
public void setName(String name) {
this.name = name == null ? "" : name;
}
// Specify whether the breakpoint hooks reads
public void setRead(boolean hook) {
hooks = hook ? hooks | READ : hooks & ~READ;
if (vue != null)
vue.update(this);
vue.updateState(this);
}
// Specify whether the breakpoint hooks reads
public void setWrite(boolean hook) {
hooks = hook ? hooks | WRITE : hooks & ~WRITE;
if (vue != null)
vue.updateState(this);
}
@ -634,7 +698,7 @@ public class Breakpoint {
///////////////////////////////////////////////////////////////////////////
// Evaluate the condition for a Java emulation context
boolean evaluate(JavaVue vue, int[] stack) {
boolean evaluate(int[] stack) {
// The condition is empty
if (tokens.length == 0)
@ -645,11 +709,11 @@ public class Breakpoint {
for (var tok : tokens) {
switch (tok.type) {
case BINARY: size =
evalBinary( tok.id, stack, size); continue;
evalBinary(tok.id, stack, size); continue;
case SYMBOL: size =
evalSymbol(vue, tok.id, stack, size); continue;
evalSymbol(tok.id, stack, size); continue;
case UNARY :
evalUnary (vue, tok.id, stack, size); continue;
evalUnary (tok.id, stack, size); continue;
}
stack[size++] = tok.type;
stack[size++] = tok.value;
@ -676,6 +740,43 @@ public class Breakpoint {
return max;
}
// Produce a one-dimensional array from the address ranges
int[] flattenRanges() {
var ret = new int[ranges.length * 2];
for (int x = 0; x < ranges.length; x++) {
var range = ranges[x];
ret[x / 2 ] = range[0];
ret[x / 2 + 1] = range[1];
}
return ret;
}
// Produce a one-dimensional array from the condition tokens
int[] flattenTokens() {
var ret = new int[tokens.length * 2];
for (int x = 0; x < tokens.length; x++) {
var token = tokens[x];
ret[x / 2 ] = token.type;
ret[x / 2 + 1] = token.flatten();
}
return ret;
}
// Retrieve the bit mask for enabled hooks
int getHooks() {
return hooks;
}
// Retrieve the list of address ranges
int[][] getRanges() {
return ranges;
}
// Retrieve the list of condition tokens
Token[] getTokens() {
return tokens;
}
// The breakpoint is being removed from its emulation context
void remove() {
vue = null;
@ -1155,8 +1256,9 @@ public class Breakpoint {
}
// Evaluate a functional symbol
private static int evalSymbol(JavaVue vue, int id, int[] stack, int size) {
private int evalSymbol(int id, int[] stack, int size) {
int ret = 0;
var vue = (JavaVue) this.vue;
if (id == Vue.PC)
ret = vue.cpu.pc;
else if (id >= 200)
@ -1166,20 +1268,20 @@ public class Breakpoint {
else if (vue.cpu.stage == CPU.EXCEPTION && id == CODE)
ret = vue.cpu.exception.code;
else if (vue.cpu.stage == CPU.EXECUTE) switch (id) {
case ADDRESS : ret = evalAddress (vue); break;
case COND : ret = evalCond (vue); break;
case DISP : ret = evalDisp (vue); break;
case ADDRESS : ret = evalAddress (); break;
case COND : ret = evalCond (); break;
case DISP : ret = evalDisp (); break;
case FORMAT : ret = vue.cpu.inst.format; break;
case ID : ret = vue.cpu.inst.id ; break;
case IMM : ret = evalImm (vue); break;
case IMM : ret = evalImm (); break;
case OPCODE : ret = vue.cpu.inst.opcode; break;
case REG1 : ret = evalReg1 (vue); break;
case REG2 : ret = evalReg2 (vue); break;
case REGID : ret = evalRegId (vue); break;
case REG1 : ret = evalReg1 (); break;
case REG2 : ret = evalReg2 (); break;
case REGID : ret = evalRegId (); break;
case SIZE : ret = vue.cpu.inst.size ; break;
case SUBOPCODE: ret = evalSubopcode (vue); break;
case VALUE : ret = evalValue (vue); break;
case VECTOR : ret = evalVector (vue); break;
case SUBOPCODE: ret = evalSubopcode (); break;
case VALUE : ret = evalValue (); break;
case VECTOR : ret = evalVector (); break;
}
stack[size++] = WORD;
stack[size++] = ret;
@ -1187,7 +1289,7 @@ public class Breakpoint {
}
// Evaluate a unary operator
private static void evalUnary(JavaVue vue, int id, int[] stack, int size) {
private void evalUnary(int id, int[] stack, int size) {
int type = stack[size - 2];
int value = stack[size - 1];
boolean isWord = type == WORD;
@ -1201,7 +1303,7 @@ public class Breakpoint {
case FLOAT : value = evalFloat (isWord, value);
type = FLOAT; break;
case FLOOR : value = evalFloor (isWord, value); break;
case READ_WORD : value = evalReadWord (isWord, value, vue);
case READ_WORD : value = evalReadWord (isWord, value);
type = WORD ; break;
case ROUND : value = evalRound (isWord, value); break;
case TRUNC : value = evalTrunc (isWord, value); break;
@ -1414,7 +1516,8 @@ public class Breakpoint {
///////////////////////////////////////////////////////////////////////////
// Evaluate address
private static int evalAddress(JavaVue vue) {
private int evalAddress() {
var vue = (JavaVue) this.vue;
if (vue.cpu.inst.format == 6)
return vue.cpu.program[vue.cpu.inst.reg1] + vue.cpu.inst.disp;
switch (vue.cpu.inst.id) {
@ -1429,7 +1532,8 @@ public class Breakpoint {
}
// Evaluate cond
private static int evalCond(JavaVue vue) {
private int evalCond() {
var vue = (JavaVue) this.vue;
switch (vue.cpu.inst.id) {
case Vue.BCOND: return vue.cpu.inst.cond;
case Vue.SETF : return vue.cpu.inst.imm;
@ -1438,7 +1542,8 @@ public class Breakpoint {
}
// Evaluate disp
private static int evalDisp(JavaVue vue) {
private int evalDisp() {
var vue = (JavaVue) this.vue;
switch (vue.cpu.inst.format) {
case 3: case 4: case 6: return vue.cpu.inst.disp;
}
@ -1446,7 +1551,8 @@ public class Breakpoint {
}
// Evaluate imm
private static int evalImm(JavaVue vue) {
private int evalImm() {
var vue = (JavaVue) this.vue;
switch (vue.cpu.inst.format) {
case 2: case 5: return vue.cpu.inst.imm;
}
@ -1454,7 +1560,8 @@ public class Breakpoint {
}
// Evaluate reg1
private static int evalReg1(JavaVue vue) {
private int evalReg1() {
var vue = (JavaVue) this.vue;
switch (vue.cpu.inst.format) {
case 1: case 5: case 6: case 7: return vue.cpu.inst.reg1;
}
@ -1462,7 +1569,8 @@ public class Breakpoint {
}
// Evaluate reg2
private static int evalReg2(JavaVue vue) {
private int evalReg2() {
var vue = (JavaVue) this.vue;
switch (vue.cpu.inst.format) {
case 1: case 2: case 5: case 6: case 7: return vue.cpu.inst.reg2;
}
@ -1470,7 +1578,8 @@ public class Breakpoint {
}
// Evaluate regid
private static int evalRegId(JavaVue vue) {
private int evalRegId() {
var vue = (JavaVue) this.vue;
switch (vue.cpu.inst.id) {
case Vue.LDSR: case Vue.STSR: return vue.cpu.inst.imm;
}
@ -1478,7 +1587,8 @@ public class Breakpoint {
}
// Evaluate subopcode
private static int evalSubopcode(JavaVue vue) {
private int evalSubopcode() {
var vue = (JavaVue) this.vue;
switch (vue.cpu.inst.opcode) {
case 0x1F: return vue.cpu.inst.imm;
case 0x3E: return vue.cpu.inst.subopcode;
@ -1487,12 +1597,14 @@ public class Breakpoint {
}
// Evaluate value
private static int evalValue(JavaVue vue) {
private int evalValue() {
var vue = (JavaVue) this.vue;
return vue.cpu.inst.format == 6 ? vue.cpu.access.value : 0;
}
// Evaluate vector
private static int evalVector(JavaVue vue) {
private int evalVector() {
var vue = (JavaVue) this.vue;
return vue.cpu.inst.id == Vue.TRAP ? vue.cpu.inst.imm : 0;
}
@ -1533,7 +1645,8 @@ public class Breakpoint {
}
// Evaluate []
private static int evalReadWord(boolean isWord, int value, JavaVue vue) {
private int evalReadWord(boolean isWord, int value) {
var vue = (JavaVue) this.vue;
return vue.read(isWord ? value : toWord(asFloat(value)), Vue.S32);
}

View File

@ -22,19 +22,27 @@ static const int TYPE_SIZES[] = { 1, 1, 2, 2, 4 };
// Types //
///////////////////////////////////////////////////////////////////////////////
// Breakpoint token
// Breakpoint address range
typedef struct {
int32_t type;
int32_t value;
int32_t start; // First address, inclusive
int32_t end; // Last address, inclusive
} Range;
// Breakpoint condition token
typedef struct {
int32_t type; // Token category
int32_t value; // Operator or symbol ID, or literal value
} Token;
// Precompiled breakpoint
typedef struct {
int32_t enabled; // The breakpoint is enabled
int32_t numAddresses; // Number of address ranges
int32_t numCondition; // Number of tokens in condition
int32_t *addresses; // Address ranges
Token *condition; // Condition tokens in RPN order
int32_t depth; // Maximum number of stack entries
int32_t enabled; // The breakpoint is enabled
int32_t hooks; // Events hooked by the breakpoint
int32_t numRanges; // Number of address ranges
int32_t numTokens; // Number of condition tokens
Range *ranges; // Address ranges
Token *tokens; // Condition tokens in RPN order
} Breakpoint;
// Core context state type
@ -67,7 +75,7 @@ typedef struct {
///////////////////////////////////////////////////////////////////////////////
// Method Functions //
// Public Methods //
///////////////////////////////////////////////////////////////////////////////
// Native constructor
@ -83,7 +91,18 @@ JNIEXPORT jlong JNICALL Java_vue_NativeVue_construct
// Release any used resources
JNIEXPORT void JNICALL Java_vue_NativeVue_dispose
(JNIEnv *env, jobject vue, jlong handle) {
Core *core = *(Core **)&handle;
Core *core = *(Core **)&handle;
Breakpoint *brk; // Handle to breakpoint
int x; // Iterator
// Delete breakpoints
for (x = 0; x < core->numBreakpoints; x++) {
brk = &core->breakpoints[x];
if (brk->ranges != NULL) free(brk->ranges);
if (brk->tokens != NULL) free(brk->tokens);
}
// Delete core
if (core->breakpoints != NULL) free(core->breakpoints);
if (core->stack != NULL) free(core->stack );
if (core->vue.pak.rom != NULL) free(core->vue.pak.rom);
@ -234,3 +253,108 @@ JNIEXPORT jboolean JNICALL Java_vue_NativeVue_writeBytes
(*env)->ReleaseByteArrayElements(env, data, elems, 0);
return JNI_TRUE;
}
///////////////////////////////////////////////////////////////////////////////
// Private Methods //
///////////////////////////////////////////////////////////////////////////////
// Produce a new breakpoint and add it to the collection
JNIEXPORT void JNICALL Java_vue_NativeVue_breakpoint
(JNIEnv *env, jobject vue, jlong handle) {
Core *core = *(Core **)&handle;
int32_t size; // Number of bytes to allocate
// Allocate memory for the breakpoint
core->numBreakpoints++;
size = core->numBreakpoints * sizeof (Breakpoint);
core->breakpoints = core->breakpoints == NULL ?
malloc(size) : realloc(core->breakpoints, size);
// Initialize the breakpoint
memset(&core->breakpoints[core->numBreakpoints-1], 0, sizeof (Breakpoint));
}
// Remove a breakpoint from the collection
JNIEXPORT void JNICALL Java_vue_NativeVue_remove
(JNIEnv *env, jobject vue, jlong handle, jint index) {
Core *core = *(Core **)&handle;
Breakpoint *brk = &core->breakpoints[index];
// Delete the breakpoint
if (brk->ranges != NULL) free(brk->ranges);
if (brk->tokens != NULL) free(brk->tokens);
// Move all subsequent breakpoints forward
if (index != core->numBreakpoints - 1)
memmove(brk, &brk[1],
(core->numBreakpoints - index) * sizeof (Breakpoint));
core->numBreakpoints--;
}
// A breakpoint's address ranges have changed
JNIEXPORT void JNICALL Java_vue_NativeVue_updateRanges
(JNIEnv *env, jobject vue, jlong handle, jint index, jintArray ranges) {
Core *core = *(Core **)&handle;
Breakpoint *brk = &core->breakpoints[index];
jint *elems; // Array elements
int32_t size; // Number of bytes to allocate
// Determine the number of address ranges
brk->numRanges = (*env)->GetArrayLength(env, ranges) / 2;
if (brk->numRanges == 0) {
if (brk->ranges != NULL)
free(brk->ranges);
brk->ranges = NULL;
return;
}
// Allocate memory for the address ranges
size = brk->numRanges * sizeof (Range);
brk->ranges = brk->ranges == NULL ?
malloc(size) : realloc(brk->ranges, size);
// Initialize address ranges
elems = (*env)->GetIntArrayElements(env, ranges, NULL);
memcpy(brk->ranges, elems, size);
(*env)->ReleaseIntArrayElements(env, ranges, elems, JNI_ABORT);
}
// A breakpoint's enabled/hook state has changed
JNIEXPORT void JNICALL Java_vue_NativeVue_updateState
(JNIEnv *env, jobject vue, jlong handle, jint index, jboolean enabled,
jint hooks) {
Core *core = *(Core **)&handle;
Breakpoint *brk = &core->breakpoints[index];
brk->enabled = enabled;
brk->hooks = hooks;
}
// A breakpoint's condition tokens have changed
JNIEXPORT void JNICALL Java_vue_NativeVue_updateTokens
(JNIEnv *env, jobject vue, jlong handle, jint index, jintArray tokens) {
Core *core = *(Core **)&handle;
Breakpoint *brk = &core->breakpoints[index];
jint *elems; // Array elements
int32_t size; // Number of bytes to allocate
// Determine the number of condition tokens
brk->numTokens = (*env)->GetArrayLength(env, tokens) / 2;
if (brk->numTokens == 0) {
if (brk->tokens != NULL)
free(brk->tokens);
brk->tokens = NULL;
return;
}
// Allocate memory for the condition tokens
size = brk->numTokens * sizeof (Token);
brk->tokens = brk->tokens == NULL ?
malloc(size) : realloc(brk->tokens, size);
// Initialize condition tokens
elems = (*env)->GetIntArrayElements(env, tokens, NULL);
memcpy(brk->tokens, elems, size);
(*env)->ReleaseIntArrayElements(env, tokens, elems, JNI_ABORT);
}

View File

@ -24,6 +24,13 @@ class NativeVue extends Vue {
// Public Methods //
///////////////////////////////////////////////////////////////////////////
// Produce a new breakpoint and add it to the collection
public Breakpoint breakpoint() {
var brk = super.breakpoint();
breakpoint(handle);
return brk;
}
// Release any used resources
private native void dispose(long handle);
public void dispose()
@ -61,6 +68,15 @@ class NativeVue extends Vue {
public boolean readBytes(int address, byte[] dest, int offset, int length)
{ return readBytes(handle, address, dest, offset, length); }
// Remove a breakpoint from the collection
public boolean remove(Breakpoint brk) {
int index = breakpoints.indexOf(brk);
if (!super.remove(brk))
return false;
remove(handle, index);
return true;
}
// Initialize all system components
private native void reset(long handle);
public void reset()
@ -100,4 +116,45 @@ class NativeVue extends Vue {
return false;
}
// A breakpoint's address ranges have changed
void updateRanges(Breakpoint brk) {
super.updateRanges(brk);
updateRanges(handle, breakpoints.indexOf(brk), brk.flattenRanges());
}
// A breakpoint's enabled/hook state has changed
void updateState(Breakpoint brk) {
super.updateState(brk);
updateState(handle, breakpoints.indexOf(brk),
brk.isEnabled(), brk.getHooks());
}
// A breakpoint's condition tokens have changed
void updateTokens(Breakpoint brk) {
super.updateTokens(brk);
updateTokens(handle, breakpoints.indexOf(brk), brk.flattenTokens());
}
///////////////////////////////////////////////////////////////////////////
// Private Methods //
///////////////////////////////////////////////////////////////////////////
// Produce a new breakpoint and add it to the collection
private native void breakpoint(long handle);
// Remove a breakpoint from the collection
private native void remove(long handle, int index);
// A breakpoint's address ranges have changed
private native void updateRanges(long handle, int index, int[] ranges);
// A breakpoint's enabled/hook state has changed
private native void updateState(long handle, int index, boolean enabled,
int hooks);
// A breakpoint's condition tokens have changed
private native void updateTokens(long handle, int index, int[] tokens);
}

View File

@ -249,8 +249,13 @@ public abstract class Vue {
// Evaluate the condition in a breakpoint
abstract boolean evaluate(Breakpoint brk);
// A breakpoint has been updated
void update(Breakpoint brk) {
}
// A breakpoint's address ranges have changed
void updateRanges(Breakpoint brk) { }
// A breakpoint's enabled/hook state has changed
void updateState(Breakpoint brk) { }
// A breakpoint's condition tokens have changed
void updateTokens(Breakpoint brk) { }
}