]> git.rm.cloudns.org Git - xonotic/darkplaces.git/commitdiff
Breakpoints and watchpoints
authordivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Tue, 8 Jan 2013 12:37:43 +0000 (12:37 +0000)
committerRudolf Polzer <divverent@xonotic.org>
Sun, 13 Jan 2013 20:13:43 +0000 (21:13 +0100)
"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
::stable-branch::merge=6b19a6786e080a7f70717b28951bd1d3b07d3fb0

progsvm.h
prvm_cmds.c
prvm_edict.c
prvm_exec.c
prvm_execprogram.h

index 5dbfd464650b97829c84f5655126e8c48003b190..569f1edb03f977b39b48f0a5af3f395d94f1b0b0 100644 (file)
--- 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);
 
index e7523057e65cc25abc281bf4ac7f00b04918add5..ce69930696e73b27c95feab656790afccbfc0de8 100644 (file)
@@ -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;
        }
 }
index 56f3602e3b98b1c76b5a3a610bec18ef322d2363..41f8b2a10f1dfb41d4385e61d7bc5c11a2b82c9d 100644 (file)
@@ -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 <program name> <function name | statement>\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 <program name> <global name>\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 <program name> <edict number> <field name>\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;
 }
 
index f2ad83edf459fed77e436b8972c065e9a5195fdf..d1ab2436ef0ed4a5a017242d6945c2d92c065838 100644 (file)
@@ -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)
index c787e6fec3583613439091b48c401baa3db67408..314f0e9abea119f678b24db2ead8e1ae8ec57620 100644 (file)
 
 // 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++;
                        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)
                                        }
                                        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);
                                startst = st;
                                if (prog->depth <= exitdepth)
                                        goto cleanup; // all done
-                               if (prog->trace != cachedpr_trace)
-                                       goto chooseexecprogram;
                                break;
 
                        case OP_STATE:
                                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