From 6b19a6786e080a7f70717b28951bd1d3b07d3fb0 Mon Sep 17 00:00:00 2001 From: divverent Date: Tue, 8 Jan 2013 12:37:43 +0000 Subject: [PATCH] Breakpoints and watchpoints "Break" on statement: prvm_breakpoint server 12345 "Break" on function: prvm_breakpoint server ClientConnect Watch for global change: prvm_globalwatchpoint server time Watch for entity field change: prvm_edictwatchpoint server 1 health There can be only one of each kind. To clear, do: prvm_breakpoint server prvm_globalwatchpoint server prvm_edictwatchpoint server git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@11874 d7cf8633-e32d-0410-b094-e92efae38249 --- progsvm.h | 17 +++- prvm_cmds.c | 2 +- prvm_edict.c | 218 +++++++++++++++++++++++++++++++++++++++++++-- prvm_exec.c | 25 +++--- prvm_execprogram.h | 62 ++++++++++++- 5 files changed, 298 insertions(+), 26 deletions(-) diff --git a/progsvm.h b/progsvm.h index 5dbfd464..569f1edb 100644 --- a/progsvm.h +++ b/progsvm.h @@ -228,13 +228,13 @@ extern prvm_eval_t prvm_badvalue; #define PRVM_menufunction(funcname) (prog->funcoffsets.funcname) #if 1 -#define PRVM_EDICTFIELDVALUE(ed, fieldoffset) (fieldoffset < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)(ed->fields.fp + fieldoffset)) +#define PRVM_EDICTFIELDVALUE(ed, fieldoffset) ((fieldoffset) < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)((ed)->fields.fp + (fieldoffset))) #define PRVM_EDICTFIELDFLOAT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->_float) #define PRVM_EDICTFIELDVECTOR(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->vector) #define PRVM_EDICTFIELDSTRING(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->string) #define PRVM_EDICTFIELDEDICT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->edict) #define PRVM_EDICTFIELDFUNCTION(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->function) -#define PRVM_GLOBALFIELDVALUE(fieldoffset) (fieldoffset < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)(prog->globals.fp + fieldoffset)) +#define PRVM_GLOBALFIELDVALUE(fieldoffset) ((fieldoffset) < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)(prog->globals.fp + (fieldoffset))) #define PRVM_GLOBALFIELDFLOAT(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->_float) #define PRVM_GLOBALFIELDVECTOR(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->vector) #define PRVM_GLOBALFIELDSTRING(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->string) @@ -591,6 +591,14 @@ typedef struct prvm_prog_s int argc; int trace; + int break_statement; + int break_stack_index; + int watch_global; + prvm_vec_t watch_global_value; + int watch_edict; + int watch_field; + prvm_vec_t watch_edictfield_value; + mfunction_t *xfunction; int xstatement; @@ -763,7 +771,7 @@ void PRVM_ChildProfile_f (void); void PRVM_CallProfile_f (void); void PRVM_PrintFunction_f (void); -void PRVM_PrintState(prvm_prog_t *prog); +void PRVM_PrintState(prvm_prog_t *prog, int stack_index); void PRVM_Crash(prvm_prog_t *prog); void PRVM_ShortStackTrace(prvm_prog_t *prog, char *buf, size_t bufsize); const char *PRVM_AllocationOrigin(prvm_prog_t *prog); @@ -855,12 +863,13 @@ Call InitProg with the num Set up the fields marked with [INIT] in the prog struct Load a program with LoadProgs */ -// Load expects to be called right after Init +// Load expects to be called right after Reset void PRVM_Prog_Init(prvm_prog_t *prog); void PRVM_Prog_Load(prvm_prog_t *prog, const char *filename, unsigned char *data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global); void PRVM_Prog_Reset(prvm_prog_t *prog); void PRVM_StackTrace(prvm_prog_t *prog); +void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text); void VM_Warning(prvm_prog_t *prog, const char *fmt, ...) DP_FUNC_PRINTF(2); diff --git a/prvm_cmds.c b/prvm_cmds.c index e7523057..ce699306 100644 --- a/prvm_cmds.c +++ b/prvm_cmds.c @@ -36,7 +36,7 @@ void VM_Warning(prvm_prog_t *prog, const char *fmt, ...) if(prvm_backtraceforwarnings.integer && recursive != realtime) // NOTE: this compares to the time, just in case if PRVM_PrintState causes a Host_Error and keeps recursive set { recursive = realtime; - PRVM_PrintState(prog); + PRVM_PrintState(prog, 0); recursive = -1; } } diff --git a/prvm_edict.c b/prvm_edict.c index 56f3602e..41f8b2a1 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -39,6 +39,7 @@ cvar_t prvm_backtraceforwarnings = {0, "prvm_backtraceforwarnings", "0", "print cvar_t prvm_leaktest = {0, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"}; cvar_t prvm_leaktest_ignore_classnames = {0, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"}; cvar_t prvm_errordump = {0, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"}; +cvar_t prvm_breakpointdump = {0, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"}; cvar_t prvm_reuseedicts_startuptime = {0, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"}; cvar_t prvm_reuseedicts_neverinsameframe = {0, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"}; @@ -1814,12 +1815,18 @@ static void PRVM_PO_Destroy(po_t *po) void PRVM_LeakTest(prvm_prog_t *prog); void PRVM_Prog_Reset(prvm_prog_t *prog) { - PRVM_LeakTest(prog); - prog->reset_cmd(prog); - Mem_FreePool(&prog->progs_mempool); - if(prog->po) - PRVM_PO_Destroy((po_t *) prog->po); + if (prog->loaded) + { + PRVM_LeakTest(prog); + prog->reset_cmd(prog); + Mem_FreePool(&prog->progs_mempool); + if(prog->po) + PRVM_PO_Destroy((po_t *) prog->po); + } memset(prog,0,sizeof(prvm_prog_t)); + prog->break_statement = -1; + prog->watch_global = -1; + prog->watch_edict = -1; } /* @@ -1875,6 +1882,7 @@ static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) { PRVM_LoadProgs =============== */ +static void PRVM_UpdateBreakpoints(prvm_prog_t *prog); void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global) { int i; @@ -2398,6 +2406,8 @@ fail: prog->loaded = TRUE; + PRVM_UpdateBreakpoints(prog); + // set flags & ddef_ts in prog prog->flag = 0; @@ -2623,6 +2633,194 @@ static void PRVM_GlobalSet_f(void) PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(3), true ); } +/* +====================== +Break- and Watchpoints +====================== +*/ +typedef struct +{ + char break_statement[256]; + char watch_global[256]; + int watch_edict; + char watch_field[256]; +} +debug_data_t; +static debug_data_t debug_data[PRVM_PROG_MAX]; + +void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text) +{ + char vabuf[1024]; + Con_Printf("PRVM_Breakpoint: %s\n", text); + PRVM_PrintState(prog, stack_index); + if (prvm_breakpointdump.integer) + Host_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name)); +} + +static void PRVM_UpdateBreakpoints(prvm_prog_t *prog) +{ + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + if (!prog->loaded) + return; + if (debug->break_statement[0]) + { + if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9') + { + prog->break_statement = atoi(debug->break_statement); + prog->break_stack_index = 0; + } + else + { + mfunction_t *func; + func = PRVM_ED_FindFunction (prog, debug->break_statement); + if (!func) + { + Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement); + prog->break_statement = -1; + } + else + { + prog->break_statement = func->first_statement; + prog->break_stack_index = 1; + } + } + if (prog->break_statement >= -1) + Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement); + } + else + prog->break_statement = -1; + + if (debug->watch_global[0]) + { + ddef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global ); + if( !global ) + { + Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global ); + prog->watch_global = -1; + } + else + { + prog->watch_global = global->ofs; + prog->watch_global_value = PRVM_GLOBALFIELDFLOAT(prog->watch_global); + } + if (prog->watch_global >= -1) + Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global); + } + else + prog->watch_global = -1; + + if (debug->watch_field[0]) + { + ddef_t *field = PRVM_ED_FindField( prog, debug->watch_field ); + if( !field ) + { + Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field ); + prog->watch_edict = -1; + } + else + { + prog->watch_edict = debug->watch_edict; + prog->watch_field = field->ofs; + if (prog->watch_edict < prog->num_edicts) + prog->watch_edictfield_value = PRVM_EDICTFIELDFLOAT(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field); + else + prog->watch_edictfield_value = 0; + } + if (prog->watch_edict >= -1) + Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field); + } + else + prog->watch_edict = -1; +} + +static void PRVM_Breakpoint_f(void) +{ + prvm_prog_t *prog; + + if( Cmd_Argc() == 2 ) { + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->break_statement[0] = 0; + } + PRVM_UpdateBreakpoints(prog); + return; + } + if( Cmd_Argc() != 3 ) { + Con_Printf( "prvm_breakpoint \n" ); + return; + } + + if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) + return; + + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + strlcpy(debug->break_statement, Cmd_Argv(2), sizeof(debug->break_statement)); + } + PRVM_UpdateBreakpoints(prog); +} + +static void PRVM_GlobalWatchpoint_f(void) +{ + prvm_prog_t *prog; + + if( Cmd_Argc() == 2 ) { + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->watch_global[0] = 0; + } + PRVM_UpdateBreakpoints(prog); + return; + } + if( Cmd_Argc() != 3 ) { + Con_Printf( "prvm_globalwatchpoint \n" ); + return; + } + + if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) + return; + + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + strlcpy(debug->watch_global, Cmd_Argv(2), sizeof(debug->watch_global)); + } + PRVM_UpdateBreakpoints(prog); +} + +static void PRVM_EdictWatchpoint_f(void) +{ + prvm_prog_t *prog; + + if( Cmd_Argc() == 2 ) { + if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) + return; + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->watch_field[0] = 0; + } + PRVM_UpdateBreakpoints(prog); + return; + } + if( Cmd_Argc() != 4 ) { + Con_Printf( "prvm_edictwatchpoint \n" ); + return; + } + + if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) + return; + + { + debug_data_t *debug = &debug_data[prog - prvm_prog_list]; + debug->watch_edict = atoi(Cmd_Argv(2)); + strlcpy(debug->watch_field, Cmd_Argv(3), sizeof(debug->watch_field)); + } + PRVM_UpdateBreakpoints(prog); +} + /* =============== PRVM_Init @@ -2648,6 +2846,10 @@ void PRVM_Init (void) Cmd_AddCommand ("menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument"); Cmd_AddCommand ("sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument"); + Cmd_AddCommand ("prvm_breakpoint", PRVM_Breakpoint_f, "marks a statement or function as breakpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear breakpoint"); + Cmd_AddCommand ("prvm_globalwatchpoint", PRVM_GlobalWatchpoint_f, "marks a global as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint"); + Cmd_AddCommand ("prvm_edictwatchpoint", PRVM_EdictWatchpoint_f, "marks an entity field as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint"); + Cvar_RegisterVariable (&prvm_language); Cvar_RegisterVariable (&prvm_traceqc); Cvar_RegisterVariable (&prvm_statementprofiling); @@ -2656,6 +2858,7 @@ void PRVM_Init (void) Cvar_RegisterVariable (&prvm_leaktest); Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames); Cvar_RegisterVariable (&prvm_errordump); + Cvar_RegisterVariable (&prvm_breakpointdump); Cvar_RegisterVariable (&prvm_reuseedicts_startuptime); Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe); @@ -2672,10 +2875,7 @@ PRVM_InitProg */ void PRVM_Prog_Init(prvm_prog_t *prog) { - if (prog->loaded) - PRVM_Prog_Reset(prog); - - memset(prog, 0, sizeof(prvm_prog_t)); + PRVM_Prog_Reset(prog); prog->leaktest_active = prvm_leaktest.integer != 0; } diff --git a/prvm_exec.c b/prvm_exec.c index f2ad83ed..d1ab2436 100644 --- a/prvm_exec.c +++ b/prvm_exec.c @@ -480,21 +480,26 @@ void PRVM_ChildProfile_f (void) PRVM_Profile(prog, howmany, 0, 1); } -void PRVM_PrintState(prvm_prog_t *prog) +void PRVM_PrintState(prvm_prog_t *prog, int stack_index) { int i; + mfunction_t *func = prog->xfunction; + int st = prog->xstatement; + if (stack_index > 0 && stack_index <= prog->depth) + { + func = prog->stack[prog->depth - stack_index].f; + st = prog->stack[prog->depth - stack_index].s; + } if (prog->statestring) { Con_Printf("Caller-provided information: %s\n", prog->statestring); } - if (prog->xfunction) + if (func) { for (i = -7; i <= 0;i++) - if (prog->xstatement + i >= prog->xfunction->first_statement) - PRVM_PrintStatement(prog, prog->statements + prog->xstatement + i); + if (st + i >= func->first_statement) + PRVM_PrintStatement(prog, prog->statements + st + i); } - else - Con_Print("null function executing??\n"); PRVM_StackTrace(prog); } @@ -512,7 +517,7 @@ void PRVM_Crash(prvm_prog_t *prog) if( prog->depth > 0 ) { Con_Printf("QuakeC crash report for %s:\n", prog->name); - PRVM_PrintState(prog); + PRVM_PrintState(prog, 0); } if(prvm_errordump.integer) @@ -706,7 +711,7 @@ void MVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessag chooseexecprogram: cachedpr_trace = prog->trace; - if (prvm_statementprofiling.integer || prog->trace) + if (prvm_statementprofiling.integer || prog->trace || prog->watch_global >= 0 || prog->watch_edict >= 0 || prog->break_statement >= 0) { #define PRVMSLOWINTERPRETER 1 if (prvm_timeprofiling.integer) @@ -797,7 +802,7 @@ void CLVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessa chooseexecprogram: cachedpr_trace = prog->trace; - if (prvm_statementprofiling.integer || prog->trace) + if (prvm_statementprofiling.integer || prog->trace || prog->watch_global >= 0 || prog->watch_edict >= 0 || prog->break_statement >= 0) { #define PRVMSLOWINTERPRETER 1 if (prvm_timeprofiling.integer) @@ -893,7 +898,7 @@ void PRVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessa chooseexecprogram: cachedpr_trace = prog->trace; - if (prvm_statementprofiling.integer || prog->trace) + if (prvm_statementprofiling.integer || prog->trace || prog->watch_global >= 0 || prog->watch_edict >= 0 || prog->break_statement >= 0) { #define PRVMSLOWINTERPRETER 1 if (prvm_timeprofiling.integer) diff --git a/prvm_execprogram.h b/prvm_execprogram.h index c787e6fe..314f0e9a 100644 --- a/prvm_execprogram.h +++ b/prvm_execprogram.h @@ -12,6 +12,32 @@ // This code isn't #ifdef/#define protectable, don't try. +#if PRVMSLOWINTERPRETER + { + char vabuf[1024]; + if (prog->watch_global >= 0) + { + prvm_vec_t f = PRVM_GLOBALFIELDFLOAT(prog->watch_global); + if (memcmp(&f, &prog->watch_global_value, sizeof(f))) + { + prog->xstatement = st + 1 - prog->statements; + PRVM_Breakpoint(prog, 1, va(vabuf, sizeof(vabuf), "Global watchpoint hit by engine: " FLOAT_LOSSLESS_FORMAT " -> " FLOAT_LOSSLESS_FORMAT, prog->watch_global_value, f)); + prog->watch_global_value = f; + } + } + if (prog->watch_edict >= 0 && prog->watch_edict < prog->max_edicts) + { + prvm_vec_t f = PRVM_EDICTFIELDFLOAT(prog->edicts + prog->watch_edict, prog->watch_field); + if (memcmp(&f, &prog->watch_edictfield_value, sizeof(f))) + { + prog->xstatement = st + 1 - prog->statements; + PRVM_Breakpoint(prog, 1, va(vabuf, sizeof(vabuf), "Entityfield watchpoint hit by engine: " FLOAT_LOSSLESS_FORMAT " -> " FLOAT_LOSSLESS_FORMAT, prog->watch_edictfield_value, f)); + prog->watch_edictfield_value = f; + } + } + } +#endif + while (1) { st++; @@ -20,6 +46,12 @@ if (prog->trace) PRVM_PrintStatement(prog, st); prog->statement_profile[st - prog->statements]++; + if (prog->break_statement >= 0) + if ((st - prog->statements) == prog->break_statement) + { + prog->xstatement = st - prog->statements; + PRVM_Breakpoint(prog, prog->break_stack_index, "Breakpoint hit"); + } #endif switch (st->op) @@ -360,6 +392,9 @@ } else prog->error_cmd("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, prog->name); + + if (prog->trace != cachedpr_trace) + goto chooseexecprogram; } else st = prog->statements + PRVM_EnterFunction(prog, newf); @@ -384,8 +419,6 @@ startst = st; if (prog->depth <= exitdepth) goto cleanup; // all done - if (prog->trace != cachedpr_trace) - goto chooseexecprogram; break; case OP_STATE: @@ -678,6 +711,31 @@ prog->error_cmd("Bad opcode %i in %s", st->op, prog->name); goto cleanup; } +#if PRVMSLOWINTERPRETER + { + char vabuf[1024]; + if (prog->watch_global >= 0) + { + prvm_vec_t f = PRVM_GLOBALFIELDFLOAT(prog->watch_global); + if (memcmp(&f, &prog->watch_global_value, sizeof(f))) + { + prog->xstatement = st - prog->statements; + PRVM_Breakpoint(prog, 0, va(vabuf, sizeof(vabuf), "Global watchpoint hit: " FLOAT_LOSSLESS_FORMAT " -> " FLOAT_LOSSLESS_FORMAT, prog->watch_global_value, f)); + prog->watch_global_value = f; + } + } + if (prog->watch_edict >= 0 && prog->watch_edict < prog->max_edicts) + { + prvm_vec_t f = PRVM_EDICTFIELDFLOAT(prog->edicts + prog->watch_edict, prog->watch_field); + if (memcmp(&f, &prog->watch_edictfield_value, sizeof(f))) + { + prog->xstatement = st - prog->statements; + PRVM_Breakpoint(prog, 0, va(vabuf, sizeof(vabuf), "Entityfield watchpoint hit: " FLOAT_LOSSLESS_FORMAT " -> " FLOAT_LOSSLESS_FORMAT, prog->watch_edictfield_value, f)); + prog->watch_edictfield_value = f; + } + } + } +#endif } #undef PreError -- 2.39.2