/*
* 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);
}