quakeforge/libs/gamecode/pr_debug.c
Bill Currie 6fea5f5e1a [build] Add -Wformat-non-literal option
While this caused some trouble for pr_strings and configurable strftime
(evil hacks abound), it's the result of discovering an ancient (from
maybe as early as 2004, definitely before 2012) bug in qwaq's printing
that somehow got past months of trial-by-fire testing (origin understood
thanks to the warning finding it).
2021-03-29 17:27:06 +09:00

1715 lines
42 KiB
C

/*
pr_debug.c
progs debugging
Copyright (C) 2001 Bill Currie <bill@tanwiha.org>
Author: Bill Currie <bill@tanwiha.org>
Date: 2001/7/13
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#include <stdlib.h>
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/mathlib.h"
#include "QF/pr_debug.h"
#include "QF/pr_type.h"
#include "QF/progs.h"
#include "QF/qendian.h"
#include "QF/quakefs.h"
#include "QF/script.h"
#include "QF/sys.h"
#include "QF/zone.h"
#include "compat.h"
typedef struct {
char *text;
size_t len;
} line_t;
typedef struct {
char *name;
char *text;
off_t size;
line_t *lines;
pr_uint_t num_lines;
progs_t *pr;
} file_t;
typedef struct compunit_s {
const char *file;
pr_compunit_t *unit;
} compunit_t;
typedef struct prdeb_resources_s {
progs_t *pr;
dstring_t *string;
dstring_t *dva;
dstring_t *line;
dstring_t *dstr;
const char *debugfile;
pr_debug_header_t *debug;
pr_auxfunction_t *auxfunctions;
pr_auxfunction_t **auxfunction_map;
pr_lineno_t *linenos;
pr_def_t *local_defs;
pr_def_t *type_encodings_def;
qfot_type_t void_type;
qfot_type_t *type_encodings[ev_type_count];
pr_def_t *debug_defs;
pr_type_t *debug_data;
hashtab_t *debug_syms;
hashtab_t *compunits; // by source file
PR_RESMAP (compunit_t) compmap; // handy allocation/freeing
hashtab_t *file_hash;
} prdeb_resources_t;
typedef struct {
progs_t *pr;
dstring_t *dstr;
} pr_debug_data_t;
cvar_t *pr_debug;
cvar_t *pr_source_path;
static char *source_path_string;
static char **source_paths;
static void pr_debug_void_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_string_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_float_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_vector_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_entity_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_field_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_func_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_pointer_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_quat_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_integer_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_uinteger_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_short_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_double_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_struct_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_union_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_enum_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_array_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static void pr_debug_class_view (qfot_type_t *type, pr_type_t *value,
void *_data);
static type_view_t raw_type_view = {
pr_debug_void_view,
pr_debug_string_view,
pr_debug_float_view,
pr_debug_vector_view,
pr_debug_entity_view,
pr_debug_field_view,
pr_debug_func_view,
pr_debug_pointer_view,
pr_debug_quat_view,
pr_debug_integer_view,
pr_debug_uinteger_view,
pr_debug_short_view,
pr_debug_double_view,
pr_debug_struct_view,
pr_debug_union_view,
pr_debug_enum_view,
pr_debug_array_view,
pr_debug_class_view,
};
static const char *
file_get_key (const void *_f, void *unused)
{
return ((file_t*)_f)->name;
}
static void
file_free (void *_f, void *unused)
{
file_t *f = (file_t*)_f;
progs_t *pr = f->pr;
free (f->lines);
((progs_t *) pr)->free_progs_mem (pr, f->text);
free (f->name);
free (f);
}
static const char *
def_get_key (const void *d, void *p)
{
__auto_type def = (pr_def_t *) d;
__auto_type pr = (progs_t *) p;
return PR_GetString (pr, def->name);
}
static const char *
compunit_get_key (const void *cu, void *p)
{
__auto_type compunit = (compunit_t *) cu;
return compunit->file;
}
static void
source_path_f (cvar_t *var)
{
int i;
char *s;
if (source_path_string) {
free (source_path_string);
}
source_path_string = strdup (var->string);
if (source_paths) {
free (source_paths);
}
// i starts at 2 because an empty path is equivalent to "." and the
// list is null terminated
for (i = 2, s = source_path_string; *s; s++) {
if (*s == ';') {
i++;
}
}
source_paths = malloc (i * sizeof (char *));
source_paths[0] = source_path_string;
// i starts at one because the first path is in 0 and any additional
// paths come after, then the null terminator
for (i = 1, s = source_path_string; *s; s++) {
if (*s == ';') {
*s = 0;
source_paths[i++] = s + 1;
}
}
source_paths[i] = 0;
}
#define RUP(x,a) (((x) + ((a) - 1)) & ~((a) - 1))
static pr_short_t __attribute__((pure))
pr_debug_type_size (const progs_t *pr, const qfot_type_t *type)
{
pr_short_t size;
qfot_type_t *aux_type;
switch (type->meta) {
case ty_basic:
return pr_type_size[type->type];
case ty_struct:
case ty_union:
size = 0;
for (pr_int_t i = 0; i < type->strct.num_fields; i++) {
const qfot_var_t *field = &type->strct.fields[i];
aux_type = &G_STRUCT (pr, qfot_type_t, field->type);
size = max (size,
field->offset + pr_debug_type_size (pr, aux_type));
}
return size;
case ty_enum:
return pr_type_size[ev_integer];
case ty_array:
aux_type = &G_STRUCT (pr, qfot_type_t, type->array.type);
size = pr_debug_type_size (pr, aux_type);
return type->array.size * size;
case ty_class:
return 1; //FIXME or should it return sizeof class struct?
case ty_alias:
aux_type = &G_STRUCT (pr, qfot_type_t, type->alias.aux_type);
return pr_debug_type_size (pr, aux_type);
}
return 0;
}
static qfot_type_t *
get_def_type (progs_t *pr, pr_def_t *def, qfot_type_t *type)
{
if (!def->type_encoding) {
// no type encoding, so use basic type data to fill in and return
// the dummy encoding
memset (type, 0, sizeof (*type));
type->type = def->type;
} else {
type = &G_STRUCT (pr, qfot_type_t, def->type_encoding);
if (!def->size) {
def->size = pr_debug_type_size (pr, type);
}
}
return type;
}
static pr_def_t
parse_expression (progs_t *pr, const char *expr, int conditional)
{
script_t *es;
char *e;
pr_type_t *expr_ptr;
pr_def_t d;
d.ofs = 0;
d.type = ev_invalid;
d.name = 0;
es = Script_New ();
Script_Start (es, "<console>", expr);
expr_ptr = 0;
es->single = "{}()':[].";
if (Script_GetToken (es, 1)) {
if (strequal (es->token->str, "[")) {
edict_t *ent;
pr_def_t *field;
if (!Script_GetToken (es, 1))
goto error;
ent = EDICT_NUM (pr, strtol (es->token->str, &e, 0));
if (e == es->token->str)
goto error;
if (!Script_GetToken (es, 1) && !strequal (es->token->str, "]" ))
goto error;
if (!Script_GetToken (es, 1) && !strequal (es->token->str, "." ))
goto error;
if (!Script_GetToken (es, 1))
goto error;
field = PR_FindField (pr, es->token->str);
if (!field)
goto error;
d = *field;
expr_ptr = &E_fld (ent, field->ofs);
d.ofs = PR_SetPointer (pr, expr_ptr);
} else if (isdigit ((byte) es->token->str[0])) {
expr_ptr = PR_GetPointer (pr, strtol (es->token->str, 0, 0));
d.type = ev_void;
d.ofs = PR_SetPointer (pr, expr_ptr);
} else {
pr_def_t *global = PR_FindGlobal (pr, es->token->str);
if (!global)
goto error;
d = *global;
}
if (conditional) {
es->single = "{}()':[]";
pr->wp_conditional = 0;
if (Script_TokenAvailable (es, 1)) {
if (!Script_GetToken (es, 1)
&& !strequal (es->token->str, "==" ))
goto error;
if (!Script_GetToken (es, 1))
goto error;
pr->wp_val.integer_var = strtol (es->token->str, &e, 0);
if (e == es->token->str)
goto error;
if (*e == '.' || *e == 'e' || *e == 'E')
pr->wp_val.float_var = strtod (es->token->str, &e);
pr->wp_conditional = 1;
}
}
if (Script_TokenAvailable (es, 1))
Sys_Printf ("ignoring tail\n");
}
error:
if (es->error) {
Sys_Printf ("%s\n", es->error);
}
Script_Delete (es);
return d;
}
static void
pr_debug_clear (progs_t *pr, void *data)
{
__auto_type res = (prdeb_resources_t *) data;
dstring_clearstr (res->string);
dstring_clearstr (res->dva);
dstring_clearstr (res->line);
dstring_clearstr (res->dstr);
if (res->debug)
pr->free_progs_mem (pr, res->debug);
Hash_FlushTable (res->file_hash);
Hash_FlushTable (res->debug_syms);
Hash_FlushTable (res->compunits);
PR_RESRESET (res->compmap);
res->debug = 0;
res->auxfunctions = 0;
if (res->auxfunction_map)
pr->free_progs_mem (pr, res->auxfunction_map);
res->auxfunction_map = 0;
res->linenos = 0;
res->local_defs = 0;
pr->pr_debug_resources = res;
pr->watch = 0;
pr->wp_conditional = 0;
pr->wp_val.integer_var = 0;
for (int i = 0; i < ev_type_count; i++ ) {
res->type_encodings[i] = &res->void_type;
}
}
static file_t *
PR_Load_Source_File (progs_t *pr, const char *fname)
{
prdeb_resources_t *res = pr->pr_debug_resources;
char *l, *p, **dir;
file_t *f = Hash_Find (res->file_hash, fname);
if (f)
return f;
f = calloc (1, sizeof (file_t));
if (!f)
return 0;
for (dir = source_paths; *dir && !f->text; dir++) {
f->text = pr->load_file (pr, dsprintf (res->dva, "%s%s%s", *dir,
**dir ? "/" : "", fname),
&f->size);
}
if (!f->text) {
pr->file_error (pr, fname);
} else {
for (f->num_lines = 1, l = f->text; *l; l++)
if (*l == '\n')
f->num_lines++;
}
f->name = strdup (fname);
if (!f->name) {
pr->free_progs_mem (pr, f->text);
free (f);
return 0;
}
if (f->num_lines) {
f->lines = malloc (f->num_lines * sizeof (line_t));
if (!f->lines) {
free (f->name);
pr->free_progs_mem (pr, f->text);
free (f);
return 0;
}
f->lines[0].text = f->text;
for (f->num_lines = 0, l = f->text; *l; l++) {
if (*l == '\n') {
for (p = l; p > f->lines[f->num_lines].text
&& isspace((byte) p[-1]); p--)
;
f->lines[f->num_lines].len = p - f->lines[f->num_lines].text;
f->lines[++f->num_lines].text = l + 1;
}
}
f->lines[f->num_lines].len = l - f->lines[f->num_lines].text;
f->num_lines++;
}
f->pr = pr;
Hash_Add (res->file_hash, f);
return f;
}
static void
byteswap_def (pr_def_t *def)
{
def->type = LittleShort (def->type);
def->size = LittleShort (def->size);
def->ofs = LittleLong (def->ofs);
def->name = LittleLong (def->name);
def->type_encoding = LittleLong (def->type_encoding);
}
static compunit_t *
new_compunit (prdeb_resources_t *res)
{
return PR_RESNEW (res->compmap);
}
static void
process_compunit (prdeb_resources_t *res, pr_def_t *def)
{
progs_t *pr = res->pr;
__auto_type compunit = (pr_compunit_t *) (res->debug_data + def->ofs);
for (unsigned i = 0; i < compunit->num_files; i++) {
compunit_t *cu = new_compunit (res);
cu->unit = compunit;
cu->file = PR_GetString (pr, compunit->files[i]);
Hash_Add (res->compunits, cu);
}
}
VISIBLE int
PR_LoadDebug (progs_t *pr)
{
prdeb_resources_t *res = PR_Resources_Find (pr, "PR_Debug");
char *sym_path;
const char *path_end, *sym_file;
off_t debug_size;
pr_uint_t i;
pr_def_t *def;
pr_type_t *str = 0;
qfot_type_encodings_t *encodings = 0;
pointer_t type_encodings = 0;
pointer_t type_ptr;
qfot_type_t *type;
string_t compunit_str;
pr->pr_debug_resources = res;
if (!pr_debug->int_val)
return 1;
def = PR_FindGlobal (pr, ".debug_file");
if (def)
str = &pr->pr_globals[def->ofs];
if (!str)
return 1;
res->debugfile = PR_GetString (pr, str->string_var);
sym_file = QFS_SkipPath (res->debugfile);
path_end = QFS_SkipPath (pr->progs_name);
sym_path = malloc (strlen (sym_file) + (path_end - pr->progs_name) + 1);
strncpy (sym_path, pr->progs_name, path_end - pr->progs_name);
strcpy (sym_path + (path_end - pr->progs_name), sym_file);
res->debug = pr->load_file (pr, sym_path, &debug_size);
if (!res->debug) {
Sys_Printf ("can't load %s for debug info\n", sym_path);
free (sym_path);
return 1;
}
res->debug->version = LittleLong (res->debug->version);
if (res->debug->version != PROG_DEBUG_VERSION) {
Sys_Printf ("ignoring %s with unsupported version %x.%03x.%03x\n",
sym_path,
(res->debug->version >> 24) & 0xff,
(res->debug->version >> 12) & 0xfff,
res->debug->version & 0xfff);
res->debug = 0;
free (sym_path);
return 1;
}
res->debug->crc = LittleShort (res->debug->crc);
if (res->debug->crc != pr->crc) {
Sys_Printf ("ignoring %s that doesn't match %s. (CRCs: "
"sym:%d dat:%d)\n",
sym_path,
pr->progs_name,
res->debug->crc,
pr->crc);
res->debug = 0;
free (sym_path);
return 1;
}
free (sym_path);
res->debug->you_tell_me_and_we_will_both_know = LittleShort
(res->debug->you_tell_me_and_we_will_both_know);
res->debug->auxfunctions = LittleLong (res->debug->auxfunctions);
res->debug->num_auxfunctions = LittleLong (res->debug->num_auxfunctions);
res->debug->linenos = LittleLong (res->debug->linenos);
res->debug->num_linenos = LittleLong (res->debug->num_linenos);
res->debug->locals = LittleLong (res->debug->locals);
res->debug->num_locals = LittleLong (res->debug->num_locals);
res->debug->debug_defs = LittleLong (res->debug->debug_defs);
res->debug->num_debug_defs = LittleLong (res->debug->num_debug_defs);
res->debug->debug_data = LittleLong (res->debug->debug_data);
res->debug->debug_data_size = LittleLong (res->debug->debug_data_size);
res->auxfunctions = (pr_auxfunction_t*)((char*)res->debug +
res->debug->auxfunctions);
res->linenos = (pr_lineno_t*)((char*)res->debug + res->debug->linenos);
res->local_defs = (pr_def_t*)((char*)res->debug + res->debug->locals);
res->debug_defs = (pr_def_t*)((char*)res->debug + res->debug->debug_defs);
res->debug_data = (pr_type_t*)((char*)res->debug + res->debug->debug_data);
i = pr->progs->numfunctions * sizeof (pr_auxfunction_t *);
res->auxfunction_map = pr->allocate_progs_mem (pr, i);
for (i = 0; i < pr->progs->numfunctions; i++)
res->auxfunction_map[i] = 0;
res->type_encodings_def = PR_FindGlobal (pr, ".type_encodings");
if (res->type_encodings_def) {
encodings = &G_STRUCT (pr, qfot_type_encodings_t,
res->type_encodings_def->ofs);
type_encodings = encodings->types;
}
for (i = 0; i < res->debug->num_auxfunctions; i++) {
res->auxfunctions[i].function = LittleLong
(res->auxfunctions[i].function);
res->auxfunctions[i].source_line = LittleLong
(res->auxfunctions[i].source_line);
res->auxfunctions[i].line_info = LittleLong
(res->auxfunctions[i].line_info);
res->auxfunctions[i].local_defs = LittleLong
(res->auxfunctions[i].local_defs);
res->auxfunctions[i].num_locals = LittleLong
(res->auxfunctions[i].num_locals);
if (type_encodings) {
res->auxfunctions[i].return_type += type_encodings;
}
res->auxfunction_map[res->auxfunctions[i].function] =
&res->auxfunctions[i];
}
for (i = 0; i < res->debug->num_linenos; i++) {
res->linenos[i].fa.func = LittleLong (res->linenos[i].fa.func);
res->linenos[i].line = LittleLong (res->linenos[i].line);
}
for (i = 0; i < res->debug->num_locals; i++) {
byteswap_def (&res->local_defs[i]);
if (type_encodings) {
res->local_defs[i].type_encoding += type_encodings;
}
}
compunit_str = PR_FindString (pr, ".compile_unit");
for (i = 0; i < res->debug->num_debug_defs; i++) {
pr_def_t *def = &res->debug_defs[i];
byteswap_def (def);
if (type_encodings) {
def->type_encoding += type_encodings;
}
Hash_Add (res->debug_syms, def);
if (def->name == compunit_str) {
process_compunit (res, def);
}
}
if (encodings) {
for (type_ptr = 4; type_ptr < encodings->size;
type_ptr += type->size) {
type = &G_STRUCT (pr, qfot_type_t, type_encodings + type_ptr);
if (type->meta == ty_basic
&& type->type >= 0 && type->type < ev_type_count) {
res->type_encodings[type->type] = type;
}
}
}
return 1;
}
VISIBLE const char *
PR_Debug_GetBaseDirectory (progs_t *pr, const char *file)
{
prdeb_resources_t *res = pr->pr_debug_resources;
__auto_type cu = (compunit_t *) Hash_Find (res->compunits, file);
if (cu) {
return PR_GetString (pr, cu->unit->basedir);
}
return 0;
}
VISIBLE pr_auxfunction_t *
PR_Debug_AuxFunction (progs_t *pr, pr_uint_t func)
{
prdeb_resources_t *res = pr->pr_debug_resources;
if (!res->debug || func >= res->debug->num_auxfunctions) {
return 0;
}
return &res->auxfunctions[func];
}
VISIBLE pr_auxfunction_t *
PR_Debug_MappedAuxFunction (progs_t *pr, pr_uint_t func)
{
prdeb_resources_t *res = pr->pr_debug_resources;
if (!res->debug || func >= pr->progs->numfunctions) {
return 0;
}
return res->auxfunction_map[func];
}
VISIBLE pr_def_t *
PR_Debug_LocalDefs (progs_t *pr, pr_auxfunction_t *aux_function)
{
prdeb_resources_t *res = pr->pr_debug_resources;
if (!res->debug || !aux_function) {
return 0;
}
if (aux_function->local_defs > res->debug->num_locals) {
return 0;
}
return res->local_defs + aux_function->local_defs;
}
VISIBLE pr_lineno_t *
PR_Debug_Linenos (progs_t *pr, pr_auxfunction_t *aux_function,
pr_uint_t *num_linenos)
{
pr_uint_t i, count;
prdeb_resources_t *res = pr->pr_debug_resources;
if (!res->debug) {
return 0;
}
if (!aux_function) {
*num_linenos = res->debug->num_linenos;
return res->linenos;
}
if (aux_function->line_info > res->debug->num_linenos) {
return 0;
}
//FIXME put lineno count in sym file
for (count = 1, i = aux_function->line_info + 1;
i < res->debug->num_linenos; i++, count++) {
if (!res->linenos[i].line) {
break;
}
}
*num_linenos = count;
return res->linenos + aux_function->line_info;
}
pr_auxfunction_t *
PR_Get_Lineno_Func (progs_t *pr, pr_lineno_t *lineno)
{
prdeb_resources_t *res = pr->pr_debug_resources;
while (lineno > res->linenos && lineno->line)
lineno--;
if (lineno->line)
return 0;
return &res->auxfunctions[lineno->fa.func];
}
pr_uint_t
PR_Get_Lineno_Addr (progs_t *pr, pr_lineno_t *lineno)
{
prdeb_resources_t *res = pr->pr_debug_resources;
pr_auxfunction_t *f;
if (lineno->line)
return lineno->fa.addr;
if (lineno->fa.func < res->debug->num_auxfunctions) {
f = &res->auxfunctions[lineno->fa.func];
return pr->pr_functions[f->function].first_statement;
}
// take a wild guess that only the line number is bogus and return
// the address anyway
return lineno->fa.addr;
}
pr_uint_t
PR_Get_Lineno_Line (progs_t *pr, pr_lineno_t *lineno)
{
if (lineno->line)
return lineno->line;
return 0;
}
pr_lineno_t *
PR_Find_Lineno (progs_t *pr, pr_uint_t addr)
{
prdeb_resources_t *res = pr->pr_debug_resources;
pr_uint_t i;
pr_lineno_t *lineno = 0;
if (!res->debug)
return 0;
if (!res->debug->num_linenos)
return 0;
for (i = res->debug->num_linenos; i > 0; i--) {
if (PR_Get_Lineno_Addr (pr, &res->linenos[i - 1]) <= addr) {
lineno = &res->linenos[i - 1];
break;
}
}
return lineno;
}
const char *
PR_Get_Source_File (progs_t *pr, pr_lineno_t *lineno)
{
pr_auxfunction_t *f;
f = PR_Get_Lineno_Func (pr, lineno);
if (f->function >= (unsigned) pr->progs->numfunctions)
return 0;
return PR_GetString(pr, pr->pr_functions[f->function].s_file);
}
const char *
PR_Get_Source_Line (progs_t *pr, pr_uint_t addr)
{
prdeb_resources_t *res = pr->pr_debug_resources;
const char *fname;
pr_uint_t line;
file_t *file;
pr_auxfunction_t *func;
pr_lineno_t *lineno;
lineno = PR_Find_Lineno (pr, addr);
if (!lineno || PR_Get_Lineno_Addr (pr, lineno) != addr)
return 0;
func = PR_Get_Lineno_Func (pr, lineno);
fname = PR_Get_Source_File (pr, lineno);
if (!func || !fname)
return 0;
line = PR_Get_Lineno_Line (pr, lineno);
line += func->source_line;
file = PR_Load_Source_File (pr, fname);
if (!file || !file->lines || !line || line > file->num_lines)
return dsprintf (res->dva, "%s:%u", fname, line);
return dsprintf (res->dva, "%s:%u:%.*s", fname, line,
(int)file->lines[line - 1].len,
file->lines[line - 1].text);
}
pr_def_t *
PR_Get_Param_Def (progs_t *pr, dfunction_t *func, unsigned parm)
{
prdeb_resources_t *res = pr->pr_debug_resources;
pr_uint_t i;
pr_auxfunction_t *aux_func;
pr_def_t *ddef = 0;
int num_params;
int param_offs = 0;
if (!res->debug)
return 0;
if (!func)
return 0;
num_params = func->numparms;
if (num_params < 0) {
num_params = ~num_params; // one's compliment
param_offs = 1; // skip over @args def
}
if (parm >= (unsigned) num_params)
return 0;
aux_func = res->auxfunction_map[func - pr->pr_functions];
if (!aux_func)
return 0;
for (i = 0; i < aux_func->num_locals; i++) {
ddef = &res->local_defs[aux_func->local_defs + param_offs + i];
if (!parm--)
break;
}
return ddef;
}
static pr_auxfunction_t *
get_aux_function (progs_t *pr)
{
prdeb_resources_t *res = pr->pr_debug_resources;
dfunction_t *func;
if (!pr->pr_xfunction || !res->auxfunction_map)
return 0;
func = pr->pr_xfunction->descriptor;
return res->auxfunction_map[func - pr->pr_functions];
}
static qfot_type_t *
get_type (prdeb_resources_t *res, int typeptr)
{
progs_t *pr = res->pr;
if (!typeptr) {
return &res->void_type;
}
return &G_STRUCT (pr, qfot_type_t, typeptr);
}
pr_def_t *
PR_Get_Local_Def (progs_t *pr, pointer_t *offset)
{
prdeb_resources_t *res = pr->pr_debug_resources;
dfunction_t *func;
pr_auxfunction_t *aux_func;
pointer_t offs = *offset;
pr_def_t *def;
if (!pr->pr_xfunction)
return 0;
func = pr->pr_xfunction->descriptor;
if (!func)
return 0;
aux_func = res->auxfunction_map[func - pr->pr_functions];
if (!aux_func)
return 0;
offs -= func->parm_start;
if (offs >= func->locals)
return 0;
if ((def = PR_SearchDefs (res->local_defs + aux_func->local_defs,
aux_func->num_locals, offs))) {
*offset = offs - def->ofs;
}
return def;
}
VISIBLE void
PR_DumpState (progs_t *pr)
{
prdeb_resources_t *res = pr->pr_debug_resources;
if (pr->pr_xfunction) {
if (pr_debug->int_val && res->debug) {
pr_lineno_t *lineno;
pr_auxfunction_t *func = 0;
dfunction_t *descriptor = pr->pr_xfunction->descriptor;
pr_int_t addr = pr->pr_xstatement;
lineno = PR_Find_Lineno (pr, addr);
if (lineno)
func = PR_Get_Lineno_Func (pr, lineno);
if (func && descriptor == pr->pr_functions + func->function)
addr = PR_Get_Lineno_Addr (pr, lineno);
else
addr = max (descriptor->first_statement, addr - 5);
while (addr != pr->pr_xstatement)
PR_PrintStatement (pr, pr->pr_statements + addr++, 3);
}
PR_PrintStatement (pr, pr->pr_statements + pr->pr_xstatement, 3);
}
PR_StackTrace (pr);
}
#define ISDENORM(x) ((x) && !((x) & 0x7f800000))
static const char *
value_string (pr_debug_data_t *data, qfot_type_t *type, pr_type_t *value)
{
switch (type->meta) {
case ty_basic:
switch (type->type) {
case ev_void:
raw_type_view.void_view (type, value, data);
break;
case ev_string:
raw_type_view.string_view (type, value, data);
break;
case ev_float:
raw_type_view.float_view (type, value, data);
break;
case ev_vector:
raw_type_view.vector_view (type, value, data);
break;
case ev_entity:
raw_type_view.entity_view (type, value, data);
break;
case ev_field:
raw_type_view.field_view (type, value, data);
break;
case ev_func:
raw_type_view.func_view (type, value, data);
break;
case ev_pointer:
raw_type_view.pointer_view (type, value, data);
break;
case ev_quat:
raw_type_view.quat_view (type, value, data);
break;
case ev_integer:
raw_type_view.integer_view (type, value, data);
break;
case ev_uinteger:
raw_type_view.uinteger_view (type, value, data);
break;
case ev_short:
raw_type_view.short_view (type, value, data);
break;
case ev_double:
raw_type_view.double_view (type, value, data);
break;
case ev_invalid:
case ev_type_count:
dstring_appendstr (data->dstr, "<?""?>");
}
break;
case ty_struct:
raw_type_view.struct_view (type, value, data);
break;
case ty_union:
raw_type_view.union_view (type, value, data);
break;
case ty_enum:
raw_type_view.enum_view (type, value, data);
break;
case ty_array:
raw_type_view.array_view (type, value, data);
break;
case ty_class:
raw_type_view.class_view (type, value, data);
break;
case ty_alias://XXX
type = &G_STRUCT (data->pr, qfot_type_t, type->alias.aux_type);
return value_string (data, type, value);
}
return data->dstr->str;
}
static pr_def_t *
pr_debug_find_def (progs_t *pr, pointer_t *ofs)
{
prdeb_resources_t *res = pr->pr_debug_resources;
pr_def_t *def = 0;
if (*ofs >= pr->progs->numglobals) {
return 0;
}
if (pr_debug->int_val && res->debug) {
def = PR_Get_Local_Def (pr, ofs);
}
if (!def) {
def = PR_GlobalAtOfs (pr, *ofs);
if (def) {
*ofs -= def->ofs;
}
}
return def;
}
static const char *
global_string (pr_debug_data_t *data, pointer_t offset, qfot_type_t *type,
int contents)
{
progs_t *pr = data->pr;
prdeb_resources_t *res = pr->pr_debug_resources;
dstring_t *dstr = data->dstr;
pr_def_t *def = NULL;
qfot_type_t dummy_type = { };
const char *name = 0;
pointer_t offs = offset;
dstring_clearstr (dstr);
if (type && type->meta == ty_basic && type->type == ev_short) {
dsprintf (dstr, "%04x", (short) offset);
return dstr->str;
}
if (offset > pr->globals_size) {
dsprintf (dstr, "%08x out of bounds", offset);
return dstr->str;
}
def = pr_debug_find_def (pr, &offs);
if (!def || !PR_StringValid (pr, def->name)
|| !*(name = PR_GetString (pr, def->name))) {
dsprintf (dstr, "[$%x]", offset);
}
if (name) {
if (strequal (name, "IMMEDIATE") || strequal (name, ".imm")) {
contents = 1;
} else {
if (offs) {
dsprintf (dstr, "{%s + %u}", name, offs);
} else {
dsprintf (dstr, "%s", name);
}
}
}
if (contents) {
if (name) {
dstring_appendstr (dstr, "(");
}
if (!type) {
if (def) {
if (!def->type_encoding) {
dummy_type.type = def->type;
type = &dummy_type;
} else {
type = &G_STRUCT (pr, qfot_type_t, def->type_encoding);
}
} else {
type = &res->void_type;
}
}
value_string (data, type, pr->pr_globals + offset);
if (name) {
dstring_appendstr (dstr, ")");
}
}
return dstr->str;
}
static void
pr_debug_void_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dasprintf (data->dstr, "<void>");
}
static void
pr_debug_string_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
string_t string = value->string_var;
if (PR_StringValid (data->pr, string)) {
const char *str = PR_GetString (data->pr, string);
dstring_appendstr (dstr, "\"");
while (*str) {
const char *s;
for (s = str; *s && !strchr ("\"\n\t", *s); s++) {
}
if (s != str) {
dstring_appendsubstr (dstr, str, s - str);
}
if (*s) {
switch (*s) {
case '\"':
dstring_appendstr (dstr, "\\\"");
break;
case '\n':
dstring_appendstr (dstr, "\\n");
break;
case '\t':
dstring_appendstr (dstr, "\\t");
break;
default:
dasprintf (dstr, "\\x%02x", *s & 0xff);
}
s++;
}
str = s;
}
dstring_appendstr (dstr, "\"");
} else {
dstring_appendstr (dstr, "*** invalid string offset ***");
}
}
static void
pr_debug_float_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
if (data->pr->progs->version == PROG_ID_VERSION
&& ISDENORM (value->integer_var)
&& value->uinteger_var != 0x80000000) {
dasprintf (dstr, "<%08x>", value->integer_var);
} else {
dasprintf (dstr, "%.9g", value->float_var);
}
}
static void
pr_debug_vector_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dasprintf (dstr, "'%.9g %.9g %.9g'", VectorExpand (&value->vector_var));
}
static void
pr_debug_entity_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
progs_t *pr = data->pr;
dstring_t *dstr = data->dstr;
if (pr->pr_edicts) {
edict_t *edict = PROG_TO_EDICT (pr, value->entity_var);
dasprintf (dstr, "entity %d", NUM_FOR_BAD_EDICT (pr, edict));
} else {
dasprintf (dstr, "entity [%x]",value->entity_var);
}
}
static void
pr_debug_field_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
progs_t *pr = data->pr;
dstring_t *dstr = data->dstr;
pr_def_t *def = PR_FieldAtOfs (pr, value->integer_var);
if (def) {
dasprintf (dstr, ".%s", PR_GetString (pr, def->name));
} else {
dasprintf (dstr, ".<$%04x>", value->integer_var);
}
}
static void
pr_debug_func_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
progs_t *pr = data->pr;
dstring_t *dstr = data->dstr;
if (value->func_var >= pr->progs->numfunctions) {
dasprintf (dstr, "INVALID:%d", value->func_var);
} else if (!value->func_var) {
dstring_appendstr (dstr, "NULL");
} else {
dfunction_t *f = pr->pr_functions + value->func_var;
dasprintf (dstr, "%s()", PR_GetString (pr, f->s_name));
}
}
static void
pr_debug_pointer_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
progs_t *pr = data->pr;
dstring_t *dstr = data->dstr;
pointer_t offset = value->integer_var;
pointer_t offs = offset;
pr_def_t *def = 0;
def = pr_debug_find_def (pr, &offs);
if (def && def->name) {
if (offs) {
dasprintf (dstr, "&%s + %u", PR_GetString (pr, def->name), offs);
} else {
dasprintf (dstr, "&%s", PR_GetString (pr, def->name));
}
} else {
dasprintf (dstr, "[$%x]", offset);
}
}
static void
pr_debug_quat_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dasprintf (dstr, "'%.9g %.9g %.9g %.9g'", QuatExpand (&value->quat_var));
}
static void
pr_debug_integer_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dasprintf (dstr, "%d", value->integer_var);
}
static void
pr_debug_uinteger_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dasprintf (dstr, "$%08x", value->uinteger_var);
}
static void
pr_debug_short_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dasprintf (dstr, "%04x", (short)value->integer_var);
}
static void
pr_debug_double_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dasprintf (dstr, "%.17g", *(double *)value);
}
static void
pr_dump_struct (qfot_type_t *type, pr_type_t *value, void *_data,
const char *struct_type)
{
__auto_type data = (pr_debug_data_t *) _data;
progs_t *pr = data->pr;
dstring_t *dstr = data->dstr;
qfot_struct_t *strct = &type->strct;
dstring_appendstr (dstr, "{");
for (int i = 0; i < strct->num_fields; i++) {
qfot_var_t *field = strct->fields + i;
qfot_type_t *val_type = &G_STRUCT (pr, qfot_type_t, field->type);
pr_type_t *val = value + field->offset;
dasprintf (dstr, "%s=", PR_GetString (pr, field->name));
value_string (data, val_type, val);
if (i < strct->num_fields - 1) {
dstring_appendstr (dstr, ", ");
}
}
dstring_appendstr (dstr, "}");
//dasprintf (dstr, "<%s>", struct_type);
}
static void
pr_debug_struct_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
pr_dump_struct (type, value, _data, "struct");
}
static void
pr_debug_union_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
pr_dump_struct (type, value, _data, "union");
}
static void
pr_debug_enum_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dstring_appendstr (dstr, "<enum>");
}
static void
pr_debug_array_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dstring_appendstr (dstr, "<array>");
}
static void
pr_debug_class_view (qfot_type_t *type, pr_type_t *value, void *_data)
{
__auto_type data = (pr_debug_data_t *) _data;
dstring_t *dstr = data->dstr;
dstring_appendstr (dstr, "<class>");
}
VISIBLE void
PR_Debug_Watch (progs_t *pr, const char *expr)
{
pr_def_t watch;
if (!expr) {
Sys_Printf ("watch <watchpoint expr>\n");
if (pr->watch) {
Sys_Printf (" watching [%d]\n",
(int) (intptr_t) (pr->watch - pr->pr_globals));
if (pr->wp_conditional)
Sys_Printf (" if new val == %d\n",
pr->wp_val.integer_var);
} else { Sys_Printf (" none active\n");
}
return;
}
pr->watch = 0;
watch = parse_expression (pr, expr, 1);
if (watch.type != ev_invalid)
pr->watch = &pr->pr_globals[watch.ofs];
if (pr->watch) {
Sys_Printf ("watchpoint set to [%d]\n", PR_SetPointer (pr, pr->watch));
if (pr->wp_conditional)
Sys_Printf (" if new val == %d\n", pr->wp_val.integer_var);
} else {
Sys_Printf ("watchpoint cleared\n");
}
}
VISIBLE void
PR_Debug_Print (progs_t *pr, const char *expr)
{
prdeb_resources_t *res = pr->pr_debug_resources;
pr_def_t print;
pr_debug_data_t data = {pr, res->dstr};
if (!expr) {
Sys_Printf ("print <print expr>\n");
return;
}
print = parse_expression (pr, expr, 0);
if (print.type_encoding) {
qfot_type_t *type = get_type (res, print.type_encoding);
const char *s = global_string (&data, print.ofs, type, 1);
Sys_Printf ("[%d] = %s\n", print.ofs, s);
}
}
VISIBLE void
PR_PrintStatement (progs_t *pr, dstatement_t *s, int contents)
{
prdeb_resources_t *res = pr->pr_debug_resources;
int addr = s - pr->pr_statements;
int dump_code = contents & 2;
const char *fmt;
opcode_t *op;
dfunction_t *call_func = 0;
pr_def_t *parm_def = 0;
pr_auxfunction_t *aux_func = 0;
pr_debug_data_t data;
dstring_clearstr (res->line);
data.pr = pr;
data.dstr = res->dstr;
if (pr_debug->int_val > 1)
dump_code = 1;
if (pr_debug->int_val && res->debug) {
const char *source_line = PR_Get_Source_Line (pr, addr);
if (source_line) {
dasprintf (res->line, "%s%s", source_line, dump_code ? "\n" : "");
if (!dump_code)
goto do_print;
}
if (!dump_code)
return;
}
op = PR_Opcode (s->op);
if (!op) {
Sys_Printf ("%sUnknown instruction %d\n", res->line->str, s->op);
return;
}
if (!(fmt = op->fmt))
fmt = "%Ga, %Gb, %gc";
dasprintf (res->line, "%04x ", addr);
if (pr_debug->int_val > 2)
dasprintf (res->line, "%02x %04x(%8s) %04x(%8s) %04x(%8s)\t",
s->op,
s->a, pr_type_name[op->type_a],
s->b, pr_type_name[op->type_b],
s->c, pr_type_name[op->type_c]);
dasprintf (res->line, "%s ", op->opname);
while (*fmt) {
if (*fmt == '%') {
if (fmt[1] == '%') {
dstring_appendsubstr (res->line, fmt + 1, 1);
fmt += 2;
} else {
const char *str;
char mode = fmt[1], opchar = fmt[2];
unsigned parm_ind = 0;
pr_int_t opval;
qfot_type_t *optype = &res->void_type;
func_t func;
if (mode == 'P') {
opchar = fmt[3];
parm_ind = fmt[2] - '0';
fmt++; // P has one extra item
if (parm_ind >= MAX_PARMS)
goto err;
}
switch (opchar) {
case 'a':
opval = s->a;
optype = res->type_encodings[op->type_a];
break;
case 'b':
opval = s->b;
optype = res->type_encodings[op->type_b];
break;
case 'c':
opval = s->c;
optype = res->type_encodings[op->type_c];
break;
case 'x':
if (mode == 'P') {
opval = pr->pr_real_params[parm_ind]
- pr->pr_globals;
break;
}
default:
goto err;
}
switch (mode) {
case 'R':
optype = &res->void_type;
aux_func = get_aux_function (pr);
if (aux_func) {
optype = get_type (res, aux_func->return_type);
}
str = global_string (&data, opval, optype,
contents & 1);
break;
case 'F':
str = global_string (&data, opval, optype,
contents & 1);
func = G_FUNCTION (pr, opval);
if (func < pr->progs->numfunctions) {
call_func = pr->pr_functions + func;
}
break;
case 'P':
parm_def = PR_Get_Param_Def (pr, call_func, parm_ind);
optype = &res->void_type;
if (parm_def) {
optype = get_type (res, parm_def->type_encoding);
}
str = global_string (&data, opval, optype,
contents & 1);
break;
case 'V':
str = global_string (&data, opval, ev_void,
contents & 1);
break;
case 'G':
str = global_string (&data, opval, optype,
contents & 1);
break;
case 'g':
str = global_string (&data, opval, optype, 0);
break;
case 's':
str = dsprintf (res->dva, "%d", (short) opval);
break;
case 'O':
str = dsprintf (res->dva, "%04x",
addr + (short) opval);
break;
case 'E':
{
edict_t *ed = 0;
opval = pr->pr_globals[s->a].entity_var;
parm_ind = pr->pr_globals[s->b].uinteger_var;
if (parm_ind < pr->progs->entityfields
&& opval >= 0
&& opval < pr->pr_edict_area_size) {
ed = PROG_TO_EDICT (pr, opval);
opval = &E_fld(ed, parm_ind) - pr->pr_globals;
}
if (!ed) {
str = "bad entity.field";
break;
}
str = global_string (&data, opval, optype,
contents & 1);
str = dsprintf (res->dva, "$%x $%x %s",
s->a, s->b, str);
}
break;
default:
goto err;
}
dstring_appendstr (res->line, str);
fmt += 3;
continue;
err:
dstring_appendstr (res->line, fmt);
break;
}
} else {
dstring_appendsubstr (res->line, fmt++, 1);
}
}
do_print:
Sys_Printf ("%s\n", res->line->str);
}
static void
dump_frame (progs_t *pr, prstack_t *frame)
{
prdeb_resources_t *res = pr->pr_debug_resources;
dfunction_t *f = frame->func ? frame->func->descriptor : 0;
if (!f) {
Sys_Printf ("<NO FUNCTION>\n");
return;
}
if (pr_debug->int_val && res->debug) {
pr_lineno_t *lineno = PR_Find_Lineno (pr, frame->staddr);
pr_auxfunction_t *func = PR_Get_Lineno_Func (pr, lineno);
pr_uint_t line = PR_Get_Lineno_Line (pr, lineno);
pr_int_t addr = PR_Get_Lineno_Addr (pr, lineno);
line += func->source_line;
if (addr == frame->staddr) {
Sys_Printf ("%12s:%u : %s: %x\n",
PR_GetString (pr, f->s_file),
line,
PR_GetString (pr, f->s_name),
frame->staddr);
} else {
Sys_Printf ("%12s:%u+%d : %s: %x\n",
PR_GetString (pr, f->s_file),
line, frame->staddr - addr,
PR_GetString (pr, f->s_name),
frame->staddr);
}
} else {
Sys_Printf ("%12s : %s: %x\n", PR_GetString (pr, f->s_file),
PR_GetString (pr, f->s_name), frame->staddr);
}
}
VISIBLE void
PR_StackTrace (progs_t *pr)
{
int i;
prstack_t top;
if (pr->pr_depth == 0) {
Sys_Printf ("<NO STACK>\n");
return;
}
top.staddr = pr->pr_xstatement;
top.func = pr->pr_xfunction;
dump_frame (pr, &top);
for (i = pr->pr_depth - 1; i >= 0; i--)
dump_frame (pr, pr->pr_stack + i);
}
VISIBLE void
PR_Profile (progs_t * pr)
{
pr_uint_t max, num, i;
dfunction_t *best, *f;
num = 0;
do {
max = 0;
best = NULL;
for (i = 0; i < pr->progs->numfunctions; i++) {
f = &pr->pr_functions[i];
if (f->profile > max) {
max = f->profile;
best = f;
}
}
if (best) {
if (num < 10)
Sys_Printf ("%7i %s\n", best->profile,
PR_GetString (pr, best->s_name));
num++;
best->profile = 0;
}
} while (best);
}
/*
ED_Print
For debugging
*/
VISIBLE void
ED_Print (progs_t *pr, edict_t *ed)
{
prdeb_resources_t *res = pr->pr_debug_resources;
int l;
pr_uint_t i, j;
const char *name;
pr_def_t *d;
pr_type_t *v;
qfot_type_t dummy_type = { };
qfot_type_t *type;
pr_debug_data_t data = {pr, res->dstr};
if (ed->free) {
Sys_Printf ("FREE\n");
return;
}
Sys_Printf ("\nEDICT %d:\n", NUM_FOR_BAD_EDICT (pr, ed));
for (i = 0; i < pr->progs->numfielddefs; i++) {
d = &pr->pr_fielddefs[i];
if (!d->name) // null field def (probably 1st)
continue;
type = get_def_type (pr, d, &dummy_type);
name = PR_GetString (pr, d->name);
if (name[strlen (name) - 2] == '_'
&& strchr ("xyz", name[strlen (name) -1]))
continue; // skip _x, _y, _z vars
for (j = 0; j < d->size; j++) {
if (E_INT (ed, d->ofs + j)) {
break;
}
}
if (j == d->size) {
continue;
}
v = &E_fld (ed, d->ofs);
l = 15 - strlen (name);
if (l < 1)
l = 1;
dstring_clearstr (res->dstr);
value_string (&data, type, v);
Sys_Printf ("%s%*s%s\n", name, l, "", res->dstr->str);
}
}
void
PR_Debug_Init (progs_t *pr)
{
prdeb_resources_t *res = calloc (1, sizeof (*res));
res->pr = pr;
res->string = dstring_newstr ();
res->dva = dstring_newstr ();
res->line = dstring_newstr ();
res->dstr = dstring_newstr ();
res->void_type.meta = ty_basic;
res->void_type.size = 4;
res->void_type.encoding = 0;
res->void_type.type = ev_void;
for (int i = 0; i < ev_type_count; i++ ) {
res->type_encodings[i] = &res->void_type;
}
res->file_hash = Hash_NewTable (509, file_get_key, file_free, 0,
pr->hashlink_freelist);
res->debug_syms = Hash_NewTable (509, def_get_key, 0, pr,
pr->hashlink_freelist);
res->compunits = Hash_NewTable (509, compunit_get_key, 0, pr,
pr->hashlink_freelist);
PR_Resources_Register (pr, "PR_Debug", res, pr_debug_clear);
}
void
PR_Debug_Init_Cvars (void)
{
pr_debug = Cvar_Get ("pr_debug", "0", CVAR_NONE, NULL,
"enable progs debugging");
pr_source_path = Cvar_Get ("pr_source_path", ".", CVAR_NONE, source_path_f,
"where to look (within gamedir) for source "
"files");
}