diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libthread_db/Makefile | 5 | ||||
-rw-r--r-- | lib/libthread_db/include/thread_db.h | 287 | ||||
-rw-r--r-- | lib/libthread_db/include/thread_db_int.h | 93 | ||||
-rw-r--r-- | lib/libthread_db/pthread/Makefile | 19 | ||||
-rw-r--r-- | lib/libthread_db/pthread/pthread_db.c | 1087 | ||||
-rw-r--r-- | lib/libthread_db/pthread/pthread_db.h | 60 | ||||
-rw-r--r-- | lib/libthread_db/pthread/pthread_db_i386.c | 105 | ||||
-rw-r--r-- | lib/libthread_db/src/Makefile | 15 | ||||
-rw-r--r-- | lib/libthread_db/src/thread_db.c | 359 |
9 files changed, 2030 insertions, 0 deletions
diff --git a/lib/libthread_db/Makefile b/lib/libthread_db/Makefile new file mode 100644 index 0000000..e629598 --- /dev/null +++ b/lib/libthread_db/Makefile @@ -0,0 +1,5 @@ +# $FreeBSD$ + +SUBDIR=pthread src + +.include <bsd.subdir.mk> diff --git a/lib/libthread_db/include/thread_db.h b/lib/libthread_db/include/thread_db.h new file mode 100644 index 0000000..e5f0c32 --- /dev/null +++ b/lib/libthread_db/include/thread_db.h @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2004 David Xu <davidxu@freebsd.org> + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _THREAD_DB_H_ +#define _THREAD_DB_H_ + +#include <sys/types.h> +#include <pthread.h> + +typedef enum +{ + TD_OK, + TD_ERR, + TD_NOTHR, + TD_NOSV, + TD_NOLWP, + TD_BADPH, + TD_BADTH, + TD_BADSH, + TD_BADTA, + TD_BADKEY, + TD_NOMSG, + TD_NOFPREGS, + TD_NOLIBTHREAD, + TD_NOEVENT, + TD_NOCAPAB, + TD_DBERR, + TD_NOAPLIC, + TD_NOTSD, + TD_MALLOC, + TD_PARTIALREG, + TD_NOXREGS +} td_err_e; + +typedef enum +{ + TD_THR_ANY_STATE, + TD_THR_UNKNOWN, + TD_THR_STOPPED, + TD_THR_RUN, + TD_THR_ACTIVE, + TD_THR_ZOMBIE, + TD_THR_SLEEP, + TD_THR_STOPPED_ASLEEP +} td_thr_state_e; + +typedef enum +{ + TD_THR_ANY_TYPE, + TD_THR_USER, + TD_THR_SYSTEM +} td_thr_type_e; + +typedef long thread_t; +typedef pthread_key_t thread_key_t; +typedef struct td_thragent td_thragent_t; + +typedef struct td_thrhandle +{ + td_thragent_t *th_ta_p; + thread_t th_unique; + int th_ta_data; +} td_thrhandle_t; + +/* Flags for `td_ta_thr_iter'. */ +#define TD_THR_ANY_USER_FLAGS 0xffffffff +#define TD_THR_LOWEST_PRIORITY 0 +#define TD_SIGNO_MASK NULL + +typedef uint32_t td_thr_events_t; + +typedef enum +{ + TD_ALL_EVENTS, + TD_EVENT_NONE = TD_ALL_EVENTS, + TD_CREATE, + TD_DEATH, + TD_REAP, + TD_READY, + TD_SLEEP, + TD_SWITCHTO, + TD_SWITCHFROM, + TD_LOCK_TRY, + TD_CATCHSIG, + TD_IDLE, + TD_PREEMPT, + TD_PRI_INHERIT, + TD_CONCURRENCY, + TD_TIMEOUT, + TD_MIN_EVENT_NUM = TD_READY, + TD_MAX_EVENT_NUM = TD_TIMEOUT, + TD_EVENTS_ENABLE = 31 +} td_event_e; + +typedef enum +{ + NOTIFY_BPT, + NOTIFY_AUTOBPT, + NOTIFY_SYSCALL +} td_notify_e; + +typedef struct td_notify +{ + td_notify_e type; + union { + psaddr_t bptaddr; + int syscallno; + } u; +} td_notify_t; + +typedef struct td_event_msg +{ + td_event_e event; + const td_thrhandle_t *th_p; + union { +#if 0 + td_synchandle_t *sh; +#endif + uintptr_t data; + } msg; +} td_event_msg_t; + +/* Structure containing event data available in each thread structure. */ +typedef struct +{ + td_thr_events_t eventmask; /* Mask of enabled events. */ + td_event_e eventnum; /* Number of last event. */ + void *eventdata; /* Data associated with event. */ +} td_eventbuf_t; + +/* Gathered statistics about the process. */ +typedef struct td_ta_stats +{ + int nthreads; /* Total number of threads in use. */ + int r_concurrency; /* Concurrency level requested by user. */ + int nrunnable_num; /* Average runnable threads, numerator. */ + int nrunnable_den; /* Average runnable threads, denominator. */ + int a_concurrency_num; /* Achieved concurrency level, numerator. */ + int a_concurrency_den; /* Achieved concurrency level, denominator. */ + int nlwps_num; /* Average number of processes in use, + numerator. */ + int nlwps_den; /* Average number of processes in use, + denominator. */ + int nidle_num; /* Average number of idling processes, + numerator. */ + int nidle_den; /* Average number of idling processes, + denominator. */ +} td_ta_stats_t; + +static inline void +td_event_emptyset(td_thr_events_t *setp) +{ + *setp = 0; +} + +static inline void +td_event_fillset(td_thr_events_t *setp) +{ + *setp = 0xFFFFFFFF; +} + +static inline void +td_event_addset(td_thr_events_t *setp, int n) +{ + *setp |= (1 << (n-1)); +} + +static inline void +td_event_delset(td_thr_events_t *setp, int n) +{ + *setp &= ~(1 << (n-1)); +} + +static inline int +td_eventismember(td_thr_events_t *setp, int n) +{ + return (*setp & (1 << (n-1)) ? 1 : 0); +} + +static inline int +td_eventisempty(td_thr_events_t *setp) +{ + return (*setp == 0); +} + +typedef int td_thr_iter_f(const td_thrhandle_t *, void *); +typedef int td_key_iter_f(thread_key_t, void (*) (void *), void *); + +struct ps_prochandle; + +typedef struct td_thrinfo +{ + td_thragent_t *ti_ta_p; + unsigned int ti_user_flags; + thread_t ti_tid; + char *ti_tls; + psaddr_t ti_startfunc; + psaddr_t ti_stkbase; + long int ti_stksize; + psaddr_t ti_ro_area; + int ti_ro_size; + td_thr_state_e ti_state; + unsigned char ti_db_suspended; + td_thr_type_e ti_type; + intptr_t ti_pc; + intptr_t ti_sp; + short int ti_flags; + int ti_pri; + lwpid_t ti_lid; + sigset_t ti_sigmask; + unsigned char ti_traceme; + unsigned char ti_preemptflag; + unsigned char ti_pirecflag; + sigset_t ti_pending; + td_thr_events_t ti_events; +} td_thrinfo_t; + +td_err_e td_init(void); +td_err_e td_log(void); +td_err_e td_ta_new(struct ps_prochandle *, td_thragent_t **); +td_err_e td_ta_delete(td_thragent_t *); +td_err_e td_ta_get_nthreads(const td_thragent_t *, int *); +td_err_e td_ta_get_ph(const td_thragent_t *, struct ps_prochandle **); +td_err_e td_ta_map_id2thr(const td_thragent_t *, thread_t, td_thrhandle_t *); +td_err_e td_ta_map_lwp2thr(const td_thragent_t *, lwpid_t lwpid, + td_thrhandle_t *); +td_err_e td_ta_thr_iter(const td_thragent_t *, td_thr_iter_f *, void *, + td_thr_state_e, int, sigset_t *, unsigned int); +td_err_e td_ta_tsd_iter(const td_thragent_t *, td_key_iter_f *, void *); +td_err_e td_ta_event_addr(const td_thragent_t *, td_event_e , td_notify_t *); +td_err_e td_ta_set_event(const td_thragent_t *, td_thr_events_t *); +td_err_e td_ta_clear_event(const td_thragent_t *, td_thr_events_t *); +td_err_e td_ta_event_getmsg(const td_thragent_t *, td_event_msg_t *); +td_err_e td_ta_setconcurrency(const td_thragent_t *, int); +td_err_e td_ta_enable_stats(const td_thragent_t *, int); +td_err_e td_ta_reset_stats(const td_thragent_t *); +td_err_e td_ta_get_stats(const td_thragent_t *, td_ta_stats_t *); +td_err_e td_thr_validate(const td_thrhandle_t *); +td_err_e td_thr_get_info(const td_thrhandle_t *, td_thrinfo_t *); +td_err_e td_thr_getfpregs(const td_thrhandle_t *, prfpregset_t *); +td_err_e td_thr_getgregs(const td_thrhandle_t *, prgregset_t); +td_err_e td_thr_getxregs(const td_thrhandle_t *, void *); +td_err_e td_thr_getxregsize(const td_thrhandle_t *, int *); +td_err_e td_thr_setfpregs(const td_thrhandle_t *, const prfpregset_t *); +td_err_e td_thr_setgregs(const td_thrhandle_t *, prgregset_t); +td_err_e td_thr_setxregs(const td_thrhandle_t *, const void *); +td_err_e td_thr_event_enable(const td_thrhandle_t *, int); +td_err_e td_thr_set_event(const td_thrhandle_t *, td_thr_events_t *); +td_err_e td_thr_clear_event(const td_thrhandle_t *, td_thr_events_t *); +td_err_e td_thr_event_getmsg(const td_thrhandle_t *, td_event_msg_t *); +td_err_e td_thr_setprio(const td_thrhandle_t *, int); +td_err_e td_thr_setsigpending(const td_thrhandle_t *, unsigned char, + const sigset_t *); +td_err_e td_thr_sigsetmask(const td_thrhandle_t *, const sigset_t *); +td_err_e td_thr_tsd(const td_thrhandle_t *, const thread_key_t, void **); +td_err_e td_thr_dbsuspend(const td_thrhandle_t *); +td_err_e td_thr_dbresume(const td_thrhandle_t *); +td_err_e td_get_ta(int pid, td_thragent_t **); +td_err_e td_ta_activated(td_thragent_t *, int *); +td_err_e td_thr_sstep(td_thrhandle_t *, int); + +#endif diff --git a/lib/libthread_db/include/thread_db_int.h b/lib/libthread_db/include/thread_db_int.h new file mode 100644 index 0000000..177cb4a --- /dev/null +++ b/lib/libthread_db/include/thread_db_int.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2004 David Xu <davidxu@freebsd.org> + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _THREAD_DB_INT_H +#define _THREAD_DB_INT_H + +#include <sys/types.h> +#include <sys/queue.h> + +struct td_thragent { + struct ta_ops *ta_ops; + TAILQ_ENTRY(td_thragent) ta_next; +}; + +struct ta_ops { + td_err_e (*to_init)(void); + td_err_e (*to_ta_new)(struct ps_prochandle *, td_thragent_t **); + td_err_e (*to_ta_delete)(td_thragent_t *); + td_err_e (*to_ta_get_nthreads)(const td_thragent_t *, int *); + td_err_e (*to_ta_get_ph)(const td_thragent_t *, struct ps_prochandle **); + td_err_e (*to_ta_map_id2thr)(const td_thragent_t *, thread_t, td_thrhandle_t *); + td_err_e (*to_ta_map_lwp2thr)(const td_thragent_t *, lwpid_t lwpid, + td_thrhandle_t *); + td_err_e (*to_ta_thr_iter)(const td_thragent_t *, td_thr_iter_f *, void *, + td_thr_state_e, int, sigset_t *, unsigned int); + td_err_e (*to_ta_tsd_iter)(const td_thragent_t *, td_key_iter_f *, void *); + td_err_e (*to_ta_event_addr)(const td_thragent_t *, td_event_e , td_notify_t *); + td_err_e (*to_ta_set_event)(const td_thragent_t *, td_thr_events_t *); + td_err_e (*to_ta_clear_event)(const td_thragent_t *, td_thr_events_t *); + td_err_e (*to_ta_event_getmsg)(const td_thragent_t *, td_event_msg_t *); + td_err_e (*to_ta_setconcurrency)(const td_thragent_t *, int); + td_err_e (*to_ta_enable_stats)(const td_thragent_t *, int); + td_err_e (*to_ta_reset_stats)(const td_thragent_t *); + td_err_e (*to_ta_get_stats)(const td_thragent_t *, td_ta_stats_t *); + td_err_e (*to_thr_validate)(const td_thrhandle_t *); + td_err_e (*to_thr_get_info)(const td_thrhandle_t *, td_thrinfo_t *); + td_err_e (*to_thr_getfpregs)(const td_thrhandle_t *, prfpregset_t *); + td_err_e (*to_thr_getgregs)(const td_thrhandle_t *, prgregset_t); + td_err_e (*to_thr_getxregs)(const td_thrhandle_t *, void *); + td_err_e (*to_thr_getxregsize)(const td_thrhandle_t *, int *); + td_err_e (*to_thr_setfpregs)(const td_thrhandle_t *, const prfpregset_t *); + td_err_e (*to_thr_setgregs)(const td_thrhandle_t *, prgregset_t); + td_err_e (*to_thr_setxregs)(const td_thrhandle_t *, const void *); + td_err_e (*to_thr_event_enable)(const td_thrhandle_t *, int); + td_err_e (*to_thr_set_event)(const td_thrhandle_t *, td_thr_events_t *); + td_err_e (*to_thr_clear_event)(const td_thrhandle_t *, td_thr_events_t *); + td_err_e (*to_thr_event_getmsg)(const td_thrhandle_t *, td_event_msg_t *); + td_err_e (*to_thr_setprio)(const td_thrhandle_t *, int); + td_err_e (*to_thr_setsigpending)(const td_thrhandle_t *, unsigned char, + const sigset_t *); + td_err_e (*to_thr_sigsetmask)(const td_thrhandle_t *, const sigset_t *); + td_err_e (*to_thr_tsd)(const td_thrhandle_t *, const thread_key_t, void **); + td_err_e (*to_thr_dbsuspend)(const td_thrhandle_t *); + td_err_e (*to_thr_dbresume)(const td_thrhandle_t *); + td_err_e (*to_get_ta)(int pid, td_thragent_t **); + td_err_e (*to_ta_activated)(td_thragent_t *, int *); + td_err_e (*to_thr_sstep)(td_thrhandle_t *, int step); +}; + +#ifdef TD_DEBUG +#define TDBG(...) ps_plog(__VA_ARGS__) +#define TDBG_FUNC() ps_plog(__func__); ps_plog("\n") +#else +#define TDBG(...) +#define TDBG_FUNC() +#endif + +#endif diff --git a/lib/libthread_db/pthread/Makefile b/lib/libthread_db/pthread/Makefile new file mode 100644 index 0000000..cbd245d --- /dev/null +++ b/lib/libthread_db/pthread/Makefile @@ -0,0 +1,19 @@ +# $FreeBSD$ + +LIB=pthread_db +INTERNALLIB=yes +NOPROFILE=yes +NOPIC=yes +CFLAGS+=-DPTHREAD_KERNEL -fpic -DPIC +CFLAGS+=-I${.CURDIR}/../include \ + -I${.CURDIR}/../../libc/include -I${.CURDIR}/../../libpthread/thread \ + -I${.CURDIR}/../../../include +CFLAGS+=-I${.CURDIR}/../../libpthread/arch/${MACHINE_ARCH}/include +CFLAGS+=-I${.CURDIR}/../../libpthread/sys +CFLAGS+=-Wall + +.PATH: ${.CURDIR} + +SRCS+=pthread_db.c pthread_db_${MACHINE_ARCH}.c + +.include <bsd.lib.mk> diff --git a/lib/libthread_db/pthread/pthread_db.c b/lib/libthread_db/pthread/pthread_db.c new file mode 100644 index 0000000..e4f74a6 --- /dev/null +++ b/lib/libthread_db/pthread/pthread_db.c @@ -0,0 +1,1087 @@ +/* + * Copyright (c) 2004 David Xu <davidxu@freebsd.org> + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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> +__FBSDID("$FreeBSD$"); + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> +#include <thr_private.h> +#include <sys/types.h> +#include <sys/kse.h> +#include <sys/ptrace.h> +#include <proc_service.h> +#include <thread_db.h> + +#include "pthread_db.h" + +struct pt_map { + enum { + PT_NONE, + PT_USER, + PT_LWP + } type; + + union { + lwpid_t lwp; + psaddr_t thr; + }; +}; + +#define P2T(c) ps2td(c) + +static td_err_e pt_ta_activated(pt_thragent_t *ta, int *a); +static long pt_map_thread(const pt_thragent_t *ta, psaddr_t pt); +static long pt_map_lwp(const pt_thragent_t *ta, lwpid_t lwp); +static void pt_unmap_lwp(const pt_thragent_t *ta, lwpid_t lwp); +static int pt_validate(const td_thrhandle_t *th); + +static int +ps2td(int c) +{ + switch (c) { + case PS_OK: + return TD_OK; + case PS_ERR: + return TD_ERR; + case PS_BADPID: + return TD_BADPH; + case PS_BADLID: + return TD_NOLWP; + case PS_BADADDR: + return TD_ERR; + case PS_NOSYM: + return TD_NOLIBTHREAD; + case PS_NOFREGS: + return TD_NOFPREGS; + default: + return TD_ERR; + } +} + +static td_err_e +pt_init(void) +{ + pt_md_init(); + return (0); +} + +static td_err_e +pt_ta_new(struct ps_prochandle *ph, pt_thragent_t **pta) +{ +#define LOOKUP_SYM(proc, sym, addr) \ + ret = ps_pglobal_lookup(proc, NULL, sym, addr); \ + if (ret != 0) { \ + TDBG("can not find symbol: %s\n", sym); \ + ret = TD_NOLIBTHREAD; \ + goto error; \ + } + + pt_thragent_t *ta; + int dbg; + int ret; + + TDBG_FUNC(); + + ta = malloc(sizeof(pt_thragent_t)); + if (ta == NULL) + return (TD_MALLOC); + + ta->ph = ph; + ta->thread_activated = 0; + ta->map = NULL; + ta->map_len = 0; + + LOOKUP_SYM(ph, "_libkse_debug", &ta->libkse_debug_addr); + LOOKUP_SYM(ph, "_thread_list", &ta->thread_list_addr); + LOOKUP_SYM(ph, "_thread_activated", &ta->thread_activated_addr); + LOOKUP_SYM(ph, "_thread_active_threads",&ta->thread_active_threads_addr); + LOOKUP_SYM(ph, "_thread_keytable", &ta->thread_keytable_addr); + + dbg = getpid(); + /* + * If this fails it probably means we're debugging a core file and + * can't write to it. + */ + ps_pdwrite(ph, ta->libkse_debug_addr, &dbg, sizeof(int)); + *pta = ta; + return (0); + +error: + free(ta); + return (ret); +} + +static td_err_e +pt_ta_delete(pt_thragent_t *ta) +{ + int dbg; + + TDBG_FUNC(); + + dbg = 0; + /* + * Error returns from this write are not really a problem; + * the process doesn't exist any more. + */ + ps_pdwrite(ta->ph, ta->libkse_debug_addr, &dbg, sizeof(int)); + if (ta->map) + free(ta->map); + free(ta); + return (TD_OK); +} + +static td_err_e +pt_ta_get_nthreads (const pt_thragent_t *ta, int *np) +{ + int ret; + + TDBG_FUNC(); + + ret = ps_pdread(ta->ph, ta->thread_active_threads_addr, np, + sizeof(int)); + return (P2T(ret)); +} + +static td_err_e +pt_ta_get_ph(const pt_thragent_t *ta, struct ps_prochandle **ph) +{ + TDBG_FUNC(); + + *ph = ta->ph; + return (TD_OK); +} + +static td_err_e +pt_ta_map_id2thr(const pt_thragent_t *ta, thread_t id, td_thrhandle_t *th) +{ + prgregset_t gregs; + TAILQ_HEAD(, pthread) thread_list; + psaddr_t pt, tcb_addr; + lwpid_t lwp; + int ret; + + TDBG_FUNC(); + + if (id < 0 || id >= ta->map_len || ta->map[id].type == PT_NONE) + return (TD_NOTHR); + ret = ps_pdread(ta->ph, ta->thread_list_addr, &thread_list, + sizeof(thread_list)); + if (ret != 0) + return (P2T(ret)); + pt = (psaddr_t)thread_list.tqh_first; + if (ta->map[id].type == PT_LWP) { + /* + * if we are referencing a lwp, make sure it was not already + * mapped to user thread. + */ + while (pt != 0) { + ret = ps_pdread(ta->ph, + pt + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + ret = ps_pdread(ta->ph, + tcb_addr + offsetof(struct tcb, + tcb_tmbx.tm_lwp), + &lwp, sizeof(lwp)); + if (ret != 0) + return (P2T(ret)); + /* + * If the lwp was already mapped to userland thread, + * we shouldn't reference it directly in future. + */ + if (lwp == ta->map[id].lwp) { + ta->map[id].type = PT_NONE; + return (TD_NOTHR); + } + /* get next thread */ + ret = ps_pdread(ta->ph, + pt + offsetof(struct pthread, tle.tqe_next), + &pt, sizeof(pt)); + if (ret != 0) + return (P2T(ret)); + } + /* check lwp */ + ret = ptrace(PT_GETREGS, ta->map[id].lwp, (caddr_t)&gregs, 0); + if (ret != 0) { + /* no longer exists */ + ta->map[id].type = PT_NONE; + return (TD_NOTHR); + } + } else { + while (pt != 0 && ta->map[id].thr != pt) { + ret = ps_pdread(ta->ph, + pt + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + /* get next thread */ + ret = ps_pdread(ta->ph, + pt + offsetof(struct pthread, tle.tqe_next), + &pt, sizeof(pt)); + if (ret != 0) + return (P2T(ret)); + } + + if (pt == 0) { + /* no longer exists */ + ta->map[id].type = PT_NONE; + return (TD_NOTHR); + } + } + th->th_ta_p = (td_thragent_t *)ta; + th->th_unique = id; + return (TD_OK); +} + +static td_err_e +pt_ta_map_lwp2thr(const pt_thragent_t *ta, lwpid_t lwp, td_thrhandle_t *th) +{ + TAILQ_HEAD(, pthread) thread_list; + psaddr_t pt, ptr; + lwpid_t tmp_lwp; + int ret; + + TDBG_FUNC(); + + ret = ps_pdread(ta->ph, ta->thread_list_addr, &thread_list, + sizeof(thread_list)); + if (ret != 0) + return (P2T(ret)); + pt = (psaddr_t)thread_list.tqh_first; + while (pt != 0) { + ret = ps_pdread(ta->ph, pt + offsetof(struct pthread, tcb), + &ptr, sizeof(ptr)); + if (ret != 0) + return (P2T(ret)); + ptr += offsetof(struct tcb, tcb_tmbx.tm_lwp); + ret = ps_pdread(ta->ph, ptr, &tmp_lwp, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + if (tmp_lwp == lwp) { + th->th_ta_p = (td_thragent_t *)ta; + th->th_unique = pt_map_thread(ta, pt); + if (th->th_unique == -1) + return (TD_MALLOC); + pt_unmap_lwp(ta, lwp); + return (TD_OK); + } + + /* get next thread */ + ret = ps_pdread(ta->ph, + pt + offsetof(struct pthread, tle.tqe_next), + &pt, sizeof(pt)); + if (ret != 0) + return (P2T(ret)); + } + + return (TD_NOTHR); +} + +static td_err_e +pt_ta_thr_iter(const pt_thragent_t *ta, + td_thr_iter_f *callback, void *cbdata_p, + td_thr_state_e state, int ti_pri, + sigset_t *ti_sigmask_p, + unsigned int ti_user_flags) +{ + TAILQ_HEAD(, pthread) thread_list; + td_thrhandle_t th; + psaddr_t pt; + int ret, activated; + + TDBG_FUNC(); + + ret = pt_ta_activated((pt_thragent_t *)ta, &activated); + if (ret != 0) + return (P2T(ret)); + if (!activated) + return (0); + ret = ps_pdread(ta->ph, ta->thread_list_addr, &thread_list, + sizeof(thread_list)); + if (ret != 0) + return (P2T(ret)); + pt = (psaddr_t)thread_list.tqh_first; + while (pt != 0) { + th.th_ta_p = (td_thragent_t *)ta; + th.th_unique = pt_map_thread(ta, pt); + /* should we unmap lwp here ? */ + if (th.th_unique == -1) + return (TD_MALLOC); + if ((*callback)(&th, cbdata_p)) + return (TD_DBERR); + /* get next thread */ + ret = ps_pdread(ta->ph, + pt + offsetof(struct pthread, tle.tqe_next), + &pt, sizeof(pt)); + if (ret != 0) + return (P2T(ret)); + } + return (TD_OK); +} + +static td_err_e +pt_ta_tsd_iter(const pt_thragent_t *ta, td_key_iter_f *ki, void *arg) +{ + struct pthread_key keytable[PTHREAD_KEYS_MAX]; + int i, ret; + + TDBG_FUNC(); + + ret = ps_pdread(ta->ph, (psaddr_t)ta->thread_keytable_addr, keytable, + sizeof(keytable)); + if (ret != 0) + return (P2T(ret)); + + for (i = 0; i < PTHREAD_KEYS_MAX; i++) { + if (keytable[i].allocated) { + ret = (ki)(i, keytable[i].destructor, arg); + if (ret != 0) + return (TD_DBERR); + } + } + return (TD_OK); +} + +static td_err_e +pt_ta_event_addr(const pt_thragent_t *ta, td_event_e event, td_notify_t *ptr) +{ + TDBG_FUNC(); + return (TD_NOEVENT); +} + +static td_err_e +pt_ta_set_event(const pt_thragent_t *ta, td_thr_events_t *events) +{ + TDBG_FUNC(); + return (TD_ERR); +} + +static td_err_e +pt_ta_clear_event(const pt_thragent_t *ta, td_thr_events_t *events) +{ + TDBG_FUNC(); + return (TD_ERR); +} + +static td_err_e +pt_ta_event_getmsg(const pt_thragent_t *ta, td_event_msg_t *msg) +{ + TDBG_FUNC(); + return (TD_NOMSG); +} + +static td_err_e +pt_ta_setconcurrency(const pt_thragent_t *ta, int level) +{ + TDBG_FUNC(); + return (TD_OK); +} + +static td_err_e +pt_ta_enable_stats(const pt_thragent_t *ta, int enable) +{ + TDBG_FUNC(); + return (TD_OK); +} + +static td_err_e +pt_ta_reset_stats(const pt_thragent_t *ta) +{ + TDBG_FUNC(); + return (TD_OK); +} + +static td_err_e +pt_ta_get_stats(const pt_thragent_t *ta, td_ta_stats_t *statsp) +{ + TDBG_FUNC(); + return (TD_OK); +} + +static td_err_e +pt_thr_validate(const td_thrhandle_t *th) +{ + td_thrhandle_t temp; + int ret; + + TDBG_FUNC(); + + ret = pt_ta_map_id2thr((pt_thragent_t *)th->th_ta_p, th->th_unique, + &temp); + return (P2T(ret)); +} + +static td_err_e +pt_thr_get_info(const td_thrhandle_t *th, td_thrinfo_t *info) +{ + struct pthread pt; + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + psaddr_t tcb_addr; + uint32_t dflags; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + memset(info, 0, sizeof(*info)); + if (ta->map[th->th_unique].type == PT_LWP) { + info->ti_type = TD_THR_SYSTEM; + info->ti_lid = ta->map[th->th_unique].lwp; + info->ti_tid = th->th_unique; + info->ti_state = TD_THR_RUN; + info->ti_type = TD_THR_SYSTEM; + return (TD_OK); + } + + ret = ps_pdread(ta->ph, (psaddr_t)(ta->map[th->th_unique].thr), + &pt, sizeof(pt)); + if (ret != 0) + return (P2T(ret)); + if (pt.magic != THR_MAGIC) + return (TD_BADTH); + tcb_addr = (psaddr_t) pt.tcb; + ret = ps_pdread(ta->ph, + tcb_addr + offsetof(struct tcb, tcb_tmbx.tm_lwp), + &info->ti_lid, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + ret = ps_pdread(ta->ph, + tcb_addr + offsetof(struct tcb, tcb_tmbx.tm_dflags), + &dflags, sizeof(dflags)); + info->ti_ta_p = th->th_ta_p; + info->ti_tid = th->th_unique; + info->ti_tls = (char *)pt.specific; + info->ti_startfunc = (psaddr_t)pt.start_routine; + info->ti_stkbase = (psaddr_t) pt.attr.stackaddr_attr; + info->ti_stksize = pt.attr.stacksize_attr; + switch (pt.state) { + case PS_RUNNING: + info->ti_state = TD_THR_RUN; + break; + case PS_LOCKWAIT: + case PS_MUTEX_WAIT: + case PS_COND_WAIT: + case PS_SIGSUSPEND: + case PS_SIGWAIT: + case PS_JOIN: + case PS_SUSPENDED: + case PS_DEADLOCK: + case PS_SLEEP_WAIT: + info->ti_state = TD_THR_SLEEP; + break; + case PS_DEAD: + info->ti_state = TD_THR_ZOMBIE; + break; + default: + info->ti_state = TD_THR_UNKNOWN; + break; + } + + info->ti_db_suspended = ((dflags & TMDF_DONOTRUNUSER) != 0); + info->ti_type = TD_THR_USER; + info->ti_pri = pt.active_priority; + info->ti_sigmask = pt.sigmask; + info->ti_traceme = 0; + info->ti_pending = pt.sigpend; + info->ti_events = 0; + return (0); +} + +static td_err_e +pt_thr_getfpregs(const td_thrhandle_t *th, prfpregset_t *fpregs) +{ + struct kse_thr_mailbox tmbx; + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + psaddr_t tcb_addr, tmbx_addr, ptr; + lwpid_t lwp; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + if (ta->map[th->th_unique].type == PT_LWP) { + ret = ps_lgetfpregs(ta->ph, ta->map[th->th_unique].lwp, fpregs); + return (P2T(ret)); + } + + ret = ps_pdread(ta->ph, ta->map[th->th_unique].thr + + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + tmbx_addr = tcb_addr + offsetof(struct tcb, tcb_tmbx); + ptr = tmbx_addr + offsetof(struct kse_thr_mailbox, tm_lwp); + ret = ps_pdread(ta->ph, ptr, &lwp, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + if (lwp != 0) { + ret = ps_lgetfpregs(ta->ph, lwp, fpregs); + return (P2T(ret)); + } + + ret = ps_pdread(ta->ph, tmbx_addr, &tmbx, sizeof(tmbx)); + if (ret != 0) + return (P2T(ret)); + pt_ucontext_to_fpreg(&tmbx.tm_context, fpregs); + return (0); +} + +static td_err_e +pt_thr_getgregs(const td_thrhandle_t *th, prgregset_t gregs) +{ + struct kse_thr_mailbox tmbx; + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + psaddr_t tcb_addr, tmbx_addr, ptr; + lwpid_t lwp; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + if (ta->map[th->th_unique].type == PT_LWP) { + ret = ps_lgetregs(ta->ph, + ta->map[th->th_unique].lwp, gregs); + return (P2T(ret)); + } + + ret = ps_pdread(ta->ph, ta->map[th->th_unique].thr + + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + tmbx_addr = tcb_addr + offsetof(struct tcb, tcb_tmbx); + ptr = tmbx_addr + offsetof(struct kse_thr_mailbox, tm_lwp); + ret = ps_pdread(ta->ph, ptr, &lwp, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + if (lwp != 0) { + ret = ps_lgetregs(ta->ph, lwp, gregs); + return (P2T(ret)); + } + ret = ps_pdread(ta->ph, tmbx_addr, &tmbx, sizeof(tmbx)); + if (ret != 0) + return (P2T(ret)); + pt_ucontext_to_reg(&tmbx.tm_context, gregs); + return (0); +} + +static td_err_e +pt_thr_getxregs (const td_thrhandle_t *th, void *xregs) +{ + return (TD_NOXREGS); +} + +static td_err_e +pt_thr_getxregsize (const td_thrhandle_t *th, int *sizep) +{ + return (TD_NOXREGS); +} + +static td_err_e +pt_thr_setfpregs(const td_thrhandle_t *th, const prfpregset_t *fpregs) +{ + struct kse_thr_mailbox tmbx; + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + psaddr_t tcb_addr, tmbx_addr, ptr; + lwpid_t lwp; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + if (ta->map[th->th_unique].type == PT_LWP) { + ret = ps_lsetfpregs(ta->ph, ta->map[th->th_unique].lwp, fpregs); + return (P2T(ret)); + } + + ret = ps_pdread(ta->ph, ta->map[th->th_unique].thr + + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + tmbx_addr = tcb_addr + offsetof(struct tcb, tcb_tmbx); + ptr = tmbx_addr + offsetof(struct kse_thr_mailbox, tm_lwp); + ret = ps_pdread(ta->ph, ptr, &lwp, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + if (lwp != 0) { + ret = ps_lsetfpregs(ta->ph, lwp, fpregs); + return (P2T(ret)); + } + /* + * Read a copy of context, this makes sure that registers + * not covered by structure reg won't be clobbered + */ + ret = ps_pdread(ta->ph, tmbx_addr, &tmbx, sizeof(tmbx)); + if (ret != 0) + return (P2T(ret)); + + pt_fpreg_to_ucontext(fpregs, &tmbx.tm_context); + ret = ps_pdwrite(ta->ph, tmbx_addr, &tmbx, sizeof(tmbx)); + return (P2T(ret)); +} + +static td_err_e +pt_thr_setgregs(const td_thrhandle_t *th, prgregset_t gregs) +{ + struct kse_thr_mailbox tmbx; + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + psaddr_t tcb_addr, tmbx_addr, ptr; + lwpid_t lwp; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + if (ta->map[th->th_unique].type == PT_LWP) { + ret = ps_lsetregs(ta->ph, ta->map[th->th_unique].lwp, gregs); + return (P2T(ret)); + } + + ret = ps_pdread(ta->ph, ta->map[th->th_unique].thr + + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + tmbx_addr = tcb_addr + offsetof(struct tcb, tcb_tmbx); + ptr = tmbx_addr + offsetof(struct kse_thr_mailbox, tm_lwp); + ret = ps_pdread(ta->ph, ptr, &lwp, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + if (lwp != 0) { + ret = ps_lsetregs(ta->ph, lwp, gregs); + return (P2T(ret)); + } + + /* + * Read a copy of context, make sure that registers + * not covered by structure reg won't be clobbered + */ + ret = ps_pdread(ta->ph, tmbx_addr, &tmbx, sizeof(tmbx)); + if (ret != 0) + return (P2T(ret)); + pt_reg_to_ucontext(gregs, &tmbx.tm_context); + ret = ps_pdwrite(ta->ph, tmbx_addr, &tmbx, sizeof(tmbx)); + return (P2T(ret)); +} + +static td_err_e +pt_thr_setxregs(const td_thrhandle_t *th, const void *addr) +{ + TDBG_FUNC(); + return (TD_NOXREGS); +} + +static td_err_e +pt_thr_event_enable(const td_thrhandle_t *th, int en) +{ + TDBG_FUNC(); + return (TD_ERR); +} + +static td_err_e +pt_thr_set_event(const td_thrhandle_t *th, td_thr_events_t *setp) +{ + TDBG_FUNC(); + return (TD_ERR); +} + +static td_err_e +pt_thr_clear_event(const td_thrhandle_t *th, td_thr_events_t *setp) +{ + TDBG_FUNC(); + return (TD_ERR); +} + +static td_err_e +pt_thr_event_getmsg(const td_thrhandle_t *th, td_event_msg_t *msg) +{ + TDBG_FUNC(); + return (TD_NOMSG); +} + +static td_err_e +pt_thr_setprio(const td_thrhandle_t *th, int pri) +{ + TDBG_FUNC(); + return (TD_OK); +} + +static td_err_e +pt_thr_setsigpending(const td_thrhandle_t *th, unsigned char n, + const sigset_t *set) +{ + TDBG_FUNC(); + return (TD_OK); +} + +static td_err_e +pt_thr_sigsetmask(const td_thrhandle_t *th, const sigset_t *set) +{ + TDBG_FUNC(); + return (TD_OK); +} + +static td_err_e +pt_thr_tsd(const td_thrhandle_t *th, const thread_key_t key, void **data) +{ + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + struct pthread_specific_elem *spec, elem; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + if (key < 0 || key >= PTHREAD_KEYS_MAX) + return (TD_BADKEY); + + if (ta->map[th->th_unique].type == PT_LWP) { + *data = NULL; + return (TD_OK); + } + + ret = ps_pdread(ta->ph, + ta->map[th->th_unique].thr + + offsetof(struct pthread, specific), + &spec, sizeof(spec)); + if (ret == 0) { + if (spec == NULL) { + *data = NULL; + return (0); + } + ret = ps_pdread(ta->ph, (psaddr_t)&spec[key], + &elem, sizeof(elem)); + if (ret == 0) + *data = (void *)elem.data; + } + return (P2T(ret)); +} + +static td_err_e +pt_dbsuspend(const td_thrhandle_t *th, int suspend) +{ + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + psaddr_t tcb_addr, tmbx_addr, ptr; + lwpid_t lwp; + uint32_t dflags; + int attrflags; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + if (ta->map[th->th_unique].type == PT_LWP) { + if (suspend) + ret = ps_lstop(ta->ph, ta->map[th->th_unique].lwp); + else + ret = ps_lcontinue(ta->ph, ta->map[th->th_unique].lwp); + return (P2T(ret)); + } + + ret = ps_pdread(ta->ph, ta->map[th->th_unique].thr + + offsetof(struct pthread, attr.flags), + &attrflags, sizeof(attrflags)); + if (ret != 0) + return (P2T(ret)); + ret = ps_pdread(ta->ph, ta->map[th->th_unique].thr + + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + tmbx_addr = tcb_addr + offsetof(struct tcb, tcb_tmbx); + ptr = tmbx_addr + offsetof(struct kse_thr_mailbox, tm_lwp); + ret = ps_pdread(ta->ph, ptr, &lwp, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + /* + * don't stop lwp assigned to a M:N thread, it belongs + * to UTS, UTS shouldn't be stopped. + */ + if (lwp != 0 && (attrflags & PTHREAD_SCOPE_SYSTEM)) { + /* dont' suspend signal thread */ + if (attrflags & THR_SIGNAL_THREAD) + return 0; + if (suspend) + ret = ps_lstop(ta->ph, lwp); + else + ret = ps_lcontinue(ta->ph, lwp); + return (P2T(ret)); + } + + ret = ps_pdread(ta->ph, + tmbx_addr + offsetof(struct kse_thr_mailbox, tm_dflags), + &dflags, sizeof(dflags)); + if (ret != 0) + return (P2T(ret)); + + if (suspend) + dflags |= TMDF_DONOTRUNUSER; + else + dflags &= ~TMDF_DONOTRUNUSER; + ret = ps_pdwrite(ta->ph, + tmbx_addr + offsetof(struct kse_thr_mailbox, tm_dflags), + &dflags, sizeof(dflags)); + return (P2T(ret)); +} + +static td_err_e +pt_thr_dbsuspend(const td_thrhandle_t *th) +{ + TDBG_FUNC(); + return pt_dbsuspend(th, 1); +} + +static td_err_e +pt_thr_dbresume(const td_thrhandle_t *th) +{ + TDBG_FUNC(); + return pt_dbsuspend(th, 0); +} + +static td_err_e +pt_ta_activated(pt_thragent_t *ta, int *a) +{ + int ret; + + TDBG_FUNC(); + + if (ta->thread_activated) { + *a = ta->thread_activated; + return (TD_OK); + } + ret = ps_pdread(ta->ph, ta->thread_activated_addr, + &ta->thread_activated, sizeof(int)); + if (ret == 0) + *a = ta->thread_activated; + return (P2T(ret)); +} + +static td_err_e +pt_thr_sstep(td_thrhandle_t *th, int step) +{ + struct kse_thr_mailbox tmbx; + struct reg regs; + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + psaddr_t tcb_addr, tmbx_addr; + uint32_t tmp; + lwpid_t lwp; + int ret; + + TDBG_FUNC(); + + ret = pt_validate(th); + if (ret) + return (ret); + + if (ta->map[th->th_unique].type == PT_LWP) { + /* Let debugger deal with single step flag in in kernel */ + return (0); + } + + ret = ps_pdread(ta->ph, ta->map[th->th_unique].thr + + offsetof(struct pthread, tcb), + &tcb_addr, sizeof(tcb_addr)); + if (ret != 0) + return (P2T(ret)); + + /* Clear or set single step flag in thread mailbox */ + tmp = step ? TMDF_SSTEP : 0; + ret = ps_pdwrite(ta->ph, tcb_addr + offsetof(struct tcb, + tcb_tmbx.tm_dflags), &tmp, sizeof(tmp)); + if (ret != 0) + return (P2T(ret)); + /* Get lwp */ + ret = ps_pdread(ta->ph, tcb_addr + offsetof(struct tcb, + tcb_tmbx.tm_lwp), &lwp, sizeof(lwpid_t)); + if (ret != 0) + return (P2T(ret)); + if (lwp != 0) /* Let debugger deal with single step flag in in kernel */ + return (0); + + tmbx_addr = tcb_addr + offsetof(struct tcb, tcb_tmbx); + /* + * context is in userland, some architectures store + * single step status in registers, we should change + * these registers. + */ + ret = ps_pdread(ta->ph, tmbx_addr, &tmbx, sizeof(tmbx)); + if (ret == 0) { + pt_ucontext_to_reg(&tmbx.tm_context, ®s); + /* only write out if it is really changed. */ + if (pt_reg_sstep(®s, step) != 0) { + pt_reg_to_ucontext(®s, &tmbx.tm_context); + ret = ps_pdwrite(ta->ph, tmbx_addr, &tmbx, + sizeof(tmbx)); + } + } + return (P2T(ret)); +} + +static long +_map_thread(pt_thragent_t *ta, psaddr_t pt, int type) +{ + struct pt_map *new; + int i, first = -1; + + /* leave zero out */ + for (i = 1; i < ta->map_len; ++i) { + if (ta->map[i].type == PT_NONE) { + if (first == -1) + first = i; + } else if (ta->map[i].type == type && ta->map[i].thr == pt) { + return (i); + } + } + + if (first == -1) { + if (ta->map_len == 0) { + ta->map = calloc(20, sizeof(struct pt_map)); + if (ta->map == NULL) + return (-1); + ta->map_len = 20; + first = 1; + } else { + new = realloc(ta->map, + sizeof(struct pt_map) * ta->map_len * 2); + if (new == NULL) + return (-1); + memset(new + ta->map_len, '\0', sizeof(struct pt_map) * + ta->map_len); + first = ta->map_len; + ta->map = new; + ta->map_len *= 2; + } + } + + ta->map[first].type = type; + ta->map[first].thr = pt; + return (first); +} + +static long +pt_map_thread(const pt_thragent_t *ta, psaddr_t pt) +{ + return _map_thread((pt_thragent_t *)ta, pt, PT_USER); +} + +#if 0 +static long +pt_map_lwp(const pt_thragent_t *ta, lwpid_t lwp) +{ + return _map_thread((pt_thragent_t *)ta, (psaddr_t)lwp, PT_LWP); +} +#endif + +static void +pt_unmap_lwp(const pt_thragent_t *ta, lwpid_t lwp) +{ + int i; + + for (i = 0; i < ta->map_len; ++i) { + if (ta->map[i].type == PT_LWP && ta->map[i].lwp == lwp) { + ta->map[i].type = PT_NONE; + return; + } + } +} + +static int +pt_validate(const td_thrhandle_t *th) +{ + pt_thragent_t *ta = (pt_thragent_t *)th->th_ta_p; + + if (th->th_unique < 0 || th->th_unique >= ta->map_len || + ta->map[th->th_unique].type == PT_NONE) + return (TD_NOTHR); + return (TD_OK); +} + +struct ta_ops pthread_ops = { + .to_init = pt_init, + .to_ta_new = (void *)pt_ta_new, + .to_ta_delete = (void *)pt_ta_delete, + .to_ta_get_nthreads = (void *)pt_ta_get_nthreads, + .to_ta_get_ph = (void *)pt_ta_get_ph, + .to_ta_map_id2thr = (void *)pt_ta_map_id2thr, + .to_ta_map_lwp2thr = (void *)pt_ta_map_lwp2thr, + .to_ta_thr_iter = (void *)pt_ta_thr_iter, + .to_ta_tsd_iter = (void *)pt_ta_tsd_iter, + .to_ta_event_addr = (void *)pt_ta_event_addr, + .to_ta_set_event = (void *)pt_ta_set_event, + .to_ta_clear_event = (void *)pt_ta_clear_event, + .to_ta_event_getmsg = (void *)pt_ta_event_getmsg, + .to_ta_setconcurrency = (void *)pt_ta_setconcurrency, + .to_ta_enable_stats = (void *)pt_ta_enable_stats, + .to_ta_reset_stats = (void *)pt_ta_reset_stats, + .to_ta_get_stats = (void *)pt_ta_get_stats, + .to_thr_validate = pt_thr_validate, + .to_thr_get_info = pt_thr_get_info, + .to_thr_getfpregs = (void *)pt_thr_getfpregs, + .to_thr_getgregs = pt_thr_getgregs, + .to_thr_getxregs = pt_thr_getxregs, + .to_thr_getxregsize = pt_thr_getxregsize, + .to_thr_setfpregs = pt_thr_setfpregs, + .to_thr_setgregs = pt_thr_setgregs, + .to_thr_setxregs = pt_thr_setxregs, + .to_thr_event_enable = pt_thr_event_enable, + .to_thr_set_event = pt_thr_set_event, + .to_thr_clear_event = pt_thr_clear_event, + .to_thr_event_getmsg = pt_thr_event_getmsg, + .to_thr_setprio = pt_thr_setprio, + .to_thr_setsigpending = pt_thr_setsigpending, + .to_thr_sigsetmask = pt_thr_sigsetmask, + .to_thr_tsd = pt_thr_tsd, + .to_thr_dbsuspend = pt_thr_dbsuspend, + .to_thr_dbresume = pt_thr_dbresume, + .to_ta_activated = (void *)pt_ta_activated, + .to_thr_sstep = pt_thr_sstep +}; diff --git a/lib/libthread_db/pthread/pthread_db.h b/lib/libthread_db/pthread/pthread_db.h new file mode 100644 index 0000000..49dcdd7 --- /dev/null +++ b/lib/libthread_db/pthread/pthread_db.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2004 David Xu <davidxu@freebsd.org> + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +#ifndef _PTHREAD_DB_H +#define _PTHREAD_DB_H + +#include <sys/ucontext.h> +#include <machine/reg.h> + +#include "thread_db_int.h" + +struct pt_thragent { + struct td_thragent base; + struct ps_prochandle *ph; + psaddr_t libkse_debug_addr; + psaddr_t thread_list_addr; + psaddr_t thread_listgen_addr; + psaddr_t thread_activated_addr; + psaddr_t thread_active_threads_addr; + psaddr_t thread_keytable_addr; + int thread_activated; + struct pt_map *map; + int map_len; +}; + +typedef struct pt_thragent pt_thragent_t; + +void pt_md_init(void); +void pt_reg_to_ucontext(const struct reg *, ucontext_t *); +void pt_ucontext_to_reg(const ucontext_t *, struct reg *); +void pt_fpreg_to_ucontext(const struct fpreg *, ucontext_t *); +void pt_ucontext_to_fpreg(const ucontext_t *, struct fpreg *); +int pt_reg_sstep(struct reg *reg, int step); + +#endif diff --git a/lib/libthread_db/pthread/pthread_db_i386.c b/lib/libthread_db/pthread/pthread_db_i386.c new file mode 100644 index 0000000..4875b74 --- /dev/null +++ b/lib/libthread_db/pthread/pthread_db_i386.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2004 David Xu <davidxu@freebsd.org> + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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> +__FBSDID("$FreeBSD$"); + +#include <string.h> +#include <sys/types.h> +#include <proc_service.h> +#include <thread_db.h> +#include <machine/npx.h> + +#include "pthread_db.h" + +static int has_xmm_regs; + +void +pt_reg_to_ucontext(const struct reg *r, ucontext_t *uc) +{ + memcpy(&uc->uc_mcontext.mc_fs, &r->r_fs, 18*4); + uc->uc_mcontext.mc_gs = r->r_gs; +} + +void +pt_ucontext_to_reg(const ucontext_t *uc, struct reg *r) +{ + memcpy(&r->r_fs, &uc->uc_mcontext.mc_fs, 18*4); + r->r_gs = uc->uc_mcontext.mc_gs; +} + +void +pt_fpreg_to_ucontext(const struct fpreg* r, ucontext_t *uc) +{ + if (!has_xmm_regs) + memcpy(&uc->uc_mcontext.mc_fpstate, r, + sizeof(struct save87)); + else { + int i; + struct savexmm *sx = (struct savexmm *)&uc->uc_mcontext.mc_fpstate; + memcpy(&sx->sv_env, &r->fpr_env, sizeof(r->fpr_env)); + for (i = 0; i < 8; ++i) + memcpy(&sx->sv_fp[i].fp_acc, &r->fpr_acc[i], 10); + } +} + +void +pt_ucontext_to_fpreg(const ucontext_t *uc, struct fpreg *r) +{ + if (!has_xmm_regs) + memcpy(r, &uc->uc_mcontext.mc_fpstate, sizeof(struct save87)); + else { + int i; + struct savexmm *sx = (struct savexmm *)&uc->uc_mcontext.mc_fpstate; + memcpy(&r->fpr_env, &sx->sv_env, sizeof(r->fpr_env)); + for (i = 0; i < 8; ++i) + memcpy(&r->fpr_acc[i], &sx->sv_fp[i].fp_acc, 10); + } +} + +void +pt_md_init(void) +{ + ucontext_t uc; + + getcontext(&uc); + if (uc.uc_mcontext.mc_fpformat == _MC_FPFMT_XMM) + has_xmm_regs = 1; +} + +int +pt_reg_sstep(struct reg *reg, int step) +{ + unsigned int old; + + old = reg->r_eflags; + if (step) + reg->r_eflags |= 0x0100; + else + reg->r_eflags &= ~0x0100; + return (old != reg->r_eflags); /* changed ? */ +} + diff --git a/lib/libthread_db/src/Makefile b/lib/libthread_db/src/Makefile new file mode 100644 index 0000000..236e7bc --- /dev/null +++ b/lib/libthread_db/src/Makefile @@ -0,0 +1,15 @@ +# $FreeBSD$ + +SHLIB_NAME=libthread_db.so.1 +NOPROFILE=yes +CFLAGS+=-Wall -I${.CURDIR}/../../../include -I${.CURDIR}/../include +LDADD=../pthread/libpthread_db.a + +PRECIOUSLIB=yes + +.PATH: ${.CURDIR} + +SRCS+=thread_db.c +INCS=../include/thread_db.h + +.include <bsd.lib.mk> diff --git a/lib/libthread_db/src/thread_db.c b/lib/libthread_db/src/thread_db.c new file mode 100644 index 0000000..24d5e7c --- /dev/null +++ b/lib/libthread_db/src/thread_db.c @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2004 David Xu <davidxu@freebsd.org> + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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> +__FBSDID("$FreeBSD$"); + +#include <stddef.h> +#include <unistd.h> + +#include <proc_service.h> +#include <thread_db.h> +#include "thread_db_int.h" + +static TAILQ_HEAD(, td_thragent) proclist = TAILQ_HEAD_INITIALIZER(proclist); + +extern struct ta_ops pthread_ops; +#if 0 +extern struct ta_ops thr_ops; +extern struct ta_ops c_r_ops; +#endif + +static struct ta_ops *ops[] = { + &pthread_ops, +#if 0 + &thr_ops, + &c_r_ops +#endif +}; + +td_err_e +td_init(void) +{ + int i, ret = 0; + + for (i = 0; i < sizeof(ops)/sizeof(ops[0]); ++i) { + int tmp; + if ((tmp = ops[i]->to_init()) != 0) + ret = tmp; + } + return (ret); +} + +td_err_e +td_ta_new(struct ps_prochandle *ph, td_thragent_t **pta) +{ + int i; + + for (i = 0; i < sizeof(ops)/sizeof(ops[0]); ++i) { + if (ops[i]->to_ta_new(ph, pta) == 0) { + (*pta)->ta_ops = ops[i]; + TAILQ_INSERT_HEAD(&proclist, *pta, ta_next); + return (0); + } + } + return (TD_NOLIBTHREAD); + +} + +td_err_e +td_ta_delete(td_thragent_t *ta) +{ + TAILQ_REMOVE(&proclist, ta, ta_next); + return ta->ta_ops->to_ta_delete(ta); +} + +td_err_e +td_ta_get_nthreads(const td_thragent_t *ta, int *np) +{ + return ta->ta_ops->to_ta_get_nthreads(ta, np); +} + +td_err_e +td_ta_get_ph(const td_thragent_t *ta, struct ps_prochandle **ph) +{ + return ta->ta_ops->to_ta_get_ph(ta, ph); +} + +td_err_e +td_ta_map_id2thr(const td_thragent_t *ta, thread_t id, td_thrhandle_t *th) +{ + return ta->ta_ops->to_ta_map_id2thr(ta, id, th); +} + +td_err_e +td_ta_map_lwp2thr(const td_thragent_t *ta, lwpid_t lwpid, td_thrhandle_t *th) +{ + return ta->ta_ops->to_ta_map_lwp2thr(ta, lwpid, th); +} + +td_err_e +td_ta_thr_iter(const td_thragent_t *ta, + td_thr_iter_f *callback, void *cbdata_p, + td_thr_state_e state, int ti_pri, + sigset_t *ti_sigmask_p, + unsigned int ti_user_flags) +{ + return ta->ta_ops->to_ta_thr_iter(ta, callback, cbdata_p, state, + ti_pri, ti_sigmask_p, ti_user_flags); +} + +td_err_e +td_ta_tsd_iter(const td_thragent_t *ta, td_key_iter_f *ki, void *arg) +{ + return ta->ta_ops->to_ta_tsd_iter(ta, ki, arg); +} + +td_err_e +td_ta_event_addr(const td_thragent_t *ta, td_event_e event, td_notify_t *ptr) +{ + return ta->ta_ops->to_ta_event_addr(ta, event, ptr); +} + +td_err_e +td_ta_set_event(const td_thragent_t *ta, td_thr_events_t *events) +{ + return ta->ta_ops->to_ta_set_event(ta, events); +} + +td_err_e +td_ta_clear_event(const td_thragent_t *ta, td_thr_events_t *events) +{ + return ta->ta_ops->to_ta_clear_event(ta, events); +} + +td_err_e +td_ta_event_getmsg(const td_thragent_t *ta, td_event_msg_t *msg) +{ + return ta->ta_ops->to_ta_event_getmsg(ta, msg); +} + +td_err_e +td_ta_setconcurrency(const td_thragent_t *ta, int level) +{ + return ta->ta_ops->to_ta_setconcurrency(ta, level); +} + +td_err_e +td_ta_enable_stats(const td_thragent_t *ta, int enable) +{ + return ta->ta_ops->to_ta_enable_stats(ta, enable); +} + +td_err_e +td_ta_reset_stats(const td_thragent_t *ta) +{ + return ta->ta_ops->to_ta_reset_stats(ta); +} + +td_err_e +td_ta_get_stats(const td_thragent_t *ta, td_ta_stats_t *statsp) +{ + return ta->ta_ops->to_ta_get_stats(ta, statsp); +} + +td_err_e +td_thr_validate(const td_thrhandle_t *th) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_validate(th); +} + +td_err_e +td_thr_get_info(const td_thrhandle_t *th, td_thrinfo_t *info) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_get_info(th, info); +} + +td_err_e +td_thr_getfpregs(const td_thrhandle_t *th, prfpregset_t *fpregset) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_getfpregs(th, fpregset); +} + +td_err_e +td_thr_getgregs(const td_thrhandle_t *th, prgregset_t gregs) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_getgregs(th, gregs); +} + +td_err_e +td_thr_getxregs(const td_thrhandle_t *th, void *xregs) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_getxregs(th, xregs); +} + +td_err_e +td_thr_getxregsize(const td_thrhandle_t *th, int *sizep) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_getxregsize(th, sizep); +} + +td_err_e +td_thr_setfpregs(const td_thrhandle_t *th, const prfpregset_t *fpregs) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_setfpregs(th, fpregs); +} + +td_err_e +td_thr_setgregs(const td_thrhandle_t *th, prgregset_t gregs) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_setgregs(th, gregs); +} + +td_err_e +td_thr_setxregs(const td_thrhandle_t *th, const void *addr) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_setxregs(th, addr); +} + +td_err_e +td_thr_event_enable(const td_thrhandle_t *th, int en) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_event_enable(th, en); +} + +td_err_e +td_thr_set_event(const td_thrhandle_t *th, td_thr_events_t *setp) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_set_event(th, setp); +} + +td_err_e +td_thr_clear_event(const td_thrhandle_t *th, td_thr_events_t *setp) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_clear_event(th, setp); +} + +td_err_e +td_thr_event_getmsg(const td_thrhandle_t *th, td_event_msg_t *msg) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_event_getmsg(th, msg); +} + +td_err_e +td_thr_setprio(const td_thrhandle_t *th, int pri) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_setprio(th, pri); +} + +td_err_e +td_thr_setsigpending(const td_thrhandle_t *th, unsigned char n, + const sigset_t *set) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_setsigpending(th, n, set); +} + +td_err_e +td_thr_sigsetmask(const td_thrhandle_t *th, const sigset_t *set) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_sigsetmask(th, set); +} + +td_err_e +td_thr_tsd(const td_thrhandle_t *th, const thread_key_t key, void **data) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_tsd(th, key, data); +} + +td_err_e +td_thr_dbsuspend(const td_thrhandle_t *th) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_dbsuspend(th); +} + +td_err_e +td_thr_dbresume(const td_thrhandle_t *th) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_dbresume(th); +} + +td_err_e +td_get_ta(int pid, td_thragent_t **ta_p) +{ + td_thragent_t *ta; + struct ps_prochandle *ph; + + TAILQ_FOREACH(ta, &proclist, ta_next) { + td_ta_get_ph(ta, &ph); + if (ps_getpid(ph) == pid) { + *ta_p = ta; + return (TD_OK); + } + } + return (TD_ERR); +} + +td_err_e +td_ta_activated(td_thragent_t *ta, int *a) +{ + return ta->ta_ops->to_ta_activated(ta, a); +} + +td_err_e +td_thr_sstep(td_thrhandle_t *th, int step) +{ + td_thragent_t *ta = th->th_ta_p; + + return ta->ta_ops->to_thr_sstep(th, step); +} |