diff options
Diffstat (limited to 'usr/src/lib/libc/port/gen/walkstack.c')
-rw-r--r-- | usr/src/lib/libc/port/gen/walkstack.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/usr/src/lib/libc/port/gen/walkstack.c b/usr/src/lib/libc/port/gen/walkstack.c new file mode 100644 index 0000000000..48829e51e1 --- /dev/null +++ b/usr/src/lib/libc/port/gen/walkstack.c @@ -0,0 +1,532 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + * + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file provides a general purpose mechanism + * for a user thread to walk its own call stack, + * calling a user-specified iterator function for each + * stack frame. Special handling is provided to indicate + * kernel-constructed signal handler frames. + * + * Adapted from usr/src/lib/libproc/common/Pstack.c: + * + * A signal handler frame is essentially a set of data pushed on to the user + * stack by the kernel prior to returning to the user program in one of the + * pre-defined signal handlers. The signal handler itself receives the signal + * number, an optional pointer to a siginfo_t, and a pointer to the interrupted + * ucontext as arguments. + * + * When performing a stack backtrace, we would like to + * detect these frames so that we can correctly return the interrupted program + * counter and frame pointer as a separate frame. + * + * The stack layout for a signal handler frame is as follows: + * + * SPARC v7/v9: Intel ia32: + * +--------------+ - high +--------------+ - + * | struct fq | ^ addrs | siginfo_t | optional + * +--------------+ | ^ +--------------+ - + * | gwindows_t | | | ucontext_t | ^ + * +--------------+ optional +--------------+ | + * | siginfo_t | | ucontext_t * | | + * +--------------+ | | +--------------+ + * | xregs data | v v | siginfo_t * | mandatory + * +--------------+ - low +--------------+ + * | ucontext_t | ^ addrs | int (signo) | | + * +--------------+ mandatory +--------------+ | + * | struct frame | v | struct frame | v + * +--------------+ - <- %sp on resume +--------------+ - <- %esp on resume + * + * amd64 (64-bit) + * +--------------+ - + * | siginfo_t | optional + * +--------------+ - + * | ucontext_t | ^ + * +--------------+ | + * | siginfo_t * | + * +--------------+ mandatory + * | int (signo) | + * +--------------+ | + * | struct frame | v + * +--------------+ - <- %rsp on resume + * + * The bottom-most struct frame is actually constructed by the kernel by + * copying the previous stack frame, allowing naive backtrace code to simply + * skip over the interrupted frame. The copied frame is never really used, + * since it is presumed the libc or libthread signal handler wrapper function + * will explicitly setcontext(2) to the interrupted context if the user + * program's handler returns. If we detect a signal handler frame, we simply + * read the interrupted context structure from the stack, use its embedded + * gregs to construct the register set for the interrupted frame, and then + * continue our backtrace. Detecting the frame itself is easy according to + * the diagram ("oldcontext" represents any element in the uc_link chain): + * + * On SPARC v7 or v9: + * %fp + sizeof (struct frame) == oldcontext + * + * On i386: + * %ebp + sizeof (struct frame) + (3 words) == oldcontext + * + * On amd64: + * %rbp + sizeof (struct frame) + (2 words) == oldcontext + * + * Since we want to provide the signal number that generated a signal stack + * frame and on sparc this information isn't written to the stack by the kernel + * the way it's done on i386, we're forced to read the signo from the stack as + * one of the arguments to the signal handler. What we hope is that no one has + * used __sigaction directly; if we're not linked with libthread + * (_thr_sighndlrinfo is NULL) then we attempt to read the signo directly from + * the register window. Otherwise we use the _thr_sighndlrinfo interface to find + * the correct frame. + * + */ + +#pragma weak walkcontext = _walkcontext +#pragma weak printstack = _printstack + +#include "synonyms.h" +#include <assert.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <link.h> +#include <procfs.h> +#include <strings.h> +#include <signal.h> +#include <sys/frame.h> +#include <sys/regset.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <thread.h> +#include <ucontext.h> +#include <unistd.h> +#include <stdarg.h> +#include <sys/stack.h> +#include <errno.h> +#include <stdio.h> +#include <alloca.h> +#include <limits.h> + +#ifdef _LP64 +#define _ELF64 +#endif + +#include <sys/machelf.h> + + +#if defined(__sparc) +#define FRAME_PTR_REGISTER REG_SP +#define PC_REGISTER REG_PC +#define CHECK_FOR_SIGFRAME(fp, oldctx) ((fp) + SA(sizeof (struct frame)) \ + == (oldctx)) + +#elif defined(__amd64) +#define FRAME_PTR_REGISTER REG_RBP +#define PC_REGISTER REG_RIP +#define CHECK_FOR_SIGFRAME(fp, oldctx) ((((fp) + sizeof (struct frame)) + \ + 2 * sizeof (long) == (oldctx)) && \ + (((struct frame *)fp)->fr_savpc == (greg_t)-1)) + +#elif defined(__i386) +#define FRAME_PTR_REGISTER EBP +#define PC_REGISTER EIP +#define CHECK_FOR_SIGFRAME(fp, oldctx) ((((fp) + sizeof (struct frame)) + \ + 3 * sizeof (int) == (oldctx)) && \ + (((struct frame *)fp)->fr_savpc == (greg_t)-1)) +#else +#error no arch defined +#endif + + +/* + * use /proc/self/as to safely dereference pointers so we don't + * die in the case of a stack smash + */ + +static int +read_safe(int fd, struct frame *fp, struct frame **savefp, uintptr_t *savepc) +{ + + uintptr_t newfp; + + if ((uintptr_t)fp & (sizeof (void *) - 1)) + return (-1); /* misaligned */ + + if ((pread(fd, (void *)&newfp, sizeof (fp->fr_savfp), + (off_t)&fp->fr_savfp) != sizeof (fp->fr_savfp)) || + pread(fd, (void *)savepc, sizeof (fp->fr_savpc), + (off_t)&fp->fr_savpc) != sizeof (fp->fr_savpc)) + return (-1); + + /* + * handle stack bias on sparcv9 + */ + + if (newfp != 0) + newfp += STACK_BIAS; + + *savefp = (struct frame *)newfp; + + return (0); +} + +int +walkcontext(const ucontext_t *uptr, int (*operate_func)(uintptr_t, int, void *), + void *usrarg) +{ + ucontext_t *oldctx = uptr->uc_link; + + int fd; + int sig; +#if defined(__sparc) + int signo = 0; +#endif + + struct frame *savefp; + uintptr_t savepc; + + /* + * snag frame point from ucontext... we'll see caller of + * getucontext since we'll start by working up the call + * stack by one + */ + + struct frame *fp = (struct frame *) + ((uintptr_t)uptr->uc_mcontext.gregs[FRAME_PTR_REGISTER] + + STACK_BIAS); + + /* + * Since we don't write signo to the stack on sparc, we need + * to extract signo from the stack frames. This is problematic + * in the case of libthread (libc has deterministic behavior) + * since we're not sure where we can do that safely. An awkward + * interface was provided for this purpose in libthread: + * _thr_sighndlrinfo; this is documented in + * /shared/sac/PSARC/1999/024. When called, this function + * returns the PC of a special function (and its size) that + * will be present in the stack frame if a signal was + * delivered and will have the following signature + * __sighndlr(int sig, siginfo_t *si, ucontex_t *uc, + * void (*hndlr)()) + * Since this function is written in assembler and doesn't + * perturb its registers, we can then read sig out of arg0 + * when the saved pc is inside this function. + * + */ +#if defined(__sparc) + + uintptr_t special_pc = NULL; + int special_size = 0; + + extern void _thr_sighndlrinfo(void (**func)(), int *funcsize); + +#pragma weak _thr_sighndlrinfo + + if (_thr_sighndlrinfo != NULL) { + _thr_sighndlrinfo((void (**)())&special_pc, &special_size); + } +#endif /* sparc */ + + + if ((fd = open("/proc/self/as", O_RDONLY)) < 0) + return (-1); + + while (fp != NULL) { + + sig = 0; + + /* + * get value of saved fp and pc w/o crashing + */ + + if (read_safe(fd, fp, &savefp, &savepc) != 0) { + (void) close(fd); + return (-1); + } + + if (savefp == NULL) + break; + + /* + * note that the following checks to see if we've got a + * special signal stack frame present; this allows us to + * detect signals and pass that info to the user stack walker + */ + + if (oldctx != NULL && + CHECK_FOR_SIGFRAME((uintptr_t)savefp, (uintptr_t)oldctx)) { + +#if defined(__i386) || defined(__amd64) + /* + * i386 and amd64 store signo on stack; + * simple to detect and use + */ + sig = *((int *)(savefp + 1)); +#endif + +#if defined(__sparc) + /* + * with sparc we need to handle + * single and multi-threaded cases + * separately + * If we're single threaded, the trampoline + * in libc will have the signo as the first + * argumment; we can snag that directly. + * In the case of threads, since there are multiple + * complex routines between kernel and user handler, + * we need to figure out where we can read signal from + * using _thr_sighndlrinfo - which we've already done + * for this signal, since it appeared on the stack + * before the signal frame.... sigh. + */ + + if (_thr_sighndlrinfo == NULL) /* single threaded */ + sig = fp->fr_arg[0]; + else + sig = signo; /* already read - see below */ +#endif + /* + * this is the special signal frame, so cons up + * the saved fp & pc to pass to user's function + */ + + savefp = (struct frame *) + ((uintptr_t)oldctx-> + uc_mcontext.gregs[FRAME_PTR_REGISTER] + + STACK_BIAS); + savepc = oldctx->uc_mcontext.gregs[PC_REGISTER]; + + oldctx = oldctx->uc_link; /* handle nested signals */ + } +#if defined(__sparc) + + /* + * lookahead code to find right spot to read signo from... + */ + + if (_thr_sighndlrinfo && + savepc >= special_pc && savepc < + (special_pc + special_size)) + signo = fp->fr_arg[0]; +#endif + + /* + * call user-supplied function and quit if non-zero return. + */ + + if (operate_func((uintptr_t)savepc, sig, usrarg) != 0) + break; + + fp = savefp; /* up one in the call stack */ + } + + (void) close(fd); + return (0); +} + +static size_t +ulongtos(char *buffer, unsigned long x, int base) +{ + char local[80]; + static const char digits[] = "0123456789abcdef"; + + unsigned int n = sizeof (local) - 1; + unsigned long rem; + unsigned int mod; + + local[n] = 0; + + rem = x; + + do { + switch (base) { + case 10: + mod = rem % 10; + rem = rem / 10; + break; + + case 16: + mod = rem & 15; + rem = rem >> 4; + break; + default: + return (0); + } + local[--n] = digits[mod]; + } while (rem != 0); + + (void) strcpy(buffer, local + n); + + return (sizeof (local) - n - 1); +} + +static void +async_filenoprintf(int filenum, const char *format, ...) +{ + const char *src = format; + va_list ap; + long i; + struct iovec *iov; + int cnt; + int iter = 0; + + /* + * count # of %'s.. max # of iovs is 2n + 1 + */ + + for (cnt = i = 0; src[i] != '\0'; i++) + if (src[i] == '%') + cnt++; + + iov = alloca((2 * cnt + 1) * sizeof (struct iovec)); + + va_start(ap, format); + + + while (*src) { + + iov[iter].iov_base = (char *)src; + iov[iter].iov_len = 0; + + while (*src && *src != '%') { + iov[iter].iov_len++; + src++; + } + + if (iov[iter].iov_len != 0) + iter++; + + if (*src == '%') { + switch (*++src) { + case 's': + iov[iter].iov_base = va_arg(ap, char *); + iov[iter].iov_len = strlen(iov[iter].iov_base); + iter++; + break; + case 'd': + iov[iter].iov_base = alloca(24); + + i = va_arg(ap, long); + if (i < 0) { + *iov[iter].iov_base = '-'; + iov[iter].iov_len = + ulongtos(iov[iter].iov_base + 1, + -i, 10) + 1; + } else + iov[iter].iov_len = + ulongtos(iov[iter].iov_base, + i, 10); + iter++; + break; + case 'x': + iov[iter].iov_base = alloca(24); + iov[iter].iov_len = ulongtos(iov[iter].iov_base, + va_arg(ap, unsigned long), 16); + iter++; + break; + + case '%': + iov[iter].iov_base = (char *)src; + iov[iter].iov_len = 1; + iter++; + break; + } + src++; + } + } + va_end(ap); + + (void) writev(filenum, iov, iter); + +} + +static int +display_stack_info(uintptr_t pc, int signo, void *arg) +{ + Dl_info info; + + char sigbuf[SIG2STR_MAX]; + + Sym *sym; + + int filenum = (intptr_t)arg; + + if (signo) { + if (sig2str(signo, sigbuf) != 0) + (void) strcpy(sigbuf, "?"); + } + + if (dladdr1((void *) pc, &info, (void**) &sym, RTLD_DL_SYMENT) == 0) { + /* no info at all */ + if (signo == 0) + async_filenoprintf(filenum, "0x%x\n", pc); + else + async_filenoprintf(filenum, + "0x%x [ Signal %d (%s)]\n", pc, + (ulong_t)signo, sigbuf); + + } else if ((pc - (unsigned long)info.dli_saddr) < + sym->st_size) { + /* found a global symbol */ + if (signo == 0) + async_filenoprintf(filenum, "%s:%s+0x%x\n", + info.dli_fname, + info.dli_sname, + pc - (unsigned long)info.dli_saddr); + else + async_filenoprintf(filenum, + "%s:%s+0x%x [ Signal %d (%s)]\n", + info.dli_fname, + info.dli_sname, + pc - (unsigned long)info.dli_saddr, + (ulong_t)signo, sigbuf); + } else { + /* found a static symbol */ + if (signo == 0) + async_filenoprintf(filenum, "%s:0x%x\n", + info.dli_fname, + pc - (unsigned long)info.dli_fbase); + else + async_filenoprintf(filenum, + "%s:0x%x [ Signal %d (%s)]\n", + info.dli_fname, + pc - (unsigned long)info.dli_fbase, + (ulong_t)signo, sigbuf); + } + + return (0); +} + +int +printstack(int dofd) +{ + ucontext_t u; + + if (getcontext(&u) < 0) + return (-1); + + return (walkcontext(&u, display_stack_info, (void*)(intptr_t)dofd)); +} |