Preliminary breakpoint registration

This commit is contained in:
Guy Perfect 2020-10-13 20:15:27 -05:00
parent f893ecd2cf
commit 2d048a74c0
7 changed files with 334 additions and 107 deletions

View File

@ -7,10 +7,10 @@ include_windows = jni-windows-include
# Default goal
.PHONY: default
default:
@echo
@echo $(include_linux)
@echo "Planet Virtual Boy Emulator"
@echo " https://www.planetvb.com/"
@echo " August 14, 2020"
@echo " October 13, 2020"
@echo
@echo "Intended build environment: Debian i386 or amd64"
@echo " gcc-multilib"

View File

@ -51,15 +51,30 @@ public class Main {
// Begin application operations
new App(useNative);
/*
var brk = new Breakpoint();
String exp = "([sp - 8] & 3) << 6 != 12.0 + r6 * ecr && [0x0500008C + r8] == 0";
System.out.println(exp);
var brk = new Breakpoint(null);
String exp =
"([sp - 8] & 3) << 6 != 12.0 + r6 * ecr && [0x0500008C + r8] == 0";
System.out.println("\n" + exp);
if (brk.setCondition(exp))
System.out.println(brk.debug());
else System.out.println(brk.getErrorCode() + "\t" +
brk.getErrorPosition() + ":" + brk.getErrorText());
*/
else System.out.println(
brk.getErrorCode (Breakpoint.CONDITION) + "\t" +
brk.getErrorPosition(Breakpoint.CONDITION) + ":" +
brk.getErrorText (Breakpoint.CONDITION)
);
exp = "123, 456 - 987 , f0-fffffff2";
System.out.println("\n" + exp);
if (brk.setAddresses(exp))
System.out.println(brk.test());
else System.out.println(
brk.getErrorCode (Breakpoint.ADDRESS) + "\t" +
brk.getErrorPosition(Breakpoint.ADDRESS) + ":" +
brk.getErrorText (Breakpoint.ADDRESS)
);
}
}

View File

@ -8,13 +8,15 @@ public class Breakpoint {
// Instance fields
private int[][] addresses; // Applicable address ranges
private String addressText; // Un-processed address ranges
private String condition; // Un-processed condition
private int errCode; // Condition error code
private int errPosition; // Character of condition error
private String errText; // Offending condition text
private int[] errCode; // Error codes
private int[] errPosition; // Character position of errors
private String[] errText; // Offending error text
private boolean isEnabled; // Breakpoint is active
private String name; // Display name
private Token[] tokens; // Evaluation tokens
private Vue vue; // Containing emulation context
@ -23,14 +25,19 @@ public class Breakpoint {
///////////////////////////////////////////////////////////////////////////
// Error codes
public static final int NONE = 0;
public static final int BADLITERAL = 1;
public static final int BADTOKEN = 2;
public static final int EARLYEOF = 3;
public static final int EMPTY = 4;
public static final int INVALID = 5;
public static final int NESTING = 6;
public static final int UNEXPECTED = 7;
public static final int BADTYPE = -1;
public static final int NONE = 0;
public static final int BADLITERAL = 1;
public static final int BADTOKEN = 2;
public static final int EARLYEOF = 3;
public static final int EMPTY = 4;
public static final int INVALID = 5;
public static final int NESTING = 6;
public static final int UNEXPECTED = 7;
// Error types
public static final int ADDRESS = 0;
public static final int CONDITION = 1;
// Token types
private static final int BINARY = 4;
@ -91,7 +98,7 @@ public class Breakpoint {
private static final HashMap<String, Integer> SYMDEFS;
// Functional symbol IDs
private static final int ADDRESS = 0;
//private static final int ADDRESS = 0; // Same as error type
private static final int CODE = 1;
private static final int COND = 2;
private static final int DISP = 3;
@ -298,14 +305,16 @@ public class Breakpoint {
///////////////////////////////////////////////////////////////////////////
// Default constructor
public Breakpoint() {
public Breakpoint(Vue vue) {
addresses = new int[0][];
addressText = "";
condition = "";
errCode = NONE;
errPosition = 0;
errText = "";
errCode = new int [] { NONE, NONE };
errPosition = new int [] { 0, 0 };
errText = new String[] { "", "" };
name = "";
tokens = new Token[0];
this.vue = vue;
}
@ -366,24 +375,48 @@ public class Breakpoint {
return ret.toString();
}
// 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];
if (x > 0)
ret.append("\n");
ret.append(String.format("%08X", range[0]));
if (range[0] != range[1])
ret.append(String.format("-%08X", range[1]));
}
return ret.toString();
}
// Evaluate the condition against its emulation context
public boolean evaluate() {
return vue == null ? false : vue.evaluate(this);
}
// Retrieve the most recent input addresses
public String getAddresses() {
return addressText;
}
// Retrieve the most recent input condition
public String getCondition() {
return condition;
}
// Retrieve the most recent error code
public int getErrorCode() {
return errCode;
public int getErrorCode(int type) {
return type < 0 || type > 1 ? BADTYPE : errCode[type];
}
// Retrieve the most recent error character position
public int getErrorPosition() {
return errPosition;
public int getErrorPosition(int type) {
return type < 0 || type > 1 ? BADTYPE : errPosition[type];
}
// Retrieve the most recent error text
public String getErrorText() {
return errText;
public String getErrorText(int type) {
return type < 0 || type > 1 ? null : errText[type];
}
// Retrieve the display name
@ -397,24 +430,137 @@ public class Breakpoint {
}
// Specify and parse a list of address ranges
public boolean setAddresses(int[][] addresses) {
public boolean setAddresses(String addresses) {
Integer first = null; // Value of first address
int mode = 0; // Processing state
var ranges = new ArrayList<int[]>(); // Output
int start = 0; // Position of address literal
int x = 0; // Input position
// Error checking
if (addresses == null)
// Configure instance fields
addressText = addresses == null ? addresses = "" : addresses;
// Parse the input
var chars = (addresses + " ").toCharArray();
for (x = 0; x < chars.length; x++) {
char c = chars[x];
boolean white = c == ' ' || c == '\t';
boolean digit =
c >= '0' && c <= '9' ||
c >= 'A' && c <= 'F' ||
c >= 'a' && c <= 'f'
;
// Before
if (mode == 0) {
// Ignore whitespace
if (white)
continue;
// Begin an address
if (digit) {
start = x;
mode = 1; // During
continue;
}
}
// During
else if (mode == 1) {
// Ignore digits
if (digit)
continue;
// The end of the address has been found
String text = new String(chars, start, x - start);
int addr = 0;
try { addr = (int) Long.parseLong(text, 16); }
// Could not parse the address
catch (Exception e) {
errCode [ADDRESS] = BADLITERAL;
errPosition[ADDRESS] = start + 1;
errText [ADDRESS] = text;
if (vue != null)
vue.update(this);
return false;
}
// Processed the first address
if (first == null)
first = addr;
// Processed the second address
else {
ranges.add(new int[] { first, addr });
first = null;
}
// Begin searching for a delimiter
mode = 2; // After
x--;
continue;
}
// After
else if (mode == 2) {
// Ignore whitespace
if (white)
continue;
// Begin searching for the second address
if (c == '-' && first != null) {
mode = 0; // Before
continue;
}
// All addresses in the range have been processed
if (c == ',') {
if (first != null) {
ranges.add(new int[] { first, first });
first = null;
}
mode = 0; // Before
continue;
}
}
// Invalid character
errCode [ADDRESS] = UNEXPECTED;
errPosition[ADDRESS] = x + 1;
errText [ADDRESS] = Character.toString(c);
if (vue != null)
vue.update(this);
return false;
for (var range : addresses)
if (range == null || range.length > 2)
return false;
} // x
// Accept the new address ranges
this.addresses = new int[addresses.length][2];
for (int x = 0; x < addresses.length; x++) {
var range = addresses[x];
this.addresses[x] = new int[] {
range[0],
range[range.length - 1]
};
// Unexpected end of input
if (mode == 0 && ranges.size() > 0) {
errCode [ADDRESS] = EARLYEOF;
errPosition[ADDRESS] = x + 1;
errText [ADDRESS] = "";
if (vue != null)
vue.update(this);
return false;
}
// A one-address range was processed
if (first != null)
ranges.add(new int[] { first, first });
// Parsing was successful
this.addresses = ranges.toArray(new int[ranges.size()][]);
this.addressText = addresses;
errCode [ADDRESS] = NONE;
errPosition[ADDRESS] = 0;
errText [ADDRESS] = "";
if (vue != null)
vue.update(this);
return true;
}
@ -423,20 +569,26 @@ public class Breakpoint {
// Configure instance fields
this.condition = condition == null ? condition = "" : condition;
errCode = NONE;
errPosition = 0;
errText = "";
tokens = new Token[0];
errCode [CONDITION] = NONE;
errPosition[CONDITION] = 0;
errText [CONDITION] = "";
tokens = new Token[0];
// Process the expression
var tokens = parse();
if (tokens == null || !validate(tokens))
if (tokens == null || !validate(tokens)) {
if (vue != null)
vue.update(this);
return false;
}
tree(tokens);
// The expression is empty
if (tokens.size() == 0)
if (tokens.size() == 0) {
if (vue != null)
vue.update(this);
return true;
}
// Produce an RPN-ordered list of tokens
var tok = tokens.remove(0);
@ -463,12 +615,16 @@ public class Breakpoint {
this.tokens = tokens.toArray(new Token[tokens.size()]);
// The expression was successfully parsed
if (vue != null)
vue.update(this);
return true;
}
// Specify the display name
public void setName(String name) {
this.name = name == null ? "" : name;
if (vue != null)
vue.update(this);
}
@ -477,12 +633,12 @@ public class Breakpoint {
// Package Methods //
///////////////////////////////////////////////////////////////////////////
// Evaluate the condition for an emulation context
// Evaluate the condition for a Java emulation context
boolean evaluate(JavaVue vue, int[] stack) {
// The condition is empty
if (tokens.length == 0)
return isEnabled && errCode == NONE;
return isEnabled && errCode[CONDITION] == NONE;
// Process tokens
int size = 0;
@ -505,21 +661,26 @@ public class Breakpoint {
int depth() {
// Error checking
if (errCode != NONE)
if (errCode[CONDITION] != NONE)
return 0;
// Count the maximum size of the stack
int max = 0;
int size = 0;
for (var tok : tokens) switch (tok.type) {
case BINARY : size--; break;
case FLOAT : // Fallthrough
case SYMBOL : // Fallthrough
case WORD : max = Math.max(max, ++size);
case BINARY: size--; break;
case FLOAT : // Fallthrough
case SYMBOL: // Fallthrough
case WORD : max = Math.max(max, ++size);
}
return max;
}
// The breakpoint is being removed from its emulation context
void remove() {
vue = null;
}
///////////////////////////////////////////////////////////////////////////
@ -586,9 +747,9 @@ public class Breakpoint {
// "x" cannot appear here
if (isFloat || x != start + 1 || chars[start] != '0') {
errCode = UNEXPECTED;
errPosition = x + 1;
errText = Character.toString(c);
errCode [CONDITION] = UNEXPECTED;
errPosition[CONDITION] = x + 1;
errText [CONDITION] = Character.toString(c);
return null;
}
@ -602,9 +763,9 @@ public class Breakpoint {
// "." cannot appear here
if (isHex || isFloat) {
errCode = UNEXPECTED;
errPosition = x + 1;
errText = Character.toString(c);
errCode [CONDITION] = UNEXPECTED;
errPosition[CONDITION] = x + 1;
errText [CONDITION] = Character.toString(c);
return null;
}
@ -637,9 +798,9 @@ public class Breakpoint {
// Could not parse the value
catch (Exception e) {
errCode = BADLITERAL;
errPosition = x + 1;
errText = ret.text;
errCode [CONDITION] = BADLITERAL;
errPosition[CONDITION] = x + 1;
errText [CONDITION] = ret.text;
return null;
}
@ -684,9 +845,9 @@ public class Breakpoint {
}
// The operator was not identified
errCode = BADTOKEN;
errPosition = start + 1;
errText = Character.toString(chars[start]);
errCode [CONDITION] = BADTOKEN;
errPosition[CONDITION] = start + 1;
errText [CONDITION] = Character.toString(chars[start]);
return null;
} // x
@ -739,9 +900,9 @@ public class Breakpoint {
}
// The token is not recognized
errCode = BADTOKEN;
errPosition = x + 1;
errText = ret.text;
errCode [CONDITION] = BADTOKEN;
errPosition[CONDITION] = x + 1;
errText [CONDITION] = ret.text;
return null;
} // x
@ -830,9 +991,9 @@ public class Breakpoint {
// The token is invalid
if (tok.id != NEGATE) {
errCode = INVALID;
errPosition = tok.start;
errText = tok.text;
errCode [CONDITION] = INVALID;
errPosition[CONDITION] = tok.start;
errText [CONDITION] = tok.text;
return false;
}
@ -845,9 +1006,9 @@ public class Breakpoint {
// Nesting error
if (tok.type == CLOSE &&
(stack.empty() || stack.pop().id != tok.id)) {
errCode = NESTING;
errPosition = tok.start;
errText = tok.text;
errCode [CONDITION] = NESTING;
errPosition[CONDITION] = tok.start;
errText [CONDITION] = tok.text;
return false;
}
@ -862,9 +1023,9 @@ public class Breakpoint {
// A group was not closed
if (!stack.empty()) {
errCode = EARLYEOF;
errPosition = condition.length();
errText = stack.pop().text;
errCode [CONDITION] = EARLYEOF;
errPosition[CONDITION] = condition.length();
errText [CONDITION] = stack.pop().text;
}
// Successfully parsed the condition

View File

@ -87,12 +87,6 @@ class JavaVue extends Vue {
return Math.max(0, maxCycles);
}
// Evaluate the condition in a breakpoint
public boolean evaluate(Breakpoint brk) {
return brk != null ? false :
brk.evaluate(this, new int[brk.depth() * 2]);
}
// Retrieve the application break code
public int getBreakCode() {
return breakCode;
@ -249,6 +243,11 @@ class JavaVue extends Vue {
// Package Methods //
///////////////////////////////////////////////////////////////////////////
// Evaluate the condition in a breakpoint
boolean evaluate(Breakpoint brk) {
return false;
}
// Exception break handler
int onException(Ecxeption exp) {
return 0;

View File

@ -84,10 +84,10 @@ JNIEXPORT jlong JNICALL Java_vue_NativeVue_construct
JNIEXPORT void JNICALL Java_vue_NativeVue_dispose
(JNIEnv *env, jobject vue, jlong handle) {
Core *core = *(Core **)&handle;
if (core->vue.pak.rom != NULL)
free(core->vue.pak.rom);
if (core->vue.pak.ram != NULL)
free(core->vue.pak.ram);
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);
if (core->vue.pak.ram != NULL) free(core->vue.pak.ram);
free(core);
}
@ -98,13 +98,6 @@ JNIEXPORT jint JNICALL Java_vue_NativeVue_emulate
return vueEmulate(&core->vue, maxCycles);
}
// Evaluate the condition in a breakpoint
JNIEXPORT jboolean JNICALL Java_vue_NativeVue_evaluate
(JNIEnv *env, jobject vue, jlong handle, jobject brk) {
Core *core = *(Core **)&handle;
return JNI_FALSE;
}
// Retrieve the application break code
JNIEXPORT jint JNICALL Java_vue_NativeVue_getBreakCode
(JNIEnv *env, jobject vue, jlong handle) {

View File

@ -34,11 +34,6 @@ class NativeVue extends Vue {
public int emulate(int maxCycles)
{ return emulate(handle, maxCycles); }
// Evaluate the condition in a breakpoint
private native boolean evaluate(long handle, Breakpoint brk);
public boolean evaluate(Breakpoint brk)
{ return evaluate(handle, brk); }
// Retrieve the application break code
private native int getBreakCode(long handle);
public int getBreakCode() { return getBreakCode(handle); }
@ -94,4 +89,15 @@ class NativeVue extends Vue {
public boolean writeBytes(int address, byte[] src, int offset, int length)
{ return writeBytes(handle, address, src, offset, length); }
///////////////////////////////////////////////////////////////////////////
// Package Methods //
///////////////////////////////////////////////////////////////////////////
// Evaluate the condition in a breakpoint
boolean evaluate(Breakpoint brk) {
return false;
}
}

View File

@ -1,8 +1,14 @@
package vue;
// Java imports
import java.util.*;
// Template for emulation core implementations
public abstract class Vue {
// Instance fields
ArrayList<Breakpoint> breakpoints; // Current breakpoints
// Static fields
private static String nativeID = null; // ID of loaded native library
@ -152,19 +158,53 @@ public abstract class Vue {
///////////////////////////////////////////////////////////////////////////
// Constructors //
///////////////////////////////////////////////////////////////////////////
// Default constructor
Vue() {
breakpoints = new ArrayList<Breakpoint>();
}
///////////////////////////////////////////////////////////////////////////
// Public Methods //
///////////////////////////////////////////////////////////////////////////
// Produce a new breakpoint and add it to the collection
public Breakpoint breakpoint() {
var brk = new Breakpoint(this);
breakpoints.add(brk);
return brk;
}
// Produce an array of the current breakpoint collection
public Breakpoint[] listBreakpoints() {
return breakpoints.toArray(new Breakpoint[breakpoints.size()]);
}
// Remove a breakpoint from the collection
public boolean remove(Breakpoint brk) {
if (!breakpoints.remove(brk))
return false;
brk.remove();
return true;
}
///////////////////////////////////////////////////////////////////////////
// Abstract Methods //
///////////////////////////////////////////////////////////////////////////
// Release any used resources
public abstract void dispose();
// Process the simulation
public abstract int emulate(int maxCycles);
// Evaluate the condition in a breakpoint
public abstract boolean evaluate(Breakpoint brk);
// Retrieve the application break code
public abstract int getBreakCode();
@ -200,4 +240,17 @@ public abstract class Vue {
public abstract boolean writeBytes(int address, byte[] src, int offset,
int length);
///////////////////////////////////////////////////////////////////////////
// Package Methods //
///////////////////////////////////////////////////////////////////////////
// Evaluate the condition in a breakpoint
abstract boolean evaluate(Breakpoint brk);
// A breakpoint has been updated
void update(Breakpoint brk) {
}
}