Merge pull request #11015 from TryTwo/Conditional_Breakpoints

Conditional breakpoints
This commit is contained in:
Admiral H. Curtiss 2022-11-13 01:06:52 +01:00 committed by GitHub
commit 2a81fa6c26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1444 additions and 37 deletions

View File

@ -967,6 +967,8 @@ endif()
include_directories(Externals/picojson) include_directories(Externals/picojson)
add_subdirectory(Externals/expr)
add_subdirectory(Externals/rangeset) add_subdirectory(Externals/rangeset)
add_subdirectory(Externals/FatFs) add_subdirectory(Externals/FatFs)

2
Externals/expr/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,2 @@
add_library(expr INTERFACE)
target_include_directories(expr INTERFACE include/)

21
Externals/expr/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Serge Zaitsev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

930
Externals/expr/include/expr.h vendored Normal file
View File

@ -0,0 +1,930 @@
#ifndef EXPR_H
#define EXPR_H
#ifdef _MSC_VER
#pragma warning(push)
// Disable warning for zero-sized array:
#pragma warning(disable : 4200)
#endif
#ifdef __cplusplus
extern "C" {
#endif
#include <ctype.h> /* for isspace */
#include <limits.h>
#include <math.h> /* for pow */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* Simple expandable vector implementation
*/
static int vec_expand(char **buf, int *length, int *cap, int memsz) {
if (*length + 1 > *cap) {
void *ptr;
int n = (*cap == 0) ? 1 : *cap << 1;
ptr = realloc(*buf, n * memsz);
if (ptr == NULL) {
return -1; /* allocation failed */
}
*buf = (char *)ptr;
*cap = n;
}
return 0;
}
#define vec(T) \
struct { \
T *buf; \
int len; \
int cap; \
}
#define vec_init() \
{ NULL, 0, 0 }
#define vec_len(v) ((v)->len)
#define vec_unpack(v) \
(char **)&(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf)
#define vec_push(v, val) \
vec_expand(vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0)
#define vec_nth(v, i) (v)->buf[i]
#define vec_peek(v) (v)->buf[(v)->len - 1]
#define vec_pop(v) (v)->buf[--(v)->len]
#define vec_free(v) (free((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0)
#define vec_foreach(v, var, iter) \
if ((v)->len > 0) \
for ((iter) = 0; (iter) < (v)->len && (((var) = (v)->buf[(iter)]), 1); \
++(iter))
/*
* Expression data types
*/
struct expr;
struct expr_func;
enum expr_type {
OP_UNKNOWN,
OP_UNARY_MINUS,
OP_UNARY_LOGICAL_NOT,
OP_UNARY_BITWISE_NOT,
OP_POWER,
OP_DIVIDE,
OP_MULTIPLY,
OP_REMAINDER,
OP_PLUS,
OP_MINUS,
OP_SHL,
OP_SHR,
OP_LT,
OP_LE,
OP_GT,
OP_GE,
OP_EQ,
OP_NE,
OP_BITWISE_AND,
OP_BITWISE_OR,
OP_BITWISE_XOR,
OP_LOGICAL_AND,
OP_LOGICAL_OR,
OP_ASSIGN,
OP_COMMA,
OP_CONST,
OP_VAR,
OP_FUNC,
};
static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5,
5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0};
typedef vec(struct expr) vec_expr_t;
typedef void (*exprfn_cleanup_t)(struct expr_func *f, void *context);
typedef double (*exprfn_t)(struct expr_func *f, vec_expr_t *args, void *context);
struct expr {
enum expr_type type;
union {
struct {
double value;
} num;
struct {
double *value;
} var;
struct {
vec_expr_t args;
} op;
struct {
struct expr_func *f;
vec_expr_t args;
void *context;
} func;
} param;
};
#define expr_init() \
{ (enum expr_type)0 }
struct expr_string {
const char *s;
int n;
};
struct expr_arg {
int oslen;
int eslen;
vec_expr_t args;
};
typedef vec(struct expr_string) vec_str_t;
typedef vec(struct expr_arg) vec_arg_t;
static int expr_is_unary(enum expr_type op) {
return op == OP_UNARY_MINUS || op == OP_UNARY_LOGICAL_NOT ||
op == OP_UNARY_BITWISE_NOT;
}
static int expr_is_binary(enum expr_type op) {
return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR &&
op != OP_FUNC && op != OP_UNKNOWN;
}
static int expr_prec(enum expr_type a, enum expr_type b) {
int left =
expr_is_binary(a) && a != OP_ASSIGN && a != OP_POWER && a != OP_COMMA;
return (left && prec[a] >= prec[b]) || (prec[a] > prec[b]);
}
#define isfirstvarchr(c) \
(((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || c == '$')
#define isvarchr(c) \
(((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || c == '$' || \
c == '#' || (c >= '0' && c <= '9'))
static struct {
const char *s;
const enum expr_type op;
} OPS[] = {
{"-u", OP_UNARY_MINUS},
{"!u", OP_UNARY_LOGICAL_NOT},
{"~u", OP_UNARY_BITWISE_NOT},
{"**", OP_POWER},
{"*", OP_MULTIPLY},
{"/", OP_DIVIDE},
{"%", OP_REMAINDER},
{"+", OP_PLUS},
{"-", OP_MINUS},
{"<<", OP_SHL},
{">>", OP_SHR},
{"<", OP_LT},
{"<=", OP_LE},
{">", OP_GT},
{">=", OP_GE},
{"==", OP_EQ},
{"!=", OP_NE},
{"&", OP_BITWISE_AND},
{"|", OP_BITWISE_OR},
{"^", OP_BITWISE_XOR},
{"&&", OP_LOGICAL_AND},
{"||", OP_LOGICAL_OR},
{"=", OP_ASSIGN},
{",", OP_COMMA},
/* These are used by lexer and must be ignored by parser, so we put
them at the end */
{"-", OP_UNARY_MINUS},
{"!", OP_UNARY_LOGICAL_NOT},
{"~", OP_UNARY_BITWISE_NOT},
};
static enum expr_type expr_op(const char *s, size_t len, int unary) {
for (unsigned int i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) {
if (strlen(OPS[i].s) == len && strncmp(OPS[i].s, s, len) == 0 &&
(unary == -1 || expr_is_unary(OPS[i].op) == unary)) {
return OPS[i].op;
}
}
return OP_UNKNOWN;
}
static double expr_parse_number(const char* s, size_t len) {
double num = 0;
char buf[32];
char* sz = buf;
char* end = NULL;
if (len >= sizeof(buf)) {
sz = (char*)calloc(1, len + 1);
if (sz == NULL) {
return NAN;
}
}
strncpy(sz, s, len);
sz[len] = '\0';
num = strtod(sz, &end);
if (sz != buf) {
free(sz);
}
return (end == sz + len ? num : NAN);
}
/*
* Functions
*/
struct expr_func {
const char *name;
exprfn_t f;
exprfn_cleanup_t cleanup;
size_t ctxsz;
};
static struct expr_func *expr_get_func(struct expr_func *funcs, const char *s,
size_t len) {
for (struct expr_func *f = funcs; f->name; f++) {
if (strlen(f->name) == len && strncmp(f->name, s, len) == 0) {
return f;
}
}
return NULL;
}
/*
* Variables
*/
struct expr_var {
double value;
struct expr_var *next;
char name[];
};
struct expr_var_list {
struct expr_var *head;
};
static struct expr_var *expr_get_var(struct expr_var_list *vars, const char *s,
size_t len) {
struct expr_var *v = NULL;
if (len == 0 || !isfirstvarchr(*s)) {
return NULL;
}
for (v = vars->head; v; v = v->next) {
if (strlen(v->name) == len && strncmp(v->name, s, len) == 0) {
return v;
}
}
v = (struct expr_var *)calloc(1, sizeof(struct expr_var) + len + 1);
if (v == NULL) {
return NULL; /* allocation failed */
}
v->next = vars->head;
v->value = 0;
strncpy(v->name, s, len);
v->name[len] = '\0';
vars->head = v;
return v;
}
static int64_t to_int(double x) {
if (isnan(x)) {
return 0;
} else if (isinf(x) != 0) {
return INT64_MAX * isinf(x);
} else {
return (int64_t)x;
}
}
static double expr_eval(struct expr *e) {
double n;
switch (e->type) {
case OP_UNARY_MINUS:
return -(expr_eval(&e->param.op.args.buf[0]));
case OP_UNARY_LOGICAL_NOT:
return !(expr_eval(&e->param.op.args.buf[0]));
case OP_UNARY_BITWISE_NOT:
return ~(to_int(expr_eval(&e->param.op.args.buf[0])));
case OP_POWER:
return pow(expr_eval(&e->param.op.args.buf[0]),
expr_eval(&e->param.op.args.buf[1]));
case OP_MULTIPLY:
return expr_eval(&e->param.op.args.buf[0]) *
expr_eval(&e->param.op.args.buf[1]);
case OP_DIVIDE:
return expr_eval(&e->param.op.args.buf[0]) /
expr_eval(&e->param.op.args.buf[1]);
case OP_REMAINDER:
return fmod(expr_eval(&e->param.op.args.buf[0]),
expr_eval(&e->param.op.args.buf[1]));
case OP_PLUS:
return expr_eval(&e->param.op.args.buf[0]) +
expr_eval(&e->param.op.args.buf[1]);
case OP_MINUS:
return expr_eval(&e->param.op.args.buf[0]) -
expr_eval(&e->param.op.args.buf[1]);
case OP_SHL:
return to_int(expr_eval(&e->param.op.args.buf[0]))
<< to_int(expr_eval(&e->param.op.args.buf[1]));
case OP_SHR:
return to_int(expr_eval(&e->param.op.args.buf[0])) >>
to_int(expr_eval(&e->param.op.args.buf[1]));
case OP_LT:
return expr_eval(&e->param.op.args.buf[0]) <
expr_eval(&e->param.op.args.buf[1]);
case OP_LE:
return expr_eval(&e->param.op.args.buf[0]) <=
expr_eval(&e->param.op.args.buf[1]);
case OP_GT:
return expr_eval(&e->param.op.args.buf[0]) >
expr_eval(&e->param.op.args.buf[1]);
case OP_GE:
return expr_eval(&e->param.op.args.buf[0]) >=
expr_eval(&e->param.op.args.buf[1]);
case OP_EQ:
return expr_eval(&e->param.op.args.buf[0]) ==
expr_eval(&e->param.op.args.buf[1]);
case OP_NE:
return expr_eval(&e->param.op.args.buf[0]) !=
expr_eval(&e->param.op.args.buf[1]);
case OP_BITWISE_AND:
return to_int(expr_eval(&e->param.op.args.buf[0])) &
to_int(expr_eval(&e->param.op.args.buf[1]));
case OP_BITWISE_OR:
return to_int(expr_eval(&e->param.op.args.buf[0])) |
to_int(expr_eval(&e->param.op.args.buf[1]));
case OP_BITWISE_XOR:
return to_int(expr_eval(&e->param.op.args.buf[0])) ^
to_int(expr_eval(&e->param.op.args.buf[1]));
case OP_LOGICAL_AND:
n = expr_eval(&e->param.op.args.buf[0]);
if (n != 0) {
n = expr_eval(&e->param.op.args.buf[1]);
if (n != 0) {
return n;
}
}
return 0;
case OP_LOGICAL_OR:
n = expr_eval(&e->param.op.args.buf[0]);
if (n != 0 && !isnan(n)) {
return n;
} else {
n = expr_eval(&e->param.op.args.buf[1]);
if (n != 0) {
return n;
}
}
return 0;
case OP_ASSIGN:
n = expr_eval(&e->param.op.args.buf[1]);
if (vec_nth(&e->param.op.args, 0).type == OP_VAR) {
*e->param.op.args.buf[0].param.var.value = n;
}
return n;
case OP_COMMA:
expr_eval(&e->param.op.args.buf[0]);
return expr_eval(&e->param.op.args.buf[1]);
case OP_CONST:
return e->param.num.value;
case OP_VAR:
return *e->param.var.value;
case OP_FUNC:
return e->param.func.f->f(e->param.func.f, &e->param.func.args,
e->param.func.context);
default:
return NAN;
}
}
#define EXPR_TOP (1 << 0)
#define EXPR_TOPEN (1 << 1)
#define EXPR_TCLOSE (1 << 2)
#define EXPR_TNUMBER (1 << 3)
#define EXPR_TWORD (1 << 4)
#define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TNUMBER | EXPR_TWORD)
#define EXPR_UNARY (1 << 5)
#define EXPR_COMMA (1 << 6)
static int expr_next_token(const char *s, size_t len, int *flags) {
unsigned int i = 0;
if (len == 0) {
return 0;
}
char c = s[0];
if (c == '#') {
for (; i < len && s[i] != '\n'; i++)
;
return i;
} else if (c == '\n') {
for (; i < len && isspace(s[i]); i++)
;
if (*flags & EXPR_TOP) {
if (i == len || s[i] == ')') {
*flags = *flags & (~EXPR_COMMA);
} else {
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA;
}
}
return i;
} else if (isspace(c)) {
while (i < len && isspace(s[i]) && s[i] != '\n') {
i++;
}
return i;
} else if (isdigit(c)) {
if ((*flags & EXPR_TNUMBER) == 0) {
return -1; // unexpected number
}
*flags = EXPR_TOP | EXPR_TCLOSE;
if (c == '0') {
i++;
if (i < len && (s[i] == 'x' || s[i] == 'X')) {
i++;
for (; i < len && isxdigit(s[i]); i++)
;
return i;
}
}
for (; i < len && (s[i] == '.' || isdigit(s[i])); i++)
;
if (i < len && (s[i] == 'e' || s[i] == 'E')) {
i++;
if (i < len && (s[i] == '+' || s[i] == '-'))
i++;
for (; i < len && isdigit(s[i]); i++)
;
}
return i;
} else if (isfirstvarchr(c)) {
if ((*flags & EXPR_TWORD) == 0) {
return -2; // unexpected word
}
*flags = EXPR_TOP | EXPR_TOPEN | EXPR_TCLOSE;
while ((isvarchr(c)) && i < len) {
i++;
c = s[i];
}
return i;
} else if (c == '(' || c == ')') {
if (c == '(' && (*flags & EXPR_TOPEN) != 0) {
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE;
} else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) {
*flags = EXPR_TOP | EXPR_TCLOSE;
} else {
return -3; // unexpected parenthesis
}
return 1;
} else {
if ((*flags & EXPR_TOP) == 0) {
if (expr_op(&c, 1, 1) == OP_UNKNOWN) {
return -4; // missing expected operand
}
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY;
return 1;
} else {
int found = 0;
while (!isvarchr(c) && !isspace(c) && c != '(' && c != ')' && i < len) {
if (expr_op(s, i + 1, 0) != OP_UNKNOWN) {
found = 1;
} else if (found) {
break;
}
i++;
c = s[i];
}
if (!found) {
return -5; // unknown operator
}
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN;
return i;
}
}
}
#define EXPR_PAREN_ALLOWED 0
#define EXPR_PAREN_EXPECTED 1
#define EXPR_PAREN_FORBIDDEN 2
static int expr_bind(const char *s, size_t len, vec_expr_t *es) {
enum expr_type op = expr_op(s, len, -1);
if (op == OP_UNKNOWN) {
return -1;
}
if (expr_is_unary(op)) {
if (vec_len(es) < 1) {
return -1;
}
struct expr arg = vec_pop(es);
struct expr unary = expr_init();
unary.type = op;
vec_push(&unary.param.op.args, arg);
vec_push(es, unary);
} else {
if (vec_len(es) < 2) {
return -1;
}
struct expr b = vec_pop(es);
struct expr a = vec_pop(es);
struct expr binary = expr_init();
binary.type = op;
if (op == OP_ASSIGN && a.type != OP_VAR) {
return -1; /* Bad assignment */
}
vec_push(&binary.param.op.args, a);
vec_push(&binary.param.op.args, b);
vec_push(es, binary);
}
return 0;
}
static struct expr expr_const(double value) {
struct expr e = expr_init();
e.type = OP_CONST;
e.param.num.value = value;
return e;
}
static struct expr expr_varref(struct expr_var *v) {
struct expr e = expr_init();
e.type = OP_VAR;
e.param.var.value = &v->value;
return e;
}
static struct expr expr_binary(enum expr_type type, struct expr a,
struct expr b) {
struct expr e = expr_init();
e.type = type;
vec_push(&e.param.op.args, a);
vec_push(&e.param.op.args, b);
return e;
}
static inline void expr_copy(struct expr *dst, struct expr *src) {
int i;
struct expr arg;
dst->type = src->type;
if (src->type == OP_FUNC) {
dst->param.func.f = src->param.func.f;
vec_foreach(&src->param.func.args, arg, i) {
struct expr tmp = expr_init();
expr_copy(&tmp, &arg);
vec_push(&dst->param.func.args, tmp);
}
if (src->param.func.f->ctxsz > 0) {
dst->param.func.context = calloc(1, src->param.func.f->ctxsz);
}
} else if (src->type == OP_CONST) {
dst->param.num.value = src->param.num.value;
} else if (src->type == OP_VAR) {
dst->param.var.value = src->param.var.value;
} else {
vec_foreach(&src->param.op.args, arg, i) {
struct expr tmp = expr_init();
expr_copy(&tmp, &arg);
vec_push(&dst->param.op.args, tmp);
}
}
}
static void expr_destroy_args(struct expr *e);
static struct expr *expr_create(const char *s, size_t len,
struct expr_var_list *vars,
struct expr_func *funcs) {
double num;
const char *id = NULL;
size_t idn = 0;
struct expr *result = NULL;
vec_expr_t es = vec_init();
vec_str_t os = vec_init();
vec_arg_t as = vec_init();
struct macro {
char *name;
vec_expr_t body;
};
vec(struct macro) macros = vec_init();
int flags = EXPR_TDEFAULT;
int paren = EXPR_PAREN_ALLOWED;
for (;;) {
int n = expr_next_token(s, len, &flags);
if (n == 0) {
break;
} else if (n < 0) {
goto cleanup;
}
const char *tok = s;
s = s + n;
len = len - n;
if (*tok == '#') {
continue;
}
if (flags & EXPR_UNARY) {
if (n == 1) {
switch (*tok) {
case '-':
tok = "-u";
break;
case '~':
tok = "~u";
break;
case '!':
tok = "!u";
break;
default:
goto cleanup;
}
n = 2;
}
}
if (*tok == '\n' && (flags & EXPR_COMMA)) {
flags = flags & (~EXPR_COMMA);
n = 1;
tok = ",";
}
if (isspace(*tok)) {
continue;
}
int paren_next = EXPR_PAREN_ALLOWED;
if (idn > 0) {
struct expr_var *v;
if (n == 1 && *tok == '(') {
int i;
int has_macro = 0;
struct macro m;
vec_foreach(&macros, m, i) {
if (strlen(m.name) == idn && strncmp(m.name, id, idn) == 0) {
has_macro = 1;
break;
}
}
if ((idn == 1 && id[0] == '$') || has_macro ||
expr_get_func(funcs, id, idn) != NULL) {
struct expr_string str = {id, (int)idn};
vec_push(&os, str);
paren = EXPR_PAREN_EXPECTED;
} else {
goto cleanup; /* invalid function name */
}
} else if ((v = expr_get_var(vars, id, idn)) != NULL) {
vec_push(&es, expr_varref(v));
paren = EXPR_PAREN_FORBIDDEN;
}
id = NULL;
idn = 0;
}
if (n == 1 && *tok == '(') {
if (paren == EXPR_PAREN_EXPECTED) {
struct expr_string str = {"{", 1};
vec_push(&os, str);
struct expr_arg arg = {vec_len(&os), vec_len(&es), vec_init()};
vec_push(&as, arg);
} else if (paren == EXPR_PAREN_ALLOWED) {
struct expr_string str = {"(", 1};
vec_push(&os, str);
} else {
goto cleanup; // Bad call
}
} else if (paren == EXPR_PAREN_EXPECTED) {
goto cleanup; // Bad call
} else if (n == 1 && *tok == ')') {
int minlen = (vec_len(&as) > 0 ? vec_peek(&as).oslen : 0);
while (vec_len(&os) > minlen && *vec_peek(&os).s != '(' &&
*vec_peek(&os).s != '{') {
struct expr_string str = vec_pop(&os);
if (expr_bind(str.s, str.n, &es) == -1) {
goto cleanup;
}
}
if (vec_len(&os) == 0) {
goto cleanup; // Bad parens
}
struct expr_string str = vec_pop(&os);
if (str.n == 1 && *str.s == '{') {
str = vec_pop(&os);
struct expr_arg arg = vec_pop(&as);
if (vec_len(&es) > arg.eslen) {
vec_push(&arg.args, vec_pop(&es));
}
if (str.n == 1 && str.s[0] == '$') {
if (vec_len(&arg.args) < 1) {
vec_free(&arg.args);
goto cleanup; /* too few arguments for $() function */
}
struct expr *u = &vec_nth(&arg.args, 0);
if (u->type != OP_VAR) {
vec_free(&arg.args);
goto cleanup; /* first argument is not a variable */
}
for (struct expr_var *v = vars->head; v; v = v->next) {
if (&v->value == u->param.var.value) {
struct macro m = {v->name, arg.args};
vec_push(&macros, m);
break;
}
}
vec_push(&es, expr_const(0));
} else {
int i = 0;
int found = -1;
struct macro m;
vec_foreach(&macros, m, i) {
if (strlen(m.name) == (size_t)str.n &&
strncmp(m.name, str.s, str.n) == 0) {
found = i;
}
}
if (found != -1) {
m = vec_nth(&macros, found);
struct expr root = expr_const(0);
struct expr *p = &root;
/* Assign macro parameters */
for (int j = 0; j < vec_len(&arg.args); j++) {
char varname[12];
snprintf(varname, sizeof(varname), "$%d", (j + 1));
struct expr_var *v = expr_get_var(vars, varname, strlen(varname));
struct expr ev = expr_varref(v);
struct expr assign =
expr_binary(OP_ASSIGN, ev, vec_nth(&arg.args, j));
*p = expr_binary(OP_COMMA, assign, expr_const(0));
p = &vec_nth(&p->param.op.args, 1);
}
/* Expand macro body */
for (int j = 1; j < vec_len(&m.body); j++) {
if (j < vec_len(&m.body) - 1) {
*p = expr_binary(OP_COMMA, expr_const(0), expr_const(0));
expr_copy(&vec_nth(&p->param.op.args, 0), &vec_nth(&m.body, j));
} else {
expr_copy(p, &vec_nth(&m.body, j));
}
p = &vec_nth(&p->param.op.args, 1);
}
vec_push(&es, root);
vec_free(&arg.args);
} else {
struct expr_func *f = expr_get_func(funcs, str.s, str.n);
struct expr bound_func = expr_init();
bound_func.type = OP_FUNC;
bound_func.param.func.f = f;
bound_func.param.func.args = arg.args;
if (f->ctxsz > 0) {
void *p = calloc(1, f->ctxsz);
if (p == NULL) {
goto cleanup; /* allocation failed */
}
bound_func.param.func.context = p;
}
vec_push(&es, bound_func);
}
}
}
paren_next = EXPR_PAREN_FORBIDDEN;
} else if (!isnan(num = expr_parse_number(tok, n))) {
vec_push(&es, expr_const(num));
paren_next = EXPR_PAREN_FORBIDDEN;
} else if (expr_op(tok, n, -1) != OP_UNKNOWN) {
enum expr_type op = expr_op(tok, n, -1);
struct expr_string o2 = {NULL, 0};
if (vec_len(&os) > 0) {
o2 = vec_peek(&os);
}
for (;;) {
if (n == 1 && *tok == ',' && vec_len(&os) > 0) {
struct expr_string str = vec_peek(&os);
if (str.n == 1 && *str.s == '{') {
struct expr e = vec_pop(&es);
vec_push(&vec_peek(&as).args, e);
break;
}
}
enum expr_type type2 = expr_op(o2.s, o2.n, -1);
if (!(type2 != OP_UNKNOWN && expr_prec(op, type2))) {
struct expr_string str = {tok, n};
vec_push(&os, str);
break;
}
if (expr_bind(o2.s, o2.n, &es) == -1) {
goto cleanup;
}
(void)vec_pop(&os);
if (vec_len(&os) > 0) {
o2 = vec_peek(&os);
} else {
o2.n = 0;
}
}
} else {
if (n > 0 && !isdigit(*tok)) {
/* Valid identifier, a variable or a function */
id = tok;
idn = n;
} else {
goto cleanup; // Bad variable name, e.g. '2.3.4' or '4ever'
}
}
paren = paren_next;
}
if (idn > 0) {
vec_push(&es, expr_varref(expr_get_var(vars, id, idn)));
}
while (vec_len(&os) > 0) {
struct expr_string rest = vec_pop(&os);
if (rest.n == 1 && (*rest.s == '(' || *rest.s == ')')) {
goto cleanup; // Bad paren
}
if (expr_bind(rest.s, rest.n, &es) == -1) {
goto cleanup;
}
}
result = (struct expr *)calloc(1, sizeof(struct expr));
if (result != NULL) {
if (vec_len(&es) == 0) {
result->type = OP_CONST;
} else {
*result = vec_pop(&es);
}
}
int i, j;
struct macro m;
struct expr e;
struct expr_arg a;
cleanup:
vec_foreach(&macros, m, i) {
struct expr e2;
vec_foreach(&m.body, e2, j) { expr_destroy_args(&e2); }
vec_free(&m.body);
}
vec_free(&macros);
vec_foreach(&es, e, i) { expr_destroy_args(&e); }
vec_free(&es);
vec_foreach(&as, a, i) {
vec_foreach(&a.args, e, j) { expr_destroy_args(&e); }
vec_free(&a.args);
}
vec_free(&as);
/*vec_foreach(&os, o, i) {vec_free(&m.body);}*/
vec_free(&os);
return result;
}
static void expr_destroy_args(struct expr *e) {
int i;
struct expr arg;
if (e->type == OP_FUNC) {
vec_foreach(&e->param.func.args, arg, i) { expr_destroy_args(&arg); }
vec_free(&e->param.func.args);
if (e->param.func.context != NULL) {
if (e->param.func.f->cleanup != NULL) {
e->param.func.f->cleanup(e->param.func.f, e->param.func.context);
}
free(e->param.func.context);
}
} else if (e->type != OP_CONST && e->type != OP_VAR) {
vec_foreach(&e->param.op.args, arg, i) { expr_destroy_args(&arg); }
vec_free(&e->param.op.args);
}
}
static void expr_destroy(struct expr *e, struct expr_var_list *vars) {
if (e != NULL) {
expr_destroy_args(e);
free(e);
}
if (vars != NULL) {
for (struct expr_var *v = vars->head; v;) {
struct expr_var *next = v->next;
free(v);
v = next;
}
}
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif /* EXPR_H */

View File

@ -14,6 +14,8 @@ Dolphin includes or links code of the following third-party software projects:
[MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE) [MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE)
- [ENet](http://enet.bespin.org/): - [ENet](http://enet.bespin.org/):
[MIT](http://enet.bespin.org/License.html) [MIT](http://enet.bespin.org/License.html)
- [expr](https://github.com/zserge/expr):
[MIT](https://github.com/zserge/expr/blob/master/LICENSE)
- [FatFs](http://elm-chan.org/fsw/ff/00index_e.html): - [FatFs](http://elm-chan.org/fsw/ff/00index_e.html):
[BSD 1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license) [BSD 1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license)
- [GCEmu](http://sourceforge.net/projects/gcemu-project/): - [GCEmu](http://sourceforge.net/projects/gcemu-project/):

View File

@ -449,6 +449,8 @@ add_library(core
PowerPC/CachedInterpreter/InterpreterBlockCache.h PowerPC/CachedInterpreter/InterpreterBlockCache.h
PowerPC/ConditionRegister.cpp PowerPC/ConditionRegister.cpp
PowerPC/ConditionRegister.h PowerPC/ConditionRegister.h
PowerPC/Expression.cpp
PowerPC/Expression.h
PowerPC/Interpreter/ExceptionUtils.h PowerPC/Interpreter/ExceptionUtils.h
PowerPC/Interpreter/Interpreter_Branch.cpp PowerPC/Interpreter/Interpreter_Branch.cpp
PowerPC/Interpreter/Interpreter_FloatingPoint.cpp PowerPC/Interpreter/Interpreter_FloatingPoint.cpp
@ -593,6 +595,7 @@ PUBLIC
cubeb cubeb
discio discio
enet enet
expr
inputcommon inputcommon
${MBEDTLS_LIBRARIES} ${MBEDTLS_LIBRARIES}
pugixml pugixml

View File

@ -13,6 +13,7 @@
#include "Common/DebugInterface.h" #include "Common/DebugInterface.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/PowerPC/Expression.h"
#include "Core/PowerPC/JitInterface.h" #include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/MMU.h" #include "Core/PowerPC/MMU.h"
@ -35,17 +36,16 @@ bool BreakPoints::IsTempBreakPoint(u32 address) const
}); });
} }
bool BreakPoints::IsBreakPointBreakOnHit(u32 address) const const TBreakPoint* BreakPoints::GetBreakpoint(u32 address) const
{ {
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) { auto bp = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) {
return bp.address == address && bp.break_on_hit; return bp.is_enabled && bp.address == address;
}); });
}
bool BreakPoints::IsBreakPointLogOnHit(u32 address) const if (bp == m_breakpoints.end() || !EvaluateCondition(bp->condition))
{ return nullptr;
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(),
[address](const auto& bp) { return bp.address == address && bp.log_on_hit; }); return &*bp;
} }
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
@ -57,10 +57,16 @@ BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
{ {
std::ostringstream ss; std::ostringstream ss;
ss.imbue(std::locale::classic()); ss.imbue(std::locale::classic());
ss << fmt::format("${:08x} ", bp.address);
ss << std::hex << bp.address << " " << (bp.is_enabled ? "n" : "") if (bp.is_enabled)
<< (bp.log_on_hit ? "l" : "") << (bp.break_on_hit ? "b" : ""); ss << "n";
bp_strings.push_back(ss.str()); if (bp.log_on_hit)
ss << "l";
if (bp.break_on_hit)
ss << "b";
if (bp.condition)
ss << "c " << bp.condition->GetText();
bp_strings.emplace_back(ss.str());
} }
} }
@ -76,32 +82,43 @@ void BreakPoints::AddFromStrings(const TBreakPointsStr& bp_strings)
std::istringstream iss(bp_string); std::istringstream iss(bp_string);
iss.imbue(std::locale::classic()); iss.imbue(std::locale::classic());
if (iss.peek() == '$')
iss.ignore();
iss >> std::hex >> bp.address; iss >> std::hex >> bp.address;
iss >> flags; iss >> flags;
bp.is_enabled = flags.find('n') != flags.npos; bp.is_enabled = flags.find('n') != flags.npos;
bp.log_on_hit = flags.find('l') != flags.npos; bp.log_on_hit = flags.find('l') != flags.npos;
bp.break_on_hit = flags.find('b') != flags.npos; bp.break_on_hit = flags.find('b') != flags.npos;
if (flags.find('c') != std::string::npos)
{
iss >> std::ws;
std::string condition;
std::getline(iss, condition);
bp.condition = Expression::TryParse(condition);
}
bp.is_temporary = false; bp.is_temporary = false;
Add(bp); Add(std::move(bp));
} }
} }
void BreakPoints::Add(const TBreakPoint& bp) void BreakPoints::Add(TBreakPoint bp)
{ {
if (IsAddressBreakPoint(bp.address)) if (IsAddressBreakPoint(bp.address))
return; return;
m_breakpoints.push_back(bp);
JitInterface::InvalidateICache(bp.address, 4, true); JitInterface::InvalidateICache(bp.address, 4, true);
m_breakpoints.emplace_back(std::move(bp));
} }
void BreakPoints::Add(u32 address, bool temp) void BreakPoints::Add(u32 address, bool temp)
{ {
BreakPoints::Add(address, temp, true, false); BreakPoints::Add(address, temp, true, false, std::nullopt);
} }
void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit) void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit,
std::optional<Expression> condition)
{ {
// Only add new addresses // Only add new addresses
if (IsAddressBreakPoint(address)) if (IsAddressBreakPoint(address))
@ -113,8 +130,9 @@ void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit
bp.break_on_hit = break_on_hit; bp.break_on_hit = break_on_hit;
bp.log_on_hit = log_on_hit; bp.log_on_hit = log_on_hit;
bp.address = address; bp.address = address;
bp.condition = std::move(condition);
m_breakpoints.push_back(bp); m_breakpoints.emplace_back(std::move(bp));
JitInterface::InvalidateICache(address, 4, true); JitInterface::InvalidateICache(address, 4, true);
} }

View File

@ -4,10 +4,12 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/PowerPC/Expression.h"
namespace Common namespace Common
{ {
@ -21,6 +23,7 @@ struct TBreakPoint
bool is_temporary = false; bool is_temporary = false;
bool log_on_hit = false; bool log_on_hit = false;
bool break_on_hit = false; bool break_on_hit = false;
std::optional<Expression> condition;
}; };
struct TMemCheck struct TMemCheck
@ -59,13 +62,13 @@ public:
bool IsAddressBreakPoint(u32 address) const; bool IsAddressBreakPoint(u32 address) const;
bool IsBreakPointEnable(u32 adresss) const; bool IsBreakPointEnable(u32 adresss) const;
bool IsTempBreakPoint(u32 address) const; bool IsTempBreakPoint(u32 address) const;
bool IsBreakPointBreakOnHit(u32 address) const; const TBreakPoint* GetBreakpoint(u32 address) const;
bool IsBreakPointLogOnHit(u32 address) const;
// Add BreakPoint // Add BreakPoint
void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit); void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit,
std::optional<Expression> condition);
void Add(u32 address, bool temp = false); void Add(u32 address, bool temp = false);
void Add(const TBreakPoint& bp); void Add(TBreakPoint bp);
// Modify Breakpoint // Modify Breakpoint
bool ToggleBreakPoint(u32 address); bool ToggleBreakPoint(u32 address);

View File

@ -0,0 +1,269 @@
// Copyright 2020 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/PowerPC/Expression.h"
#include <algorithm>
#include <cstdlib>
#include <fmt/format.h>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <expr.h>
#include "Common/BitUtils.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/Core.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"
template <typename T>
static T HostRead(u32 address);
template <typename T>
static void HostWrite(T var, u32 address);
template <>
u8 HostRead(u32 address)
{
return PowerPC::HostRead_U8(address);
}
template <>
u16 HostRead(u32 address)
{
return PowerPC::HostRead_U16(address);
}
template <>
u32 HostRead(u32 address)
{
return PowerPC::HostRead_U32(address);
}
template <>
u64 HostRead(u32 address)
{
return PowerPC::HostRead_U64(address);
}
template <>
void HostWrite(u8 var, u32 address)
{
PowerPC::HostWrite_U8(var, address);
}
template <>
void HostWrite(u16 var, u32 address)
{
PowerPC::HostWrite_U16(var, address);
}
template <>
void HostWrite(u32 var, u32 address)
{
PowerPC::HostWrite_U32(var, address);
}
template <>
void HostWrite(u64 var, u32 address)
{
PowerPC::HostWrite_U64(var, address);
}
template <typename T, typename U = T>
static double HostReadFunc(expr_func* f, vec_expr_t* args, void* c)
{
if (vec_len(args) != 1)
return 0;
const u32 address = static_cast<u32>(expr_eval(&vec_nth(args, 0)));
return Common::BitCast<T>(HostRead<U>(address));
}
template <typename T, typename U = T>
static double HostWriteFunc(expr_func* f, vec_expr_t* args, void* c)
{
if (vec_len(args) != 2)
return 0;
const T var = static_cast<T>(expr_eval(&vec_nth(args, 0)));
const u32 address = static_cast<u32>(expr_eval(&vec_nth(args, 1)));
HostWrite<U>(Common::BitCast<U>(var), address);
return var;
}
template <typename T, typename U = T>
static double CastFunc(expr_func* f, vec_expr_t* args, void* c)
{
if (vec_len(args) != 1)
return 0;
return Common::BitCast<T>(static_cast<U>(expr_eval(&vec_nth(args, 0))));
}
static std::array<expr_func, 21> g_expr_funcs{{
// For internal storage and comparisons, everything is auto-converted to Double.
// If u64 ints are added, this could produce incorrect results.
{"read_u8", HostReadFunc<u8>},
{"read_s8", HostReadFunc<s8, u8>},
{"read_u16", HostReadFunc<u16>},
{"read_s16", HostReadFunc<s16, u16>},
{"read_u32", HostReadFunc<u32>},
{"read_s32", HostReadFunc<s32, u32>},
{"read_f32", HostReadFunc<float, u32>},
{"read_f64", HostReadFunc<double, u64>},
{"write_u8", HostWriteFunc<u8>},
{"write_u16", HostWriteFunc<u16>},
{"write_u32", HostWriteFunc<u32>},
{"write_f32", HostWriteFunc<float, u32>},
{"write_f64", HostWriteFunc<double, u64>},
{"u8", CastFunc<u8>},
{"s8", CastFunc<s8, u8>},
{"u16", CastFunc<u16>},
{"s16", CastFunc<s16, u16>},
{"u32", CastFunc<u32>},
{"s32", CastFunc<s32, u32>},
{},
}};
void ExprDeleter::operator()(expr* expression) const
{
expr_destroy(expression, nullptr);
}
void ExprVarListDeleter::operator()(expr_var_list* vars) const
{
// Free list elements
expr_destroy(nullptr, vars);
// Free list object
delete vars;
}
Expression::Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars)
: m_text(text), m_expr(std::move(ex)), m_vars(std::move(vars))
{
for (auto* v = m_vars->head; v != nullptr; v = v->next)
{
const std::string_view name = v->name;
VarBinding bind;
if (name.length() >= 2 && name.length() <= 3)
{
if (name[0] == 'r' || name[0] == 'f')
{
char* end = nullptr;
const int index = std::strtol(name.data() + 1, &end, 10);
if (index >= 0 && index <= 31 && end == name.data() + name.length())
{
bind.type = name[0] == 'r' ? VarBindingType::GPR : VarBindingType::FPR;
bind.index = index;
}
}
else if (name == "lr")
{
bind.type = VarBindingType::SPR;
bind.index = SPR_LR;
}
else if (name == "ctr")
{
bind.type = VarBindingType::SPR;
bind.index = SPR_CTR;
}
else if (name == "pc")
{
bind.type = VarBindingType::PCtr;
}
}
m_binds.emplace_back(bind);
}
}
std::optional<Expression> Expression::TryParse(std::string_view text)
{
ExprVarListPointer vars{new expr_var_list{}};
ExprPointer ex{expr_create(text.data(), text.length(), vars.get(), g_expr_funcs.data())};
if (!ex)
return std::nullopt;
return Expression{text, std::move(ex), std::move(vars)};
}
double Expression::Evaluate() const
{
SynchronizeBindings(SynchronizeDirection::From);
double result = expr_eval(m_expr.get());
SynchronizeBindings(SynchronizeDirection::To);
Reporting(result);
return result;
}
void Expression::SynchronizeBindings(SynchronizeDirection dir) const
{
auto bind = m_binds.begin();
for (auto* v = m_vars->head; v != nullptr; v = v->next, ++bind)
{
switch (bind->type)
{
case VarBindingType::Zero:
if (dir == SynchronizeDirection::From)
v->value = 0;
break;
case VarBindingType::GPR:
if (dir == SynchronizeDirection::From)
v->value = static_cast<double>(GPR(bind->index));
else
GPR(bind->index) = static_cast<u32>(static_cast<s64>(v->value));
break;
case VarBindingType::FPR:
if (dir == SynchronizeDirection::From)
v->value = rPS(bind->index).PS0AsDouble();
else
rPS(bind->index).SetPS0(v->value);
break;
case VarBindingType::SPR:
if (dir == SynchronizeDirection::From)
v->value = static_cast<double>(rSPR(bind->index));
else
rSPR(bind->index) = static_cast<u32>(static_cast<s64>(v->value));
break;
case VarBindingType::PCtr:
if (dir == SynchronizeDirection::From)
v->value = static_cast<double>(PC);
break;
}
}
}
void Expression::Reporting(const double result) const
{
bool is_nan = std::isnan(result);
std::string message;
for (auto* v = m_vars->head; v != nullptr; v = v->next)
{
if (std::isnan(v->value))
is_nan = true;
fmt::format_to(std::back_inserter(message), " {}={}", v->name, v->value);
}
if (is_nan)
{
message.append("\nBreakpoint condition encountered a NaN");
Core::DisplayMessage("Breakpoint condition has encountered a NaN.", 2000);
}
if (result != 0.0 || is_nan)
NOTICE_LOG_FMT(MEMMAP, "Breakpoint condition returned: {}. Vars:{}", result, message);
}
std::string Expression::GetText() const
{
return m_text;
}

View File

@ -0,0 +1,74 @@
// Copyright 2020 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
struct expr;
struct expr_var_list;
struct ExprDeleter
{
void operator()(expr* expression) const;
};
using ExprPointer = std::unique_ptr<expr, ExprDeleter>;
struct ExprVarListDeleter
{
void operator()(expr_var_list* vars) const;
};
using ExprVarListPointer = std::unique_ptr<expr_var_list, ExprVarListDeleter>;
class Expression
{
public:
static std::optional<Expression> TryParse(std::string_view text);
double Evaluate() const;
std::string GetText() const;
private:
enum class SynchronizeDirection
{
From,
To,
};
enum class VarBindingType
{
Zero,
GPR,
FPR,
SPR,
PCtr,
};
struct VarBinding
{
VarBindingType type = VarBindingType::Zero;
int index = -1;
};
Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars);
void SynchronizeBindings(SynchronizeDirection dir) const;
void Reporting(const double result) const;
std::string m_text;
ExprPointer m_expr;
ExprVarListPointer m_vars;
std::vector<VarBinding> m_binds;
};
inline bool EvaluateCondition(const std::optional<Expression>& condition)
{
return !condition || condition->Evaluate() != 0.0;
}

View File

@ -610,16 +610,18 @@ void CheckExternalExceptions()
void CheckBreakPoints() void CheckBreakPoints()
{ {
if (!PowerPC::breakpoints.IsBreakPointEnable(PC)) const TBreakPoint* bp = PowerPC::breakpoints.GetBreakpoint(PC);
if (bp == nullptr)
return; return;
if (PowerPC::breakpoints.IsBreakPointBreakOnHit(PC)) if (bp->break_on_hit)
{ {
CPU::Break(); CPU::Break();
if (GDBStub::IsActive()) if (GDBStub::IsActive())
GDBStub::TakeControl(); GDBStub::TakeControl();
} }
if (PowerPC::breakpoints.IsBreakPointLogOnHit(PC)) if (bp->log_on_hit)
{ {
NOTICE_LOG_FMT(MEMMAP, NOTICE_LOG_FMT(MEMMAP,
"BP {:08x} {}({:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} " "BP {:08x} {}({:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} "

View File

@ -405,6 +405,7 @@
<ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" /> <ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" />
<ClInclude Include="Core\PowerPC\ConditionRegister.h" /> <ClInclude Include="Core\PowerPC\ConditionRegister.h" />
<ClInclude Include="Core\PowerPC\CPUCoreBase.h" /> <ClInclude Include="Core\PowerPC\CPUCoreBase.h" />
<ClInclude Include="Core\PowerPC\Expression.h" />
<ClInclude Include="Core\PowerPC\GDBStub.h" /> <ClInclude Include="Core\PowerPC\GDBStub.h" />
<ClInclude Include="Core\PowerPC\Gekko.h" /> <ClInclude Include="Core\PowerPC\Gekko.h" />
<ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" /> <ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" />
@ -1029,6 +1030,7 @@
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" /> <ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
<ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" /> <ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" />
<ClCompile Include="Core\PowerPC\ConditionRegister.cpp" /> <ClCompile Include="Core\PowerPC\ConditionRegister.cpp" />
<ClCompile Include="Core\PowerPC\Expression.cpp" />
<ClCompile Include="Core\PowerPC\GDBStub.cpp" /> <ClCompile Include="Core\PowerPC\GDBStub.cpp" />
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Branch.cpp" /> <ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Branch.cpp" />
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_FloatingPoint.cpp" /> <ClCompile Include="Core\PowerPC\Interpreter\Interpreter_FloatingPoint.cpp" />

View File

@ -15,6 +15,7 @@
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/PowerPC/BreakPoints.h" #include "Core/PowerPC/BreakPoints.h"
#include "Core/PowerPC/Expression.h"
#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
@ -86,7 +87,7 @@ void BreakpointWidget::CreateWidgets()
m_table = new QTableWidget; m_table = new QTableWidget;
m_table->setTabKeyNavigation(false); m_table->setTabKeyNavigation(false);
m_table->setContentsMargins(0, 0, 0, 0); m_table->setContentsMargins(0, 0, 0, 0);
m_table->setColumnCount(5); m_table->setColumnCount(6);
m_table->setSelectionMode(QAbstractItemView::SingleSelection); m_table->setSelectionMode(QAbstractItemView::SingleSelection);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows); m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
@ -160,7 +161,7 @@ void BreakpointWidget::Update()
m_table->clear(); m_table->clear();
m_table->setHorizontalHeaderLabels( m_table->setHorizontalHeaderLabels(
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags")}); {tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags"), tr("Condition")});
int i = 0; int i = 0;
m_table->setRowCount(i); m_table->setRowCount(i);
@ -203,6 +204,13 @@ void BreakpointWidget::Update()
m_table->setItem(i, 4, create_item(flags)); m_table->setItem(i, 4, create_item(flags));
QString condition;
if (bp.condition)
condition = QString::fromStdString(bp.condition->GetText());
m_table->setItem(i, 5, create_item(condition));
i++; i++;
} }
@ -387,12 +395,15 @@ void BreakpointWidget::OnContextMenu()
void BreakpointWidget::AddBP(u32 addr) void BreakpointWidget::AddBP(u32 addr)
{ {
AddBP(addr, false, true, true); AddBP(addr, false, true, true, {});
} }
void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit) void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit,
const QString& condition)
{ {
PowerPC::breakpoints.Add(addr, temp, break_on_hit, log_on_hit); PowerPC::breakpoints.Add(
addr, temp, break_on_hit, log_on_hit,
!condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt);
emit BreakpointsChanged(); emit BreakpointsChanged();
Update(); Update();

View File

@ -21,7 +21,7 @@ public:
~BreakpointWidget(); ~BreakpointWidget();
void AddBP(u32 addr); void AddBP(u32 addr);
void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit); void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, const QString& condition);
void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true, void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true,
bool do_break = true); bool do_break = true);
void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true, void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true,

View File

@ -11,9 +11,11 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QPushButton>
#include <QRadioButton> #include <QRadioButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "Core/PowerPC/Expression.h"
#include "DolphinQt/Debugger/BreakpointWidget.h" #include "DolphinQt/Debugger/BreakpointWidget.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ModalMessageBox.h"
@ -35,16 +37,25 @@ void NewBreakpointDialog::CreateWidgets()
auto* type_group = new QButtonGroup(this); auto* type_group = new QButtonGroup(this);
// Instruction BP // Instruction BP
auto* top_layout = new QHBoxLayout;
m_instruction_bp = new QRadioButton(tr("Instruction Breakpoint")); m_instruction_bp = new QRadioButton(tr("Instruction Breakpoint"));
m_instruction_bp->setChecked(true); m_instruction_bp->setChecked(true);
type_group->addButton(m_instruction_bp); type_group->addButton(m_instruction_bp);
m_instruction_box = new QGroupBox; m_instruction_box = new QGroupBox;
m_instruction_address = new QLineEdit; m_instruction_address = new QLineEdit;
m_instruction_condition = new QLineEdit;
m_cond_help_btn = new QPushButton(tr("Help"));
auto* instruction_layout = new QHBoxLayout; top_layout->addWidget(m_instruction_bp);
top_layout->addStretch();
top_layout->addWidget(m_cond_help_btn);
auto* instruction_layout = new QGridLayout;
m_instruction_box->setLayout(instruction_layout); m_instruction_box->setLayout(instruction_layout);
instruction_layout->addWidget(new QLabel(tr("Address:"))); instruction_layout->addWidget(new QLabel(tr("Address:")), 0, 0);
instruction_layout->addWidget(m_instruction_address); instruction_layout->addWidget(m_instruction_address, 0, 1);
instruction_layout->addWidget(new QLabel(tr("Condition:")), 1, 0);
instruction_layout->addWidget(m_instruction_condition, 1, 1);
// Memory BP // Memory BP
m_memory_bp = new QRadioButton(tr("Memory Breakpoint")); m_memory_bp = new QRadioButton(tr("Memory Breakpoint"));
@ -102,7 +113,7 @@ void NewBreakpointDialog::CreateWidgets()
auto* layout = new QVBoxLayout; auto* layout = new QVBoxLayout;
layout->addWidget(m_instruction_bp); layout->addLayout(top_layout);
layout->addWidget(m_instruction_box); layout->addWidget(m_instruction_box);
layout->addWidget(m_memory_bp); layout->addWidget(m_memory_bp);
layout->addWidget(m_memory_box); layout->addWidget(m_memory_box);
@ -119,6 +130,8 @@ void NewBreakpointDialog::ConnectWidgets()
connect(m_buttons, &QDialogButtonBox::accepted, this, &NewBreakpointDialog::accept); connect(m_buttons, &QDialogButtonBox::accepted, this, &NewBreakpointDialog::accept);
connect(m_buttons, &QDialogButtonBox::rejected, this, &NewBreakpointDialog::reject); connect(m_buttons, &QDialogButtonBox::rejected, this, &NewBreakpointDialog::reject);
connect(m_cond_help_btn, &QPushButton::clicked, this, &NewBreakpointDialog::ShowConditionHelp);
connect(m_instruction_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged); connect(m_instruction_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
connect(m_memory_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged); connect(m_memory_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
@ -174,7 +187,15 @@ void NewBreakpointDialog::accept()
return; return;
} }
m_parent->AddBP(address, false, do_break, do_log); const QString condition = m_instruction_condition->text().trimmed();
if (!condition.isEmpty() && !Expression::TryParse(condition.toUtf8().constData()))
{
invalid_input(tr("Condition"));
return;
}
m_parent->AddBP(address, false, do_break, do_log, condition);
} }
else else
{ {
@ -205,3 +226,46 @@ void NewBreakpointDialog::accept()
QDialog::accept(); QDialog::accept();
} }
void NewBreakpointDialog::ShowConditionHelp()
{
const auto message = QStringLiteral(
"Set a code breakpoint for when an instruction is executed. Use with the code widget.\n"
"\n"
"Conditions:\n"
"Sets an expression that is evaluated when a breakpoint is hit. If the expression is false "
"or 0, the breakpoint is ignored until hit again. Statements should be separated by a comma. "
"Only the last statement will be used to determine what to do.\n"
"\n"
"Registers that can be referenced:\n"
"GPRs : r0..r31\n"
"FPRs : f0..f31\n LR, CTR, PC\n"
"\n"
"Functions:\n"
"Set a register: r1 = 8\n"
"Casts: s8(0xff). Available: s8, u8, s16, u16, s32, u32\n"
"Read Memory: read_u32(0x80000000). Available: u8, s8, u16, s16, u32, s32, f32, f64\n"
"Write Memory: write_u32(r3, 0x80000000). Available: u8, u16, u32, f32, f64\n"
"*currently writing will always be triggered\n"
"\n"
"Operations:\n"
"Unary: -u, !u, ~u\n"
"Math: * / + -, power: **, remainder: %, shift: <<, >>\n"
"Compare: <, <=, >, >=, ==, !=, &&, ||\n"
"Bitwise: &, |, ^\n"
"\n"
"Examples:\n"
"r4 == 1\n"
"f0 == 1.0 && f2 < 10.0\n"
"r26 <= r0 && ((r5 + 3) & -4) * ((r6 + 3) & -4)* 4 > r0\n"
"p = r3 + 0x8, p == 0x8003510 && read_u32(p) != 0\n"
"Write and break: r4 = 8, 1\n"
"Write and continue: f3 = f1 + f2, 0\n"
"The condition must always be last\n\n"
"All variables will be printed in the Memory Interface log, if there's a hit or a NaN "
"result. To check for issues, assign a variable to your equation, so it can be printed.\n\n"
"Note: All values are internally converted to Doubles for calculations. It's possible for "
"them to go out of range or to become NaN. A warning will be given if NaN is returned, and "
"the var that became NaN will be logged.");
ModalMessageBox::information(this, tr("Conditional help"), message);
}

View File

@ -29,11 +29,14 @@ private:
void OnBPTypeChanged(); void OnBPTypeChanged();
void OnAddressTypeChanged(); void OnAddressTypeChanged();
void ShowConditionHelp();
// Instruction BPs // Instruction BPs
QRadioButton* m_instruction_bp; QRadioButton* m_instruction_bp;
QGroupBox* m_instruction_box; QGroupBox* m_instruction_box;
QLineEdit* m_instruction_address; QLineEdit* m_instruction_address;
QLineEdit* m_instruction_condition;
QPushButton* m_cond_help_btn;
// Memory BPs // Memory BPs
QRadioButton* m_memory_bp; QRadioButton* m_memory_bp;

View File

@ -12,6 +12,7 @@
<!--For now, header-only libs don't have their own vcxproj/exports, so just supply their include paths to all Dolphin code--> <!--For now, header-only libs don't have their own vcxproj/exports, so just supply their include paths to all Dolphin code-->
<AdditionalIncludeDirectories>$(ExternalsDir)FFmpeg-bin\$(Platform)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ExternalsDir)FFmpeg-bin\$(Platform)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)expr\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>