root/usr/src/lib/abi/apptrace/common/apptrace.c
/*
 * 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.
 */

#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);
}