quakeforge/ruamoko/qwaq/builtins/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

742 lines
20 KiB
C

/*
debug.c
Debugging support
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
Author: Bill Currie <bill@taniwha.org>
Date: 2020/03/24
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
#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/keys.h"
#include "QF/quakefs.h"
#include "QF/sys.h"
#include "ruamoko/qwaq/qwaq.h"
#include "ruamoko/qwaq/ui/event.h"
#include "ruamoko/qwaq/ui/curses.h"
#include "ruamoko/qwaq/debugger/debug.h"
typedef struct qwaq_target_s {
progs_t *pr;
struct qwaq_debug_s *debugger;
int handle;
prdebug_t event;
void *param;
rwcond_t run_cond;
int run_command;
} qwaq_target_t;
typedef struct qwaq_debug_s {
progs_t *pr;
qwaq_input_resources_t *input; // to communicate with the debugger thread
PR_RESMAP (qwaq_target_t) targets;
} qwaq_debug_t;
#define always_inline inline __attribute__((__always_inline__))
static qwaq_target_t *
target_new (qwaq_debug_t *debug)
{
return PR_RESNEW (debug->targets);
}
static void
target_free (qwaq_debug_t *debug, qwaq_target_t *target)
{
PR_RESFREE (debug->targets, target);
}
static void
target_reset (qwaq_debug_t *debug)
{
PR_RESRESET (debug->targets);
}
static inline qwaq_target_t *
target_get (qwaq_debug_t *debug, unsigned index)
{
return PR_RESGET (debug->targets, index);
}
static inline int __attribute__((pure))
target_index (qwaq_debug_t *debug, qwaq_target_t *target)
{
return PR_RESINDEX (debug->targets, target);
}
static always_inline qwaq_target_t * __attribute__((pure))
get_target (qwaq_debug_t *debug, const char *name, int handle)
{
qwaq_target_t *target = target_get (debug, handle);
if (!target || !target->debugger) {
PR_RunError (debug->pr, "invalid target passed to %s", name + 4);
}
return target;
}
static void
qwaq_debug_handler (prdebug_t debug_event, void *param, void *data)
{
__auto_type target = (qwaq_target_t *) data;
qwaq_debug_t *debug = target->debugger;
qwaq_event_t event = {};
int ret;
target->event = debug_event;
target->param = param;
event.what = qe_debug_event;
event.message.pointer_val = target->handle;
while ((ret = qwaq_add_event (debug->input, &event)) == ETIMEDOUT) {
// spin
}
if (ret == EINVAL) {
Sys_Error ("event queue broke");
}
pthread_mutex_lock (&target->run_cond.mut);
while (!target->run_command) {
pthread_cond_wait (&target->run_cond.rcond, &target->run_cond.mut);
}
target->run_command = 0;
pthread_mutex_unlock (&target->run_cond.mut);
if (debug_event == prd_runerror || debug_event == prd_error) {
pthread_exit (param);
}
}
//FIXME need a better way to get this from one thread to the others
pthread_cond_t debug_data_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t debug_data_mutex = PTHREAD_MUTEX_INITIALIZER;
static qwaq_debug_t *qwaq_debug_data;
static int
qwaq_debug_load (progs_t *pr)
{
__auto_type debug = (qwaq_debug_t *) PR_Resources_Find (pr, "qwaq-debug");
pthread_mutex_lock (&debug_data_mutex);
qwaq_debug_data = debug; // FIXME ? see decl
pthread_cond_broadcast (&debug_data_cond);
pthread_mutex_unlock (&debug_data_mutex);
return 1;
}
static void
qwaq_debug_clear (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
target_reset (debug);
}
static void
qwaq_debug_destroy (progs_t *pr, void *_res)
{
}
static void
qwaq_target_clear (progs_t *pr, void *_res)
{
qwaq_target_t *target = pr->debug_data;
if (target) {
target_free (target->debugger, target);
}
}
static void
qwaq_target_destroy (progs_t *pr, void *_res)
{
}
static int
qwaq_target_load (progs_t *pr)
{
pthread_mutex_lock (&debug_data_mutex);
while (!qwaq_debug_data) {
pthread_cond_wait (&debug_data_cond, &debug_data_mutex);
}
pthread_mutex_unlock (&debug_data_mutex);
qwaq_target_t *target = target_new (qwaq_debug_data);
target->pr = pr;
target->debugger = qwaq_debug_data;
target->handle = target_index (qwaq_debug_data, target);
qwaq_init_cond (&target->run_cond);
target->run_command = 0;
pr->debug_handler = qwaq_debug_handler;
pr->debug_data = target;
// start tracing immediately so the debugger has a chance to start up
// before the target progs begin running
pr->pr_trace = 1;
pr->pr_trace_depth = -1;
return 1;
}
static void
qdb_set_trace (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
int state = P_INT (pr, 1);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
tpr->pr_trace = state;
}
static void
qdb_set_breakpoint (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
unsigned staddr = P_INT (pr, 1);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
if (staddr >= tpr->progs->statements.count) {
R_INT (pr) = -1;
return;
}
int set = (tpr->pr_statements[staddr].op & OP_BREAK) != 0;
tpr->pr_statements[staddr].op |= OP_BREAK;
R_INT (pr) = set;
}
static void
qdb_clear_breakpoint (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
unsigned staddr = P_UINT (pr, 1);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
if (staddr >= tpr->progs->statements.count) {
R_INT (pr) = -1;
return;
}
tpr->pr_statements[staddr].op &= ~OP_BREAK;
R_INT (pr) = 0;
}
static void
qdb_set_watchpoint (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
pr_ptr_t offset = P_UINT (pr, 1);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
if (offset >= tpr->globals_size) {
R_INT (pr) = -1;
return;
}
tpr->watch = &tpr->pr_globals[offset];
R_INT (pr) = 0;
}
static void
qdb_clear_watchpoint (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
tpr->watch = 0;
R_INT (pr) = 0;
}
static void
qdb_continue (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
pthread_mutex_lock (&target->run_cond.mut);
target->run_command = 1;
pthread_cond_signal (&target->run_cond.rcond);
pthread_mutex_unlock (&target->run_cond.mut);
}
static void
qdb_get_state (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
pr_lineno_t *lineno;
pr_auxfunction_t *f;
pr_string_t file = 0;
unsigned line = 0;
unsigned staddr = tpr->pr_xstatement;
pr_func_t func = 0;
if (tpr->pr_xfunction) {
func = tpr->pr_xfunction - tpr->function_table;
}
lineno = PR_Find_Lineno (tpr, staddr);
if (lineno) {
f = PR_Get_Lineno_Func (tpr, lineno);
//FIXME file is a permanent string. dynamic would be better
//but they're not merged (and would need refcounting)
file = PR_SetString (pr, PR_Get_Source_File (tpr, lineno));
func = f->function;
line = PR_Get_Lineno_Line (tpr, lineno);
line += f->source_line;
}
qdb_state_t state = {};
state.staddr = staddr;
state.func = func;
state.file = file;
state.line = line;
R_PACKED (pr, qdb_state_t) = state;
}
static void
qdb_get_stack_depth (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
R_INT (pr) = tpr->pr_depth;
}
static void
qdb_get_stack (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
int count = tpr->pr_depth;
R_POINTER (pr) = 0;
if (count > 0) {
size_t size = count * sizeof (qdb_stack_t);
pr_string_t stack_block = PR_AllocTempBlock (pr, size);
__auto_type stack = (qdb_stack_t *) PR_GetString (pr, stack_block);
for (int i = 0; i < count; i++) {
stack[i].staddr = tpr->pr_stack[i].staddr;
stack[i].func = tpr->pr_stack[i].func - tpr->pr_xfunction;
//XXX temp strings (need access somehow)
}
R_POINTER (pr) = PR_SetPointer (pr, stack);
}
}
static void
qdb_get_event (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
__auto_type event = &P_STRUCT (pr, qdb_event_t, 1);
memset (event, 0, sizeof (*event));
event->what = target->event;
switch (event->what) {
case prd_subenter:
event->function = *(pr_func_t *) target->param;
break;
case prd_runerror:
case prd_error:
event->message = PR_SetReturnString (pr, (char *) target->param);
break;
case prd_begin:
event->function = *(pr_func_t *) target->param;
break;
case prd_terminate:
event->exit_code = *(int *) target->param;
break;
case prd_trace:
case prd_breakpoint:
case prd_watchpoint:
case prd_subexit:
case prd_none:
break;
}
R_INT (pr) = target->event != prd_none;
}
static void
qdb_get_data (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
pr_ptr_t srcoff = P_POINTER (pr, 1);
unsigned length = P_UINT (pr, 2);
pr_ptr_t dstoff = P_POINTER(pr, 3);
pr_type_t *src = PR_GetPointer(tpr, srcoff);
pr_type_t *dst = PR_GetPointer(pr, dstoff);
if (srcoff >= tpr->globals_size || srcoff + length >= tpr->globals_size) {
R_INT (pr) = -1;
return;
}
if (dstoff >= pr->globals_size || dstoff + length >= pr->globals_size) {
R_INT (pr) = -1;
return;
}
memcpy (dst, src, length * sizeof (pr_type_t));
R_INT (pr) = 0;
}
static void
qdb_get_string (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
pr_string_t string = P_STRING (pr, 1);
R_STRING (pr) = 0;
if (PR_StringValid (tpr, string)) {
RETURN_STRING (pr, PR_GetString (tpr, string));
}
}
static void
qdb_get_file_path (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
const char *file = P_GSTRING (pr, 1);
const char *basedir = PR_Debug_GetBaseDirectory (tpr, file);
if (basedir) {
size_t baselen = strlen (basedir);
size_t filelen = strlen (file);
char *path = alloca (baselen + filelen + 2);
strcpy (path, basedir);
path[baselen] = '/';
strcpy (path + baselen + 1, file);
path = QFS_CompressPath (path);
RETURN_STRING (pr, path);
free (path);
} else {
R_STRING (pr) = P_STRING (pr, 1);
}
}
static void
qdb_find_string (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
const char *str = P_GSTRING (pr, 1);
R_INT (pr) = PR_FindString (tpr, str);
}
static void
qdb_find_global (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
const char *name = P_GSTRING (pr, 1);
pr_def_t *def = PR_FindGlobal (tpr, name);
if (def) {
R_PACKED (pr, qdb_def_t).type_size = (def->size << 16) | def->type;
R_PACKED (pr, qdb_def_t).offset = def->ofs;
R_PACKED (pr, qdb_def_t).name = def->name;
R_PACKED (pr, qdb_def_t).type_encoding = def->type_encoding;
} else {
memset (&R_PACKED (pr, qdb_def_t), 0, sizeof (qdb_def_t));
}
}
static void
qdb_find_field (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
const char *name = P_GSTRING (pr, 1);
pr_def_t *def = PR_FindField (tpr, name);
if (def) {
R_PACKED (pr, qdb_def_t).type_size = (def->size << 16) | def->type;
R_PACKED (pr, qdb_def_t).offset = def->ofs;
R_PACKED (pr, qdb_def_t).name = def->name;
R_PACKED (pr, qdb_def_t).type_encoding = def->type_encoding;
} else {
memset (&R_PACKED (pr, qdb_def_t), 0, sizeof (qdb_def_t));
}
}
static void
return_function (progs_t *pr, dfunction_t *func)
{
R_POINTER (pr) = 0;
if (func) {
__auto_type f
= (qdb_function_t *) PR_Zone_Malloc (pr, sizeof (qdb_function_t));
f->staddr = func->first_statement;
f->local_data = func->params_start;
f->local_size = func->locals;
f->profile = func->profile;
f->name = func->name;
f->file = func->file;
f->num_params = func->numparams;
R_POINTER (pr) = PR_SetPointer (pr, f);
}
}
static void
qdb_find_function (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
const char *name = P_GSTRING (pr, 1);
dfunction_t *func = PR_FindFunction (tpr, name);
return_function (pr, func);
}
static void
qdb_get_function (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
pr_uint_t fnum = P_INT (pr, 1);
dfunction_t *func = tpr->pr_functions + fnum;
if (fnum >= tpr->progs->functions.count) {
func = 0;
}
return_function (pr, func);
}
static void
return_auxfunction (progs_t *pr, pr_auxfunction_t *auxfunc)
{
R_POINTER (pr) = 0;
if (auxfunc) {
__auto_type f
= (qdb_auxfunction_t *) PR_Zone_Malloc (pr,
sizeof (qdb_auxfunction_t));
f->function = auxfunc->function;
f->source_line = auxfunc->source_line;
f->line_info = auxfunc->line_info;
f->local_defs = auxfunc->local_defs;
f->num_locals = auxfunc->num_locals;
f->return_type = auxfunc->return_type;
R_POINTER (pr) = PR_SetPointer (pr, f);
}
}
static void
qdb_find_auxfunction (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
const char *name = P_GSTRING (pr, 1);
dfunction_t *func = PR_FindFunction (tpr, name);
pr_uint_t fnum = func - tpr->pr_functions;
pr_auxfunction_t *auxfunc = PR_Debug_MappedAuxFunction (tpr, fnum);
if (fnum >= tpr->progs->functions.count) {
func = 0;
}
return_auxfunction (pr, auxfunc);
}
static void
qdb_get_auxfunction (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
pr_uint_t fnum = P_UINT (pr, 1);
pr_auxfunction_t *auxfunc = PR_Debug_MappedAuxFunction (tpr, fnum);
return_auxfunction (pr, auxfunc);
}
static void
qdb_get_local_defs (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
pr_uint_t fnum = P_UINT (pr, 1);
pr_auxfunction_t *auxfunc = PR_Debug_MappedAuxFunction (tpr, fnum);
R_POINTER (pr) = 0;
if (auxfunc && auxfunc->num_locals) {
pr_def_t *defs = PR_Debug_LocalDefs (tpr, auxfunc);
__auto_type qdefs
= (qdb_def_t *) PR_Zone_Malloc (pr,
auxfunc->num_locals * sizeof (qdb_def_t));
for (unsigned i = 0; i < auxfunc->num_locals; i++) {
qdefs[i].type_size = (defs[i].size << 16) | defs[i].type;
qdefs[i].offset = defs[i].ofs;
qdefs[i].name = defs[i].name;
qdefs[i].type_encoding = defs[i].type_encoding;
}
R_POINTER (pr) = PR_SetPointer (pr, qdefs);
}
}
static void
qdb_get_source_line_addr (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
const char *file = P_GSTRING (pr, 1);
pr_uint_t line = P_UINT (pr, 2);
R_UINT (pr) = PR_FindSourceLineAddr (tpr, file, line);
}
static void
qdb_has_data_stack (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
R_INT (pr) = tpr->progs->version == PROG_VERSION;
}
static void
qdb_get_frame_addr (progs_t *pr, void *_res)
{
__auto_type debug = (qwaq_debug_t *) _res;
pr_ptr_t handle = P_INT (pr, 0);
qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
progs_t *tpr = target->pr;
R_UINT (pr) = 0;
if (tpr->progs->version == PROG_VERSION) {
if (tpr->pr_depth) {
prstack_t *frame = tpr->pr_stack + tpr->pr_depth - 1;
R_UINT (pr) = frame->stack_ptr;
}
}
}
#define bi(x,np,params...) {#x, x, -1, np, {params}}
#define p(type) PR_PARAM(type)
static builtin_t builtins[] = {
bi(qdb_set_trace, 2, p(int), p(int)),
bi(qdb_set_breakpoint, 2, p(int), p(uint)),
bi(qdb_clear_breakpoint, 2, p(int), p(uint)),
bi(qdb_set_watchpoint, 2, p(int), p(uint)),
bi(qdb_clear_watchpoint, 1, p(int)),
bi(qdb_continue, 1, p(int)),
bi(qdb_get_state, 1, p(int)),
bi(qdb_get_stack_depth, 1, p(int)),
bi(qdb_get_stack, 1, p(int)),
bi(qdb_get_event, 2, p(int), p(ptr)),
bi(qdb_get_data, 4, p(int), p(uint), p(uint), p(ptr)),
{"qdb_get_string|{tag qdb_target_s=}I", qdb_get_string, -1, 1, {p(uint)}},
{"qdb_get_string|{tag qdb_target_s=}*", qdb_get_string, -1, 1, {p(string)}},
bi(qdb_get_file_path, 2, p(int), p(string)),
bi(qdb_find_string, 2, p(int), p(string)),
bi(qdb_find_global, 2, p(int), p(string)),
bi(qdb_find_field, 2, p(int), p(string)),
bi(qdb_find_function, 2, p(int), p(string)),
bi(qdb_get_function, 2, p(int), p(uint)),
bi(qdb_find_auxfunction, 2, p(int), p(string)),
bi(qdb_get_auxfunction, 2, p(int), p(uint)),
bi(qdb_get_local_defs, 2, p(int), p(uint)),
bi(qdb_get_source_line_addr, 3, p(int), p(string), p(uint)),
bi(qdb_has_data_stack, 1, p(int)),
bi(qdb_get_frame_addr, 1, p(int)),
{}
};
void
QWAQ_Debug_Init (progs_t *pr)
{
qwaq_debug_t *debug = calloc (sizeof (*debug), 1);
debug->pr = pr;
debug->input = PR_Resources_Find (pr, "input");
PR_AddLoadFunc (pr, qwaq_debug_load);
PR_Resources_Register (pr, "qwaq-debug", debug, qwaq_debug_clear,
qwaq_debug_destroy);
PR_RegisterBuiltins (pr, builtins, debug);
}
void
QWAQ_DebugTarget_Init (progs_t *pr)
{
PR_AddLoadFunc (pr, qwaq_target_load);
PR_Resources_Register (pr, "qwaq-target", 0, qwaq_target_clear,
qwaq_target_destroy);
}