The pthread_dbg library provides an implementation of the standard POSIX threads library debugging facilities. The NetBSD implementation is based on 1:1 thread model, therefore each pthread(3) has a kernel thread, called a light-weight process (LWP). Note that the system private thread interfaces upon which the pthread(3) library is built are subject to change without notice. In order to remain compatible with future NetBSD releases, programs must be linked against the dynamic version of the thread library. Statically linked programs using the POSIX threads framework may not work when run on a future version of the system. The pthread_dbg library is designed to be used in debuggers and to control and introspect the NetBSD implementation of the POSIX threads. Software may use native LWP threads without pthread(3) layer, in that case pthread_dbg cannot be used. Sponsored by <The NetBSD Foundation>
520 lines
11 KiB
C
520 lines
11 KiB
C
/* $NetBSD: pthread_dbg.c,v 1.1 2017/02/08 01:02:19 kamil Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2002 Wasabi Systems, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Written by Nathan J. Williams for Wasabi Systems, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed for the NetBSD Project by
|
|
* Wasabi Systems, Inc.
|
|
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
|
|
* or promote products derived from this software without specific
|
|
* prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__RCSID("$NetBSD: pthread_dbg.c,v 1.1 2017/02/08 01:02:19 kamil Exp $");
|
|
|
|
#define __EXPOSE_STACK 1
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/lock.h>
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <lwp.h>
|
|
|
|
#include <machine/reg.h>
|
|
|
|
#include "pthread.h"
|
|
#include "pthread_int.h"
|
|
#include "pthread_dbg.h"
|
|
#include "pthread_dbg_int.h"
|
|
|
|
#define PT_STACKMASK (proc->stackmask)
|
|
#define OFFSET(thread, field) \
|
|
(((char *)(thread)->addr) + offsetof(struct __pthread_st, field))
|
|
|
|
/* Compensate for debuggers that want a zero ID to be a sentinel */
|
|
#define TN_OFFSET 1
|
|
|
|
static int td__getthread(td_proc_t *proc, caddr_t addr, td_thread_t **threadp);
|
|
|
|
int
|
|
td_open(struct td_proc_callbacks_t *cb, void *arg, td_proc_t **procp)
|
|
{
|
|
td_proc_t *proc;
|
|
caddr_t addr;
|
|
int dbg;
|
|
int val;
|
|
|
|
proc = malloc(sizeof(*proc));
|
|
if (proc == NULL)
|
|
return TD_ERR_NOMEM;
|
|
proc->cb = cb;
|
|
proc->arg = arg;
|
|
|
|
val = LOOKUP(proc, "pthread__dbg", &addr);
|
|
if (val != 0) {
|
|
if (val == TD_ERR_NOSYM)
|
|
val = TD_ERR_NOLIB;
|
|
goto error;
|
|
}
|
|
proc->dbgaddr = addr;
|
|
|
|
val = LOOKUP(proc, "pthread__allqueue", &addr);
|
|
if (val != 0)
|
|
goto error;
|
|
proc->allqaddr = addr;
|
|
|
|
val = LOOKUP(proc, "pthread__tsd_list", &addr);
|
|
if (val != 0)
|
|
goto error;
|
|
proc->tsdlistaddr = addr;
|
|
|
|
val = LOOKUP(proc, "pthread__tsd_destructors", &addr);
|
|
if (val != 0)
|
|
goto error;
|
|
proc->tsddestaddr = addr;
|
|
|
|
val = READ(proc, proc->dbgaddr, &dbg, sizeof(int));
|
|
if (val != 0)
|
|
goto error;
|
|
|
|
if (dbg != 0) {
|
|
/* Another instance of libpthread_dbg is already attached. */
|
|
val = TD_ERR_INUSE;
|
|
goto error;
|
|
}
|
|
|
|
val = LOOKUP(proc, "pthread__stacksize_lg", &addr);
|
|
if (val == 0)
|
|
proc->stacksizeaddr = addr;
|
|
else
|
|
proc->stacksizeaddr = NULL;
|
|
proc->stacksizelg = -1;
|
|
proc->stacksize = 0;
|
|
proc->stackmask = 0;
|
|
|
|
proc->regbuf = NULL;
|
|
proc->fpregbuf = NULL;
|
|
|
|
dbg = getpid();
|
|
/*
|
|
* If this fails it probably means we're debugging a core file and
|
|
* can't write to it.
|
|
* If it's something else we'll lose the next time we hit WRITE,
|
|
* but not before, and that's OK.
|
|
*/
|
|
WRITE(proc, proc->dbgaddr, &dbg, sizeof(int));
|
|
|
|
PTQ_INIT(&proc->threads);
|
|
|
|
*procp = proc;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
free(proc);
|
|
return val;
|
|
}
|
|
|
|
int
|
|
td_close(td_proc_t *proc)
|
|
{
|
|
int dbg;
|
|
td_thread_t *t, *next;
|
|
|
|
dbg = 0;
|
|
/*
|
|
* Error returns from this write are mot really a problem;
|
|
* the process doesn't exist any more.
|
|
*/
|
|
WRITE(proc, proc->dbgaddr, &dbg, sizeof(int));
|
|
|
|
/* Deallocate the list of thread structures */
|
|
for (t = PTQ_FIRST(&proc->threads); t; t = next) {
|
|
next = PTQ_NEXT(t, list);
|
|
PTQ_REMOVE(&proc->threads, t, list);
|
|
free(t);
|
|
}
|
|
if (proc->regbuf != NULL) {
|
|
free(proc->regbuf);
|
|
free(proc->fpregbuf);
|
|
}
|
|
|
|
free(proc);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
td_thr_iter(td_proc_t *proc, int (*call)(td_thread_t *, void *), void *callarg)
|
|
{
|
|
int val;
|
|
caddr_t next;
|
|
pthread_queue_t allq;
|
|
td_thread_t *thread;
|
|
|
|
val = READ(proc, proc->allqaddr, &allq, sizeof(allq));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
next = (void *)allq.ptqh_first;
|
|
while (next != NULL) {
|
|
val = td__getthread(proc, next, &thread);
|
|
if (val != 0)
|
|
return val;
|
|
val = (*call)(thread, callarg);
|
|
if (val != 0)
|
|
return 0;
|
|
|
|
val = READ(proc,
|
|
next + offsetof(struct __pthread_st, pt_allq.ptqe_next),
|
|
&next, sizeof(next));
|
|
if (val != 0)
|
|
return val;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_thr_info(td_thread_t *thread, td_thread_info_t *info)
|
|
{
|
|
int tmp, val;
|
|
|
|
val = READ(thread->proc, OFFSET(thread, pt_magic), &tmp, sizeof(tmp));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
if (tmp != PT_MAGIC)
|
|
return TD_ERR_BADTHREAD;
|
|
|
|
info->thread_addr = thread->addr;
|
|
if ((val = READ(thread->proc,
|
|
OFFSET(thread, pt_state), &tmp, sizeof(tmp))) != 0)
|
|
return val;
|
|
switch (tmp) {
|
|
case PT_STATE_RUNNING:
|
|
info->thread_state = TD_STATE_RUNNING;
|
|
break;
|
|
case PT_STATE_ZOMBIE:
|
|
info->thread_state = TD_STATE_ZOMBIE;
|
|
break;
|
|
case PT_STATE_DEAD:
|
|
info->thread_state = TD_STATE_DEAD;
|
|
break;
|
|
default:
|
|
info->thread_state = TD_STATE_UNKNOWN;
|
|
}
|
|
|
|
if ((val = READ(thread->proc, OFFSET(thread, pt_stack),
|
|
&info->thread_stack, sizeof(stack_t))) != 0)
|
|
return val;
|
|
|
|
if ((val = READ(thread->proc, OFFSET(thread, pt_errno),
|
|
&info->thread_errno, sizeof(info->thread_errno))) != 0)
|
|
return val;
|
|
|
|
if ((val = READ(thread->proc, OFFSET(thread, pt_lid),
|
|
&info->thread_id, sizeof(info->thread_id))) != 0)
|
|
return val;
|
|
|
|
info->thread_id += TN_OFFSET;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_thr_getname(td_thread_t *thread, char *name, int len)
|
|
{
|
|
int val, tmp;
|
|
caddr_t nameaddr;
|
|
|
|
|
|
val = READ(thread->proc, OFFSET(thread, pt_magic), &tmp, sizeof(tmp));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
if (tmp != PT_MAGIC)
|
|
return TD_ERR_BADTHREAD;
|
|
|
|
if ((val = READ(thread->proc, OFFSET(thread, pt_name),
|
|
&nameaddr, sizeof(nameaddr))) != 0)
|
|
return val;
|
|
|
|
if (nameaddr == 0)
|
|
name[0] = '\0';
|
|
else if ((val = READ(thread->proc, nameaddr,
|
|
name, (size_t)MIN(PTHREAD_MAX_NAMELEN_NP, len))) != 0)
|
|
return val;
|
|
|
|
if (len < PTHREAD_MAX_NAMELEN_NP)
|
|
name[len - 1] = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_thr_getregs(td_thread_t *thread, int regset, void *buf)
|
|
{
|
|
int tmp, val;
|
|
|
|
if ((val = READ(thread->proc, OFFSET(thread, pt_state),
|
|
&tmp, sizeof(tmp))) != 0)
|
|
return val;
|
|
|
|
switch (tmp) {
|
|
case PT_STATE_RUNNING:
|
|
/*
|
|
* The register state of the thread is live in the
|
|
* inferior process's register state.
|
|
*/
|
|
val = GETREGS(thread->proc, regset, thread->lwp, buf);
|
|
if (val != 0)
|
|
return val;
|
|
break;
|
|
case PT_STATE_ZOMBIE:
|
|
case PT_STATE_DEAD:
|
|
default:
|
|
return TD_ERR_BADTHREAD;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_thr_setregs(td_thread_t *thread, int regset, void *buf)
|
|
{
|
|
int val, tmp;
|
|
|
|
if ((val = READ(thread->proc, OFFSET(thread, pt_state),
|
|
&tmp, sizeof(tmp))) != 0)
|
|
return val;
|
|
|
|
switch (tmp) {
|
|
case PT_STATE_RUNNING:
|
|
/*
|
|
* The register state of the thread is live in the
|
|
* inferior process's register state.
|
|
*/
|
|
val = SETREGS(thread->proc, regset, thread->lwp, buf);
|
|
if (val != 0)
|
|
return val;
|
|
break;
|
|
case PT_STATE_ZOMBIE:
|
|
case PT_STATE_DEAD:
|
|
default:
|
|
return TD_ERR_BADTHREAD;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_map_pth2thr(td_proc_t *proc, pthread_t thread, td_thread_t **threadp)
|
|
{
|
|
int magic, val;
|
|
|
|
val = READ(proc, (caddr_t)&thread->pt_magic, &magic, sizeof(magic));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
if (magic != PT_MAGIC)
|
|
return TD_ERR_NOOBJ;
|
|
|
|
val = td__getthread(proc, (void *)thread, threadp);
|
|
if (val != 0)
|
|
return val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_map_id2thr(td_proc_t *proc, int threadid, td_thread_t **threadp)
|
|
{
|
|
int val, num;
|
|
caddr_t next;
|
|
pthread_queue_t allq;
|
|
td_thread_t *thread;
|
|
|
|
|
|
val = READ(proc, proc->allqaddr, &allq, sizeof(allq));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
/* Correct for offset */
|
|
threadid -= TN_OFFSET;
|
|
next = (void *)allq.ptqh_first;
|
|
while (next != NULL) {
|
|
val = READ(proc, next + offsetof(struct __pthread_st, pt_lid),
|
|
&num, sizeof(num));
|
|
|
|
if (num == threadid)
|
|
break;
|
|
|
|
val = READ(proc,
|
|
next + offsetof(struct __pthread_st, pt_allq.ptqe_next),
|
|
&next, sizeof(next));
|
|
if (val != 0)
|
|
return val;
|
|
}
|
|
|
|
if (next == 0) {
|
|
/* A matching thread was not found. */
|
|
return TD_ERR_NOOBJ;
|
|
}
|
|
|
|
val = td__getthread(proc, next, &thread);
|
|
if (val != 0)
|
|
return val;
|
|
*threadp = thread;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_tsd_iter(td_proc_t *proc,
|
|
int (*call)(pthread_key_t, void (*)(void *), void *), void *arg)
|
|
{
|
|
#ifdef notyet
|
|
int val;
|
|
int i;
|
|
void *allocated;
|
|
void (*destructor)(void *);
|
|
|
|
for (i = 0; i < pthread_keys_max; i++) {
|
|
val = READ(proc, proc->tsdlistaddr + i * sizeof(allocated),
|
|
&allocated, sizeof(allocated));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
if ((uintptr_t)allocated) {
|
|
val = READ(proc, proc->tsddestaddr +
|
|
i * sizeof(destructor),
|
|
&destructor, sizeof(destructor));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
val = (call)(i, destructor, arg);
|
|
if (val != 0)
|
|
return val;
|
|
}
|
|
}
|
|
#else
|
|
abort();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Suspend a thread from running */
|
|
int
|
|
td_thr_suspend(td_thread_t *thread)
|
|
{
|
|
int tmp, val;
|
|
|
|
/* validate the thread */
|
|
val = READ(thread->proc, OFFSET(thread, pt_magic), &tmp, sizeof(tmp));
|
|
if (val != 0)
|
|
return val;
|
|
if (tmp != PT_MAGIC)
|
|
return TD_ERR_BADTHREAD;
|
|
|
|
val = READ(thread->proc, OFFSET(thread, pt_lid), &tmp, sizeof(tmp));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
/* XXXLWP continue the sucker */;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Restore a suspended thread to its previous state */
|
|
int
|
|
td_thr_resume(td_thread_t *thread)
|
|
{
|
|
int tmp, val;
|
|
|
|
/* validate the thread */
|
|
val = READ(thread->proc, OFFSET(thread, pt_magic), &tmp, sizeof(tmp));
|
|
if (val != 0)
|
|
return val;
|
|
if (tmp != PT_MAGIC)
|
|
return TD_ERR_BADTHREAD;
|
|
|
|
val = READ(thread->proc, OFFSET(thread, pt_lid), &tmp, sizeof(tmp));
|
|
if (val != 0)
|
|
return val;
|
|
|
|
/* XXXLWP continue the sucker */;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
td__getthread(td_proc_t *proc, caddr_t addr, td_thread_t **threadp)
|
|
{
|
|
td_thread_t *thread;
|
|
|
|
/*
|
|
* Check if we've allocated a descriptor for this thread.
|
|
* Sadly, this makes iterating over a set of threads O(N^2)
|
|
* in the number of threads. More sophisticated data structures
|
|
* can wait.
|
|
*/
|
|
PTQ_FOREACH(thread, &proc->threads, list) {
|
|
if (thread->addr == addr)
|
|
break;
|
|
}
|
|
if (thread == NULL) {
|
|
thread = malloc(sizeof(*thread));
|
|
if (thread == NULL)
|
|
return TD_ERR_NOMEM;
|
|
thread->proc = proc;
|
|
thread->addr = addr;
|
|
thread->lwp = 0;
|
|
PTQ_INSERT_HEAD(&proc->threads, thread, list);
|
|
}
|
|
|
|
*threadp = thread;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
td_thr_tsd(td_thread_t *thread, pthread_key_t key, void **value)
|
|
{
|
|
int val;
|
|
|
|
val = READ(thread->proc, OFFSET(thread, pt_specific) +
|
|
key * sizeof(void *), value, sizeof(*value));
|
|
|
|
return val;
|
|
}
|