/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <link.h> #include <dlfcn.h> #include <sys/types.h> #include <sys/param.h> #include <sys/resource.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <regex.h> #include <signal.h> #include <synch.h> #include <fcntl.h> #include <sys/stat.h> #include <apptrace.h> #include <libintl.h> #include <locale.h> #include <limits.h> #include <sys/sysmacros.h> #include "abienv.h" #include "mach.h" #include <libproc.h> #include <libctf.h> #define NUM_ARGS 40 extern const char *type_name(ctf_file_t *, ctf_id_t, char *, size_t); extern void print_value(ctf_file_t *, ctf_id_t, ulong_t); static struct ps_prochandle *proc_hdl = NULL; static Liblist *bindto_list; static Liblist *bindto_excl; static Liblist *bindfrom_list; static Liblist *bindfrom_excl; static Liblist *intlib_list; static uint_t pidout; static Intlist *trace_list; static Intlist *trace_excl; static Intlist *verbose_list; static Intlist *verbose_excl; /* * Required for calls to build_env_list1 where * things are added to the end of the list (preserving * search order implied by the setting of env variables * in apptracecmd.c) */ static Liblist *intlib_listend; /* * These globals are sought and used by interceptlib.c * which goes into all interceptor objects. */ FILE *ABISTREAM = stderr; sigset_t abisigset; /* * Strings are printed with "%.*s", abi_strpsz, string */ int abi_strpsz = 20; /* * Special function pointers that'll be set up to point at the * libc/libthread versions in the _application's_ link map (as opposed * to our own). * * Additionally, it is impossible to generalize the programmatic * creation of interceptor functions for variable argument list * functions. However, in the case of the printf family, there is a * vprintf equivalent. The interceptors for the printf family live in * interceptor.c and they call the appropriate vprintf interface * instead of the printf interface that they're intercepting. The * link map issue remains, however, so function pointers for the * vprintf family in the application's link map are set up here. * * The interceptors also need to examine errno which also needs to be * extracted from the base link map. * * All of these pointers are initialized in la_preinit(). */ thread_t (*abi_thr_self)(void); int (*abi_thr_main)(void); int (*ABI_VFPRINTF)(FILE *, char const *, va_list); int (*ABI_VFWPRINTF)(FILE *, const wchar_t *, va_list); int (*ABI_VPRINTF)(char const *, va_list); int (*ABI_VSNPRINTF)(char *, size_t, char const *, va_list); int (*ABI_VSPRINTF)(char *, char const *, va_list); int (*ABI_VSWPRINTF)(wchar_t *, size_t, const wchar_t *, va_list); int (*ABI_VWPRINTF)(const wchar_t *, va_list); int *(*__abi_real_errno)(void); #if defined(__sparcv9) static char const *libcpath = "/lib/sparcv9/libc.so.1"; #elif defined(__amd64) static char const *libcpath = "/lib/amd64/libc.so.1"; #else static char const *libcpath = "/lib/libc.so.1"; #endif /* Used as arguments later to dlsym */ static char const *thr_main_sym = "thr_main"; static char const *thr_self_sym = "thr_self"; static char const *vfprintf_sym = "vfprintf"; static char const *vfwprintf_sym = "vfwprintf"; static char const *vprintf_sym = "vprintf"; static char const *vsnprintf_sym = "vsnprintf"; static char const *vsprintf_sym = "vsprintf"; static char const *vswprintf_sym = "vswprintf"; static char const *vwprintf_sym = "vwprintf"; static char const *errno_sym = "___errno"; /* * The list of functions below are functions for which * apptrace.so will not perform any tracing. * * The user visible failure of tracing these functions * is a core dump of the application under observation. * * This list was originally discovered during sotruss * development. Attempts lacking sufficient determination * to shrink this list have failed. * * There are a number of different kinds of issues here. * * The .stretX functions have to do with the relationship * that the caller and callee has with functions that * return structures and the altered calling convention * that results. * * We cannot trace *setjmp because the caller of these routines * is not allow to return which is exactly what an interceptor * function is going to do. * * The *context functions are on the list because we cannot trace * netscape without them on the list, but the exact mechanics of the * failure are not known at this time. * * The leaf functions *getsp can probably be removed given the * presence of an interceptor but that experiment has not been * conducted. * * NOTE: this list *must* be maintained in alphabetical order. * if this list ever became too long a faster search mechanism * should be considered. */ static char *spec_sym[] = { #if defined(sparc) ".stret1", ".stret2", ".stret4", ".stret8", #endif "__getcontext", "_getcontext", "_getsp", "_longjmp", "_setcontext", "_setjmp", "_siglongjmp", "_sigsetjmp", "_vfork", "getcontext", "getsp", "longjmp", "setcontext", "setjmp", "siglongjmp", "sigsetjmp", "vfork", NULL }; uint_t la_version(uint_t version) { char *str; FILE *fp; if (version > LAV_CURRENT) (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "apptrace: unexpected version: %u\n"), version); build_env_list(&bindto_list, "APPTRACE_BINDTO"); build_env_list(&bindto_excl, "APPTRACE_BINDTO_EXCLUDE"); build_env_list(&bindfrom_list, "APPTRACE_BINDFROM"); build_env_list(&bindfrom_excl, "APPTRACE_BINDFROM_EXCLUDE"); if (checkenv("APPTRACE_PID") != NULL) { pidout = 1; } else { char *str = "LD_AUDIT="; char *str2 = "LD_AUDIT64="; /* * This disables apptrace output in subsequent exec'ed * processes. */ (void) putenv(str); (void) putenv(str2); } if ((str = checkenv("APPTRACE_OUTPUT")) != NULL) { int fd, newfd, targetfd, lowerlimit; struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "apptrace: getrlimit: %s\n"), strerror(errno)); exit(EXIT_FAILURE); } fd = open(str, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd == -1) { (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "apptrace: %s: %s\n"), str, strerror(errno)); exit(EXIT_FAILURE); } /* * Those fans of dup2 should note that dup2 cannot * be used below because dup2 closes the target file * descriptor. Thus, if we're apptracing say, ksh * we'd have closed the fd it uses for the history * file (63 on my box). * * fcntl with F_DUPFD returns first available >= arg3 * so we iterate from the top until we find a available * fd. * * Not finding an fd after 10 tries is a failure. * * Since the _file member of the FILE structure is an * unsigned char, we must clamp our fd request to * UCHAR_MAX */ lowerlimit = ((rl.rlim_cur > UCHAR_MAX) ? UCHAR_MAX : rl.rlim_cur) - 10; for (targetfd = lowerlimit + 10; targetfd > lowerlimit; targetfd--) { if ((newfd = fcntl(fd, F_DUPFD, targetfd)) != -1) break; } if (newfd == -1) { (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "apptrace: F_DUPFD: %s\n"), strerror(errno)); exit(EXIT_FAILURE); } (void) close(fd); if (fcntl(newfd, F_SETFD, FD_CLOEXEC) == -1) { (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "apptrace: fcntl FD_CLOEXEC: %s\n"), strerror(errno)); exit(EXIT_FAILURE); } if ((fp = fdopen(newfd, "wF")) != NULL) { ABISTREAM = fp; } else { (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "apptrace: fdopen: %s\n"), strerror(errno)); exit(EXIT_FAILURE); } } #if defined(_LP64) build_env_list1(&intlib_list, &intlib_listend, "APPTRACE_INTERCEPTORS64"); #else build_env_list1(&intlib_list, &intlib_listend, "APPTRACE_INTERCEPTORS"); #endif /* Set up lists interfaces to trace or ignore */ env_to_intlist(&trace_list, "APPTRACE_INTERFACES"); env_to_intlist(&trace_excl, "APPTRACE_INTERFACES_EXCLUDE"); env_to_intlist(&verbose_list, "APPTRACE_VERBOSE"); env_to_intlist(&verbose_excl, "APPTRACE_VERBOSE_EXCLUDE"); return (LAV_CURRENT); } /* ARGSUSED1 */ uint_t la_objopen(Link_map *lmp, Lmid_t lmid, uintptr_t *cookie) { uint_t flags; static int first = 1; int perr; /* * If this is the first time in, then l_name is the app * and unless the user gave an explict from list * we will trace calls from it. */ if (first && bindfrom_list == NULL) { flags = LA_FLG_BINDFROM | LA_FLG_BINDTO; first = 0; goto work; } /* * If we have no bindto_list, then we assume that we * bindto everything (apptrace -T \*) * * Otherwise we make sure that l_name is on the list. */ flags = 0; if (bindto_list == NULL) { flags = LA_FLG_BINDTO; } else if (check_list(bindto_list, lmp->l_name) != NULL) { flags |= LA_FLG_BINDTO; } /* * If l_name is on the exclusion list, zero the bit. */ if ((bindto_excl != NULL) && check_list(bindto_excl, lmp->l_name) != NULL) { flags &= ~LA_FLG_BINDTO; } /* * If l_name is on the bindfrom list then trace */ if (check_list(bindfrom_list, lmp->l_name) != NULL) { flags |= LA_FLG_BINDFROM; } /* * If l_name is on the exclusion list, zero the bit * else trace, (this allows "-F !foo" to imply * "-F '*' -F !foo") */ if (check_list(bindfrom_excl, lmp->l_name) != NULL) { flags &= ~LA_FLG_BINDFROM; } else if (bindfrom_excl != NULL && bindfrom_list == NULL) { flags |= LA_FLG_BINDFROM; } work: if (flags) { *cookie = (uintptr_t)abibasename(lmp->l_name); /* * only call Pgrab() once to get the ps_prochandle */ if (proc_hdl == NULL) proc_hdl = Pgrab(getpid(), PGRAB_RDONLY, &perr); } return (flags); } static void apptrace_preinit_fail(void) { (void) fprintf(stderr, dgettext(TEXT_DOMAIN, "apptrace: la_preinit: %s\n"), dlerror()); exit(EXIT_FAILURE); } /* ARGSUSED */ void la_preinit(uintptr_t *cookie) { void *h = NULL; (void) sigfillset(&abisigset); h = dlmopen(LM_ID_BASE, libcpath, RTLD_LAZY | RTLD_NOLOAD); if (h == NULL) apptrace_preinit_fail(); if ((abi_thr_self = (thread_t (*)(void)) dlsym(h, thr_self_sym)) == NULL) apptrace_preinit_fail(); if ((abi_thr_main = (int (*)(void)) dlsym(h, thr_main_sym)) == NULL) apptrace_preinit_fail(); /* Do printf style pointers */ if ((ABI_VFPRINTF = (int (*)(FILE *, char const *, va_list)) dlsym(h, vfprintf_sym)) == NULL) apptrace_preinit_fail(); if ((ABI_VFWPRINTF = (int (*)(FILE *, const wchar_t *, va_list)) dlsym(h, vfwprintf_sym)) == NULL) apptrace_preinit_fail(); if ((ABI_VPRINTF = (int (*)(char const *, va_list)) dlsym(h, vprintf_sym)) == NULL) apptrace_preinit_fail(); if ((ABI_VSNPRINTF = (int (*)(char *, size_t, char const *, va_list)) dlsym(h, vsnprintf_sym)) == NULL) apptrace_preinit_fail(); if ((ABI_VSPRINTF = (int (*)(char *, char const *, va_list)) dlsym(h, vsprintf_sym)) == NULL) apptrace_preinit_fail(); if ((ABI_VSWPRINTF = (int (*)(wchar_t *, size_t, const wchar_t *, va_list)) dlsym(h, vswprintf_sym)) == NULL) apptrace_preinit_fail(); if ((ABI_VWPRINTF = (int (*)(const wchar_t *, va_list)) dlsym(h, vwprintf_sym)) == NULL) apptrace_preinit_fail(); if ((__abi_real_errno = (int *(*)(void)) dlsym(h, errno_sym)) == NULL) apptrace_preinit_fail(); (void) dlclose(h); } /* ARGSUSED1 */ #if defined(_LP64) uintptr_t la_symbind64(Elf64_Sym *symp, uint_t symndx, uintptr_t *refcook, uintptr_t *defcook, uint_t *sb_flags, char const *sym_name) #else uintptr_t la_symbind32(Elf32_Sym *symp, uint_t symndx, uintptr_t *refcook, uintptr_t *defcook, uint_t *sb_flags) #endif { #if !defined(_LP64) char const *sym_name = (char const *) symp->st_name; #endif int intercept = 0, verbose = 0; uintptr_t ret = symp->st_value; uint_t ndx; char *str; #if defined(_LP64) if (ELF64_ST_TYPE(symp->st_info) != STT_FUNC) goto end; #else /* If we're not looking at a function, bug out */ if (ELF32_ST_TYPE(symp->st_info) != STT_FUNC) goto end; #endif if (verbose_list != NULL) { /* apptrace ... -v verbose_list ... cmd */ if (check_intlist(verbose_list, sym_name)) verbose = 1; } if (verbose_excl != NULL) { /* apptrace ... -v !verbose_excl ... cmd */ if (check_intlist(verbose_excl, sym_name)) verbose = 0; else if (verbose_list == NULL && trace_list == NULL && trace_excl == NULL) /* apptrace -v !verbose_excl cmd */ intercept = 1; } if (trace_list != NULL) { /* apptrace ... -t trace_list ... cmd */ if (check_intlist(trace_list, sym_name)) intercept = 1; } else if (verbose_list == NULL && verbose_excl == NULL) /* default (implies -t '*'): apptrace cmd */ intercept = 1; if (trace_excl != NULL) { /* apptrace ... -t !trace_excl ... cmd */ if (check_intlist(trace_excl, sym_name)) intercept = 0; } if (verbose == 0 && intercept == 0) { *sb_flags |= (LA_SYMB_NOPLTEXIT | LA_SYMB_NOPLTENTER); goto end; } /* * Check to see if this symbol is one of the 'special' symbols. * If so we disable calls for that symbol. */ for (ndx = 0; (str = spec_sym[ndx]) != NULL; ndx++) { int cmpval; cmpval = strcmp(sym_name, str); if (cmpval < 0) break; if (cmpval == 0) { intercept = verbose = 0; *sb_flags |= (LA_SYMB_NOPLTEXIT | LA_SYMB_NOPLTENTER); break; } } end: return (ret); } /* ARGSUSED1 */ #if defined(__sparcv9) uintptr_t la_sparcv9_pltenter(Elf64_Sym *symp, uint_t symndx, uintptr_t *refcookie, uintptr_t *defcookie, La_sparcv9_regs *regset, uint_t *sb_flags, char const *sym_name) #elif defined(__sparc) uintptr_t la_sparcv8_pltenter(Elf32_Sym *symp, uint_t symndx, uintptr_t *refcookie, uintptr_t *defcookie, La_sparcv8_regs *regset, uint_t *sb_flags) #elif defined(__amd64) uintptr_t la_amd64_pltenter(Elf64_Sym *symp, uint_t symndx, uintptr_t *refcookie, uintptr_t *defcookie, La_amd64_regs *regset, uint_t *sb_flags, char const *sym_name) #elif defined(__i386) uintptr_t la_i86_pltenter(Elf32_Sym *symp, uint_t symndx, uintptr_t *refcookie, uintptr_t *defcookie, La_i86_regs *regset, uint_t *sb_flags) #endif { char *defname = (char *)(*defcookie); char *refname = (char *)(*refcookie); sigset_t omask; #if !defined(_LP64) char const *sym_name = (char const *)symp->st_name; #endif char buf[256]; GElf_Sym sym; prsyminfo_t si; ctf_file_t *ctfp; ctf_funcinfo_t finfo; int argc; ctf_id_t argt[NUM_ARGS]; ulong_t argv[NUM_ARGS]; int i; char *sep = ""; ctf_id_t type, rtype; int kind; abilock(&omask); if (pidout) (void) fprintf(ABISTREAM, "%7u:", (unsigned int)getpid()); if ((ctfp = Pname_to_ctf(proc_hdl, defname)) == NULL) goto fail; if (Pxlookup_by_name(proc_hdl, PR_LMID_EVERY, defname, sym_name, &sym, &si) != 0) goto fail; if (ctf_func_info(ctfp, si.prs_id, &finfo) == CTF_ERR) goto fail; (void) type_name(ctfp, finfo.ctc_return, buf, sizeof (buf)); (void) fprintf(ABISTREAM, "-> %-8s -> %8s:%s %s(", refname, defname, buf, sym_name); /* * According to bug in la_pltexit(), it can't return * if the type is just a struct/union. So, if the return * type is a struct/union, la_pltexit() should be off. */ rtype = ctf_type_resolve(ctfp, finfo.ctc_return); type = ctf_type_reference(ctfp, rtype); rtype = ctf_type_resolve(ctfp, type); kind = ctf_type_kind(ctfp, rtype); if ((kind == CTF_K_STRUCT || kind == CTF_K_UNION) && strpbrk(buf, "*") == NULL) *sb_flags |= LA_SYMB_NOPLTEXIT; argc = MIN(sizeof (argt) / sizeof (argt[0]), finfo.ctc_argc); (void) ctf_func_args(ctfp, si.prs_id, argc, argt); argv[0] = GETARG0(regset); if (argc > 1) argv[1] = GETARG1(regset); if (argc > 2) argv[2] = GETARG2(regset); if (argc > 3) argv[3] = GETARG3(regset); if (argc > 4) argv[4] = GETARG4(regset); if (argc > 5) argv[5] = GETARG5(regset); if (argc > 6) { for (i = 6; i < argc; i++) argv[i] = GETARG_6NUP(i, regset); } for (i = 0; i < argc; i++) { (void) type_name(ctfp, argt[i], buf, sizeof (buf)); (void) fprintf(ABISTREAM, "%s%s = ", sep, buf); rtype = ctf_type_resolve(ctfp, argt[i]); type = ctf_type_reference(ctfp, rtype); rtype = ctf_type_resolve(ctfp, type); kind = ctf_type_kind(ctfp, rtype); if (kind == CTF_K_STRUCT || kind == CTF_K_UNION) (void) fprintf(ABISTREAM, "0x%p", (void *)argv[i]); else print_value(ctfp, argt[i], argv[i]); sep = ", "; } if (finfo.ctc_flags & CTF_FUNC_VARARG) (void) fprintf(ABISTREAM, "%s...", sep); else if (argc == 0) (void) fprintf(ABISTREAM, "void"); if ((*sb_flags & LA_SYMB_NOPLTEXIT) != 0) (void) fprintf(ABISTREAM, ") ** ST\n"); else (void) fprintf(ABISTREAM, ")\n"); if (verbose_list != NULL && check_intlist(verbose_list, sym_name) != 0) { for (i = 0; i < argc; i++) { (void) type_name(ctfp, argt[i], buf, sizeof (buf)); (void) fprintf(ABISTREAM, "\targ%d = (%s) ", i, buf); print_value(ctfp, argt[i], argv[i]); (void) fprintf(ABISTREAM, "\n"); } if ((*sb_flags & LA_SYMB_NOPLTEXIT) != 0) { if (kind == CTF_K_STRUCT) (void) fprintf(ABISTREAM, "\treturn = (struct), apptrace " "will not trace the return\n"); else (void) fprintf(ABISTREAM, "\treturn = (union), apptrace " "will not trace the return\n"); } } (void) fflush(ABISTREAM); abiunlock(&omask); return (symp->st_value); fail: (void) fprintf(ABISTREAM, "-> %-8s -> %8s:%s(0x%lx, 0x%lx, 0x%lx) ** NR\n", refname, defname, sym_name, (ulong_t)GETARG0(regset), (ulong_t)GETARG1(regset), (ulong_t)GETARG2(regset)); *sb_flags |= LA_SYMB_NOPLTEXIT; (void) fflush(ABISTREAM); abiunlock(&omask); return (symp->st_value); } /* ARGSUSED */ #if defined(_LP64) uintptr_t la_pltexit64(Elf64_Sym *symp, uint_t symndx, uintptr_t *refcookie, uintptr_t *defcookie, uintptr_t retval, const char *sym_name) #else uintptr_t la_pltexit(Elf32_Sym *symp, uint_t symndx, uintptr_t *refcookie, uintptr_t *defcookie, uintptr_t retval) #endif { #if !defined(_LP64) const char *sym_name = (const char *)symp->st_name; #endif sigset_t omask; char buf[256]; GElf_Sym sym; prsyminfo_t si; ctf_file_t *ctfp; ctf_funcinfo_t finfo; char *defname = (char *)(*defcookie); char *refname = (char *)(*refcookie); abilock(&omask); if (pidout) (void) fprintf(ABISTREAM, "%7u:", (unsigned int)getpid()); if (retval == 0) { if (verbose_list == NULL) { (void) fprintf(ABISTREAM, "<- %-8s -> %8s:%s()\n", refname, defname, sym_name); (void) fflush(ABISTREAM); } abiunlock(&omask); return (retval); } if ((ctfp = Pname_to_ctf(proc_hdl, defname)) == NULL) goto fail; if (Pxlookup_by_name(proc_hdl, PR_LMID_EVERY, defname, sym_name, &sym, &si) != 0) goto fail; if (ctf_func_info(ctfp, si.prs_id, &finfo) == CTF_ERR) goto fail; if (verbose_list != NULL) { if (check_intlist(verbose_list, sym_name) != 0) { (void) type_name(ctfp, finfo.ctc_return, buf, sizeof (buf)); (void) fprintf(ABISTREAM, "\treturn = (%s) ", buf); print_value(ctfp, finfo.ctc_return, retval); (void) fprintf(ABISTREAM, "\n"); (void) fprintf(ABISTREAM, "<- %-8s -> %8s:%s()", refname, defname, sym_name); (void) fprintf(ABISTREAM, " = 0x%p\n", (void *)retval); } } else { (void) fprintf(ABISTREAM, "<- %-8s -> %8s:%s()", refname, defname, sym_name); (void) fprintf(ABISTREAM, " = 0x%p\n", (void *)retval); } (void) fflush(ABISTREAM); abiunlock(&omask); return (retval); fail: if (verbose_list != NULL) { if (check_intlist(verbose_list, sym_name) != 0) { (void) fprintf(ABISTREAM, "\treturn = 0x%p\n", (void *)retval); (void) fprintf(ABISTREAM, "<- %-8s -> %8s:%s()", refname, defname, sym_name); (void) fprintf(ABISTREAM, " = 0x%p\n", (void *)retval); } } else { (void) fprintf(ABISTREAM, "<- %-8s -> %8s:%s()", refname, defname, sym_name); (void) fprintf(ABISTREAM, " = 0x%p\n", (void *)retval); } (void) fflush(ABISTREAM); abiunlock(&omask); return (retval); }