mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-25 02:53:48 +08:00
7a052092c3
* config/alpha/alpha-osf3.mh (NATDEPFILES): Add dec-thread.o. (NAT_CLIBS): Define.
684 lines
17 KiB
C
684 lines
17 KiB
C
/* Copyright (C) 2008 Free Software Foundation, Inc.
|
|
|
|
This file is part of GDB.
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "defs.h"
|
|
#include "command.h"
|
|
#include "gdbcmd.h"
|
|
#include "target.h"
|
|
#include "observer.h"
|
|
#include <sys/procfs.h>
|
|
#include "gregset.h"
|
|
#include "regcache.h"
|
|
#include "inferior.h"
|
|
#include "gdbthread.h"
|
|
|
|
#include <pthread_debug.h>
|
|
|
|
/* Print debugging traces if set to non-zero. */
|
|
static int debug_dec_thread = 0;
|
|
|
|
/* Non-zero if the dec-thread layer is active. */
|
|
static int dec_thread_active = 0;
|
|
|
|
/* The pthread_debug context. */
|
|
pthreadDebugContext_t debug_context;
|
|
|
|
/* The dec-thread target_ops structure. */
|
|
static struct target_ops dec_thread_ops;
|
|
|
|
/* A copy of the target_ops over which our dec_thread_ops is pushed. */
|
|
static struct target_ops base_target;
|
|
|
|
/* Print a debug trace if DEBUG_DEC_THREAD is set (its value is adjusted
|
|
by the user using "set debug dec-thread ..."). */
|
|
|
|
static void
|
|
debug (char *format, ...)
|
|
{
|
|
if (debug_dec_thread)
|
|
{
|
|
va_list args;
|
|
|
|
va_start (args, format);
|
|
printf_unfiltered ("DEC Threads: ");
|
|
vprintf_unfiltered (format, args);
|
|
printf_unfiltered ("\n");
|
|
va_end (args);
|
|
}
|
|
}
|
|
|
|
/* pthread debug callbacks. */
|
|
|
|
static int
|
|
suspend_clbk (void *caller_context)
|
|
{
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static int
|
|
resume_clbk (void *caller_context)
|
|
{
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static int
|
|
hold_clbk (void *caller_context, pthreadDebugKId_t kernel_tid)
|
|
{
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static int
|
|
unhold_clbk (void *caller_context, pthreadDebugKId_t kernel_tid)
|
|
{
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static int
|
|
read_clbk (void *caller_context, void *address, void *buffer,
|
|
unsigned long size)
|
|
{
|
|
int status = target_read_memory ((CORE_ADDR) address, buffer, size);
|
|
|
|
if (status != 0)
|
|
return EINVAL;
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static int
|
|
write_clbk (void *caller_context, void *address, void *buffer,
|
|
unsigned long size)
|
|
{
|
|
int status = target_write_memory ((CORE_ADDR) address, buffer, size);
|
|
|
|
if (status != 0)
|
|
return EINVAL;
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
/* Get integer regs */
|
|
|
|
static int
|
|
get_reg_clbk(void *caller_context, pthreadDebugGetRegRtn_t regs,
|
|
pthreadDebugKId_t kernel_tid)
|
|
{
|
|
debug ("get_reg_clbk");
|
|
|
|
/* Not sure that we actually need to do anything in this callback. */
|
|
return ESUCCESS;
|
|
}
|
|
|
|
/* Set integer regs */
|
|
|
|
static int
|
|
set_reg_clbk(void *caller_context, const pthreadDebugRegs_t *regs,
|
|
pthreadDebugKId_t kernel_tid)
|
|
{
|
|
debug ("set_reg_clbk");
|
|
|
|
/* Not sure that we actually need to do anything in this callback. */
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static int
|
|
output_clbk (void *caller_context, char *line)
|
|
{
|
|
printf_filtered ("%s\n", line);
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static int
|
|
error_clbk (void *caller_context, char *line)
|
|
{
|
|
fprintf_filtered (gdb_stderr, "%s\n", line);
|
|
return ESUCCESS;
|
|
}
|
|
|
|
/* Get floating-point regs. */
|
|
|
|
static int
|
|
get_fpreg_clbk (void *caller_context, pthreadDebugFregs_p fregs,
|
|
pthreadDebugKId_t kernel_tid)
|
|
{
|
|
debug ("get_fpreg_clbk");
|
|
|
|
/* Not sure that we actually need to do anything in this callback. */
|
|
return ESUCCESS;
|
|
}
|
|
|
|
/* Set floating-point regs. */
|
|
|
|
static int
|
|
set_fpreg_clbk (void *caller_context, const pthreadDebugFregs_t *fregs,
|
|
pthreadDebugKId_t kernel_tid)
|
|
{
|
|
debug ("set_fpreg_clbk");
|
|
|
|
/* Not sure that we actually need to do anything in this callback. */
|
|
return ESUCCESS;
|
|
}
|
|
|
|
static void *
|
|
malloc_clbk (void *caller_context, size_t size)
|
|
{
|
|
return xmalloc (size);
|
|
}
|
|
|
|
static void
|
|
free_clbk (void *caller_context, void *address)
|
|
{
|
|
xfree (address);
|
|
}
|
|
|
|
static int
|
|
kthdinfo_clbk (pthreadDebugClient_t caller_context,
|
|
pthreadDebugKId_t kernel_tid,
|
|
pthreadDebugKThreadInfo_p thread_info)
|
|
{
|
|
return ENOTSUP;
|
|
}
|
|
|
|
static int
|
|
speckthd_clbk (pthreadDebugClient_t caller_context,
|
|
pthreadDebugSpecialType_t type,
|
|
pthreadDebugKId_t *kernel_tid)
|
|
{
|
|
return ENOTSUP;
|
|
}
|
|
|
|
static pthreadDebugCallbacks_t debug_callbacks =
|
|
{
|
|
PTHREAD_DEBUG_VERSION,
|
|
(pthreadDebugGetMemRtn_t) read_clbk,
|
|
(pthreadDebugSetMemRtn_t) write_clbk,
|
|
suspend_clbk,
|
|
resume_clbk,
|
|
kthdinfo_clbk,
|
|
hold_clbk,
|
|
unhold_clbk,
|
|
(pthreadDebugGetFregRtn_t) get_fpreg_clbk,
|
|
(pthreadDebugSetFregRtn_t) set_fpreg_clbk,
|
|
(pthreadDebugGetRegRtn_t) get_reg_clbk,
|
|
(pthreadDebugSetRegRtn_t) set_reg_clbk,
|
|
(pthreadDebugOutputRtn_t) output_clbk,
|
|
(pthreadDebugOutputRtn_t) error_clbk,
|
|
malloc_clbk,
|
|
free_clbk,
|
|
speckthd_clbk
|
|
};
|
|
|
|
/* Activate thread support if appropriate. Do nothing if thread
|
|
support is already active. */
|
|
|
|
static void
|
|
enable_dec_thread (void)
|
|
{
|
|
struct minimal_symbol *msym;
|
|
void* caller_context;
|
|
int status;
|
|
|
|
/* If already active, nothing more to do. */
|
|
if (dec_thread_active)
|
|
return;
|
|
|
|
msym = lookup_minimal_symbol ("__pthread_dbg_symtable", NULL, NULL);
|
|
if (msym == NULL)
|
|
{
|
|
debug ("enable_dec_thread: No __pthread_dbg_symtable");
|
|
return;
|
|
}
|
|
|
|
status = pthreadDebugContextInit (&caller_context, &debug_callbacks,
|
|
(void *) SYMBOL_VALUE_ADDRESS (msym),
|
|
&debug_context);
|
|
if (status != ESUCCESS)
|
|
{
|
|
debug ("enable_dec_thread: pthreadDebugContextInit -> %d",
|
|
status);
|
|
return;
|
|
}
|
|
|
|
base_target = current_target;
|
|
push_target (&dec_thread_ops);
|
|
dec_thread_active = 1;
|
|
|
|
debug ("enable_dec_thread: Thread support enabled.");
|
|
}
|
|
|
|
/* Deactivate thread support. Do nothing is thread support is
|
|
already inactive. */
|
|
|
|
static void
|
|
disable_dec_thread (void)
|
|
{
|
|
if (!dec_thread_active)
|
|
return;
|
|
|
|
pthreadDebugContextDestroy (debug_context);
|
|
unpush_target (&dec_thread_ops);
|
|
dec_thread_active = 0;
|
|
}
|
|
|
|
/* A structure that contains a thread ID and is associated
|
|
pthreadDebugThreadInfo_t data. */
|
|
|
|
struct dec_thread_info
|
|
{
|
|
pthreadDebugId_t thread;
|
|
pthreadDebugThreadInfo_t info;
|
|
};
|
|
typedef struct dec_thread_info dec_thread_info_s;
|
|
|
|
/* The list of user threads. */
|
|
|
|
DEF_VEC_O (dec_thread_info_s);
|
|
VEC(dec_thread_info_s) *dec_thread_list;
|
|
|
|
/* Release the memory used by the given VECP thread list pointer.
|
|
Then set *VECP to NULL. */
|
|
|
|
static void
|
|
free_dec_thread_info_vec (VEC(dec_thread_info_s) **vecp)
|
|
{
|
|
int i;
|
|
struct dec_thread_info *item;
|
|
VEC(dec_thread_info_s) *vec = *vecp;
|
|
|
|
for (i = 0; VEC_iterate (dec_thread_info_s, vec, i, item); i++)
|
|
xfree (item);
|
|
VEC_free (dec_thread_info_s, vec);
|
|
*vecp = NULL;
|
|
}
|
|
|
|
/* Return a thread's ptid given its associated INFO. */
|
|
|
|
static ptid_t
|
|
ptid_build_from_info (struct dec_thread_info info)
|
|
{
|
|
int pid = ptid_get_pid (inferior_ptid);
|
|
|
|
return ptid_build (pid, 0, (long) info.thread);
|
|
}
|
|
|
|
/* Recompute the list of user threads and store the result in
|
|
DEC_THREAD_LIST. */
|
|
|
|
static void
|
|
update_dec_thread_list (void)
|
|
{
|
|
pthreadDebugId_t thread;
|
|
pthreadDebugThreadInfo_t info;
|
|
int res;
|
|
|
|
free_dec_thread_info_vec (&dec_thread_list);
|
|
res = pthreadDebugThdSeqInit (debug_context, &thread);
|
|
while (res == ESUCCESS)
|
|
{
|
|
|
|
res = pthreadDebugThdGetInfo (debug_context, thread, &info);
|
|
if (res != ESUCCESS)
|
|
warning (_("unable to get thread info, ignoring thread %ld"),
|
|
thread);
|
|
else if (info.kind == PTHREAD_DEBUG_THD_KIND_INITIAL
|
|
|| info.kind == PTHREAD_DEBUG_THD_KIND_NORMAL)
|
|
{
|
|
struct dec_thread_info *item =
|
|
xmalloc (sizeof (struct dec_thread_info));
|
|
|
|
item->thread = thread;
|
|
item->info = info;
|
|
VEC_safe_push (dec_thread_info_s, dec_thread_list, item);
|
|
}
|
|
res = pthreadDebugThdSeqNext (debug_context, &thread);
|
|
}
|
|
pthreadDebugThdSeqDestroy (debug_context);
|
|
}
|
|
|
|
/* A callback to count the number of threads known to GDB. */
|
|
|
|
static int
|
|
dec_thread_count_gdb_threads (struct thread_info *ignored, void *context)
|
|
{
|
|
int *count = (int *) context;
|
|
|
|
*count++;
|
|
return 0;
|
|
}
|
|
|
|
/* A callback that saves the given thread INFO at the end of an
|
|
array. The end of the array is given in the CONTEXT and is
|
|
incremented once the info has been added. */
|
|
|
|
static int
|
|
dec_thread_add_gdb_thread (struct thread_info *info, void *context)
|
|
{
|
|
struct thread_info ***listp = (struct thread_info ***) context;
|
|
|
|
**listp = info;
|
|
*listp++;
|
|
return 0;
|
|
}
|
|
|
|
/* Resynchronize the list of threads known by GDB with the actual
|
|
list of threads reported by libpthread_debug. */
|
|
|
|
static void
|
|
resync_thread_list (void)
|
|
{
|
|
int i;
|
|
struct dec_thread_info *info;
|
|
int num_gdb_threads = 0;
|
|
struct thread_info **gdb_thread_list;
|
|
struct thread_info **next_thread_info;
|
|
|
|
update_dec_thread_list ();
|
|
|
|
/* Add new threads. */
|
|
|
|
for (i = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, i, info);
|
|
i++)
|
|
{
|
|
ptid_t ptid = ptid_build_from_info (*info);
|
|
|
|
if (!in_thread_list (ptid))
|
|
add_thread (ptid);
|
|
}
|
|
|
|
/* Remove threads that no longer exist. To help with the search,
|
|
we build an array of GDB threads, and then iterate over this
|
|
array. */
|
|
|
|
iterate_over_threads (dec_thread_count_gdb_threads,
|
|
(void *) &num_gdb_threads);
|
|
gdb_thread_list = alloca (num_gdb_threads * sizeof (struct thread_info *));
|
|
next_thread_info = gdb_thread_list;
|
|
iterate_over_threads (dec_thread_add_gdb_thread, (void *) &next_thread_info);
|
|
for (i = 0; i < num_gdb_threads; i++)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, j, info);
|
|
j++)
|
|
if (ptid_equal (gdb_thread_list[i]->ptid,
|
|
ptid_build_from_info (*info)))
|
|
break;
|
|
delete_thread (gdb_thread_list[i]->ptid);
|
|
}
|
|
}
|
|
|
|
/* The "to_detach" method of the dec_thread_ops. */
|
|
|
|
static void
|
|
dec_thread_detach (char *args, int from_tty)
|
|
{
|
|
debug ("dec_thread_detach");
|
|
|
|
disable_dec_thread ();
|
|
base_target.to_detach (args, from_tty);
|
|
}
|
|
|
|
/* Return the ptid of the thread that is currently active. */
|
|
|
|
static ptid_t
|
|
get_active_ptid (void)
|
|
{
|
|
int i;
|
|
struct dec_thread_info *info;
|
|
|
|
for (i = 0; VEC_iterate (dec_thread_info_s, dec_thread_list, i, info);
|
|
i++)
|
|
if (info->info.state == PTHREAD_DEBUG_STATE_RUNNING)
|
|
return ptid_build_from_info (*info);
|
|
|
|
/* No active thread found. This can happen when the program
|
|
has just exited. */
|
|
return null_ptid;
|
|
}
|
|
|
|
/* The "to_wait" method of the dec_thread_ops. */
|
|
|
|
static ptid_t
|
|
dec_thread_wait (ptid_t ptid, struct target_waitstatus *status)
|
|
{
|
|
ptid_t active_ptid;
|
|
|
|
debug ("dec_thread_wait");
|
|
|
|
ptid = base_target.to_wait (ptid, status);
|
|
|
|
/* The ptid returned by the base_target is the ptid of the process.
|
|
We need to find which thread is currently active and return its
|
|
ptid. */
|
|
resync_thread_list ();
|
|
active_ptid = get_active_ptid ();
|
|
if (ptid_equal (active_ptid, null_ptid))
|
|
return ptid;
|
|
return active_ptid;
|
|
}
|
|
|
|
/* Fetch the general purpose and floating point registers for the given
|
|
thread TID, and store the result in GREGSET and FPREGSET. Return
|
|
zero if successful. */
|
|
|
|
static int
|
|
dec_thread_get_regsets (pthreadDebugId_t tid, gdb_gregset_t *gregset,
|
|
gdb_fpregset_t *fpregset)
|
|
{
|
|
int res;
|
|
pthreadDebugRegs_t regs;
|
|
pthreadDebugFregs_t fregs;
|
|
|
|
res = pthreadDebugThdGetReg (debug_context, tid, ®s);
|
|
if (res != ESUCCESS)
|
|
{
|
|
debug ("dec_thread_fetch_registers: pthreadDebugThdGetReg -> %d", res);
|
|
return -1;
|
|
}
|
|
memcpy (gregset->regs, ®s, sizeof (regs));
|
|
|
|
res = pthreadDebugThdGetFreg (debug_context, tid, &fregs);
|
|
if (res != ESUCCESS)
|
|
{
|
|
debug ("dec_thread_fetch_registers: pthreadDebugThdGetFreg -> %d", res);
|
|
return -1;
|
|
}
|
|
memcpy (fpregset->regs, &fregs, sizeof (fregs));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The "to_fetch_registers" method of the dec_thread_ops.
|
|
|
|
Because the dec-thread debug API doesn't allow us to fetch
|
|
only one register, we simply ignore regno and fetch+supply all
|
|
registers. */
|
|
|
|
static void
|
|
dec_thread_fetch_registers (struct regcache *regcache, int regno)
|
|
{
|
|
pthreadDebugId_t tid = ptid_get_tid (inferior_ptid);
|
|
gregset_t gregset;
|
|
fpregset_t fpregset;
|
|
int res;
|
|
|
|
debug ("dec_thread_fetch_registers (tid=%ld, regno=%d)", tid, regno);
|
|
|
|
|
|
if (tid == 0 || ptid_equal (inferior_ptid, get_active_ptid ()))
|
|
{
|
|
base_target.to_fetch_registers (regcache, regno);
|
|
return;
|
|
}
|
|
|
|
res = dec_thread_get_regsets (tid, &gregset, &fpregset);
|
|
if (res != 0)
|
|
return;
|
|
|
|
supply_gregset (regcache, &gregset);
|
|
supply_fpregset (regcache, &fpregset);
|
|
}
|
|
|
|
/* Store the registers given in GREGSET and FPREGSET into the associated
|
|
general purpose and floating point registers of thread TID. Return
|
|
zero if successful. */
|
|
|
|
static int
|
|
dec_thread_set_regsets (pthreadDebugId_t tid, gdb_gregset_t gregset,
|
|
gdb_fpregset_t fpregset)
|
|
{
|
|
int res;
|
|
pthreadDebugRegs_t regs;
|
|
pthreadDebugFregs_t fregs;
|
|
|
|
memcpy (®s, gregset.regs, sizeof (regs));
|
|
res = pthreadDebugThdSetReg (debug_context, tid, ®s);
|
|
if (res != ESUCCESS)
|
|
{
|
|
debug ("dec_thread_fetch_registers: pthreadDebugThdSetReg -> %d", res);
|
|
return -1;
|
|
}
|
|
|
|
memcpy (&fregs, fpregset.regs, sizeof (fregs));
|
|
res = pthreadDebugThdSetFreg (debug_context, tid, &fregs);
|
|
if (res != ESUCCESS)
|
|
{
|
|
debug ("dec_thread_fetch_registers: pthreadDebugThdSetFreg -> %d", res);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The "to_store_registers" method of the dec_thread_ops.
|
|
|
|
Because the dec-thread debug API doesn't allow us to store
|
|
just one register, we store all the registers. */
|
|
|
|
static void
|
|
dec_thread_store_registers (struct regcache *regcache, int regno)
|
|
{
|
|
pthreadDebugId_t tid = ptid_get_tid (inferior_ptid);
|
|
gregset_t gregset;
|
|
fpregset_t fpregset;
|
|
int res;
|
|
|
|
debug ("dec_thread_store_registers (tid=%ld, regno=%d)", tid, regno);
|
|
|
|
if (tid == 0 || ptid_equal (inferior_ptid, get_active_ptid ()))
|
|
{
|
|
base_target.to_store_registers (regcache, regno);
|
|
return;
|
|
}
|
|
|
|
/* FIXME: brobecker/2008-05-28: I wonder if we could simply check
|
|
in which register set the register is and then only store the
|
|
registers for that register set, instead of storing both register
|
|
sets. */
|
|
fill_gregset (regcache, &gregset, -1);
|
|
fill_fpregset (regcache, &fpregset, -1);
|
|
|
|
res = dec_thread_set_regsets (tid, gregset, fpregset);
|
|
if (res != 0)
|
|
warning (_("failed to store registers."));
|
|
}
|
|
|
|
/* The "to_mourn_inferior" method of the dec_thread_ops. */
|
|
|
|
static void
|
|
dec_thread_mourn_inferior (void)
|
|
{
|
|
debug ("dec_thread_mourn_inferior");
|
|
|
|
disable_dec_thread ();
|
|
base_target.to_mourn_inferior ();
|
|
}
|
|
|
|
/* The "to_thread_alive" method of the dec_thread_ops. */
|
|
static int
|
|
dec_thread_thread_alive (ptid_t ptid)
|
|
{
|
|
debug ("dec_thread_thread_alive (tid=%ld)", ptid_get_tid (ptid));
|
|
|
|
/* The thread list maintained by GDB is up to date, since we update
|
|
it everytime we stop. So check this list. */
|
|
return in_thread_list (ptid);
|
|
}
|
|
|
|
/* The "to_pid_to_str" method of the dec_thread_ops. */
|
|
|
|
static char *
|
|
dec_thread_pid_to_str (ptid_t ptid)
|
|
{
|
|
static char *ret = NULL;
|
|
|
|
if (ptid_get_tid (ptid) == 0)
|
|
return base_target.to_pid_to_str (ptid);
|
|
|
|
/* Free previous return value; a new one will be allocated by
|
|
xstrprintf(). */
|
|
xfree (ret);
|
|
|
|
ret = xstrprintf (_("Thread %ld"), ptid_get_tid (ptid));
|
|
return ret;
|
|
}
|
|
|
|
/* A "new-objfile" observer. Used to activate/deactivate dec-thread
|
|
support. */
|
|
|
|
static void
|
|
dec_thread_new_objfile_observer (struct objfile *objfile)
|
|
{
|
|
if (objfile != NULL)
|
|
enable_dec_thread ();
|
|
else
|
|
disable_dec_thread ();
|
|
}
|
|
|
|
static void
|
|
init_dec_thread_ops (void)
|
|
{
|
|
dec_thread_ops.to_shortname = "dec-threads";
|
|
dec_thread_ops.to_longname = _("DEC threads support");
|
|
dec_thread_ops.to_doc = _("DEC threads support");
|
|
dec_thread_ops.to_detach = dec_thread_detach;
|
|
dec_thread_ops.to_wait = dec_thread_wait;
|
|
dec_thread_ops.to_fetch_registers = dec_thread_fetch_registers;
|
|
dec_thread_ops.to_store_registers = dec_thread_store_registers;
|
|
dec_thread_ops.to_mourn_inferior = dec_thread_mourn_inferior;
|
|
dec_thread_ops.to_thread_alive = dec_thread_thread_alive;
|
|
dec_thread_ops.to_pid_to_str = dec_thread_pid_to_str;
|
|
dec_thread_ops.to_stratum = thread_stratum;
|
|
dec_thread_ops.to_magic = OPS_MAGIC;
|
|
}
|
|
|
|
void
|
|
_initialize_dec_thread (void)
|
|
{
|
|
init_dec_thread_ops ();
|
|
add_target (&dec_thread_ops);
|
|
|
|
observer_attach_new_objfile (dec_thread_new_objfile_observer);
|
|
|
|
add_setshow_boolean_cmd ("dec-thread", class_maintenance, &debug_dec_thread,
|
|
_("Set debugging of DEC threads module."),
|
|
_("Show debugging of DEC threads module."),
|
|
_("Enables debugging output (used to debug GDB)."),
|
|
NULL, NULL,
|
|
&setdebuglist, &showdebuglist);
|
|
}
|