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"};
break;
case ev_float:
// LordHavoc: changed from %5.1f to %10.4f
- dpsnprintf (line, linelength, "%10.4f", val->_float);
+ dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float);
break;
case ev_vector:
// LordHavoc: changed from %5.1f to %10.4f
- dpsnprintf (line, linelength, "'%10.4f %10.4f %10.4f'", val->vector[0], val->vector[1], val->vector[2]);
+ dpsnprintf (line, linelength, "'" VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]);
break;
case ev_pointer:
dpsnprintf (line, linelength, "pointer");
line[i] = '\0';
break;
case ev_entity:
- dpsnprintf (line, linelength, "%i", PRVM_NUM_FOR_EDICT(PRVM_PROG_TO_EDICT(val->edict)));
+ dpsnprintf (line, linelength, "%i", val->edict);
break;
case ev_function:
f = prog->functions + val->function;
dpsnprintf (line, linelength, "void");
break;
case ev_float:
- dpsnprintf (line, linelength, "%.9g", val->_float);
+ dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float);
break;
case ev_vector:
- dpsnprintf (line, linelength, "%.9g %.9g %.9g", val->vector[0], val->vector[1], val->vector[2]);
+ dpsnprintf (line, linelength, VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]);
break;
default:
dpsnprintf (line, linelength, "bad type %i", type);
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_type = ev_void;
+ prog->watch_field_type = ev_void;
}
/*
PRVM_LoadProgs
===============
*/
-void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global)
+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;
dprograms_t *dprograms;
Host_LockSession(); // all progs can use the session cvar
Crypto_LoadKeys(); // all progs might use the keys at init time
- dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
+ if (data)
+ {
+ dprograms = (dprograms_t *) data;
+ filesize = size;
+ }
+ else
+ dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize);
if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t))
prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name);
// TODO bounds check header fields (e.g. numstatements), they must never go behind end of file
prog->numglobals = prog->progs_numglobals;
prog->entityfields = prog->progs_entityfields;
- if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings >= (int)filesize)
+ if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize)
prog->error_cmd("%s: %s strings go past end of file", prog->name, filename);
prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings);
memcpy(prog->strings, instrings, prog->progs_numstrings);
// we need to expand the globaldefs and fielddefs to include engine defs
prog->globaldefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(ddef_t));
- prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace) * sizeof(prvm_vec_t));
+ prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t));
+ // + 2 is because of an otherwise occurring overrun in RETURN instruction
+ // when trying to return the last or second-last global
+ // (RETURN always returns a vector, there is no RETURN_F instruction)
prog->fielddefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(ddef_t));
// we need to convert the statements to our memory format
prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t));
}
// we're done with the file now
- Mem_Free(dprograms);
+ if(!data)
+ Mem_Free(dprograms);
dprograms = NULL;
// check required functions
prog->loaded = TRUE;
+ PRVM_UpdateBreakpoints(prog);
+
// set flags & ddef_ts in prog
prog->flag = 0;
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));
+}
+
+void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n)
+{
+ size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
+ if (memcmp(o, n, sz))
+ {
+ char buf[1024];
+ char valuebuf_o[128];
+ char valuebuf_n[128];
+ PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o));
+ PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n));
+ dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n);
+ PRVM_Breakpoint(prog, stack_index, buf);
+ memcpy(o, n, sz);
+ }
+}
+
+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_type = ev_void;
+ }
+ else
+ {
+ size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
+ prog->watch_global = global->ofs;
+ prog->watch_global_type = (etype_t)global->type;
+ memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz);
+ }
+ if (prog->watch_global_type != ev_void)
+ Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global);
+ }
+ else
+ prog->watch_global_type = ev_void;
+
+ 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_field_type = ev_void;
+ }
+ else
+ {
+ size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1);
+ prog->watch_edict = debug->watch_edict;
+ prog->watch_field = field->ofs;
+ prog->watch_field_type = (etype_t)field->type;
+ if (prog->watch_edict < prog->num_edicts)
+ memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz);
+ else
+ memset(&prog->watch_edictfield_value, 0, sz);
+ }
+ if (prog->watch_edict != ev_void)
+ 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_field_type = ev_void;
+}
+
+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
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);
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);
*/
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;
}