quakeforge/libs/gamecode/pr_debug.c
Bill Currie 973ae0ad54 [gamecode] Add PR_Shutdown for tearing down a VM
This is meant for a "permanent" tear-down before freeing the memory
holding the VM state or at program shutdown. As a consequence, builtin
sub-systems registering resources are now required to pass a "destroy"
function pointer that will be called just before the memory holding
those resources is freed by the VM resource manager (ie, the manager
owns the resource memory block, but each subsystem is responsible for
cleaning up any resources held within that block).

This even enhances thread-safety in rua_obj (there are some problems
with cmd, cvar, and gib).
2022-05-12 19:58:18 +09:00

1983 lines
49 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 <inttypes.h>
#include "QF/fbsearch.h"
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/heapsort.h"
#include "QF/mathlib.h"
#include "QF/progs.h"
#include "QF/qendian.h"
#include "QF/quakefs.h"
#include "QF/script.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "QF/zone.h"
#include "QF/progs/pr_debug.h"
#include "QF/progs/pr_type.h"
#include "QF/simd/types.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 {
const char *file;
pr_uint_t line;
} func_key_t;
typedef struct prdeb_resources_s {
progs_t *pr;
dstring_t *string;
dstring_t *dva;
dstring_t *line;
dstring_t *dstr;
va_ctx_t *va;
const char *debugfile;
pr_debug_header_t *debug;
pr_auxfunction_t *auxfunctions;
pr_auxfunction_t **auxfunction_map;
pr_func_t *sorted_functions;
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;
int pr_debug;
static cvar_t pr_debug_cvar = {
.name = "pr_debug",
.description =
"enable progs debugging",
.default_value = "0",
.flags = CVAR_NONE,
.value = { .type = &cexpr_int, .value = &pr_debug },
};
char *pr_source_path;
static cvar_t pr_source_path_cvar = {
.name = "pr_source_path",
.description =
"where to look (within gamedir) for source files",
.default_value = ".",
.flags = CVAR_NONE,
.value = { .type = 0, .value = &pr_source_path },
};
static char *source_path_string;
static char **source_paths;
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);
#define EV_TYPE(t) \
static void pr_debug_##t##_view (qfot_type_t *type, pr_type_t *value, \
void *_data);
#include "QF/progs/pr_type_names.h"
static type_view_t raw_type_view = {
pr_debug_struct_view,
pr_debug_union_view,
pr_debug_enum_view,
pr_debug_array_view,
pr_debug_class_view,
#define EV_TYPE(t) \
pr_debug_##t##_view,
#include "QF/progs/pr_type_names.h"
};
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 (void *data, const cvar_t *cvar)
{
int i;
char *s;
if (source_path_string) {
free (source_path_string);
}
source_path_string = strdup (pr_source_path);
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_int];
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_PTR (int, &pr->wp_val) = strtol (es->token->str, &e, 0);
if (e == es->token->str)
goto error;
if (*e == '.' || *e == 'e' || *e == 'E')
PR_PTR (float, &pr->wp_val) = 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;
if (res->sorted_functions)
pr->free_progs_mem (pr, res->sorted_functions);
res->sorted_functions = 0;
res->linenos = 0;
res->local_defs = 0;
pr->watch = 0;
pr->wp_conditional = 0;
PR_PTR (int, &pr->wp_val) = 0;
for (int i = 0; i < ev_type_count; i++ ) {
res->type_encodings[i] = &res->void_type;
}
}
static void
pr_debug_destroy (progs_t *pr, void *_res)
{
__auto_type res = (prdeb_resources_t *) _res;
dstring_delete (res->string);
dstring_delete (res->dva);
dstring_delete (res->line);
dstring_delete (res->dstr);
va_destroy_context (res->va);
Hash_DelTable (res->file_hash);
Hash_DelTable (res->debug_syms);
Hash_DelTable (res->compunits);
pr->pr_debug_resources = 0;
}
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);
}
}
static int
def_compare_sort (const void *_da, const void *_db, void *_res)
{
pr_def_t da = *(const pr_def_t *)_da;
pr_def_t db = *(const pr_def_t *)_db;
return da.ofs - db.ofs;
}
static int
func_compare_sort (const void *_fa, const void *_fb, void *_res)
{
prdeb_resources_t *res = _res;
progs_t *pr = res->pr;
pr_func_t fa = *(const pr_func_t *)_fa;
pr_func_t fb = *(const pr_func_t *)_fb;
const char *fa_file = PR_GetString (pr, pr->pr_functions[fa].file);
const char *fb_file = PR_GetString (pr, pr->pr_functions[fb].file);
int cmp = strcmp (fa_file, fb_file);
if (cmp) {
return cmp;
}
pr_auxfunction_t *fa_aux = res->auxfunction_map[fa];
pr_auxfunction_t *fb_aux = res->auxfunction_map[fb];
pr_uint_t fa_line = fa_aux ? fa_aux->source_line : 0;
pr_uint_t fb_line = fb_aux ? fb_aux->source_line : 0;
return fa_line - fb_line;
}
static int
func_compare_search (const void *_key, const void *_f, void *_res)
{
prdeb_resources_t *res = _res;
progs_t *pr = res->pr;
const func_key_t *key = _key;
pr_func_t f = *(const pr_func_t *)_f;
const char *f_file = PR_GetString (pr, pr->pr_functions[f].file);
int cmp = strcmp (key->file, f_file);
if (cmp) {
return cmp;
}
pr_auxfunction_t *f_aux = res->auxfunction_map[f];
pr_uint_t f_line = f_aux ? f_aux->source_line : 0;
return key->line - f_line;
}
VISIBLE void
PR_DebugSetSym (progs_t *pr, pr_debug_header_t *debug)
{
prdeb_resources_t *res = pr->pr_debug_resources;
res->auxfunctions = (pr_auxfunction_t*)((char*)debug + debug->auxfunctions);
res->linenos = (pr_lineno_t*)((char*)debug + debug->linenos);
res->local_defs = (pr_def_t*)((char*)debug + debug->locals);
res->debug_defs = (pr_def_t*)((char*)debug + debug->debug_defs);
res->debug_data = (pr_type_t*)((char*)debug + debug->debug_data);
size_t size;
size = pr->progs->functions.count * sizeof (pr_auxfunction_t *);
res->auxfunction_map = pr->allocate_progs_mem (pr, size);
size = pr->progs->functions.count * sizeof (pr_func_t);
res->sorted_functions = pr->allocate_progs_mem (pr, size);
for (pr_uint_t i = 0; i < pr->progs->functions.count; i++) {
res->auxfunction_map[i] = 0;
res->sorted_functions[i] = i;
}
qfot_type_encodings_t *encodings = 0;
pr_ptr_t type_encodings = 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 (pr_uint_t i = 0; i < debug->num_auxfunctions; i++) {
if (type_encodings) {
res->auxfunctions[i].return_type += type_encodings;
}
res->auxfunction_map[res->auxfunctions[i].function] =
&res->auxfunctions[i];
heapsort_r (res->local_defs + res->auxfunctions[i].local_defs,
res->auxfunctions[i].num_locals, sizeof (pr_def_t),
def_compare_sort, res);
}
heapsort_r (res->sorted_functions, pr->progs->functions.count,
sizeof (pr_func_t), func_compare_sort, res);
for (pr_uint_t i = 0; i < debug->num_locals; i++) {
if (type_encodings) {
res->local_defs[i].type_encoding += type_encodings;
}
}
pr_string_t compunit_str = PR_FindString (pr, ".compile_unit");
for (pr_uint_t i = 0; i < debug->num_debug_defs; i++) {
pr_def_t *def = &res->debug_defs[i];
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) {
qfot_type_t *type;
for (pr_ptr_t 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
&& (unsigned) type->type < ev_type_count) {
res->type_encodings[type->type] = type;
}
}
}
res->debug = debug;
}
VISIBLE int
PR_LoadDebug (progs_t *pr)
{
prdeb_resources_t *res = pr->pr_debug_resources;
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;
pr_debug_header_t *debug;
res->debug = 0;
if (!pr_debug)
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, PR_PTR (string, str));
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);
debug = pr->load_file (pr, sym_path, &debug_size);
if (!debug) {
Sys_Printf ("can't load %s for debug info\n", sym_path);
free (sym_path);
return 1;
}
debug->version = LittleLong (debug->version);
if (debug->version != PROG_DEBUG_VERSION) {
Sys_Printf ("ignoring %s with unsupported version %x.%03x.%03x\n",
sym_path,
(debug->version >> 24) & 0xff,
(debug->version >> 12) & 0xfff,
debug->version & 0xfff);
free (sym_path);
return 1;
}
debug->crc = LittleShort (debug->crc);
if (debug->crc != pr->crc) {
Sys_Printf ("ignoring %s that doesn't match %s. (CRCs: "
"sym:%d dat:%d)\n",
sym_path, pr->progs_name, debug->crc, pr->crc);
free (sym_path);
return 1;
}
free (sym_path);
debug->you_tell_me_and_we_will_both_know = LittleShort
(debug->you_tell_me_and_we_will_both_know);
debug->auxfunctions = LittleLong (debug->auxfunctions);
debug->num_auxfunctions = LittleLong (debug->num_auxfunctions);
debug->linenos = LittleLong (debug->linenos);
debug->num_linenos = LittleLong (debug->num_linenos);
debug->locals = LittleLong (debug->locals);
debug->num_locals = LittleLong (debug->num_locals);
debug->debug_defs = LittleLong (debug->debug_defs);
debug->num_debug_defs = LittleLong (debug->num_debug_defs);
debug->debug_data = LittleLong (debug->debug_data);
debug->debug_data_size = LittleLong (debug->debug_data_size);
__auto_type auxfuncs = (pr_auxfunction_t*)((char*)debug
+ debug->auxfunctions);
for (i = 0; i < debug->num_auxfunctions; i++) {
auxfuncs[i].function = LittleLong (auxfuncs[i].function);
auxfuncs[i].source_line = LittleLong (auxfuncs[i].source_line);
auxfuncs[i].line_info = LittleLong (auxfuncs[i].line_info);
auxfuncs[i].local_defs = LittleLong (auxfuncs[i].local_defs);
auxfuncs[i].num_locals = LittleLong (auxfuncs[i].num_locals);
}
__auto_type linenos = (pr_lineno_t*)((char*)debug + debug->linenos);
for (i = 0; i < debug->num_linenos; i++) {
linenos[i].fa.func = LittleLong (linenos[i].fa.func);
linenos[i].line = LittleLong (linenos[i].line);
}
__auto_type local_defs = (pr_def_t*)((char*)debug + debug->locals);
for (i = 0; i < debug->num_locals; i++) {
byteswap_def (&local_defs[i]);
}
__auto_type debug_defs = (pr_def_t*)((char*)debug + debug->locals);
for (i = 0; i < debug->num_debug_defs; i++) {
byteswap_def (&debug_defs[i]);
}
PR_DebugSetSym (pr, debug);
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->functions.count) {
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;
}
pr_uint_t
PR_FindSourceLineAddr (progs_t *pr, const char *file, pr_uint_t line)
{
prdeb_resources_t *res = pr->pr_debug_resources;
func_key_t key = { file, line };
pr_func_t *f = fbsearch_r (&key, res->sorted_functions,
pr->progs->functions.count, sizeof (pr_func_t),
func_compare_search, res);
if (!f) {
return 0;
}
dfunction_t *func = &pr->pr_functions[*f];
if (func->first_statement <= 0
|| strcmp (file, PR_GetString (pr, func->file)) != 0) {
return 0;
}
pr_auxfunction_t *aux = res->auxfunction_map[*f];
if (!aux) {
return 0;
}
pr_uint_t addr = func->first_statement;
line -= aux->source_line;
//FIXME put lineno count in sym file
for (pr_uint_t i = aux->line_info + 1; i < res->debug->num_linenos; i++) {
if (!res->linenos[i].line) {
break;
}
if (res->linenos[i].line <= line) {
addr = res->linenos[i].fa.addr;
} else {
break;
}
}
return addr;
}
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->functions.count)
return 0;
return PR_GetString(pr, pr->pr_functions[f->function].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 param)
{
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->numparams;
if (num_params < 0) {
num_params = ~num_params; // one's compliment
param_offs = 1; // skip over @args def
}
if (param >= (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 (!param--)
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, pr_ptr_t *offset)
{
prdeb_resources_t *res = pr->pr_debug_resources;
dfunction_t *func;
pr_auxfunction_t *aux_func;
pr_ptr_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;
pr_ptr_t locals_start;
if (pr->progs->version == PROG_VERSION) {
if (pr->pr_depth) {
prstack_t *frame = pr->pr_stack + pr->pr_depth - 1;
locals_start = frame->stack_ptr - func->params_start;
} else {
locals_start = 0; //FIXME ? when disassembling in qfprogs
}
} else {
locals_start = func->params_start;
}
offs -= locals_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 && 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 void
value_string (pr_debug_data_t *data, qfot_type_t *type, pr_type_t *value)
{
switch (type->meta) {
case ty_basic:
switch (type->type) {
#define EV_TYPE(t) \
case ev_##t: \
raw_type_view.t##_view (type, value, data); \
break;
#include "QF/progs/pr_type_names.h"
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);
value_string (data, type, value);
break;
}
}
static pr_def_t *
pr_debug_find_def (progs_t *pr, pr_ptr_t *ofs)
{
prdeb_resources_t *res = pr->pr_debug_resources;
pr_def_t *def = 0;
if (pr_debug && res->debug) {
def = PR_Get_Local_Def (pr, ofs);
}
if (*ofs >= pr->progs->globals.count) {
return 0;
}
if (!def) {
def = PR_GlobalAtOfs (pr, *ofs);
if (def) {
*ofs -= def->ofs;
}
}
return def;
}
static const char *
global_string (pr_debug_data_t *data, pr_ptr_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;
pr_ptr_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;
pr_string_t string = PR_PTR (string, value);
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 (PR_PTR (int, value))
&& PR_PTR (uint, value) != 0x80000000) {
dasprintf (dstr, "<%08x>", PR_PTR (int, value));
} else {
dasprintf (dstr, "%.9g", PR_PTR (float, value));
}
}
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 (&PR_PTR (float, value)));
}
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
&& PR_PTR (entity, value) < pr->max_edicts
&& !(PR_PTR (entity, value) % pr->pr_edict_size)) {
edict_t *edict = PROG_TO_EDICT (pr, PR_PTR (entity, value));
if (edict) {
dasprintf (dstr, "entity %d", NUM_FOR_BAD_EDICT (pr, edict));
return;
}
}
dasprintf (dstr, "entity [%x]", PR_PTR (entity, value));
}
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, PR_PTR (int, value));
if (def) {
dasprintf (dstr, ".%s", PR_GetString (pr, def->name));
} else {
dasprintf (dstr, ".<$%04x>", PR_PTR (int, value));
}
}
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 (PR_PTR (func, value) >= pr->progs->functions.count) {
dasprintf (dstr, "INVALID:%d", PR_PTR (func, value));
} else if (!PR_PTR (func, value)) {
dstring_appendstr (dstr, "NULL");
} else {
dfunction_t *f = pr->pr_functions + PR_PTR (func, value);
dasprintf (dstr, "%s()", PR_GetString (pr, f->name));
}
}
static void
pr_debug_ptr_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_ptr_t offset = PR_PTR (int, value);
pr_ptr_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_quaternion_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 (&PR_PTR (float, value)));
}
static void
pr_debug_int_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", PR_PTR (int, value));
}
static void
pr_debug_uint_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", PR_PTR (uint, value));
}
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)PR_PTR (int, value));
}
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_debug_long_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, "%" PRIi64, *(int64_t *)value);
}
static void
pr_debug_ulong_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, "%" PRIu64, *(uint64_t *)value);
}
static void
pr_debug_ushort_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", (pr_ushort_t)PR_PTR (int, 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_PTR (int, &pr->wp_val));
} 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_PTR (int, &pr->wp_val));
} 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);
}
}
static const char *
print_raw_op (progs_t *pr, pr_ushort_t op, pr_ushort_t base_ind,
etype_t op_type, int op_width)
{
prdeb_resources_t *res = pr->pr_debug_resources;
const char *width = va (res->va, "%d", op_width);
return va (res->va, "%d:%04x<%08x>%s:%-8s",
base_ind, op, op + pr->pr_bases[base_ind],
op_width > 0 ? width : op_width < 0 ? "X" : "?",
pr_type_name[op_type]);
}
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;
const char *mnemonic;
const char *width = "";
dfunction_t *call_func = 0;
pr_def_t *param_def = 0;
pr_auxfunction_t *aux_func = 0;
pr_debug_data_t data;
etype_t op_type[3];
int op_width[3];
dstring_clearstr (res->line);
data.pr = pr;
data.dstr = res->dstr;
if (pr_debug > 1)
dump_code = 1;
if (pr_debug && 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;
}
if (pr->progs->version < PROG_VERSION) {
const v6p_opcode_t *op = PR_v6p_Opcode (s->op);
if (!op) {
Sys_Printf ("%sUnknown instruction %d\n", res->line->str, s->op);
return;
}
VectorSet (op->type_a, op->type_b, op->type_c, op_type);
VectorSet (1, 1, 1, op_width);
fmt = op->fmt;
mnemonic = op->opname;
} else {
const opcode_t *op = PR_Opcode (s->op);
if (!op) {
Sys_Printf ("%sUnknown instruction %d\n", res->line->str, s->op);
return;
}
VectorCopy (op->widths, op_width);
VectorCopy (op->types, op_type);
fmt = op->fmt;
mnemonic = op->mnemonic;
}
if (!fmt) {
fmt = "%Ga, %Gb, %gc";
}
dasprintf (res->line, "%04x ", addr);
if (pr_debug > 2) {
if (pr->progs->version < PROG_VERSION) {
dasprintf (res->line,
"%03x %04x(%8s) %04x(%8s) %04x(%8s)\t",
s->op,
s->a, pr_type_name[op_type[0]],
s->b, pr_type_name[op_type[1]],
s->c, pr_type_name[op_type[2]]);
} else {
dasprintf (res->line, "%04x %s %s %s\t",
s->op,
print_raw_op (pr, s->a, PR_BASE_IND (s->op, A),
op_type[0], op_width[0]),
print_raw_op (pr, s->b, PR_BASE_IND (s->op, B),
op_type[1], op_width[1]),
print_raw_op (pr, s->c, PR_BASE_IND (s->op, C),
op_type[2], op_width[2]));
}
} else if (op_width[0] > 1 || op_width[1] > 1 || op_width[2] > 1) {
width = va (res->va, "{%d,%d,%d}", VectorExpand (op_width));
}
dasprintf (res->line, "%s%s ", mnemonic, width);
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 param_ind = 0;
pr_uint_t shift = 0;
pr_uint_t opreg;
pr_uint_t opval;
qfot_type_t *optype = &res->void_type;
pr_func_t func;
if (mode == 'P') {
opchar = fmt[3];
param_ind = fmt[2] - '0';
fmt++; // P has one extra item
if (param_ind >= PR_MAX_PARAMS)
goto err;
}
if (mode == 'M' || mode == 'm') {
if (!isxdigit (fmt[3])) {
goto err;
}
shift = fmt[3];
shift = (shift & 0xf)
+ (((shift & 0x40) >> 3) | ((shift & 0x40) >> 5));
fmt++; // M/m have one extra item
}
switch (opchar) {
case 'a':
opreg = PR_BASE_IND (s->op, A);
opval = s->a;
optype = res->type_encodings[op_type[0]];
break;
case 'b':
opreg = PR_BASE_IND (s->op, B);
opval = s->b;
optype = res->type_encodings[op_type[1]];
break;
case 'c':
opreg = PR_BASE_IND (s->op, C);
opval = s->c;
optype = res->type_encodings[op_type[2]];
break;
case 'o':
opreg = 0;
opval = s->op;
break;
case 'x':
if (mode == 'P') {
opval = pr->pr_real_params[param_ind]
- pr->pr_globals;
break;
}
goto err;
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->functions.count) {
call_func = pr->pr_functions + func;
}
break;
case 'P':
param_def = PR_Get_Param_Def (pr, call_func, param_ind);
optype = &res->void_type;
if (param_def) {
optype = get_type (res, param_def->type_encoding);
}
str = global_string (&data, opval, optype,
contents & 1);
break;
case 'V':
opval += pr->pr_bases[opreg];
optype = &res->void_type;
str = global_string (&data, opval, optype,
contents & 1);
break;
case 'G':
opval += pr->pr_bases[opreg];
str = global_string (&data, opval, optype,
contents & 1);
break;
case 'g':
opval += pr->pr_bases[opreg];
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 'C':
str = dsprintf (res->dva, "%03o", opval);
break;
case 'E':
{
edict_t *ed = 0;
opval = G_ENTITY (pr, s->a);
param_ind = G_FIELD (pr, s->b);
if (param_ind < pr->progs->entityfields
&& opval > 0
&& opval < pr->pr_edict_area_size) {
ed = PROG_TO_EDICT (pr, opval);
opval = &E_fld(ed, param_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;
case 'M':
case 'm':
{
pr_ptr_t ptr = 0;
pr_int_t offs = 0;
switch ((opval >> shift) & 3) {
case 0:
ptr = s->a + PR_BASE (pr, s, A);
break;
case 1:
break;
case 2:
ptr = s->a + PR_BASE (pr, s, A);
ptr = G_POINTER (pr, ptr);
offs = (short) s->b;
break;
case 3:
ptr = s->a + PR_BASE (pr, s, A);
ptr = G_POINTER (pr, ptr);
offs = s->b + PR_BASE (pr, s, B);
offs = G_INT (pr, offs);
break;
}
ptr += offs;
str = global_string (&data, ptr, optype,
mode == 'M');
}
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 && 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_uint_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->file),
line,
PR_GetString (pr, f->name),
frame->staddr);
} else {
Sys_Printf ("%12s:%u+%d : %s: %x\n",
PR_GetString (pr, f->file),
line, frame->staddr - addr,
PR_GetString (pr, f->name),
frame->staddr);
}
} else {
Sys_Printf ("%12s : %s: %x\n", PR_GetString (pr, f->file),
PR_GetString (pr, f->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_ulong_t max, num, i;
pr_ulong_t total;
dfunction_t *f;
bfunction_t *best, *bf;
num = 0;
total = 0;
for (i = 0; i < pr->progs->functions.count; i++) {
bf = &pr->function_table[i];
total += bf->profile;
}
do {
max = 0;
best = NULL;
for (i = 0; i < pr->progs->functions.count; i++) {
bf = &pr->function_table[i];
if (bf->profile > max) {
max = bf->profile;
best = bf;
}
}
if (best) {
if (num < 10) {
f = pr->pr_functions + (best - pr->function_table);
Sys_Printf ("%7"PRIu64" %s%s\n", best->profile,
PR_GetString (pr, f->name),
f->first_statement < 0 ? " (builtin)" : "");
}
num++;
best->profile = 0;
}
} while (best);
Sys_Printf ("total: %7"PRIu64"\n", total);
}
static void
print_field (progs_t *pr, edict_t *ed, pr_def_t *d)
{
prdeb_resources_t *res = pr->pr_debug_resources;
int l;
pr_type_t *v;
qfot_type_t dummy_type = { };
qfot_type_t *type;
pr_debug_data_t data = {pr, res->dstr};
const char *name;
name = PR_GetString (pr, d->name);
type = get_def_type (pr, d, &dummy_type);
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);
}
/*
ED_Print
For debugging
*/
VISIBLE void
ED_Print (progs_t *pr, edict_t *ed, const char *fieldname)
{
pr_uint_t i, j;
const char *name;
pr_def_t *d;
int l;
if (ed->free) {
Sys_Printf ("FREE\n");
return;
}
Sys_Printf ("\nEDICT %d:\n", NUM_FOR_BAD_EDICT (pr, ed));
if (fieldname) {
d = PR_FindField(pr, fieldname);
if (!d) {
Sys_Printf ("unknown field '%s'\n", fieldname);
} else {
print_field (pr, ed, d);
}
return;
}
for (i = 0; i < pr->progs->fielddefs.count; i++) {
d = &pr->pr_fielddefs[i];
if (!d->name) // null field def (probably 1st)
continue;
name = PR_GetString (pr, d->name);
l = strlen (name);
if (l >= 2 && name[l - 2] == '_' && strchr ("xyz", name[l - 1]))
continue; // skip _x, _y, _z vars
qfot_type_t dummy_type = { };
get_def_type (pr, d, &dummy_type);
for (j = 0; j < d->size; j++) {
if (E_INT (ed, d->ofs + j)) {
break;
}
}
if (j == d->size) {
continue;
}
print_field (pr, ed, d);
}
}
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->va = va_create_context (8);
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->hashctx);
res->debug_syms = Hash_NewTable (509, def_get_key, 0, pr, pr->hashctx);
res->compunits = Hash_NewTable (509, compunit_get_key, 0, pr, pr->hashctx);
PR_Resources_Register (pr, "PR_Debug", res, pr_debug_clear,
pr_debug_destroy);
pr->pr_debug_resources = res;
}
void
PR_Debug_Init_Cvars (void)
{
Cvar_Register (&pr_debug_cvar, 0, 0);
Cvar_Register (&pr_source_path_cvar, source_path_f, 0);
}