quakeforge/libs/gamecode/engine/pr_debug.c
Bill Currie dfc83c1f5f add PR_PushFrame and PR_PopFrame so temp strings are easy to use for
parameters to progs functions.

double the progs stack sizes (call depth of 32? eek)

allow one extra call on the progs stack :)

misc minor cleanups
2004-01-05 07:10:32 +00:00

616 lines
14 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
static __attribute__ ((unused)) const char rcsid[] =
"$Id$";
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/pr_debug.h"
#include "QF/progs.h"
#include "QF/qendian.h"
#include "QF/quakefs.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;
line_t *lines;
unsigned int num_lines;
progs_t *pr;
} file_t;
cvar_t *pr_debug;
cvar_t *pr_source_path;
static hashtab_t *file_hash;
static char *source_path_string;
static char **source_paths;
static const char *
file_get_key (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 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);
for (i = 2, s = source_path_string; *s; s++)
if (*s == ';')
i++;
source_paths = malloc (i * sizeof (char **));
source_paths[0] = source_path_string;
for (i = 1, s = source_path_string; *s; s++)
if (*s == ';') {
*s++ = 0;
source_paths[i++] = s;
}
source_paths[i] = 0;
}
void
PR_Debug_Init (void)
{
file_hash = Hash_NewTable (1024, file_get_key, file_free, 0);
}
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");
}
static file_t *
PR_Load_Source_File (progs_t *pr, const char *fname)
{
char *path = 0, *l, **dir;
file_t *f = Hash_Find (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++) {
int len;
len = strlen (*dir) + strlen (fname) + 2;
path = Hunk_TempAlloc (len);
sprintf (path, "%s%s%s", *dir, **dir ? "/" : "", fname);
f->text = pr->load_file (pr, path);
}
if (!f->text) {
pr->file_error (pr, path);
free (f);
return 0;
}
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;
}
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') {
f->lines[f->num_lines].len = l - 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->pr = pr;
Hash_Add (file_hash, f);
return f;
}
int
PR_LoadDebug (progs_t *pr)
{
char *sym_path;
const char *path_end, *sym_file;
unsigned int i;
int start = Hunk_LowMark ();
ddef_t *def;
pr_type_t *str = 0;
pr->debug = 0;
pr->auxfunctions = 0;
pr->auxfunction_map = 0;
pr->linenos = 0;
pr->local_defs = 0;
if (!pr_debug->int_val)
return 1;
def = PR_FindGlobal (pr, ".debug_file");
if (def)
str = &pr->pr_globals[def->ofs];
Hash_FlushTable (file_hash);
if (!str)
return 1;
pr->debugfile = PR_GetString (pr, str->string_var);
sym_file = QFS_SkipPath (pr->debugfile);
path_end = QFS_SkipPath (pr->progs_name);
sym_path = Hunk_TempAlloc (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);
pr->debug = pr->load_file (pr, sym_path);
if (!pr->debug) {
Sys_Printf ("can't load %s for debug info\n", sym_path);
return 1;
}
pr->debug->version = LittleLong (pr->debug->version);
if (pr->debug->version != PROG_DEBUG_VERSION) {
Sys_Printf ("ignoring %s with unsupported version %x.%03x.%03x\n",
sym_path,
(pr->debug->version >> 24) & 0xff,
(pr->debug->version >> 12) & 0xfff,
pr->debug->version & 0xfff);
Hunk_FreeToLowMark (start);
pr->debug = 0;
return 1;
}
pr->debug->crc = LittleShort (pr->debug->crc);
if (pr->debug->crc != pr->crc) {
Sys_Printf ("ignoring %s that doesn't match %s. (CRCs: "
"sym:%d dat:%d)\n",
sym_path,
pr->progs_name,
pr->debug->crc,
pr->crc);
Hunk_FreeToLowMark (start);
pr->debug = 0;
return 1;
}
pr->debug->you_tell_me_and_we_will_both_know = LittleShort
(pr->debug->you_tell_me_and_we_will_both_know);
pr->debug->auxfunctions = LittleLong (pr->debug->auxfunctions);
pr->debug->num_auxfunctions = LittleLong (pr->debug->num_auxfunctions);
pr->debug->linenos = LittleLong (pr->debug->linenos);
pr->debug->num_linenos = LittleLong (pr->debug->num_linenos);
pr->debug->locals = LittleLong (pr->debug->locals);
pr->debug->num_locals = LittleLong (pr->debug->num_locals);
pr->auxfunctions = (pr_auxfunction_t*)((char*)pr->debug +
pr->debug->auxfunctions);
pr->linenos = (pr_lineno_t*)((char*)pr->debug + pr->debug->linenos);
pr->local_defs = (ddef_t*)((char*)pr->debug + pr->debug->locals);
pr->auxfunction_map = Hunk_Alloc (pr->progs->numfunctions *
sizeof (pr_auxfunction_t*));
for (i = 0; i < pr->debug->num_auxfunctions; i++) {
pr->auxfunctions[i].function = LittleLong
(pr->auxfunctions[i].function);
pr->auxfunctions[i].source_line = LittleLong
(pr->auxfunctions[i].source_line);
pr->auxfunctions[i].line_info = LittleLong
(pr->auxfunctions[i].line_info);
pr->auxfunctions[i].local_defs = LittleLong
(pr->auxfunctions[i].local_defs);
pr->auxfunctions[i].num_locals = LittleLong
(pr->auxfunctions[i].num_locals);
pr->auxfunction_map[pr->auxfunctions[i].function] =
&pr->auxfunctions[i];
}
for (i = 0; i < pr->debug->num_linenos; i++) {
pr->linenos[i].fa.func = LittleLong (pr->linenos[i].fa.func);
pr->linenos[i].line = LittleLong (pr->linenos[i].line);
}
for (i = 0; i < pr->debug->num_locals; i++) {
pr->local_defs[i].type = LittleShort (pr->local_defs[i].type);
pr->local_defs[i].ofs = LittleShort (pr->local_defs[i].ofs);
pr->local_defs[i].s_name = LittleLong (pr->local_defs[i].s_name);
}
return 1;
}
pr_auxfunction_t *
PR_Get_Lineno_Func (progs_t *pr, pr_lineno_t *lineno)
{
while (lineno > pr->linenos && lineno->line)
lineno--;
if (lineno->line)
return 0;
return &pr->auxfunctions[lineno->fa.func];
}
unsigned int
PR_Get_Lineno_Addr (progs_t *pr, pr_lineno_t *lineno)
{
pr_auxfunction_t *f;
if (lineno->line)
return lineno->fa.addr;
f = &pr->auxfunctions[lineno->fa.func];
return pr->pr_functions[f->function].first_statement;
}
unsigned int
PR_Get_Lineno_Line (progs_t *pr, pr_lineno_t *lineno)
{
pr_auxfunction_t *f;
if (lineno->line)
return lineno->line;
f = &pr->auxfunctions[lineno->fa.func];
return f->source_line;
}
pr_lineno_t *
PR_Find_Lineno (progs_t *pr, unsigned int addr)
{
int i;
pr_lineno_t *lineno = 0;
if (!pr->debug)
return 0;
if (!pr->debug->num_linenos)
return 0;
for (i = pr->debug->num_linenos - 1; i >= 0; i--) {
if (PR_Get_Lineno_Addr (pr, &pr->linenos[i]) <= addr) {
lineno = &pr->linenos[i];
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);
return PR_GetString(pr, pr->pr_functions[f->function].s_file);
}
const char *
PR_Get_Source_Line (progs_t *pr, unsigned int addr)
{
char *str;
const char *fname;
unsigned int 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);
str = Hunk_TempAlloc (strlen (fname) + 12);
sprintf (str, "%s:%d", fname, line);
if (!file || line > file->num_lines)
return str;
str = Hunk_TempAlloc (strlen (str) + file->lines[line - 1].len + 2);
sprintf (str, "%s:%d:%.*s", fname, line,
(int)file->lines[line - 1].len, file->lines[line - 1].text);
return str;
}
ddef_t *
PR_Get_Local_Def (progs_t *pr, int offs)
{
unsigned int i;
dfunction_t *func = pr->pr_xfunction;
pr_auxfunction_t *aux_func;
if (!func)
return 0;
aux_func = pr->auxfunction_map[func - pr->pr_functions];
if (!aux_func)
return 0;
offs -= func->parm_start;
if (offs < 0 || offs >= func->locals)
return 0;
for (i = 0; i < aux_func->num_locals; i++)
if (pr->local_defs[aux_func->local_defs + i].ofs == offs)
return &pr->local_defs[aux_func->local_defs + i];
return 0;
}
void
PR_DumpState (progs_t *pr)
{
if (pr->pr_xfunction) {
if (pr_debug->int_val && pr->debug) {
pr_lineno_t *lineno;
pr_auxfunction_t *func = 0;
int addr = pr->pr_xstatement;
lineno = PR_Find_Lineno (pr, addr);
if (lineno)
func = PR_Get_Lineno_Func (pr, lineno);
if (func && pr->pr_xfunction == pr->pr_functions + func->function)
addr = PR_Get_Lineno_Addr (pr, lineno);
else
addr = max (pr->pr_xfunction->first_statement, addr - 5);
while (addr != pr->pr_xstatement)
PR_PrintStatement (pr, pr->pr_statements + addr++);
}
PR_PrintStatement (pr, pr->pr_statements + pr->pr_xstatement);
}
PR_StackTrace (pr);
}
void
PR_PrintStatement (progs_t * pr, dstatement_t *s)
{
int addr = s - pr->pr_statements;
const char *fmt;
opcode_t *op;
static dstring_t *line;
if (!line)
line = dstring_new ();
dstring_clearstr (line);
if (pr_debug->int_val && pr->debug) {
const char *source_line = PR_Get_Source_Line (pr, addr);
if (source_line)
dasprintf (line, "%s\n", source_line);
}
op = PR_Opcode (s->op);
if (!op) {
Sys_Printf ("%sUnknown instruction %d\n", line->str, s->op);
return;
}
if (!(fmt = op->fmt))
fmt = "%Ga, %Gb, %gc";
dasprintf (line, "%04x ", addr);
if (pr_debug->int_val > 1)
dasprintf (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 (line, "%s ", op->opname);
while (*fmt) {
if (*fmt == '%') {
if (fmt[1] == '%') {
dstring_appendsubstr (line, fmt + 1, 1);
fmt += 2;
} else {
char mode = fmt[1];
char opchar = fmt[2];
long opval;
etype_t optype;
switch (opchar) {
case 'a':
opval = s->a;
optype = op->type_a;
break;
case 'b':
opval = s->b;
optype = op->type_b;
break;
case 'c':
opval = s->c;
optype = op->type_c;
break;
default:
goto err;
}
switch (mode) {
case 'V':
dstring_appendstr (line,
PR_GlobalString (pr, opval,
ev_void));
break;
case 'G':
dstring_appendstr (line,
PR_GlobalString (pr, opval,
optype));
break;
case 'g':
dstring_appendstr (line,
PR_GlobalStringNoContents (pr,
opval,
optype));
break;
case 's':
dasprintf (line, "%d", (short) opval);
break;
case 'O':
dasprintf (line, "%04x", addr + (short) opval);
break;
default:
goto err;
}
fmt += 3;
continue;
err:
dstring_appendstr (line, fmt);
break;
}
} else {
dstring_appendsubstr (line, fmt++, 1);
}
}
Sys_Printf ("%s\n", line->str);
}
static void
dump_frame (progs_t *pr, prstack_t *frame)
{
dfunction_t *f = frame->f;
if (!f) {
Sys_Printf ("<NO FUNCTION>\n");
return;
}
if (pr_debug->int_val && pr->debug) {
pr_lineno_t *lineno = PR_Find_Lineno (pr, frame->s);
pr_auxfunction_t *func = PR_Get_Lineno_Func (pr, lineno);
unsigned int line = PR_Get_Lineno_Line (pr, lineno);
int addr = PR_Get_Lineno_Addr (pr, lineno);
line += func->source_line;
if (addr == frame->s) {
Sys_Printf ("%12s:%d : %s: %x\n",
PR_GetString (pr, f->s_file),
line,
PR_GetString (pr, f->s_name),
frame->s);
} else {
Sys_Printf ("%12s:%d+%d : %s: %x\n",
PR_GetString (pr, f->s_file),
line, frame->s - addr,
PR_GetString (pr, f->s_name),
frame->s);
}
} else {
Sys_Printf ("%12s : %s: %x\n", PR_GetString (pr, f->s_file),
PR_GetString (pr, f->s_name), frame->s);
}
}
void
PR_StackTrace (progs_t *pr)
{
int i;
prstack_t top;
if (pr->pr_depth == 0) {
Sys_Printf ("<NO STACK>\n");
return;
}
top.s = pr->pr_xstatement;
top.f = pr->pr_xfunction;
dump_frame (pr, &top);
for (i = pr->pr_depth - 1; i >= 0; i--)
dump_frame (pr, pr->pr_stack + i);
}
void
PR_Profile (progs_t * pr)
{
int 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);
}