Add callstack to conditional breakpoints. Checks entire stack for value.
Use: callstack(0x80000000). !callstack(value) works as a 'does not contain'. Add strings to expr.h conditionals. Use quotations: callstack("anim") to check symbols/name.
This commit is contained in:
parent
7cd9a78ebf
commit
76bf1b5f7d
|
@ -99,15 +99,17 @@ enum expr_type {
|
||||||
|
|
||||||
OP_CONST,
|
OP_CONST,
|
||||||
OP_VAR,
|
OP_VAR,
|
||||||
|
OP_STRING,
|
||||||
OP_FUNC,
|
OP_FUNC,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5,
|
static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 5,
|
||||||
5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0};
|
5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 0};
|
||||||
|
|
||||||
typedef vec(struct expr) vec_expr_t;
|
typedef vec(struct expr) vec_expr_t;
|
||||||
typedef void (*exprfn_cleanup_t)(struct expr_func *f, void *context);
|
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);
|
typedef double (*exprfn_t)(struct expr_func *f, vec_expr_t *args,
|
||||||
|
void *context);
|
||||||
|
|
||||||
struct expr {
|
struct expr {
|
||||||
enum expr_type type;
|
enum expr_type type;
|
||||||
|
@ -121,6 +123,9 @@ struct expr {
|
||||||
struct {
|
struct {
|
||||||
vec_expr_t args;
|
vec_expr_t args;
|
||||||
} op;
|
} op;
|
||||||
|
struct {
|
||||||
|
char *s;
|
||||||
|
} str;
|
||||||
struct {
|
struct {
|
||||||
struct expr_func *f;
|
struct expr_func *f;
|
||||||
vec_expr_t args;
|
vec_expr_t args;
|
||||||
|
@ -152,7 +157,7 @@ static int expr_is_unary(enum expr_type op) {
|
||||||
|
|
||||||
static int expr_is_binary(enum expr_type op) {
|
static int expr_is_binary(enum expr_type op) {
|
||||||
return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR &&
|
return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR &&
|
||||||
op != OP_FUNC && op != OP_UNKNOWN;
|
op != OP_FUNC && op != OP_UNKNOWN && op != OP_STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int expr_prec(enum expr_type a, enum expr_type b) {
|
static int expr_prec(enum expr_type a, enum expr_type b) {
|
||||||
|
@ -164,8 +169,8 @@ static int expr_prec(enum expr_type a, enum expr_type b) {
|
||||||
#define isfirstvarchr(c) \
|
#define isfirstvarchr(c) \
|
||||||
(((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || c == '$')
|
(((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || c == '$')
|
||||||
#define isvarchr(c) \
|
#define isvarchr(c) \
|
||||||
(((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || c == '$' || \
|
(((unsigned char)c >= '@' && c != '^' && c != '|' && c != '~') || \
|
||||||
c == '#' || (c >= '0' && c <= '9'))
|
c == '$' || c == '#' || (c >= '0' && c <= '9'))
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
const char *s;
|
const char *s;
|
||||||
|
@ -213,13 +218,13 @@ static enum expr_type expr_op(const char *s, size_t len, int unary) {
|
||||||
return OP_UNKNOWN;
|
return OP_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
static double expr_parse_number(const char* s, size_t len) {
|
static double expr_parse_number(const char *s, size_t len) {
|
||||||
double num = 0;
|
double num = 0;
|
||||||
char buf[32];
|
char buf[32];
|
||||||
char* sz = buf;
|
char *sz = buf;
|
||||||
char* end = NULL;
|
char *end = NULL;
|
||||||
if (len >= sizeof(buf)) {
|
if (len >= sizeof(buf)) {
|
||||||
sz = (char*)calloc(1, len + 1);
|
sz = (char *)calloc(1, len + 1);
|
||||||
if (sz == NULL) {
|
if (sz == NULL) {
|
||||||
return NAN;
|
return NAN;
|
||||||
}
|
}
|
||||||
|
@ -299,6 +304,13 @@ static int64_t to_int(double x) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *expr_get_str(struct expr *e) {
|
||||||
|
if (e->type != OP_STRING)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return e->param.str.s;
|
||||||
|
}
|
||||||
|
|
||||||
static double expr_eval(struct expr *e) {
|
static double expr_eval(struct expr *e) {
|
||||||
double n;
|
double n;
|
||||||
switch (e->type) {
|
switch (e->type) {
|
||||||
|
@ -403,9 +415,9 @@ static double expr_eval(struct expr *e) {
|
||||||
#define EXPR_TOP (1 << 0)
|
#define EXPR_TOP (1 << 0)
|
||||||
#define EXPR_TOPEN (1 << 1)
|
#define EXPR_TOPEN (1 << 1)
|
||||||
#define EXPR_TCLOSE (1 << 2)
|
#define EXPR_TCLOSE (1 << 2)
|
||||||
#define EXPR_TNUMBER (1 << 3)
|
#define EXPR_TLITERAL (1 << 3)
|
||||||
#define EXPR_TWORD (1 << 4)
|
#define EXPR_TWORD (1 << 4)
|
||||||
#define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TNUMBER | EXPR_TWORD)
|
#define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TLITERAL | EXPR_TWORD)
|
||||||
|
|
||||||
#define EXPR_UNARY (1 << 5)
|
#define EXPR_UNARY (1 << 5)
|
||||||
#define EXPR_COMMA (1 << 6)
|
#define EXPR_COMMA (1 << 6)
|
||||||
|
@ -427,7 +439,7 @@ static int expr_next_token(const char *s, size_t len, int *flags) {
|
||||||
if (i == len || s[i] == ')') {
|
if (i == len || s[i] == ')') {
|
||||||
*flags = *flags & (~EXPR_COMMA);
|
*flags = *flags & (~EXPR_COMMA);
|
||||||
} else {
|
} else {
|
||||||
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA;
|
*flags = EXPR_TLITERAL | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
|
@ -437,7 +449,7 @@ static int expr_next_token(const char *s, size_t len, int *flags) {
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
} else if (isdigit(c)) {
|
} else if (isdigit(c)) {
|
||||||
if ((*flags & EXPR_TNUMBER) == 0) {
|
if ((*flags & EXPR_TLITERAL) == 0) {
|
||||||
return -1; // unexpected number
|
return -1; // unexpected number
|
||||||
}
|
}
|
||||||
*flags = EXPR_TOP | EXPR_TCLOSE;
|
*flags = EXPR_TOP | EXPR_TCLOSE;
|
||||||
|
@ -460,6 +472,19 @@ static int expr_next_token(const char *s, size_t len, int *flags) {
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
|
} else if (c == '"') {
|
||||||
|
if ((*flags & EXPR_TLITERAL) == 0) {
|
||||||
|
return -6; // unexpected string
|
||||||
|
}
|
||||||
|
*flags = EXPR_TOP | EXPR_TCLOSE;
|
||||||
|
i++;
|
||||||
|
for (; i < len && s[i] != '"'; i++)
|
||||||
|
;
|
||||||
|
if (i >= len) {
|
||||||
|
return -7; // missing expected quote
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
return i;
|
||||||
} else if (isfirstvarchr(c)) {
|
} else if (isfirstvarchr(c)) {
|
||||||
if ((*flags & EXPR_TWORD) == 0) {
|
if ((*flags & EXPR_TWORD) == 0) {
|
||||||
return -2; // unexpected word
|
return -2; // unexpected word
|
||||||
|
@ -472,7 +497,7 @@ static int expr_next_token(const char *s, size_t len, int *flags) {
|
||||||
return i;
|
return i;
|
||||||
} else if (c == '(' || c == ')') {
|
} else if (c == '(' || c == ')') {
|
||||||
if (c == '(' && (*flags & EXPR_TOPEN) != 0) {
|
if (c == '(' && (*flags & EXPR_TOPEN) != 0) {
|
||||||
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE;
|
*flags = EXPR_TLITERAL | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE;
|
||||||
} else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) {
|
} else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) {
|
||||||
*flags = EXPR_TOP | EXPR_TCLOSE;
|
*flags = EXPR_TOP | EXPR_TCLOSE;
|
||||||
} else {
|
} else {
|
||||||
|
@ -484,7 +509,7 @@ static int expr_next_token(const char *s, size_t len, int *flags) {
|
||||||
if (expr_op(&c, 1, 1) == OP_UNKNOWN) {
|
if (expr_op(&c, 1, 1) == OP_UNKNOWN) {
|
||||||
return -4; // missing expected operand
|
return -4; // missing expected operand
|
||||||
}
|
}
|
||||||
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY;
|
*flags = EXPR_TLITERAL | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY;
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
int found = 0;
|
int found = 0;
|
||||||
|
@ -500,7 +525,7 @@ static int expr_next_token(const char *s, size_t len, int *flags) {
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return -5; // unknown operator
|
return -5; // unknown operator
|
||||||
}
|
}
|
||||||
*flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN;
|
*flags = EXPR_TLITERAL | EXPR_TWORD | EXPR_TOPEN;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -584,6 +609,12 @@ static inline void expr_copy(struct expr *dst, struct expr *src) {
|
||||||
dst->param.num.value = src->param.num.value;
|
dst->param.num.value = src->param.num.value;
|
||||||
} else if (src->type == OP_VAR) {
|
} else if (src->type == OP_VAR) {
|
||||||
dst->param.var.value = src->param.var.value;
|
dst->param.var.value = src->param.var.value;
|
||||||
|
} else if (src->type == OP_STRING) {
|
||||||
|
size_t len = strlen(src->param.str.s);
|
||||||
|
dst->param.str.s = (char *)calloc(1, len + 1);
|
||||||
|
if (dst->param.str.s != NULL) {
|
||||||
|
strncpy(dst->param.str.s, src->param.str.s, len);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
vec_foreach(&src->param.op.args, arg, i) {
|
vec_foreach(&src->param.op.args, arg, i) {
|
||||||
struct expr tmp = expr_init();
|
struct expr tmp = expr_init();
|
||||||
|
@ -794,6 +825,17 @@ static struct expr *expr_create(const char *s, size_t len,
|
||||||
} else if (!isnan(num = expr_parse_number(tok, n))) {
|
} else if (!isnan(num = expr_parse_number(tok, n))) {
|
||||||
vec_push(&es, expr_const(num));
|
vec_push(&es, expr_const(num));
|
||||||
paren_next = EXPR_PAREN_FORBIDDEN;
|
paren_next = EXPR_PAREN_FORBIDDEN;
|
||||||
|
} else if (n > 1 && *tok == '"') {
|
||||||
|
char *s = (char *)calloc(1, n - 1);
|
||||||
|
if (s == NULL) {
|
||||||
|
goto cleanup; /* allocation failed */
|
||||||
|
}
|
||||||
|
strncpy(s, tok + 1, n - 2);
|
||||||
|
struct expr e = expr_init();
|
||||||
|
e.type = OP_STRING;
|
||||||
|
e.param.str.s = s;
|
||||||
|
vec_push(&es, e);
|
||||||
|
paren_next = EXPR_PAREN_FORBIDDEN;
|
||||||
} else if (expr_op(tok, n, -1) != OP_UNKNOWN) {
|
} else if (expr_op(tok, n, -1) != OP_UNKNOWN) {
|
||||||
enum expr_type op = expr_op(tok, n, -1);
|
enum expr_type op = expr_op(tok, n, -1);
|
||||||
struct expr_string o2 = {NULL, 0};
|
struct expr_string o2 = {NULL, 0};
|
||||||
|
@ -899,6 +941,8 @@ static void expr_destroy_args(struct expr *e) {
|
||||||
}
|
}
|
||||||
free(e->param.func.context);
|
free(e->param.func.context);
|
||||||
}
|
}
|
||||||
|
} else if (e->type == OP_STRING) {
|
||||||
|
free(e->param.str.s);
|
||||||
} else if (e->type != OP_CONST && e->type != OP_VAR) {
|
} else if (e->type != OP_CONST && e->type != OP_VAR) {
|
||||||
vec_foreach(&e->param.op.args, arg, i) { expr_destroy_args(&arg); }
|
vec_foreach(&e->param.op.args, arg, i) { expr_destroy_args(&arg); }
|
||||||
vec_free(&e->param.op.args);
|
vec_free(&e->param.op.args);
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
|
#include "Core/Debugger/Debugger_SymbolMap.h"
|
||||||
#include "Core/PowerPC/MMU.h"
|
#include "Core/PowerPC/MMU.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
|
|
||||||
|
@ -102,7 +103,35 @@ static double CastFunc(expr_func* f, vec_expr_t* args, void* c)
|
||||||
return Common::BitCast<T>(static_cast<U>(expr_eval(&vec_nth(args, 0))));
|
return Common::BitCast<T>(static_cast<U>(expr_eval(&vec_nth(args, 0))));
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::array<expr_func, 21> g_expr_funcs{{
|
static double CallstackFunc(expr_func* f, vec_expr_t* args, void* c)
|
||||||
|
{
|
||||||
|
if (vec_len(args) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
std::vector<Dolphin_Debugger::CallstackEntry> stack;
|
||||||
|
bool success = Dolphin_Debugger::GetCallstack(stack);
|
||||||
|
if (!success)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
double num = expr_eval(&vec_nth(args, 0));
|
||||||
|
if (!std::isnan(num))
|
||||||
|
{
|
||||||
|
u32 address = static_cast<u32>(num);
|
||||||
|
return std::any_of(stack.begin(), stack.end(),
|
||||||
|
[address](const auto& s) { return s.vAddress == address; });
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* cstr = expr_get_str(&vec_nth(args, 0));
|
||||||
|
if (cstr != nullptr)
|
||||||
|
{
|
||||||
|
return std::any_of(stack.begin(), stack.end(),
|
||||||
|
[cstr](const auto& s) { return s.Name.find(cstr) != std::string::npos; });
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::array<expr_func, 22> g_expr_funcs{{
|
||||||
// For internal storage and comparisons, everything is auto-converted to Double.
|
// For internal storage and comparisons, everything is auto-converted to Double.
|
||||||
// If u64 ints are added, this could produce incorrect results.
|
// If u64 ints are added, this could produce incorrect results.
|
||||||
{"read_u8", HostReadFunc<u8>},
|
{"read_u8", HostReadFunc<u8>},
|
||||||
|
@ -124,6 +153,7 @@ static std::array<expr_func, 21> g_expr_funcs{{
|
||||||
{"s16", CastFunc<s16, u16>},
|
{"s16", CastFunc<s16, u16>},
|
||||||
{"u32", CastFunc<u32>},
|
{"u32", CastFunc<u32>},
|
||||||
{"s32", CastFunc<s32, u32>},
|
{"s32", CastFunc<s32, u32>},
|
||||||
|
{"callstack", CallstackFunc},
|
||||||
{},
|
{},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|
|
@ -324,6 +324,7 @@ void BreakpointDialog::ShowConditionHelp()
|
||||||
"Functions:\n"
|
"Functions:\n"
|
||||||
"Set a register: r1 = 8\n"
|
"Set a register: r1 = 8\n"
|
||||||
"Casts: s8(0xff). Available: s8, u8, s16, u16, s32, u32\n"
|
"Casts: s8(0xff). Available: s8, u8, s16, u16, s32, u32\n"
|
||||||
|
"Callstack: callstack(0x80123456), callstack(\"anim\")\n"
|
||||||
"Read Memory: read_u32(0x80000000). Available: u8, s8, u16, s16, u32, s32, f32, f64\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"
|
"Write Memory: write_u32(r3, 0x80000000). Available: u8, u16, u32, f32, f64\n"
|
||||||
"*currently writing will always be triggered\n"
|
"*currently writing will always be triggered\n"
|
||||||
|
@ -342,6 +343,8 @@ void BreakpointDialog::ShowConditionHelp()
|
||||||
"Write and break: r4 = 8, 1\n"
|
"Write and break: r4 = 8, 1\n"
|
||||||
"Write and continue: f3 = f1 + f2, 0\n"
|
"Write and continue: f3 = f1 + f2, 0\n"
|
||||||
"The condition must always be last\n\n"
|
"The condition must always be last\n\n"
|
||||||
|
"Strings should only be used in callstack() and \"quoted\". Do not assign strings to a "
|
||||||
|
"variable.\n"
|
||||||
"All variables will be printed in the Memory Interface log, if there's a hit or a NaN "
|
"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"
|
"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 "
|
"Note: All values are internally converted to Doubles for calculations. It's possible for "
|
||||||
|
|
Loading…
Reference in New Issue