# # 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 (c) 1993 by Sun Microsystems, Inc. # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # include ../../../Makefile.master # Hammerhead: amd64-only SUBDIRS= $(MACH64) all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber .KEEP_STATE: all clean clobber install: $(SUBDIRS) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: # # 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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # PROG= gprof include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/sgs/Makefile.com COMOBJS= gprof.o arcs.o dfn.o lookup.o calls.o \ printgprof.o printlist.o readelf.o OBJS= $(COMOBJS) BLURBS= gprof.callg.blurb gprof.flat.blurb SRCS= $(COMOBJS:%.o=../common/%.c) INCLIST= -I../common -I../../include -I../../include/$(MACH) DEFLIST= -DELF_OBJ -DELF CPPFLAGS= $(INCLIST) $(DEFLIST) $(CPPFLAGS.master) -I$(ELFCAP) CFLAGS += $(CCVERBOSE) CSTD= $(CSTD_GNU99) # Hammerhead: Use 64-bit lib paths; -ldl for dlopen/dlsym in libconv LDLIBS += $(CONVLIBDIR64) -lconv $(ELFLIBDIR64) -lelf -ldl ROOTLIBBLURB= $(BLURBS:%=$(ROOTSHLIBCCS)/%) $(ROOTLIBBLURB) : FILEMODE= 444 %.o: ../common/%.c $(COMPILE.c) $< .PARALLEL: $(OBJS) # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) $(CLEANFILES) include $(SRC)/cmd/Makefile.targ # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # Copyright 2025 Hammerhead Project # include ../Makefile.com include $(SRC)/Makefile.master.64 ROOTPROG = $(ROOTPROG64) all: $(PROG) install: all $(ROOTPROG) include ../Makefile.targ /* * 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. */ #include #include "gprof.h" double printtime; sztype total_names; int ncycle; nltype *cyclenl; /* * add (or just increment) an arc */ void addarc(nltype *parentp, nltype *childp, actype count) { arctype *arcp; #ifdef DEBUG if (debug & TALLYDEBUG) { (void) printf("[addarc] %lld arcs from %s to %s\n", count, parentp->name, childp->name); } #endif /* DEBUG */ arcp = arclookup(parentp, childp); if (arcp != 0) { /* * a hit: just increment the count. */ #ifdef DEBUG if (!Dflag) { if (debug & TALLYDEBUG) { (void) printf("[tally] hit %lld += %lld\n", arcp->arc_count, count); } } else { if (debug & TALLYDEBUG) { (void) printf("[tally] hit %lld -= %lld\n", arcp->arc_count, count); } } #endif /* DEBUG */ if (!Dflag) arcp->arc_count += count; else { arcp->arc_count -= count; if (arcp->arc_count < 0) arcp->arc_count = 0; } return; } arcp = (arctype *)calloc(1, sizeof (*arcp)); arcp->arc_parentp = parentp; arcp->arc_childp = childp; arcp->arc_count = count; /* * prepend this child to the children of this parent */ arcp->arc_childlist = parentp->children; parentp->children = arcp; /* * prepend this parent to the parents of this child */ arcp->arc_parentlist = childp->parents; childp->parents = arcp; } /* * the code below topologically sorts the graph (collapsing cycles), * and propagates time bottom up and flags top down. */ /* * the topologically sorted name list pointers */ nltype **topsortnlp; static int topcmp(const void *arg1, const void *arg2) { nltype **npp1 = (nltype **)arg1; nltype **npp2 = (nltype **)arg2; return ((*npp1)->toporder - (*npp2)->toporder); } static void timepropagate(nltype *parentp) { arctype *arcp; nltype *childp; double share; double propshare; if (parentp->propfraction == 0.0) { return; } /* * gather time from children of this parent. */ for (arcp = parentp->children; arcp; arcp = arcp->arc_childlist) { childp = arcp->arc_childp; if (arcp->arc_count == 0) { continue; } if (childp == parentp) { continue; } if (childp->propfraction == 0.0) { continue; } if (childp->cyclehead != childp) { if (parentp->cycleno == childp->cycleno) { continue; } if (parentp->toporder <= childp->toporder) { (void) fprintf(stderr, "[propagate] toporder botches\n"); } childp = childp->cyclehead; } else { if (parentp->toporder <= childp->toporder) { (void) fprintf(stderr, "[propagate] toporder botches\n"); continue; } } if (childp->ncall == 0) { continue; } /* * distribute time for this arc */ arcp->arc_time = childp->time * (((double)arcp->arc_count) / ((double)childp->ncall)); arcp->arc_childtime = childp->childtime * (((double)arcp->arc_count) / ((double)childp->ncall)); share = arcp->arc_time + arcp->arc_childtime; parentp->childtime += share; /* * (1 - propfraction) gets lost along the way */ propshare = parentp->propfraction * share; /* * fix things for printing */ parentp->propchild += propshare; arcp->arc_time *= parentp->propfraction; arcp->arc_childtime *= parentp->propfraction; /* * add this share to the parent's cycle header, if any. */ if (parentp->cyclehead != parentp) { parentp->cyclehead->childtime += share; parentp->cyclehead->propchild += propshare; } #ifdef DEBUG if (debug & PROPDEBUG) { (void) printf("[dotime] child \t"); printname(childp); (void) printf(" with %f %f %lld/%lld\n", childp->time, childp->childtime, arcp->arc_count, childp->ncall); (void) printf("[dotime] parent\t"); printname(parentp); (void) printf("\n[dotime] share %f\n", share); } #endif /* DEBUG */ } } static void cycletime(void) { int cycle; nltype *cyclenlp; nltype *childp; for (cycle = 1; cycle <= ncycle; cycle += 1) { cyclenlp = &cyclenl[cycle]; for (childp = cyclenlp->cnext; childp; childp = childp->cnext) { if (childp->propfraction == 0.0) { /* * all members have the same propfraction * except those that were excluded with -E */ continue; } cyclenlp->time += childp->time; } cyclenlp->propself = cyclenlp->propfraction * cyclenlp->time; } } static void dotime(void) { int index; cycletime(); for (index = 0; index < total_names; index += 1) { timepropagate(topsortnlp[index]); } } static void cyclelink(void) { nltype *nlp; nltype *cyclenlp; int cycle; nltype *memberp; arctype *arcp; mod_info_t *mi; /* * Count the number of cycles, and initialize the cycle lists */ ncycle = 0; for (mi = &modules; mi; mi = mi->next) { for (nlp = mi->nl; nlp < mi->npe; nlp++) { /* * this is how you find unattached cycles */ if (nlp->cyclehead == nlp && nlp->cnext != 0) { ncycle += 1; } } } /* * cyclenl is indexed by cycle number: * i.e. it is origin 1, not origin 0. */ cyclenl = (nltype *) calloc(ncycle + 1, sizeof (nltype)); if (cyclenl == 0) { (void) fprintf(stderr, "%s: No room for %d bytes of cycle headers\n", whoami, (ncycle + 1) * sizeof (nltype)); done(); } /* * now link cycles to true cycleheads, * number them, accumulate the data for the cycle */ cycle = 0; for (mi = &modules; mi; mi = mi->next) { for (nlp = mi->nl; nlp < mi->npe; nlp++) { if (!(nlp->cyclehead == nlp && nlp->cnext != 0)) { continue; } cycle += 1; cyclenlp = &cyclenl[cycle]; cyclenlp->name = 0; /* the name */ cyclenlp->value = 0; /* pc entry point */ cyclenlp->time = 0.0; /* ticks in routine */ cyclenlp->childtime = 0.0; /* cumulative ticks */ /* in children */ cyclenlp->ncall = 0; /* how many times */ /* called */ cyclenlp->selfcalls = 0; /* how many calls */ /* to self */ cyclenlp->propfraction = 0.0; /* what % of time */ /* propagates */ cyclenlp->propself = 0.0; /* how much self time */ /* propagates */ cyclenlp->propchild = 0.0; /* how much of child */ /* time propagates */ cyclenlp->printflag = TRUE; /* should this be */ /* printed? */ cyclenlp->index = 0; /* index in the */ /* graph list */ cyclenlp->toporder = DFN_NAN; /* graph call chain */ /* top-sort order */ cyclenlp->cycleno = cycle; /* internal number */ /* of cycle on */ cyclenlp->cyclehead = cyclenlp; /* head of cycle ptr */ cyclenlp->cnext = nlp; /* ptr to next member */ /* of cycle */ cyclenlp->parents = 0; /* caller arcs list */ cyclenlp->children = 0; /* callee arcs list */ #ifdef DEBUG if (debug & CYCLEDEBUG) { (void) printf("[cyclelink] "); printname(nlp); (void) printf(" is the head of cycle %d\n", cycle); } #endif /* DEBUG */ /* * link members to cycle header */ for (memberp = nlp; memberp; memberp = memberp->cnext) { memberp->cycleno = cycle; memberp->cyclehead = cyclenlp; } /* * count calls from outside the cycle * and those among cycle members */ for (memberp = nlp; memberp; memberp = memberp->cnext) { for (arcp = memberp->parents; arcp; arcp = arcp->arc_parentlist) { if (arcp->arc_parentp == memberp) continue; if (arcp->arc_parentp->cycleno == cycle) { cyclenlp->selfcalls += arcp->arc_count; } else { cyclenlp->ncall += arcp->arc_count; } } } } } } /* * check if any parent of this child * (or outside parents of this cycle) * have their print flags on and set the * print flag of the child (cycle) appropriately. * similarly, deal with propagation fractions from parents. */ static void inheritflags(nltype *childp) { nltype *headp; arctype *arcp; nltype *parentp; nltype *memp; headp = childp->cyclehead; if (childp == headp) { /* * just a regular child, check its parents */ childp->printflag = FALSE; childp->propfraction = 0.0; for (arcp = childp->parents; arcp; arcp = arcp->arc_parentlist) { parentp = arcp->arc_parentp; if (childp == parentp) { continue; } childp->printflag |= parentp->printflag; /* * if the child was never actually called * (e.g. this arc is static (and all others * are, too)) no time propagates along this arc. */ if (childp->ncall) { childp->propfraction += parentp->propfraction * (((double)arcp->arc_count) / ((double)childp->ncall)); } } } else { /* * its a member of a cycle, look at all parents from * outside the cycle */ headp->printflag = FALSE; headp->propfraction = 0.0; for (memp = headp->cnext; memp; memp = memp->cnext) { for (arcp = memp->parents; arcp; arcp = arcp->arc_parentlist) { if (arcp->arc_parentp->cyclehead == headp) { continue; } parentp = arcp->arc_parentp; headp->printflag |= parentp->printflag; /* * if the cycle was never actually called * (e.g. this arc is static (and all * others are, too)) no time propagates * along this arc. */ if (headp->ncall) { headp->propfraction += parentp->propfraction * (((double)arcp->arc_count) / ((double)headp->ncall)); } } } for (memp = headp; memp; memp = memp->cnext) { memp->printflag = headp->printflag; memp->propfraction = headp->propfraction; } } } /* * check here if *any* of its parents is printable * then return true else return false */ static int check_ancestors(nltype *siblingp) { arctype *parentsp; if (!siblingp->parents) return (1); for (parentsp = siblingp->parents; parentsp; parentsp = parentsp->arc_parentlist) { if (parentsp->arc_parentp->printflag) return (1); } return (0); } /* * check if the parents it passes time are *all* on * the Elist in which case we do not pass the time */ static int check_parents(nltype *siblingp) { arctype *parentsp; if (!siblingp->parents) return (1); for (parentsp = siblingp->parents; parentsp; parentsp = parentsp->arc_parentlist) { if (!onlist(Elist, parentsp->arc_parentp->name)) return (1); } return (0); } /* * in one top to bottom pass over the topologically sorted namelist * propagate: * printflag as the union of parents' printflags * propfraction as the sum of fractional parents' propfractions * and while we're here, sum time for functions. */ static void doflags(void) { int index; nltype *childp; nltype *oldhead; oldhead = 0; for (index = total_names - 1; index >= 0; index -= 1) { childp = topsortnlp[index]; /* * if we haven't done this function or cycle, * inherit things from parent. * this way, we are linear in the number of arcs * since we do all members of a cycle (and the * cycle itself) as we hit the first member * of the cycle. */ if (childp->cyclehead != oldhead) { oldhead = childp->cyclehead; inheritflags(childp); } #ifdef DEBUG if (debug & PROPDEBUG) { (void) printf("[doflags] "); printname(childp); (void) printf( " inherits printflag %d and propfraction %f\n", childp->printflag, childp->propfraction); } #endif /* DEBUG */ if (!childp->printflag) { bool on_flist; /* * printflag is off * it gets turned on by * being on -f list, * or there not being any -f list * and not being on -e list. */ if (((on_flist = onlist(flist, childp->name)) != 0) || (!fflag && !onlist(elist, childp->name))) { if (on_flist || check_ancestors(childp)) childp->printflag = TRUE; } } else { /* * this function has printing parents: * maybe someone wants to shut it up * by putting it on -e list. (but favor -f * over -e) */ if ((!onlist(flist, childp->name)) && onlist(elist, childp->name)) { childp->printflag = FALSE; } } if (childp->propfraction == 0.0) { /* * no parents to pass time to. * collect time from children if * its on -F list, * or there isn't any -F list and its not * on -E list. */ if (onlist(Flist, childp->name) || (!Fflag && !onlist(Elist, childp->name))) { childp->propfraction = 1.0; } } else { /* * it has parents to pass time to, * but maybe someone wants to shut it up * by putting it on -E list. (but favor -F * over -E) */ if (!onlist(Flist, childp->name) && onlist(Elist, childp->name)) { if (check_parents(childp)) childp->propfraction = 0.0; } } childp->propself = childp->time * childp->propfraction; printtime += childp->propself; #ifdef DEBUG if (debug & PROPDEBUG) { (void) printf("[doflags] "); printname(childp); (void) printf(" ends up with printflag %d and " "propfraction %f\n", childp->printflag, childp->propfraction); (void) printf("time %f propself %f printtime %f\n", childp->time, childp->propself, printtime); } #endif /* DEBUG */ } } nltype ** doarcs(void) { nltype *parentp, **timesortnlp; arctype *arcp; long i, index; extern mod_info_t modules; mod_info_t *mi; /* * initialize various things: * zero out child times. * count self-recursive calls. * indicate that nothing is on cycles. */ for (mi = &modules; mi; mi = mi->next) { for (parentp = mi->nl; parentp < mi->npe; parentp++) { parentp->childtime = 0.0; arcp = arclookup(parentp, parentp); if (arcp != 0) { parentp->ncall -= arcp->arc_count; parentp->selfcalls = arcp->arc_count; } else { parentp->selfcalls = 0; } parentp->propfraction = 0.0; parentp->propself = 0.0; parentp->propchild = 0.0; parentp->printflag = FALSE; parentp->toporder = DFN_NAN; parentp->cycleno = 0; parentp->cyclehead = parentp; parentp->cnext = 0; /* * Inspecting text space is valid only for * the program executable. */ if (cflag && (mi == &modules)) { findcalls(parentp, parentp->value, parentp->value + parentp->sz); } } } /* * topologically order things * if any node is unnumbered, * number it and any of its descendents. */ for (mi = &modules; mi; mi = mi->next) { for (parentp = mi->nl; parentp < mi->npe; parentp++) { if (parentp->toporder == DFN_NAN) { dfn(parentp); } } } /* * link together nodes on the same cycle */ cyclelink(); /* * Sort the symbol tables in reverse topological order */ topsortnlp = (nltype **) calloc(total_names, sizeof (nltype *)); if (topsortnlp == (nltype **) 0) { (void) fprintf(stderr, "[doarcs] ran out of memory for topo sorting\n"); } index = 0; for (mi = &modules; mi; mi = mi->next) { for (i = 0; i < mi->nname; i++) topsortnlp[index++] = &(mi->nl[i]); } qsort(topsortnlp, total_names, sizeof (nltype *), topcmp); #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[doarcs] topological sort listing\n"); for (index = 0; index < total_names; index += 1) { (void) printf("[doarcs] "); (void) printf("%d:", topsortnlp[ index ]->toporder); printname(topsortnlp[ index ]); (void) printf("\n"); } } #endif /* DEBUG */ /* * starting from the topological top, * propagate print flags to children. * also, calculate propagation fractions. * this happens before time propagation * since time propagation uses the fractions. */ doflags(); /* * starting from the topological bottom, * propogate children times up to parents. */ dotime(); /* * Now, sort by propself + propchild. * sorting both the regular function names * and cycle headers. */ timesortnlp = (nltype **) calloc(total_names + ncycle, sizeof (nltype *)); if (timesortnlp == (nltype **) 0) { (void) fprintf(stderr, "%s: ran out of memory for sorting\n", whoami); } index = 0; for (mi = &modules; mi; mi = mi->next) { for (i = 0; i < mi->nname; i++) timesortnlp[index++] = &(mi->nl[i]); } for (index = 1; index <= ncycle; index++) timesortnlp[total_names+index-1] = &cyclenl[index]; qsort(timesortnlp, total_names + ncycle, sizeof (nltype *), totalcmp); for (index = 0; index < total_names + ncycle; index++) timesortnlp[index]->index = index + 1; return (timesortnlp); } /* * 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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "gprof.h" /* * a namelist entry to be the child of indirect calls */ nltype indirectchild = { "(*)", /* the name */ &modules, /* module [-c only for prog txtspace] */ (pctype)0, /* the pc entry point */ (pctype)0, /* aligned entry point */ (unsigned long)0, /* function size */ (unsigned char)0, /* symbol information */ (size_t)0, /* ticks in this routine */ (double)0.0, /* ticks in this routine (as double) */ (double)0.0, /* cumulative ticks in children */ (long)0, /* how many times called */ (long)0, /* how many calls to self */ (double)1.0, /* propagation fraction */ (double)0.0, /* self propagation time */ (double)0.0, /* child propagation time */ (bool)0, /* print flag */ (int)0, /* index in the graph list */ (int)0, /* graph call chain top-sort order */ (int)0, /* internal number of cycle on */ (struct nl *)&indirectchild, /* pointer to head of cycle */ (struct nl *)0, /* pointer to next member of cycle */ (arctype *)0, /* list of caller arcs */ (arctype *)0, /* list of callee arcs */ (unsigned long)0 /* number of callers */ }; void findcalls(nltype *parentp, pctype p_lowpc, pctype p_highpc) { unsigned long instructp; sztype length; nltype *childp; pctype destpc; if (textspace == 0) { return; } if (p_lowpc > s_highpc) return; if (p_highpc < s_lowpc) return; if (p_lowpc < s_lowpc) p_lowpc = s_lowpc; if (p_highpc > s_highpc) p_highpc = s_highpc; #ifdef DEBUG if (debug & CALLSDEBUG) { printf("[findcalls] %s: 0x%llx to 0x%llx\n", parentp->name, p_lowpc, p_highpc); } #endif /* DEBUG */ length = 4; for (instructp = (uintptr_t)textspace + p_lowpc - TORIGIN; instructp < (uintptr_t)textspace + p_highpc - TORIGIN; instructp += length) { switch (OP(instructp)) { case CALL: /* * May be a call, better check it out. */ #ifdef DEBUG if (debug & CALLSDEBUG) { printf("[findcalls]\t0x%x:call\n", PC_VAL(instructp)); } #endif /* DEBUG */ destpc = (DISP30(instructp) << 2) + PC_VAL(instructp); break; case FMT3_0x10: if (OP3(instructp) != JMPL) continue; #ifdef DEBUG if (debug & CALLSDEBUG) printf("[findcalls]\t0x%x:jmpl", PC_VAL(instructp)); #endif /* DEBUG */ if (RD(instructp) == R_G0) { #ifdef DEBUG if (debug & CALLSDEBUG) { switch (RS1(instructp)) { case R_O7: printf("\tprobably a RETL\n"); break; case R_I7: printf("\tprobably a RET\n"); break; default: printf(", but not a call: " "linked to g0\n"); } } #endif /* DEBUG */ continue; } #ifdef DEBUG if (debug & CALLSDEBUG) { printf("\toperands are DST = R%d,\tSRC = R%d", RD(instructp), RS1(instructp)); } #endif /* DEBUG */ if (IMMED(instructp)) { #ifdef DEBUG if (debug & CALLSDEBUG) { if (SIMM13(instructp) < 0) { printf(" - 0x%x\n", -(SIMM13(instructp))); } else { printf(" + 0x%x\n", SIMM13(instructp)); } } #endif /* DEBUG */ switch (RS1(instructp)) { case R_G0: /* * absolute address, simm 13 */ destpc = SIMM13(instructp); break; default: /* * indirect call */ addarc(parentp, &indirectchild, 0); continue; } } else { /* * two register sources, all cases are indirect */ #ifdef DEBUG if (debug & CALLSDEBUG) { printf(" + R%d\n", RS2(instructp)); } #endif /* DEBUG */ addarc(parentp, &indirectchild, 0); continue; } break; default: continue; } /* * Check that the destination is the address of * a function; this allows us to differentiate * real calls from someone trying to get the PC, * e.g. position independent switches. */ if (destpc >= s_lowpc && destpc <= s_highpc) { childp = nllookup(&modules, destpc, NULL); #ifdef DEBUG if (debug & CALLSDEBUG) { printf("[findcalls]\tdestpc 0x%llx", destpc); printf(" childp->name %s", childp->name); printf(" childp->value 0x%llx\n", childp->value); } #endif /* DEBUG */ if (childp->value == destpc) { /* * a hit */ addarc(parentp, childp, 0); continue; } } /* * else: * it looked like a call, * but it wasn't to anywhere. */ #ifdef DEBUG if (debug & CALLSDEBUG) { printf("[findcalls]\tbut it's a switch or a botch\n"); } #endif /* DEBUG */ continue; } } /* * 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. */ #include #include "gprof.h" #define DFN_DEPTH 100 struct dfnstruct { nltype *nlentryp; int cycletop; }; typedef struct dfnstruct dfntype; dfntype *dfn_stack = NULL; int dfn_depth = 0; int dfn_sz = 0; int dfn_counter = DFN_NAN; /* * given this parent, depth first number its children. */ void dfn(nltype *parentp) { arctype *arcp; #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn] dfn("); printname(parentp); (void) printf(")\n"); } #endif /* DEBUG */ if (!dfn_stack) { dfn_sz = DFN_DEPTH; dfn_stack = (dfntype *) malloc(dfn_sz * sizeof (dfntype)); if (!dfn_stack) { (void) fprintf(stderr, "fatal: can't malloc %d objects\n", dfn_sz); exit(1); } } /* * if we're already numbered, no need to look any furthur. */ if (dfn_numbered(parentp)) return; /* * if we're already busy, must be a cycle */ if (dfn_busy(parentp)) { dfn_findcycle(parentp); return; } /* * visit yourself before your children */ dfn_pre_visit(parentp); /* * visit children */ for (arcp = parentp->children; arcp; arcp = arcp->arc_childlist) dfn(arcp->arc_childp); /* * visit yourself after your children */ dfn_post_visit(parentp); } /* * push a parent onto the stack and mark it busy */ void dfn_pre_visit(nltype *parentp) { if (!dfn_stack) { dfn_sz = DFN_DEPTH; dfn_stack = (dfntype *) malloc(dfn_sz * sizeof (dfntype)); if (!dfn_stack) { (void) printf("fatal: can't malloc %d objects\n", dfn_sz); exit(1); } } dfn_depth += 1; if (dfn_depth >= dfn_sz) { dfn_sz += DFN_DEPTH; dfn_stack = (dfntype *) realloc(dfn_stack, dfn_sz * sizeof (dfntype)); if (!dfn_stack) { (void) fprintf(stderr, "fatal: can't realloc %d objects\n", dfn_sz); exit(1); } } dfn_stack[dfn_depth].nlentryp = parentp; dfn_stack[dfn_depth].cycletop = dfn_depth; parentp->toporder = DFN_BUSY; #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_pre_visit]\t\t%d:", dfn_depth); printname(parentp); (void) printf("\n"); } #endif /* DEBUG */ } /* * are we already numbered? */ bool dfn_numbered(nltype *childp) { return (childp->toporder != DFN_NAN && childp->toporder != DFN_BUSY); } /* * are we already busy? */ bool dfn_busy(nltype *childp) { if (childp->toporder == DFN_NAN) return (FALSE); return (TRUE); } void dfn_findcycle(nltype *childp) { int cycletop; nltype *cycleheadp = NULL; nltype *tailp; int index; for (cycletop = dfn_depth; cycletop > 0; cycletop -= 1) { cycleheadp = dfn_stack[cycletop].nlentryp; if (childp == cycleheadp) break; if (childp->cyclehead != childp && childp->cyclehead == cycleheadp) break; } if (cycletop <= 0) { /* * don't report non existent functions */ if (childp->value) { (void) fprintf(stderr, "[dfn_findcycle] couldn't find head " "of cycle for %s\n", childp->name); return; } } #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_findcycle] dfn_depth %d cycletop %d ", dfn_depth, cycletop); printname(cycleheadp); (void) printf("\n"); } #endif /* DEBUG */ if (cycletop == dfn_depth) { /* * this is previous function, e.g. this calls itself * sort of boring */ dfn_self_cycle(childp); } else { /* * glom intervening functions that aren't already * glommed into this cycle. * things have been glommed when their cyclehead field * points to the head of the cycle they are glommed into. */ for (tailp = cycleheadp; tailp->cnext; tailp = tailp->cnext) { /* void: chase down to tail of things already glommed */ #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_findcycle] tail "); printname(tailp); (void) printf("\n"); } #endif /* DEBUG */ } /* * if what we think is the top of the cycle * has a cyclehead field, then it's not really the * head of the cycle, which is really what we want */ if (cycleheadp->cyclehead != cycleheadp) { cycleheadp = cycleheadp->cyclehead; #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_findcycle] new cyclehead "); printname(cycleheadp); (void) printf("\n"); } #endif /* DEBUG */ } for (index = cycletop + 1; index <= dfn_depth; index += 1) { childp = dfn_stack[index].nlentryp; if (childp->cyclehead == childp) { /* * not yet glommed anywhere, glom it * and fix any children it has glommed */ tailp->cnext = childp; childp->cyclehead = cycleheadp; #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf( "[dfn_findcycle] glomming "); printname(childp); (void) printf(" onto "); printname(cycleheadp); (void) printf("\n"); } #endif /* DEBUG */ for (tailp = childp; tailp->cnext; tailp = tailp->cnext) { tailp->cnext->cyclehead = cycleheadp; #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_findcycle]" " and its tail "); printname(tailp->cnext); (void) printf(" onto "); printname(cycleheadp); (void) printf("\n"); } #endif /* DEBUG */ } } else if (childp->cyclehead != cycleheadp) { (void) fprintf(stderr, "[dfn_busy] glommed," " but not to cyclehead\n"); } } } } /* * deal with self-cycles * for lint: ARGSUSED */ /* ARGSUSED */ void dfn_self_cycle(nltype *parentp) { /* * since we are taking out self-cycles elsewhere * no need for the special case, here. */ #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_self_cycle] "); printname(parentp); (void) printf("\n"); } #endif /* DEBUG */ } /* * visit a node after all its children * [MISSING: an explanation] * and pop it off the stack */ void dfn_post_visit(nltype *parentp) { nltype *memberp; #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_post_visit]\t%d: ", dfn_depth); printname(parentp); (void) printf("\n"); } #endif /* DEBUG */ /* * number functions and things in their cycles * unless the function is itself part of a cycle */ if (parentp->cyclehead == parentp) { dfn_counter += 1; for (memberp = parentp; memberp; memberp = memberp->cnext) { memberp->toporder = dfn_counter; #ifdef DEBUG if (debug & DFNDEBUG) { (void) printf("[dfn_post_visit]\t\tmember "); printname(memberp); (void) printf(" -> toporder = %d\n", dfn_counter); } #endif /* DEBUG */ } #ifdef DEBUG } else { if (debug & DFNDEBUG) (void) printf( "[dfn_post_visit]\t\tis part of a cycle\n"); #endif /* DEBUG */ } dfn_depth -= 1; } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include "gprof.h" #include "profile.h" bool aflag; bool bflag; bool Bflag; bool cflag; bool Cflag; bool dflag; bool Dflag; bool eflag; bool Eflag; bool fflag; bool Fflag; bool lflag; bool sflag; bool zflag; bool nflag; bool rflag; bool first_file; bool old_style; double scale; double totime; Size n_pcsamples; mod_info_t modules; pctype s_lowpc; pctype s_highpc; sztype n_modules; sztype sampbytes; sztype nsamples; unsigned short *samples; fl_info_t aout_info; fl_info_t gmonout_info; long hz; struct hdr h; unsigned char *textspace; int debug; int number_funcs_toprint; char *a_outname; char *prog_name; char *gmonname; char *whoami = "gprof"; static pctype lowpc, highpc; /* range profiled, in UNIT's */ /* * things which get -E excluded by default. */ static char *defaultEs[] = { "mcount", "__mcleanup", NULL }; #ifdef DEBUG static char *objname[] = { "", "PROF_BUFFER_T", "PROF_CALLGRAPH_T", "PROF_MODULES_T", NULL }; #define MAX_OBJTYPES 3 #endif /* DEBUG */ void done(void) { exit(EX_OK); } static pctype max(pctype a, pctype b) { if (a > b) return (a); return (b); } static pctype min(pctype a, pctype b) { if (a < b) return (a); return (b); } /* * calculate scaled entry point addresses (to save time in asgnsamples), * and possibly push the scaled entry points over the entry mask, * if it turns out that the entry point is in one bucket and the code * for a routine is in the next bucket. * */ static void alignentries(void) { struct nl *nlp; #ifdef DEBUG pctype bucket_of_entry; pctype bucket_of_code; #endif /* DEBUG */ /* for old-style gmon.out, nameslist is only in modules.nl */ for (nlp = modules.nl; nlp < modules.npe; nlp++) { nlp->svalue = nlp->value / sizeof (UNIT); #ifdef DEBUG bucket_of_entry = (nlp->svalue - lowpc) / scale; bucket_of_code = (nlp->svalue + UNITS_TO_CODE - lowpc) / scale; if (bucket_of_entry < bucket_of_code) { if (debug & SAMPLEDEBUG) { (void) printf( "[alignentries] pushing svalue 0x%llx " "to 0x%llx\n", nlp->svalue, nlp->svalue + UNITS_TO_CODE); } } #endif /* DEBUG */ } } /* * old-style gmon.out * ------------------ * * Assign samples to the procedures to which they belong. * * There are three cases as to where pcl and pch can be * with respect to the routine entry addresses svalue0 and svalue1 * as shown in the following diagram. overlap computes the * distance between the arrows, the fraction of the sample * that is to be credited to the routine which starts at svalue0. * * svalue0 svalue1 * | | * v v * * +-----------------------------------------------+ * | | * | ->| |<- ->| |<- ->| |<- | * | | | | | | * +---------+ +---------+ +---------+ * * ^ ^ ^ ^ ^ ^ * | | | | | | * pcl pch pcl pch pcl pch * * For the vax we assert that samples will never fall in the first * two bytes of any routine, since that is the entry mask, * thus we give call alignentries() to adjust the entry points if * the entry mask falls in one bucket but the code for the routine * doesn't start until the next bucket. In conjunction with the * alignment of routine addresses, this should allow us to have * only one sample for every four bytes of text space and never * have any overlap (the two end cases, above). */ static void asgnsamples(void) { sztype i, j; unsigned_UNIT ccnt; double time; pctype pcl, pch; pctype overlap; pctype svalue0, svalue1; extern mod_info_t modules; nltype *nl = modules.nl; sztype nname = modules.nname; /* read samples and assign to namelist symbols */ scale = highpc - lowpc; scale /= nsamples; alignentries(); for (i = 0, j = 1; i < nsamples; i++) { ccnt = samples[i]; if (ccnt == 0) continue; /*LINTED: E_ASSIGMENT_CAUSE_LOSS_PREC*/ pcl = lowpc + scale * i; /*LINTED: E_ASSIGMENT_CAUSE_LOSS_PREC*/ pch = lowpc + scale * (i + 1); time = ccnt; #ifdef DEBUG if (debug & SAMPLEDEBUG) { (void) printf( "[asgnsamples] pcl 0x%llx pch 0x%llx ccnt %d\n", pcl, pch, ccnt); } #endif /* DEBUG */ totime += time; for (j = (j ? j - 1 : 0); j < nname; j++) { svalue0 = nl[j].svalue; svalue1 = nl[j+1].svalue; /* * if high end of tick is below entry address, * go for next tick. */ if (pch < svalue0) break; /* * if low end of tick into next routine, * go for next routine. */ if (pcl >= svalue1) continue; overlap = min(pch, svalue1) - max(pcl, svalue0); if (overlap != 0) { #ifdef DEBUG if (debug & SAMPLEDEBUG) { (void) printf("[asgnsamples] " "(0x%llx->0x%llx-0x%llx) %s gets " "%f ticks %lld overlap\n", nl[j].value/sizeof (UNIT), svalue0, svalue1, nl[j].name, overlap * time / scale, overlap); } #endif /* DEBUG */ nl[j].time += overlap * time / scale; } } } #ifdef DEBUG if (debug & SAMPLEDEBUG) { (void) printf("[asgnsamples] totime %f\n", totime); } #endif /* DEBUG */ } static void dump_callgraph(FILE *fp, char *filename, unsigned long tarcs, unsigned long ncallees) { ProfCallGraph prof_cgraph; ProfFunction prof_func; arctype *arcp; mod_info_t *mi; nltype *nlp; size_t cur_offset; unsigned long caller_id = 0, callee_id = 0; /* * Write the callgraph header */ prof_cgraph.type = PROF_CALLGRAPH_T; prof_cgraph.version = PROF_CALLGRAPH_VER; prof_cgraph.functions = PROFCGRAPH_SZ; prof_cgraph.size = PROFCGRAPH_SZ + tarcs * PROFFUNC_SZ; if (fwrite(&prof_cgraph, sizeof (ProfCallGraph), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } /* CONSTCOND */ if (CGRAPH_FILLER) (void) fseek(fp, CGRAPH_FILLER, SEEK_CUR); /* Current offset inside the callgraph object */ cur_offset = prof_cgraph.functions; for (mi = &modules; mi; mi = mi->next) { for (nlp = mi->nl; nlp < mi->npe; nlp++) { if (nlp->ncallers == 0) continue; /* If this is the last callee, set next_to to 0 */ callee_id++; if (callee_id == ncallees) prof_func.next_to = 0; else { prof_func.next_to = cur_offset + nlp->ncallers * PROFFUNC_SZ; } /* * Dump this callee's raw arc information with all * its callers */ caller_id = 1; for (arcp = nlp->parents; arcp; arcp = arcp->arc_parentlist) { /* * If no more callers for this callee, set * next_from to 0 */ if (caller_id == nlp->ncallers) prof_func.next_from = 0; else { prof_func.next_from = cur_offset + PROFFUNC_SZ; } prof_func.frompc = arcp->arc_parentp->module->load_base + (arcp->arc_parentp->value - arcp->arc_parentp->module->txt_origin); prof_func.topc = mi->load_base + (nlp->value - mi->txt_origin); prof_func.count = arcp->arc_count; if (fwrite(&prof_func, sizeof (ProfFunction), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } /* CONSTCOND */ if (FUNC_FILLER) (void) fseek(fp, FUNC_FILLER, SEEK_CUR); cur_offset += PROFFUNC_SZ; caller_id++; } } /* for nlp... */ } /* for mi... */ } /* * To save all pc-hits in all the gmon.out's is infeasible, as this * may become quite huge even with a small number of files to sum. * Instead, we'll dump *fictitious hits* to correct functions * by scanning module namelists. Again, since this is summing * pc-hits, we may have to dump the pcsamples out in chunks if the * number of pc-hits is high. */ static void dump_hits(FILE *fp, char *filename, nltype *nlp) { Address *p, hitpc; size_t i, nelem, ntowrite; if ((nelem = nlp->nticks) > PROF_BUFFER_SIZE) nelem = PROF_BUFFER_SIZE; if ((p = (Address *) calloc(nelem, sizeof (Address))) == NULL) { (void) fprintf(stderr, "%s: no room for %d pcsamples\n", whoami, nelem); exit(EX_OSERR); } /* * Set up *fictitious* hits (to function entry) buffer */ hitpc = nlp->module->load_base + (nlp->value - nlp->module->txt_origin); for (i = 0; i < nelem; i++) p[i] = hitpc; for (ntowrite = nlp->nticks; ntowrite >= nelem; ntowrite -= nelem) { if (fwrite(p, nelem * sizeof (Address), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } } if (ntowrite) { if (fwrite(p, ntowrite * sizeof (Address), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } } free(p); } static void dump_pcsamples(FILE *fp, char *filename, unsigned long *tarcs, unsigned long *ncallees) { ProfBuffer prof_buffer; arctype *arcp; mod_info_t *mi; nltype *nlp; prof_buffer.type = PROF_BUFFER_T; prof_buffer.version = PROF_BUFFER_VER; prof_buffer.buffer = PROFBUF_SZ; prof_buffer.bufsize = n_pcsamples; prof_buffer.size = PROFBUF_SZ + n_pcsamples * sizeof (Address); if (fwrite(&prof_buffer, sizeof (ProfBuffer), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } /* CONSTCOND */ if (BUF_FILLER) (void) fseek(fp, BUF_FILLER, SEEK_CUR); *tarcs = 0; *ncallees = 0; for (mi = &modules; mi; mi = mi->next) { for (nlp = mi->nl; nlp < mi->npe; nlp++) { if (nlp->nticks) dump_hits(fp, filename, nlp); nlp->ncallers = 0; for (arcp = nlp->parents; arcp; arcp = arcp->arc_parentlist) { (nlp->ncallers)++; } if (nlp->ncallers) { (*tarcs) += nlp->ncallers; (*ncallees)++; } } } } static void dump_modules(FILE *fp, char *filename, size_t pbuf_sz) { char *pbuf, *p; size_t namelen; Index off_nxt, off_path; mod_info_t *mi; ProfModuleList prof_modlist; ProfModule prof_mod; /* Allocate for path strings buffer */ pbuf_sz = CEIL(pbuf_sz, STRUCT_ALIGN); if ((p = pbuf = calloc(pbuf_sz, sizeof (char))) == NULL) { (void) fprintf(stderr, "%s: no room for %d bytes\n", whoami, pbuf_sz * sizeof (char)); exit(EX_OSERR); } /* Dump out PROF_MODULE_T info for all non-aout modules */ prof_modlist.type = PROF_MODULES_T; prof_modlist.version = PROF_MODULES_VER; prof_modlist.modules = PROFMODLIST_SZ; prof_modlist.size = PROFMODLIST_SZ + (n_modules - 1) * PROFMOD_SZ + pbuf_sz; if (fwrite(&prof_modlist, sizeof (ProfModuleList), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } /* CONSTCOND */ if (MODLIST_FILLER) (void) fseek(fp, MODLIST_FILLER, SEEK_CUR); /* * Initialize offsets for ProfModule elements. */ off_nxt = PROFMODLIST_SZ + PROFMOD_SZ; off_path = PROFMODLIST_SZ + (n_modules - 1) * PROFMOD_SZ; for (mi = modules.next; mi; mi = mi->next) { if (mi->next) prof_mod.next = off_nxt; else prof_mod.next = 0; prof_mod.path = off_path; prof_mod.startaddr = mi->load_base; prof_mod.endaddr = mi->load_end; if (fwrite(&prof_mod, sizeof (ProfModule), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } /* CONSTCOND */ if (MOD_FILLER) (void) fseek(fp, MOD_FILLER, SEEK_CUR); (void) strcpy(p, mi->name); namelen = strlen(mi->name); p += namelen + 1; /* Note that offset to every path str need not be aligned */ off_nxt += PROFMOD_SZ; off_path += namelen + 1; } /* Write out the module path strings */ if (pbuf_sz) { if (fwrite(pbuf, pbuf_sz, 1, fp) != 1) { perror(filename); exit(EX_IOERR); } free(pbuf); } } /* * If we have inactive modules, their current load addresses may overlap with * active ones, and so we've to assign fictitious, non-overlapping addresses * to all modules before we dump them. */ static void fixup_maps(size_t *pathsz) { unsigned int n_inactive = 0; Address lbase = 0, lend; mod_info_t *mi; /* Pick the lowest load address among modules */ *pathsz = 0; for (mi = &modules; mi; mi = mi->next) { if (mi->active == FALSE) n_inactive++; if (mi == &modules || mi->load_base < lbase) lbase = mi->load_base; /* * Return total path size of non-aout modules only */ if (mi != &modules) *pathsz = (*pathsz) + strlen(mi->name) + 1; } /* * All module info is in fine shape already if there are no * inactive modules */ if (n_inactive == 0) return; /* * Assign fictitious load addresses to all (non-aout) modules so * that sum info can be dumped out. */ for (mi = modules.next; mi; mi = mi->next) { lend = lbase + (mi->data_end - mi->txt_origin); if ((lbase < modules.load_base && lend < modules.load_base) || (lbase > modules.load_end && lend > modules.load_end)) { mi->load_base = lbase; mi->load_end = lend; /* just to give an appearance of reality */ lbase = CEIL(lend + PGSZ, PGSZ); } else { /* * can't use this lbase & lend pair, as it * overlaps with aout's addresses */ mi->load_base = CEIL(modules.load_end + PGSZ, PGSZ); mi->load_end = mi->load_base + (lend - lbase); lbase = CEIL(mi->load_end + PGSZ, PGSZ); } } } static void dump_gprofhdr(FILE *fp, char *filename) { ProfHeader prof_hdr; prof_hdr.h_magic = PROF_MAGIC; prof_hdr.h_major_ver = PROF_MAJOR_VERSION; prof_hdr.h_minor_ver = PROF_MINOR_VERSION; prof_hdr.size = PROFHDR_SZ; if (fwrite(&prof_hdr, sizeof (prof_hdr), 1, fp) != 1) { perror(filename); exit(EX_IOERR); } /* CONSTCOND */ if (HDR_FILLER) (void) fseek(fp, HDR_FILLER, SEEK_CUR); } static void dumpsum_ostyle(char *sumfile) { nltype *nlp; arctype *arcp; struct rawarc arc; struct rawarc32 arc32; FILE *sfile; if ((sfile = fopen(sumfile, "w")) == NULL) { perror(sumfile); exit(EX_IOERR); } /* * dump the header; use the last header read in */ if (Bflag) { if (fwrite(&h, sizeof (h), 1, sfile) != 1) { perror(sumfile); exit(EX_IOERR); } } else { struct hdr32 hdr; hdr.lowpc = (pctype32)h.lowpc; hdr.highpc = (pctype32)h.highpc; hdr.ncnt = (pctype32)h.ncnt; if (fwrite(&hdr, sizeof (hdr), 1, sfile) != 1) { perror(sumfile); exit(EX_IOERR); } } /* * dump the samples */ if (fwrite(samples, sizeof (unsigned_UNIT), nsamples, sfile) != nsamples) { perror(sumfile); exit(EX_IOERR); } /* * dump the normalized raw arc information. For old-style dumping, * the only namelist is in modules.nl */ for (nlp = modules.nl; nlp < modules.npe; nlp++) { for (arcp = nlp->children; arcp; arcp = arcp->arc_childlist) { if (Bflag) { arc.raw_frompc = arcp->arc_parentp->value; arc.raw_selfpc = arcp->arc_childp->value; arc.raw_count = arcp->arc_count; if (fwrite(&arc, sizeof (arc), 1, sfile) != 1) { perror(sumfile); exit(EX_IOERR); } } else { arc32.raw_frompc = (pctype32)arcp->arc_parentp->value; arc32.raw_selfpc = (pctype32)arcp->arc_childp->value; arc32.raw_count = (actype32)arcp->arc_count; if (fwrite(&arc32, sizeof (arc32), 1, sfile) != 1) { perror(sumfile); exit(EX_IOERR); } } #ifdef DEBUG if (debug & SAMPLEDEBUG) { (void) printf( "[dumpsum_ostyle] frompc 0x%llx selfpc " "0x%llx count %lld\n", arc.raw_frompc, arc.raw_selfpc, arc.raw_count); } #endif /* DEBUG */ } } (void) fclose(sfile); } /* * dump out the gmon.sum file */ static void dumpsum(char *sumfile) { FILE *sfile; size_t pathbuf_sz; unsigned long total_arcs; /* total number of arcs in all */ unsigned long ncallees; /* no. of callees with parents */ if (old_style) { dumpsum_ostyle(sumfile); return; } if ((sfile = fopen(sumfile, "w")) == NULL) { perror(sumfile); exit(EX_IOERR); } /* * Dump the new-style gprof header. Even if one of the original * profiled-files was of a older version, the summed file is of * current version only. */ dump_gprofhdr(sfile, sumfile); /* * Fix up load-maps and dump out modules info * * Fix up module load maps so inactive modules get *some* address * (and btw, could you get the total size of non-aout module path * strings please ?) */ fixup_maps(&pathbuf_sz); dump_modules(sfile, sumfile, pathbuf_sz); /* * Dump out the summ'd pcsamples * * For dumping call graph information later, we need certain * statistics (like total arcs, number of callers for each node); * collect these also while we are at it. */ dump_pcsamples(sfile, sumfile, &total_arcs, &ncallees); /* * Dump out the summ'd call graph information */ dump_callgraph(sfile, sumfile, total_arcs, ncallees); (void) fclose(sfile); } static void tally(mod_info_t *caller_mod, mod_info_t *callee_mod, struct rawarc *rawp) { nltype *parentp; nltype *childp; /* * if count == 0 this is a null arc and * we don't need to tally it. */ if (rawp->raw_count == 0) return; /* * Lookup the caller and callee pcs in namelists of * appropriate modules */ parentp = nllookup(caller_mod, rawp->raw_frompc, NULL); childp = nllookup(callee_mod, rawp->raw_selfpc, NULL); if (childp && parentp) { if (!Dflag) childp->ncall += rawp->raw_count; else { if (first_file) childp->ncall += rawp->raw_count; else { childp->ncall -= rawp->raw_count; if (childp->ncall < 0) childp->ncall = 0; } } #ifdef DEBUG if (debug & TALLYDEBUG) { (void) printf("[tally] arc from %s to %s traversed " "%lld times\n", parentp->name, childp->name, rawp->raw_count); } #endif /* DEBUG */ addarc(parentp, childp, rawp->raw_count); } } /* * Look up a module's base address in a sorted list of pc-hits. Unlike * nllookup(), this deals with misses by mapping them to the next *higher* * pc-hit. This is so that we get into the module's first pc-hit rightaway, * even if the module's entry-point (load_base) itself is not a hit. */ static Address * locate(Address *pclist, size_t nelem, Address keypc) { size_t low = 0, middle, high = nelem - 1; if (keypc <= pclist[low]) return (pclist); if (keypc > pclist[high]) return (NULL); while (low != high) { middle = (high + low) >> 1; if ((pclist[middle] < keypc) && (pclist[middle + 1] >= keypc)) return (&pclist[middle + 1]); if (pclist[middle] >= keypc) high = middle; else low = middle + 1; } /* must never reach here! */ return (NULL); } static void assign_pcsamples(mod_info_t *module, Address *pcsmpl, size_t n_samples) { Address *pcptr, *pcse = pcsmpl + n_samples; pctype nxt_func; nltype *fnl; size_t func_nticks; #ifdef DEBUG size_t n_hits_in_module = 0; #endif /* DEBUG */ /* Locate the first pc-hit for this module */ if ((pcptr = locate(pcsmpl, n_samples, module->load_base)) == NULL) { #ifdef DEBUG if (debug & PCSMPLDEBUG) { (void) printf("[assign_pcsamples] no pc-hits in\n"); (void) printf( " `%s'\n", module->name); } #endif /* DEBUG */ return; /* no pc-hits in this module */ } /* Assign all pc-hits in this module to appropriate functions */ while ((pcptr < pcse) && (*pcptr < module->load_end)) { /* Update the corresponding function's time */ fnl = nllookup(module, (pctype) *pcptr, &nxt_func); if (fnl != NULL) { /* * Collect all pc-hits in this function. Each * pc-hit counts as 1 tick. */ func_nticks = 0; while ((pcptr < pcse) && (*pcptr < nxt_func)) { func_nticks++; pcptr++; } if (func_nticks == 0) pcptr++; else { fnl->nticks += func_nticks; fnl->time += func_nticks; totime += func_nticks; } #ifdef DEBUG n_hits_in_module += func_nticks; #endif /* DEBUG */ } else { /* * pc sample could not be assigned to function; * probably in a PLT */ pcptr++; } } #ifdef DEBUG if (debug & PCSMPLDEBUG) { (void) printf( "[assign_pcsamples] %ld hits in\n", n_hits_in_module); (void) printf(" `%s'\n", module->name); } #endif /* DEBUG */ } int pc_cmp(const void *arg1, const void *arg2) { Address *pc1 = (Address *)arg1; Address *pc2 = (Address *)arg2; if (*pc1 > *pc2) return (1); if (*pc1 < *pc2) return (-1); return (0); } static void process_pcsamples(ProfBuffer *bufp) { Address *pc_samples; mod_info_t *mi; caddr_t p; size_t chunk_size, nelem_read, nelem_to_read; #ifdef DEBUG if (debug & PCSMPLDEBUG) { (void) printf( "[process_pcsamples] number of pcsamples = %lld\n", bufp->bufsize); } #endif /* DEBUG */ /* buffer with no pc samples ? */ if (bufp->bufsize == 0) return; /* * If we're processing pcsamples of a profile sum, we could have * more than PROF_BUFFER_SIZE number of samples. In such a case, * we must read the pcsamples in chunks. */ if ((chunk_size = bufp->bufsize) > PROF_BUFFER_SIZE) chunk_size = PROF_BUFFER_SIZE; /* Allocate for the pcsample chunk */ pc_samples = (Address *) calloc(chunk_size, sizeof (Address)); if (pc_samples == NULL) { (void) fprintf(stderr, "%s: no room for %d sample pc's\n", whoami, chunk_size); exit(EX_OSERR); } /* Copy the current set of pcsamples */ nelem_read = 0; nelem_to_read = bufp->bufsize; p = (char *)bufp + bufp->buffer; while (nelem_read < nelem_to_read) { (void) memcpy((void *) pc_samples, p, chunk_size * sizeof (Address)); /* Sort the pc samples */ qsort(pc_samples, chunk_size, sizeof (Address), pc_cmp); /* * Assign pcsamples to functions in the currently active * module list */ for (mi = &modules; mi; mi = mi->next) { if (mi->active == FALSE) continue; assign_pcsamples(mi, pc_samples, chunk_size); } p += (chunk_size * sizeof (Address)); nelem_read += chunk_size; if ((nelem_to_read - nelem_read) < chunk_size) chunk_size = nelem_to_read - nelem_read; } free(pc_samples); /* Update total number of pcsamples read so far */ n_pcsamples += bufp->bufsize; } static mod_info_t * find_module(Address addr) { mod_info_t *mi; for (mi = &modules; mi; mi = mi->next) { if (mi->active == FALSE) continue; if (addr >= mi->load_base && addr < mi->load_end) return (mi); } return (NULL); } static void process_cgraph(ProfCallGraph *cgp) { struct rawarc arc; mod_info_t *callee_mi, *caller_mi; ProfFunction *calleep, *callerp; Index caller_off, callee_off; /* * Note that *callee_off* increment in the for loop below * uses *calleep* and *calleep* doesn't get set until the for loop * is entered. We don't expect the increment to be executed before * the loop body is executed atleast once, so this should be ok. */ for (callee_off = cgp->functions; callee_off; callee_off = calleep->next_to) { /* LINTED: pointer cast */ calleep = (ProfFunction *)((char *)cgp + callee_off); /* * We could choose either to sort the {caller, callee} * list twice and assign callee/caller to modules or inspect * each callee/caller in the active modules list. Since * the modules list is usually very small, we'l choose the * latter. */ /* * If we cannot identify a callee with a module, there's * no use worrying about who called it. */ if ((callee_mi = find_module(calleep->topc)) == NULL) { #ifdef DEBUG if (debug & CGRAPHDEBUG) { (void) printf( "[process_cgraph] callee %#llx missed\n", calleep->topc); } #endif /* DEBUG */ continue; } else arc.raw_selfpc = calleep->topc; for (caller_off = callee_off; caller_off; caller_off = callerp->next_from) { /* LINTED: pointer cast */ callerp = (ProfFunction *)((char *)cgp + caller_off); if ((caller_mi = find_module(callerp->frompc)) == NULL) { #ifdef DEBUG if (debug & CGRAPHDEBUG) { (void) printf( "[process_cgraph] caller %#llx " "missed\n", callerp->frompc); } #endif /* DEBUG */ continue; } arc.raw_frompc = callerp->frompc; arc.raw_count = callerp->count; #ifdef DEBUG if (debug & CGRAPHDEBUG) { (void) printf( "[process_cgraph] arc <%#llx, %#llx, " "%lld>\n", arc.raw_frompc, arc.raw_selfpc, arc.raw_count); } #endif /* DEBUG */ tally(caller_mi, callee_mi, &arc); } } #ifdef DEBUG (void) puts("\n"); #endif /* DEBUG */ } /* * Two modules overlap each other if they don't lie completely *outside* * each other. */ static bool does_overlap(ProfModule *new, mod_info_t *old) { /* case 1: new module lies completely *before* the old one */ if (new->startaddr < old->load_base && new->endaddr <= old->load_base) return (FALSE); /* case 2: new module lies completely *after* the old one */ if (new->startaddr >= old->load_end && new->endaddr >= old->load_end) return (FALSE); /* probably a dlopen: the modules overlap each other */ return (TRUE); } static bool is_same_as_aout(char *modpath, struct stat *buf) { if (stat(modpath, buf) == -1) { (void) fprintf(stderr, "%s: can't get info on `%s'\n", whoami, modpath); exit(EX_NOINPUT); } if ((buf->st_dev == aout_info.dev) && (buf->st_ino == aout_info.ino)) return (TRUE); else return (FALSE); } static void process_modules(ProfModuleList *modlp) { ProfModule *newmodp; mod_info_t *mi, *last, *new_module; char *so_path; bool more_modules = TRUE; struct stat so_statbuf; #ifdef DEBUG if (debug & MODULEDEBUG) { (void) printf("[process_modules] module obj version %u\n", modlp->version); } #endif /* DEBUG */ /* Check version of module type object */ if (modlp->version > PROF_MODULES_VER) { (void) fprintf(stderr, "%s: version %d for module type objects" "is not supported\n", whoami, modlp->version); exit(EX_SOFTWARE); } /* * Scan the PROF_MODULES_T list and add modules to current list * of modules, if they're not present already */ /* LINTED: pointer cast */ newmodp = (ProfModule *)((char *)modlp + modlp->modules); do { /* * Since the prog could've been renamed after its run, we * should see if this overlaps a.out. If it does, it is * probably the renamed aout. We should also skip any other * non-sharedobj's that we see (or should we report an error ?) */ so_path = (caddr_t)modlp + newmodp->path; if (does_overlap(newmodp, &modules) || is_same_as_aout(so_path, &so_statbuf) || (!is_shared_obj(so_path))) { if (!newmodp->next) more_modules = FALSE; /* LINTED: pointer cast */ newmodp = (ProfModule *) ((caddr_t)modlp + newmodp->next); #ifdef DEBUG if (debug & MODULEDEBUG) { (void) printf( "[process_modules] `%s'\n", so_path); (void) printf(" skipped\n"); } #endif /* DEBUG */ continue; } #ifdef DEBUG if (debug & MODULEDEBUG) (void) printf("[process_modules] `%s'...\n", so_path); #endif /* DEBUG */ /* * Check all modules (leave the first one, 'cos that * is the program executable info). If this module is already * there in the list, update the load addresses and proceed. */ last = &modules; while ((mi = last->next) != NULL) { /* * We expect the full pathname for all shared objects * needed by the program executable. In this case, we * simply need to compare the paths to see if they are * the same file. */ if (strcmp(mi->name, so_path) == 0) break; /* * Check if this new shared object will overlap * any existing module. If yes, remove the old one * from the linked list (but don't free it, 'cos * there may be symbols referring to this module * still) */ if (does_overlap(newmodp, mi)) { #ifdef DEBUG if (debug & MODULEDEBUG) { (void) printf( "[process_modules] `%s'\n", so_path); (void) printf( " overlaps\n"); (void) printf( " `%s'\n", mi->name); } #endif /* DEBUG */ mi->active = FALSE; } last = mi; } /* Module already there, skip it */ if (mi != NULL) { mi->load_base = newmodp->startaddr; mi->load_end = newmodp->endaddr; mi->active = TRUE; if (!newmodp->next) more_modules = FALSE; /* LINTED: pointer cast */ newmodp = (ProfModule *) ((caddr_t)modlp + newmodp->next); #ifdef DEBUG if (debug & MODULEDEBUG) { (void) printf("[process_modules] base=%#llx, " "end=%#llx\n", mi->load_base, mi->load_end); } #endif /* DEBUG */ continue; } /* * Check if gmon.out is outdated with respect to the new * module we want to add */ if (gmonout_info.mtime < so_statbuf.st_mtime) { (void) fprintf(stderr, "%s: shared obj outdates prof info\n", whoami); (void) fprintf(stderr, "\t(newer %s)\n", so_path); exit(EX_NOINPUT); } /* Create a new module element */ new_module = malloc(sizeof (mod_info_t)); if (new_module == NULL) { (void) fprintf(stderr, "%s: no room for %d bytes\n", whoami, sizeof (mod_info_t)); exit(EX_OSERR); } /* and fill in info... */ new_module->id = n_modules + 1; new_module->load_base = newmodp->startaddr; new_module->load_end = newmodp->endaddr; new_module->name = malloc(strlen(so_path) + 1); if (new_module->name == NULL) { (void) fprintf(stderr, "%s: no room for %d bytes\n", whoami, strlen(so_path) + 1); exit(EX_OSERR); } (void) strcpy(new_module->name, so_path); #ifdef DEBUG if (debug & MODULEDEBUG) { (void) printf( "[process_modules] base=%#llx, end=%#llx\n", new_module->load_base, new_module->load_end); } #endif /* DEBUG */ /* Create this module's nameslist */ process_namelist(new_module); /* Add it to the tail of active module list */ last->next = new_module; n_modules++; #ifdef DEBUG if (debug & MODULEDEBUG) { (void) printf( "[process_modules] total shared objects = %ld\n", n_modules - 1); } #endif /* DEBUG */ /* * Move to the next module in the PROF_MODULES_T list * (if present) */ if (!newmodp->next) more_modules = FALSE; /* LINTED: pointer cast */ newmodp = (ProfModule *)((caddr_t)modlp + newmodp->next); } while (more_modules); } static void reset_active_modules(void) { mod_info_t *mi; /* Except the executable, no other module should remain active */ for (mi = modules.next; mi; mi = mi->next) mi->active = FALSE; } static void getpfiledata(caddr_t memp, size_t fsz) { ProfObject *objp; caddr_t file_end; bool found_pcsamples = FALSE, found_cgraph = FALSE; /* * Before processing a new gmon.out, all modules except the * program executable must be made inactive, so that symbols * are searched only in the program executable, if we don't * find a MODULES_T object. Don't do it *after* we read a gmon.out, * because we need the active module data after we're done with * the last gmon.out, if we're doing summing. */ reset_active_modules(); file_end = memp + fsz; /* LINTED: pointer cast */ objp = (ProfObject *)(memp + ((ProfHeader *)memp)->size); while ((caddr_t)objp < file_end) { #ifdef DEBUG { unsigned int type = 0; if (debug & MONOUTDEBUG) { if (objp->type <= MAX_OBJTYPES) type = objp->type; (void) printf( "\n[getpfiledata] object %s [%#lx]\n", objname[type], objp->type); } } #endif /* DEBUG */ switch (objp->type) { case PROF_MODULES_T : process_modules((ProfModuleList *) objp); break; case PROF_CALLGRAPH_T : process_cgraph((ProfCallGraph *) objp); found_cgraph = TRUE; break; case PROF_BUFFER_T : process_pcsamples((ProfBuffer *) objp); found_pcsamples = TRUE; break; default : (void) fprintf(stderr, "%s: unknown prof object type=%d\n", whoami, objp->type); exit(EX_SOFTWARE); } /* LINTED: pointer cast */ objp = (ProfObject *)((caddr_t)objp + objp->size); } if (!found_cgraph || !found_pcsamples) { (void) fprintf(stderr, "%s: missing callgraph/pcsamples object\n", whoami); exit(EX_SOFTWARE); } if ((caddr_t)objp > file_end) { (void) fprintf(stderr, "%s: malformed profile file.\n", whoami); exit(EX_SOFTWARE); } if (first_file) first_file = FALSE; } static void readarcs(FILE *pfile) { /* * the rest of the file consists of * a bunch of tuples. */ /* CONSTCOND */ while (1) { struct rawarc arc; if (rflag) { if (Bflag) { L_cgarc64 rtld_arc64; /* * If rflag is set then this is an profiled * image generated by rtld. It needs to be * 'converted' to the standard data format. */ if (fread(&rtld_arc64, sizeof (L_cgarc64), 1, pfile) != 1) break; if (rtld_arc64.cg_from == PRF_OUTADDR64) arc.raw_frompc = s_highpc + 0x10; else arc.raw_frompc = (pctype)rtld_arc64.cg_from; arc.raw_selfpc = (pctype)rtld_arc64.cg_to; arc.raw_count = (actype)rtld_arc64.cg_count; } else { L_cgarc rtld_arc; /* * If rflag is set then this is an profiled * image generated by rtld. It needs to be * 'converted' to the standard data format. */ if (fread(&rtld_arc, sizeof (L_cgarc), 1, pfile) != 1) break; if (rtld_arc.cg_from == PRF_OUTADDR) arc.raw_frompc = s_highpc + 0x10; else arc.raw_frompc = (pctype) (uintptr_t)rtld_arc.cg_from; arc.raw_selfpc = (pctype) (uintptr_t)rtld_arc.cg_to; arc.raw_count = (actype)rtld_arc.cg_count; } } else { if (Bflag) { if (fread(&arc, sizeof (struct rawarc), 1, pfile) != 1) { break; } } else { /* * If these aren't big %pc's, we need to read * into the 32-bit raw arc structure, and * assign the members into the actual arc. */ struct rawarc32 arc32; if (fread(&arc32, sizeof (struct rawarc32), 1, pfile) != 1) break; arc.raw_frompc = (pctype)arc32.raw_frompc; arc.raw_selfpc = (pctype)arc32.raw_selfpc; arc.raw_count = (actype)arc32.raw_count; } } #ifdef DEBUG if (debug & SAMPLEDEBUG) { (void) printf("[getpfile] frompc 0x%llx selfpc " "0x%llx count %lld\n", arc.raw_frompc, arc.raw_selfpc, arc.raw_count); } #endif /* DEBUG */ /* * add this arc */ tally(&modules, &modules, &arc); } if (first_file) first_file = FALSE; } static void readsamples(FILE *pfile) { sztype i; unsigned_UNIT sample; if (samples == 0) { samples = (unsigned_UNIT *) calloc(nsamples, sizeof (unsigned_UNIT)); if (samples == 0) { (void) fprintf(stderr, "%s: No room for %d sample pc's\n", whoami, sampbytes / sizeof (unsigned_UNIT)); exit(EX_OSERR); } } for (i = 0; i < nsamples; i++) { (void) fread(&sample, sizeof (unsigned_UNIT), 1, pfile); if (feof(pfile)) break; samples[i] += sample; } if (i != nsamples) { (void) fprintf(stderr, "%s: unexpected EOF after reading %d/%d samples\n", whoami, --i, nsamples); exit(EX_IOERR); } } static void * handle_versioned(FILE *pfile, char *filename, size_t *fsz) { int fd; bool invalid_version; caddr_t fmem; struct stat buf; ProfHeader prof_hdr; off_t lret; /* * Check versioning info. For now, let's say we provide * backward compatibility, so we accept all older versions. */ if (fread(&prof_hdr, sizeof (ProfHeader), 1, pfile) == 0) { perror("fread()"); exit(EX_IOERR); } invalid_version = FALSE; if (prof_hdr.h_major_ver > PROF_MAJOR_VERSION) invalid_version = TRUE; else if (prof_hdr.h_major_ver == PROF_MAJOR_VERSION) { if (prof_hdr.h_minor_ver > PROF_MINOR_VERSION) invalid_version = FALSE; } if (invalid_version) { (void) fprintf(stderr, "%s: version %d.%d not supported\n", whoami, prof_hdr.h_major_ver, prof_hdr.h_minor_ver); exit(EX_SOFTWARE); } /* * Map gmon.out onto memory. */ (void) fclose(pfile); if ((fd = open(filename, O_RDONLY)) == -1) { perror(filename); exit(EX_IOERR); } if ((lret = lseek(fd, 0, SEEK_END)) == -1) { perror(filename); exit(EX_IOERR); } *fsz = lret; fmem = mmap(0, *fsz, PROT_READ, MAP_PRIVATE, fd, 0); if (fmem == MAP_FAILED) { (void) fprintf(stderr, "%s: can't map %s\n", whoami, filename); exit(EX_IOERR); } /* * Before we close this fd, save this gmon.out's info to later verify * if the shared objects it references have changed since the time * they were used to generate this gmon.out */ if (fstat(fd, &buf) == -1) { (void) fprintf(stderr, "%s: can't get info on `%s'\n", whoami, filename); exit(EX_NOINPUT); } gmonout_info.dev = buf.st_dev; gmonout_info.ino = buf.st_ino; gmonout_info.mtime = buf.st_mtime; gmonout_info.size = buf.st_size; (void) close(fd); return ((void *) fmem); } static void * openpfile(char *filename, size_t *fsz) { struct hdr tmp; FILE *pfile; unsigned long magic_num; size_t hdrsize; static bool first_time = TRUE; extern bool old_style; if ((pfile = fopen(filename, "r")) == NULL) { perror(filename); exit(EX_IOERR); } /* * Read in the magic. Note that we changed the cast "unsigned long" * to "unsigned int" because that's how h_magic is defined in the * new format ProfHeader. */ if (fread(&magic_num, sizeof (unsigned int), 1, pfile) == 0) { perror("fread()"); exit(EX_IOERR); } rewind(pfile); /* * First check if this is versioned or *old-style* gmon.out */ if (magic_num == (unsigned int)PROF_MAGIC) { if ((!first_time) && (old_style == TRUE)) { (void) fprintf(stderr, "%s: can't mix old & new format " "profiled files\n", whoami); exit(EX_SOFTWARE); } first_time = FALSE; old_style = FALSE; return (handle_versioned(pfile, filename, fsz)); } if ((!first_time) && (old_style == FALSE)) { (void) fprintf(stderr, "%s: can't mix old & new format " "profiled files\n", whoami); exit(EX_SOFTWARE); } first_time = FALSE; old_style = TRUE; fsz = 0; /* * Now, we need to determine if this is a run-time linker * profiled file or if it is a standard gmon.out. * * We do this by checking if magic matches PRF_MAGIC. If it * does, then this is a run-time linker profiled file, if it * doesn't, it must be a gmon.out file. */ if (magic_num == (unsigned long)PRF_MAGIC) rflag = TRUE; else rflag = FALSE; hdrsize = Bflag ? sizeof (struct hdr) : sizeof (struct hdr32); if (rflag) { if (Bflag) { L_hdr64 l_hdr64; /* * If the rflag is set then the input file is * rtld profiled data, we'll read it in and convert * it to the standard format (ie: make it look like * a gmon.out file). */ if (fread(&l_hdr64, sizeof (L_hdr64), 1, pfile) == 0) { perror("fread()"); exit(EX_IOERR); } if (l_hdr64.hd_version != PRF_VERSION_64) { (void) fprintf(stderr, "%s: expected version %d, " "got version %d when processing 64-bit " "run-time linker profiled file.\n", whoami, PRF_VERSION_64, l_hdr64.hd_version); exit(EX_SOFTWARE); } tmp.lowpc = 0; tmp.highpc = (pctype)l_hdr64.hd_hpc; tmp.ncnt = hdrsize + l_hdr64.hd_psize; } else { L_hdr l_hdr; /* * If the rflag is set then the input file is * rtld profiled data, we'll read it in and convert * it to the standard format (ie: make it look like * a gmon.out file). */ if (fread(&l_hdr, sizeof (L_hdr), 1, pfile) == 0) { perror("fread()"); exit(EX_IOERR); } if (l_hdr.hd_version != PRF_VERSION) { (void) fprintf(stderr, "%s: expected version %d, " "got version %d when processing " "run-time linker profiled file.\n", whoami, PRF_VERSION, l_hdr.hd_version); exit(EX_SOFTWARE); } tmp.lowpc = 0; tmp.highpc = (pctype)(uintptr_t)l_hdr.hd_hpc; tmp.ncnt = hdrsize + l_hdr.hd_psize; } } else { if (Bflag) { if (fread(&tmp, sizeof (struct hdr), 1, pfile) == 0) { perror("fread()"); exit(EX_IOERR); } } else { /* * If we're not reading big %pc's, we need to read * the 32-bit header, and assign the members to * the actual header. */ struct hdr32 hdr32; if (fread(&hdr32, sizeof (hdr32), 1, pfile) == 0) { perror("fread()"); exit(EX_IOERR); } tmp.lowpc = hdr32.lowpc; tmp.highpc = hdr32.highpc; tmp.ncnt = hdr32.ncnt; } } /* * perform sanity check on profiled file we've opened. */ if (tmp.lowpc >= tmp.highpc) { if (rflag) (void) fprintf(stderr, "%s: badly formed profiled data.\n", filename); else (void) fprintf(stderr, "%s: badly formed gmon.out file.\n", filename); exit(EX_SOFTWARE); } if (s_highpc != 0 && (tmp.lowpc != h.lowpc || tmp.highpc != h.highpc || tmp.ncnt != h.ncnt)) { (void) fprintf(stderr, "%s: incompatible with first gmon file\n", filename); exit(EX_IOERR); } h = tmp; s_lowpc = h.lowpc; s_highpc = h.highpc; lowpc = h.lowpc / sizeof (UNIT); highpc = h.highpc / sizeof (UNIT); sampbytes = h.ncnt > hdrsize ? h.ncnt - hdrsize : 0; nsamples = sampbytes / sizeof (unsigned_UNIT); #ifdef DEBUG if (debug & SAMPLEDEBUG) { (void) printf("[openpfile] hdr.lowpc 0x%llx hdr.highpc " "0x%llx hdr.ncnt %lld\n", h.lowpc, h.highpc, h.ncnt); (void) printf( "[openpfile] s_lowpc 0x%llx s_highpc 0x%llx\n", s_lowpc, s_highpc); (void) printf( "[openpfile] lowpc 0x%llx highpc 0x%llx\n", lowpc, highpc); (void) printf("[openpfile] sampbytes %d nsamples %d\n", sampbytes, nsamples); } #endif /* DEBUG */ return ((void *) pfile); } /* * Information from a gmon.out file depends on whether it's versioned * or non-versioned, *old style* gmon.out. If old-style, it is in two * parts : an array of sampling hits within pc ranges, and the arcs. If * versioned, it contains a header, followed by any number of * modules/callgraph/pcsample_buffer objects. */ static void getpfile(char *filename) { void *handle; size_t fsz; handle = openpfile(filename, &fsz); if (old_style) { readsamples((FILE *)handle); readarcs((FILE *)handle); (void) fclose((FILE *)handle); return; } getpfiledata((caddr_t)handle, fsz); (void) munmap(handle, fsz); } int main(int argc, char **argv) { char **sp; nltype **timesortnlp; int c; int errflg; prog_name = *argv; /* preserve program name */ debug = 0; nflag = FALSE; bflag = TRUE; lflag = FALSE; Cflag = FALSE; first_file = TRUE; rflag = FALSE; Bflag = FALSE; errflg = FALSE; while ((c = getopt(argc, argv, "abd:CcDE:e:F:f:ln:sz")) != EOF) switch (c) { case 'a': aflag = TRUE; break; case 'b': bflag = FALSE; break; case 'c': cflag = TRUE; break; case 'C': Cflag = TRUE; break; case 'd': dflag = TRUE; debug |= atoi(optarg); (void) printf("[main] debug = 0x%x\n", debug); break; case 'D': Dflag = TRUE; break; case 'E': addlist(Elist, optarg); Eflag = TRUE; addlist(elist, optarg); eflag = TRUE; break; case 'e': addlist(elist, optarg); eflag = TRUE; break; case 'F': addlist(Flist, optarg); Fflag = TRUE; addlist(flist, optarg); fflag = TRUE; break; case 'f': addlist(flist, optarg); fflag = TRUE; break; case 'l': lflag = TRUE; break; case 'n': nflag = TRUE; number_funcs_toprint = atoi(optarg); break; case 's': sflag = TRUE; break; case 'z': zflag = TRUE; break; case '?': errflg++; } if (errflg) { (void) fprintf(stderr, "usage: gprof [ -abcCDlsz ] [ -e function-name ] " "[ -E function-name ]\n\t[ -f function-name ] " "[ -F function-name ]\n\t[ image-file " "[ profile-file ... ] ]\n"); exit(EX_USAGE); } if (optind < argc) { a_outname = argv[optind++]; } else { a_outname = A_OUTNAME; } if (optind < argc) { gmonname = argv[optind++]; } else { gmonname = GMONNAME; } /* * turn off default functions */ for (sp = &defaultEs[0]; *sp; sp++) { Eflag = TRUE; addlist(Elist, *sp); eflag = TRUE; addlist(elist, *sp); } /* * how many ticks per second? * if we can't tell, report time in ticks. */ hz = sysconf(_SC_CLK_TCK); if (hz == -1) { hz = 1; (void) fprintf(stderr, "time is in ticks, not seconds\n"); } getnfile(a_outname); /* * get information about mon.out file(s). */ do { getpfile(gmonname); if (optind < argc) gmonname = argv[optind++]; else optind++; } while (optind <= argc); /* * dump out a gmon.sum file if requested */ if (sflag || Dflag) dumpsum(GMONSUM); if (old_style) { /* * assign samples to procedures */ asgnsamples(); } /* * assemble the dynamic profile */ timesortnlp = doarcs(); /* * print the dynamic profile */ #ifdef DEBUG if (debug & ANYDEBUG) { /* raw output of all symbols in all their glory */ int i; (void) printf(" Name, pc_entry_pt, svalue, tix_in_routine, " "#calls, selfcalls, index \n"); for (i = 0; i < modules.nname; i++) { /* Print each symbol */ if (timesortnlp[i]->name) (void) printf(" %s ", timesortnlp[i]->name); else (void) printf(" "); (void) printf(" %lld ", timesortnlp[i]->value); (void) printf(" %lld ", timesortnlp[i]->svalue); (void) printf(" %f ", timesortnlp[i]->time); (void) printf(" %lld ", timesortnlp[i]->ncall); (void) printf(" %lld ", timesortnlp[i]->selfcalls); (void) printf(" %d ", timesortnlp[i]->index); (void) printf(" \n"); } } #endif /* DEBUG */ printgprof(timesortnlp); /* * print the flat profile */ printprof(); /* * print the index */ printindex(); /* * print the modules */ printmodules(); done(); /* NOTREACHED */ return (0); } call graph profile: The sum of self and descendents is the major sort for this listing. function entries: index the index of the function in the call graph listing, as an aid to locating it (see below). %time the percentage of the total time of the program accounted for by this function and its descendents. self the number of seconds spent in this function itself. descendents the number of seconds spent in the descendents of this function on behalf of this function. called the number of times this function is called (other than recursive calls). self the number of times this function calls itself recursively. name the name of the function, with an indication of its membership in a cycle, if any. index the index of the function in the call graph listing, as an aid to locating it. parent listings: self* the number of seconds of this function's self time which is due to calls from this parent. descendents* the number of seconds of this function's descendent time which is due to calls from this parent. called** the number of times this function is called by this parent. This is the numerator of the fraction which divides up the function's time to its parents. total* the number of times this function was called by all of its parents. This is the denominator of the propagation fraction. parents the name of this parent, with an indication of the parent's membership in a cycle, if any. index the index of this parent in the call graph listing, as an aid in locating it. children listings: self* the number of seconds of this child's self time which is due to being called by this function. descendent* the number of seconds of this child's descendent's time which is due to being called by this function. called** the number of times this child is called by this function. This is the numerator of the propagation fraction for this child. total* the number of times this child is called by all functions. This is the denominator of the propagation fraction. children the name of this child, and an indication of its membership in a cycle, if any. index the index of this child in the call graph listing, as an aid to locating it. * these fields are omitted for parents (or children) in the same cycle as the function. If the function (or child) is a member of a cycle, the propagated times and propagation denominator represent the self time and descendent time of the cycle as a whole. ** static-only parents and children are indicated by a call count of 0. cycle listings: the cycle as a whole is listed with the same fields as a function entry. Below it are listed the members of the cycle, and their contributions to the time and call counts of the cycle. flat profile: % the percentage of the total running time of the time program used by this function. cumulative a running sum of the number of seconds accounted seconds for by this function and those listed above it. self the number of seconds accounted for by this seconds function alone. This is the major sort for this listing. calls the number of times this function was invoked, if this function is profiled, else blank. self the average number of milliseconds spent in this ms/call function per call, if this function is profiled, else blank. total the average number of milliseconds spent in this ms/call function and its descendents per call, if this function is profiled, else blank. name the name of the function. This is the minor sort for this listing. The index shows the location of the function in the gprof listing. If the index is in parenthesis it shows where it would appear in the gprof listing if it were to be printed. Index by function names and Object modules listing: The index entries are mapped to the function names. If the same function name appears in multiple object modules, each of these names is prefixed with a module identifier followed by a colon. The shared object modules listing that follows maps these identifiers to appropriate objects. /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SGS_GPROF_H #define _SGS_GPROF_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include "sparc.h" #include "gelf.h" #include "monv.h" #include "sgs.h" /* * who am i, for error messages. */ extern char *whoami; /* * booleans */ typedef Boolean bool; /* * Alignment related constants */ #define PGSZ 4096 #define STRUCT_ALIGN 8 /* * Macros related to structure alignment */ #define FLOOR(x, align) (((Address) x) & ~((align) - 1l)) #define CEIL(x, align) FLOOR(((Address) x) + (align) - 1l, align) #define PROFHDR_SZ (CEIL(sizeof (ProfHeader), STRUCT_ALIGN)) #define PROFMODLIST_SZ (CEIL(sizeof (ProfModuleList), STRUCT_ALIGN)) #define PROFMOD_SZ (CEIL(sizeof (ProfModule), STRUCT_ALIGN)) #define PROFBUF_SZ (CEIL(sizeof (ProfBuffer), STRUCT_ALIGN)) #define PROFCGRAPH_SZ (CEIL(sizeof (ProfCallGraph), STRUCT_ALIGN)) #define PROFFUNC_SZ (CEIL(sizeof (ProfFunction), STRUCT_ALIGN)) #define HDR_FILLER (PROFHDR_SZ - sizeof (ProfHeader)) #define MODLIST_FILLER (PROFMODLIST_SZ - sizeof (ProfModuleList)) #define MOD_FILLER (PROFMOD_SZ - sizeof (ProfModule)) #define BUF_FILLER (PROFBUF_SZ - sizeof (ProfBuffer)) #define CGRAPH_FILLER (PROFCGRAPH_SZ - sizeof (ProfCallGraph)) #define FUNC_FILLER (PROFFUNC_SZ - sizeof (ProfFunction)) /* * ticks per second */ extern long hz; typedef short UNIT; /* unit of profiling */ typedef unsigned short unsigned_UNIT; /* to remove warnings from gprof.c */ extern char *a_outname; extern char *prog_name; /* keep the program name for error messages */ #define A_OUTNAME "a.out" typedef unsigned long long pctype; typedef uint32_t pctype32; typedef size_t sztype; /* * Type definition for the arc count. */ typedef long long actype; typedef int32_t actype32; extern char *gmonname; #define GMONNAME "gmon.out" #define GMONSUM "gmon.sum" /* * Special symbols used for profiling of shared libraries through * the run-time linker. */ #define PRF_ETEXT "_etext" #define PRF_EXTSYM "" #define PRF_MEMTERM "_END_OF_VIRTUAL_MEMORY" #define PRF_SYMCNT 3 /* * Special symbol needed to determine the program exec's end addr. * Note that since this symbol doesn't get added to the nameslist, * it doesn't have to be counted in PRF_SYMCNT */ #define PRF_END "_end" /* * blurbs on the flat and graph profiles. */ #define FLAT_BLURB "/usr/share/lib/ccs/gprof.flat.blurb" #define CALLG_BLURB "/usr/share/lib/ccs/gprof.callg.blurb" /* * a raw arc, * with pointers to the calling site and the called site * and a count. */ struct rawarc { pctype raw_frompc; pctype raw_selfpc; actype raw_count; }; struct rawarc32 { pctype32 raw_frompc; pctype32 raw_selfpc; actype32 raw_count; }; /* * a constructed arc, * with pointers to the namelist entry of the parent and the child, * a count of how many times this arc was traversed, * and pointers to the next parent of this child and * the next child of this parent. */ struct arcstruct { struct nl *arc_parentp; /* pointer to parent's nl entry */ struct nl *arc_childp; /* pointer to child's nl entry */ actype arc_count; /* how calls from parent to child */ double arc_time; /* time inherited along arc */ double arc_childtime; /* childtime inherited along arc */ struct arcstruct *arc_parentlist; /* parents-of-this-child list */ struct arcstruct *arc_childlist; /* children-of-this-parent list */ }; typedef struct arcstruct arctype; /* * Additions for new-style gmon.out */ extern bool old_style; /* gmon.out versioned/non-versioned ? */ /* * Executable file info. * * All info that is required to identify a file or see if it has changed * relative to another file. */ struct fl_info { dev_t dev; /* device associated with this file */ ino_t ino; /* i-number of this file */ time_t mtime; /* last modified time of this file */ off_t size; /* size of file */ }; typedef struct fl_info fl_info_t; /* * Saved file info. */ extern fl_info_t aout_info; /* saved file info for program exec */ extern fl_info_t gmonout_info; /* current gmonout's info */ /* * Module info. */ struct mod_info { struct mod_info *next; /* ptr to next in the modules list */ char *name; /* name of this module */ int id; /* id, used while printing */ bool active; /* is this module active or not ? */ struct nl *nl; /* ptr to nameslist for this module */ struct nl *npe; /* virtual end of module's namelist */ sztype nname; /* number of funcs in this module */ GElf_Addr txt_origin; /* module's start as given in file */ GElf_Addr data_end; /* module's end addr as in file */ Address load_base; /* actual pcaddr where modl's loaded */ Address load_end; /* actual pcaddr where modl ends */ }; typedef struct mod_info mod_info_t; extern sztype total_names; /* from all modules */ /* * List of shared object modules. Note that this always includes the * program executable as the first element. */ extern mod_info_t modules; extern sztype n_modules; /* * The symbol table; * for each external in the specified file we gather * its address, the number of calls and compute its share of cpu time. */ struct nl { char *name; /* the name */ mod_info_t *module; /* module to which this belongs */ pctype value; /* the pc entry point */ pctype svalue; /* entry point aligned to histograms */ unsigned long sz; /* function size */ unsigned char syminfo; /* sym info */ size_t nticks; /* ticks in this routine */ double time; /* ticks in this routine as double */ double childtime; /* cumulative ticks in children */ actype ncall; /* how many times called */ actype selfcalls; /* how many calls to self */ double propfraction; /* what % of time propagates */ double propself; /* how much self time propagates */ double propchild; /* how much child time propagates */ bool printflag; /* should this be printed? */ int index; /* index in the graph list */ int toporder; /* graph call chain top-sort order */ int cycleno; /* internal number of cycle on */ struct nl *cyclehead; /* pointer to head of cycle */ struct nl *cnext; /* pointer to next member of cycle */ arctype *parents; /* list of caller arcs */ arctype *children; /* list of callee arcs */ unsigned long ncallers; /* no. of callers - dumpsum use only */ }; typedef struct nl nltype; /* * flag which marks a nl entry as topologically ``busy'' * flag which marks a nl entry as topologically ``not_numbered'' */ #define DFN_BUSY -1 #define DFN_NAN 0 /* * namelist entries for cycle headers. * the number of discovered cycles. */ extern nltype *cyclenl; /* cycle header namelist */ extern int ncycle; /* number of cycles discovered */ /* * The header on the gmon.out file. * old-style gmon.out consists of one of these headers, * and then an array of ncnt samples * representing the discretized program counter values. * this should be a struct phdr, but since everything is done * as UNITs, this is in UNITs too. */ struct hdr { pctype lowpc; pctype highpc; pctype ncnt; }; struct hdr32 { pctype32 lowpc; pctype32 highpc; pctype32 ncnt; }; extern struct hdr h; /* header of profiled data */ extern int debug; extern int number_funcs_toprint; /* * Each discretized pc sample has * a count of the number of samples in its range */ extern unsigned short *samples; extern pctype s_lowpc; /* lowpc from profile file in o-s gmon.out */ extern pctype s_highpc; /* highpc from profile file in o-s gmon.out */ extern sztype sampbytes; /* number of bytes of samples in o-s gmon.out */ extern sztype nsamples; /* number of samples for old-style gmon.out */ extern double actime; /* accumulated time thus far for putprofline */ extern double totime; /* total time for all routines */ extern double printtime; /* total of time being printed */ extern double scale; /* scale factor converting samples to pc */ /* values: each sample covers scale bytes */ /* -- all this is for old-style gmon.out only */ extern unsigned char *textspace; /* text space of a.out in core */ extern bool first_file; /* for difference option */ /* * Total number of pcsamples read so far (across gmon.out's) */ extern Size n_pcsamples; /* * option flags, from a to z. */ extern bool aflag; /* suppress static functions */ extern bool bflag; /* blurbs, too */ extern bool Bflag; /* big pc's (i.e. 64 bits) */ extern bool cflag; /* discovered call graph, too */ extern bool Cflag; /* gprofing c++ -- need demangling */ extern bool dflag; /* debugging options */ extern bool Dflag; /* difference option */ extern bool eflag; /* specific functions excluded */ extern bool Eflag; /* functions excluded with time */ extern bool fflag; /* specific functions requested */ extern bool Fflag; /* functions requested with time */ extern bool lflag; /* exclude LOCAL syms in output */ extern bool sflag; /* sum multiple gmon.out files */ extern bool zflag; /* zero time/called functions, too */ extern bool nflag; /* print only n functions in report */ extern bool rflag; /* profiling input generated by */ /* run-time linker */ /* * structure for various string lists */ struct stringlist { struct stringlist *next; char *string; }; extern struct stringlist *elist; extern struct stringlist *Elist; extern struct stringlist *flist; extern struct stringlist *Flist; /* * function declarations */ void addlist(struct stringlist *, char *); void addarc(nltype *, nltype *, actype); int arccmp(arctype *, arctype *); arctype *arclookup(nltype *, nltype *); void printblurb(char *); void dfn(nltype *); bool dfn_busy(nltype *); void dfn_findcycle(nltype *); bool dfn_numbered(nltype *); void dfn_post_visit(nltype *); void dfn_pre_visit(nltype *); void dfn_self_cycle(nltype *); nltype **doarcs(void); void done(void); void findcalls(nltype *, pctype, pctype); void flatprofheader(void); void flatprofline(nltype *); bool is_shared_obj(char *); void getnfile(char *); void process_namelist(mod_info_t *); void gprofheader(void); void gprofline(nltype *); int pc_cmp(const void *arg1, const void *arg2); int membercmp(nltype *, nltype *); nltype *nllookup(mod_info_t *, pctype, pctype *); bool onlist(struct stringlist *, char *); void printchildren(nltype *); void printcycle(nltype *); void printgprof(nltype **); void printindex(void); void printmembers(nltype *); void printmodules(void); void printname(nltype *); void printparents(nltype *); void printprof(void); void sortchildren(nltype *); void sortmembers(nltype *); void sortparents(nltype *); int timecmp(const void *arg1, const void *arg2); int totalcmp(const void *arg1, const void *arg2); #define LESSTHAN -1 #define EQUALTO 0 #define GREATERTHAN 1 /* * Macros related to debug messages. */ #define DFNDEBUG 0x0001 #define CYCLEDEBUG 0x0002 #define ARCDEBUG 0x0004 #define TALLYDEBUG 0x0008 #define TIMEDEBUG 0x0010 #define SAMPLEDEBUG 0x0020 #define ELFDEBUG 0x0040 #define CALLSDEBUG 0x0080 #define LOOKUPDEBUG 0x0100 #define PROPDEBUG 0x0200 #define ANYDEBUG 0x0400 #define MONOUTDEBUG 0x0800 #define MODULEDEBUG 0x1000 #define CGRAPHDEBUG 0x2000 #define PCSMPLDEBUG 0x4000 #ifdef __cplusplus } #endif #endif /* _SGS_GPROF_H */ /* * 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. */ #include "gprof.h" /* * look up an address in a sorted-by-address namelist * this deals with misses by mapping them to the next lower * entry point. */ static int searchmsg = 0; /* Emit the diagnostic only once */ nltype * nllookup(mod_info_t *module, pctype address, pctype *nxtsym) { size_t low = 0, middle, high = module->nname - 1; pctype keyval; nltype *mnl = module->nl; /* * If this is the program executable in which we are looking up * a symbol, then the actual load address will be the same as the * address specified in the ELF file. For shared objects, the * load address may differ from what is specified in the file. In * this case, we may need to look for a different value altogether. */ keyval = module->txt_origin + (address - module->load_base); if (keyval < mnl[low].value) { if (nxtsym) { *nxtsym = module->load_base + (mnl[low].value - module->txt_origin); } return (NULL); } if (keyval >= mnl[high].value) { if (nxtsym) *nxtsym = module->load_end; return (&mnl[high]); } while (low != high) { middle = (high + low) >> 1; if (mnl[middle].value <= keyval && mnl[middle + 1].value > keyval) { if (nxtsym) { *nxtsym = module->load_base + (mnl[middle + 1].value - module->txt_origin); } return (&mnl[middle]); } if (mnl[middle].value > keyval) { high = middle; } else { low = middle + 1; } } if (searchmsg++ == 0) (void) fprintf(stderr, "[nllookup] binary search fails???\n"); /* must never reach here! */ return (0); } arctype * arclookup(nltype *parentp, nltype *childp) { arctype *arcp; if (parentp == 0 || childp == 0) { (void) fprintf(stderr, "[arclookup] parentp == 0 || childp == 0\n"); return (0); } #ifdef DEBUG if (debug & LOOKUPDEBUG) { (void) printf("[arclookup] parent %s child %s\n", parentp->name, childp->name); } #endif /* DEBUG */ for (arcp = parentp->children; arcp; arcp = arcp->arc_childlist) { #ifdef DEBUG if (debug & LOOKUPDEBUG) { (void) printf( "[arclookup]\t arc_parent %s arc_child %s\n", arcp->arc_parentp->name, arcp->arc_childp->name); } #endif /* DEBUG */ if (arcp->arc_childp == childp) { return (arcp); } } return (0); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2018 Jason King * Copyright 2018, Joyent, Inc. */ #include #include #include #include #include "conv.h" #include "gprof.h" double actime; void print_demangled_name(int, nltype *); static void stripped_name(char **, size_t *, nltype **); /* * Symbols that must never be printed, no matter what. */ char *splsym[] = { PRF_ETEXT, PRF_EXTSYM, PRF_MEMTERM, NULL }; static bool is_special_sym(nltype *nlp); const char * demangled_name(nltype *selfp) { if (!Cflag) return (selfp->name); return (conv_demangle_name(selfp->name)); } void printprof(void) { nltype *np; nltype **sortednlp; int i, index; int print_count = number_funcs_toprint; bool print_flag = TRUE; mod_info_t *mi; actime = 0.0; (void) printf("\f\n"); flatprofheader(); /* * Sort the symbol table in by time */ sortednlp = (nltype **) calloc(total_names, sizeof (nltype *)); if (sortednlp == (nltype **) 0) { (void) fprintf(stderr, "[printprof] ran out of memory for time sorting\n"); } index = 0; for (mi = &modules; mi; mi = mi->next) { for (i = 0; i < mi->nname; i++) sortednlp[index++] = &(mi->nl[i]); } qsort(sortednlp, total_names, sizeof (nltype *), timecmp); for (index = 0; (index < total_names) && print_flag; index += 1) { np = sortednlp[index]; flatprofline(np); if (nflag) { if (--print_count == 0) print_flag = FALSE; } } actime = 0.0; free(sortednlp); } int timecmp(const void *arg1, const void *arg2) { nltype **npp1 = (nltype **)arg1; nltype **npp2 = (nltype **)arg2; double timediff; long calldiff; timediff = (*npp2)->time - (*npp1)->time; if (timediff > 0.0) return (1); if (timediff < 0.0) return (-1); calldiff = (*npp2)->ncall - (*npp1)->ncall; if (calldiff > 0) return (1); if (calldiff < 0) return (-1); return (strcmp((*npp1)->name, (*npp2)->name)); } /* * header for flatprofline */ void flatprofheader() { if (bflag) printblurb(FLAT_BLURB); if (old_style) { (void) printf( "\ngranularity: each sample hit covers %d byte(s)", (long)scale * sizeof (UNIT)); if (totime > 0.0) { (void) printf(" for %.2f%% of %.2f seconds\n\n", 100.0/totime, totime / hz); } else { (void) printf(" no time accumulated\n\n"); /* * this doesn't hurt since all the numerators will * be zero. */ totime = 1.0; } } (void) printf("%5.5s %10.10s %8.8s %8.8s %8.8s %8.8s %-8.8s\n", "% ", "cumulative", "self ", "", "self ", "total ", ""); (void) printf("%5.5s %10.10s %8.8s %8.8s %8.8s %8.8s %-8.8s\n", "time", "seconds ", "seconds", "calls", "ms/call", "ms/call", "name"); } void flatprofline(nltype *np) { if (zflag == 0 && np->ncall == 0 && np->time == 0) return; /* * Do not print certain special symbols, like PRF_EXTSYM, etc. * even if zflag was on. */ if (is_special_sym(np)) return; actime += np->time; (void) printf("%5.1f %10.2f %8.2f", 100 * np->time / totime, actime / hz, np->time / hz); if (np->ncall != 0) { (void) printf(" %8lld %8.2f %8.2f ", np->ncall, 1000 * np->time / hz / np->ncall, 1000 * (np->time + np->childtime) / hz / np->ncall); } else { if (!Cflag) (void) printf(" %8.8s %8.8s %8.8s ", "", "", ""); else (void) printf(" %8.8s %8.8s %8.8s ", "", "", ""); } printname(np); if (Cflag) print_demangled_name(55, np); (void) printf("\n"); } void gprofheader() { if (bflag) printblurb(CALLG_BLURB); if (old_style) { (void) printf( "\ngranularity: each sample hit covers %d byte(s)", (long)scale * sizeof (UNIT)); if (printtime > 0.0) { (void) printf(" for %.2f%% of %.2f seconds\n\n", 100.0/printtime, printtime / hz); } else { (void) printf(" no time propagated\n\n"); /* * this doesn't hurt, since all the numerators * will be 0.0 */ printtime = 1.0; } } else { (void) printf( "\ngranularity: each pc-hit is considered 1 tick"); if (hz != 1) { (void) printf(" (@ %4.3f seconds per tick)", (double)1.0 / hz); } (void) puts("\n\n"); } (void) printf("%6.6s %5.5s %7.7s %11.11s %7.7s/%-7.7s %-8.8s\n", "", "", "", "", "called", "total", "parents"); (void) printf("%-6.6s %5.5s %7.7s %11.11s %7.7s+%-7.7s %-8.8s\t%5.5s\n", "index", "%time", "self", "descendents", "called", "self", "name", "index"); (void) printf("%6.6s %5.5s %7.7s %11.11s %7.7s/%-7.7s %-8.8s\n", "", "", "", "", "called", "total", "children"); (void) printf("\n"); } void gprofline(nltype *np) { char kirkbuffer[BUFSIZ]; (void) sprintf(kirkbuffer, "[%d]", np->index); (void) printf("%-6.6s %5.1f %7.2f %11.2f", kirkbuffer, 100 * (np->propself + np->propchild) / printtime, np->propself / hz, np->propchild / hz); if ((np->ncall + np->selfcalls) != 0) { (void) printf(" %7lld", np->ncall); if (np->selfcalls != 0) (void) printf("+%-7lld ", np->selfcalls); else (void) printf(" %7.7s ", ""); } else { (void) printf(" %7.7s %7.7s ", "", ""); } printname(np); if (Cflag) print_demangled_name(50, np); (void) printf("\n"); } static bool is_special_sym(nltype *nlp) { int i; if (nlp->name == NULL) return (FALSE); for (i = 0; splsym[i]; i++) if (strcmp(splsym[i], nlp->name) == 0) return (TRUE); return (FALSE); } void printgprof(nltype **timesortnlp) { int index; nltype *parentp; int print_count = number_funcs_toprint; bool count_flag = TRUE; /* * Print out the structured profiling list */ gprofheader(); for (index = 0; index < total_names + ncycle && count_flag; index++) { parentp = timesortnlp[index]; if (zflag == 0 && parentp->ncall == 0 && parentp->selfcalls == 0 && parentp->propself == 0 && parentp -> propchild == 0) continue; if (!parentp->printflag) continue; /* * Do not print certain special symbols, like PRF_EXTSYM, etc. * even if zflag was on. */ if (is_special_sym(parentp)) continue; if (parentp->name == 0 && parentp->cycleno != 0) { /* * cycle header */ printcycle(parentp); printmembers(parentp); } else { printparents(parentp); gprofline(parentp); printchildren(parentp); } (void) printf("\n"); (void) printf( "-----------------------------------------------\n"); (void) printf("\n"); if (nflag) { --print_count; if (print_count == 0) count_flag = FALSE; } } free(timesortnlp); } /* * sort by decreasing propagated time * if times are equal, but one is a cycle header, * say that's first (e.g. less, i.e. -1). * if one's name doesn't have an underscore and the other does, * say the one is first. * all else being equal, sort by names. */ int totalcmp(const void *arg1, const void *arg2) { nltype **npp1 = (nltype **)arg1; nltype **npp2 = (nltype **)arg2; nltype *np1 = *npp1; nltype *np2 = *npp2; double diff; diff = (np1->propself + np1->propchild) - (np2->propself + np2->propchild); if (diff < 0.0) return (1); if (diff > 0.0) return (-1); if (np1->name == 0 && np1->cycleno != 0) return (-1); if (np2->name == 0 && np2->cycleno != 0) return (1); if (np1->name == 0) return (-1); if (np2->name == 0) return (1); if (*(np1->name) != '_' && *(np2->name) == '_') return (-1); if (*(np1->name) == '_' && *(np2->name) != '_') return (1); if (np1->ncall > np2->ncall) return (-1); if (np1->ncall < np2->ncall) return (1); return (strcmp(np1->name, np2->name)); } void printparents(nltype *childp) { nltype *parentp; arctype *arcp; nltype *cycleheadp; if (childp->cyclehead != 0) cycleheadp = childp -> cyclehead; else cycleheadp = childp; if (childp->parents == 0) { (void) printf("%6.6s %5.5s %7.7s %11.11s %7.7s %7.7s" " \n", "", "", "", "", "", ""); return; } sortparents(childp); for (arcp = childp->parents; arcp; arcp = arcp->arc_parentlist) { parentp = arcp -> arc_parentp; if (childp == parentp || (childp->cycleno != 0 && parentp->cycleno == childp->cycleno)) { /* * selfcall or call among siblings */ (void) printf( "%6.6s %5.5s %7.7s %11.11s %7lld %7.7s ", "", "", "", "", arcp->arc_count, ""); printname(parentp); if (Cflag) print_demangled_name(54, parentp); (void) printf("\n"); } else { /* * regular parent of child */ (void) printf( "%6.6s %5.5s %7.2f %11.2f %7lld/%-7lld ", "", "", arcp->arc_time / hz, arcp->arc_childtime / hz, arcp->arc_count, cycleheadp->ncall); printname(parentp); if (Cflag) print_demangled_name(54, parentp); (void) printf("\n"); } } } void printchildren(nltype *parentp) { nltype *childp; arctype *arcp; sortchildren(parentp); for (arcp = parentp->children; arcp; arcp = arcp->arc_childlist) { childp = arcp->arc_childp; if (childp == parentp || (childp->cycleno != 0 && childp->cycleno == parentp->cycleno)) { /* * self call or call to sibling */ (void) printf( "%6.6s %5.5s %7.7s %11.11s %7lld %7.7s ", "", "", "", "", arcp->arc_count, ""); printname(childp); if (Cflag) print_demangled_name(54, childp); (void) printf("\n"); } else { /* * regular child of parent */ if (childp->cyclehead) (void) printf("%6.6s %5.5s %7.2f %11.2f " "%7lld/%-7lld ", "", "", arcp->arc_time / hz, arcp->arc_childtime / hz, arcp->arc_count, childp->cyclehead->ncall); else (void) printf("%6.6s %5.5s %7.2f %11.2f " "%7lld %7.7s ", "", "", arcp->arc_time / hz, arcp->arc_childtime / hz, arcp->arc_count, ""); printname(childp); if (Cflag) print_demangled_name(54, childp); (void) printf("\n"); } } } void printname(nltype *selfp) { const char *c; c = demangled_name(selfp); if (selfp->name != 0) { (void) printf("%s", c); #ifdef DEBUG if (debug & DFNDEBUG) (void) printf("{%d} ", selfp->toporder); if (debug & PROPDEBUG) (void) printf("%5.2f%% ", selfp->propfraction); #endif /* DEBUG */ } if (selfp->cycleno != 0) (void) printf("\t", selfp->cycleno); if (selfp->index != 0) { if (selfp->printflag) (void) printf(" [%d]", selfp->index); else (void) printf(" (%d)", selfp->index); } if (c != selfp->name) free((void *)c); } void print_demangled_name(int n, nltype *selfp) { char *c = (char *)demangled_name(selfp); int i; if (c == selfp->name) return; (void) printf("\n"); for (i = 1; i < n; i++) (void) printf(" "); (void) printf("[%s]", selfp->name); free(c); } void sortchildren(nltype *parentp) { arctype *arcp; arctype *detachedp; arctype sorted; arctype *prevp; /* * unlink children from parent, * then insertion sort back on to sorted's children. * *arcp the arc you have detached and are inserting. * *detachedp the rest of the arcs to be sorted. * sorted arc list onto which you insertion sort. * *prevp arc before the arc you are comparing. */ sorted.arc_childlist = 0; arcp = parentp->children; if (arcp != NULL) detachedp = arcp->arc_childlist; while (arcp != NULL) { /* * consider *arcp as disconnected * insert it into sorted */ for (prevp = &sorted; prevp->arc_childlist; prevp = prevp->arc_childlist) { if (arccmp(arcp, prevp->arc_childlist) != LESSTHAN) break; } arcp->arc_childlist = prevp->arc_childlist; prevp->arc_childlist = arcp; arcp = detachedp; if (arcp != NULL) detachedp = detachedp->arc_childlist; } /* * reattach sorted children to parent */ parentp->children = sorted.arc_childlist; } void sortparents(nltype *childp) { arctype *arcp; arctype *detachedp; arctype sorted; arctype *prevp; /* * unlink parents from child, * then insertion sort back on to sorted's parents. * *arcp the arc you have detached and are inserting. * *detachedp the rest of the arcs to be sorted. * sorted arc list onto which you insertion sort. * *prevp arc before the arc you are comparing. */ sorted.arc_parentlist = 0; arcp = childp->parents; if (arcp != NULL) detachedp = arcp->arc_parentlist; while (arcp != NULL) { /* * consider *arcp as disconnected * insert it into sorted */ for (prevp = &sorted; prevp->arc_parentlist; prevp = prevp->arc_parentlist) { if (arccmp(arcp, prevp->arc_parentlist) != GREATERTHAN) break; } arcp->arc_parentlist = prevp->arc_parentlist; prevp->arc_parentlist = arcp; arcp = detachedp; if (detachedp != NULL) detachedp = detachedp->arc_parentlist; } /* * reattach sorted arcs to child */ childp->parents = sorted.arc_parentlist; } void printcycle(nltype *cyclep) { char kirkbuffer[BUFSIZ]; (void) sprintf(kirkbuffer, "[%d]", cyclep->index); (void) printf("%-6.6s %5.1f %7.2f %11.2f %7lld", kirkbuffer, 100 * (cyclep->propself + cyclep->propchild) / printtime, cyclep -> propself / hz, cyclep -> propchild / hz, cyclep -> ncall); if (cyclep->selfcalls != 0) (void) printf("+%-7lld", cyclep->selfcalls); else (void) printf(" %7.7s", ""); (void) printf(" \t[%d]\n", cyclep->cycleno, cyclep->index); } /* * print the members of a cycle */ void printmembers(nltype *cyclep) { nltype *memberp; sortmembers(cyclep); for (memberp = cyclep->cnext; memberp; memberp = memberp->cnext) { (void) printf("%6.6s %5.5s %7.2f %11.2f %7lld", "", "", memberp->propself / hz, memberp->propchild / hz, memberp->ncall); if (memberp->selfcalls != 0) (void) printf("+%-7lld", memberp->selfcalls); else (void) printf(" %7.7s", ""); (void) printf(" "); printname(memberp); if (Cflag) print_demangled_name(54, memberp); (void) printf("\n"); } } /* * sort members of a cycle */ void sortmembers(nltype *cyclep) { nltype *todo; nltype *doing; nltype *prev; /* * detach cycle members from cyclehead, * and insertion sort them back on. */ todo = cyclep->cnext; cyclep->cnext = 0; doing = todo; if (doing != NULL) todo = doing->cnext; while (doing != NULL) { for (prev = cyclep; prev->cnext; prev = prev->cnext) { if (membercmp(doing, prev->cnext) == GREATERTHAN) break; } doing->cnext = prev->cnext; prev->cnext = doing; doing = todo; if (doing != NULL) todo = doing->cnext; } } /* * major sort is on propself + propchild, * next is sort on ncalls + selfcalls. */ int membercmp(nltype *this, nltype *that) { double thistime = this->propself + this->propchild; double thattime = that->propself + that->propchild; actype thiscalls = this->ncall + this->selfcalls; actype thatcalls = that->ncall + that->selfcalls; if (thistime > thattime) return (GREATERTHAN); if (thistime < thattime) return (LESSTHAN); if (thiscalls > thatcalls) return (GREATERTHAN); if (thiscalls < thatcalls) return (LESSTHAN); return (EQUALTO); } /* * compare two arcs to/from the same child/parent. * - if one arc is a self arc, it's least. * - if one arc is within a cycle, it's less than. * - if both arcs are within a cycle, compare arc counts. * - if neither arc is within a cycle, compare with * arc_time + arc_childtime as major key * arc count as minor key */ int arccmp(arctype *thisp, arctype *thatp) { nltype *thisparentp = thisp->arc_parentp; nltype *thischildp = thisp->arc_childp; nltype *thatparentp = thatp->arc_parentp; nltype *thatchildp = thatp->arc_childp; double thistime; double thattime; #ifdef DEBUG if (debug & TIMEDEBUG) { (void) printf("[arccmp] "); printname(thisparentp); (void) printf(" calls "); printname(thischildp); (void) printf(" %f + %f %lld/%lld\n", thisp->arc_time, thisp->arc_childtime, thisp->arc_count, thischildp->ncall); (void) printf("[arccmp] "); printname(thatparentp); (void) printf(" calls "); printname(thatchildp); (void) printf(" %f + %f %lld/%lld\n", thatp->arc_time, thatp->arc_childtime, thatp->arc_count, thatchildp->ncall); (void) printf("\n"); } #endif /* DEBUG */ if (thisparentp == thischildp) { /* * this is a self call */ return (LESSTHAN); } if (thatparentp == thatchildp) { /* * that is a self call */ return (GREATERTHAN); } if (thisparentp->cycleno != 0 && thischildp->cycleno != 0 && thisparentp->cycleno == thischildp->cycleno) { /* * this is a call within a cycle */ if (thatparentp->cycleno != 0 && thatchildp->cycleno != 0 && thatparentp->cycleno == thatchildp->cycleno) { /* * that is a call within the cycle, too */ if (thisp->arc_count < thatp->arc_count) return (LESSTHAN); if (thisp->arc_count > thatp->arc_count) return (GREATERTHAN); return (EQUALTO); } else { /* * that isn't a call within the cycle */ return (LESSTHAN); } } else { /* * this isn't a call within a cycle */ if (thatparentp->cycleno != 0 && thatchildp->cycleno != 0 && thatparentp->cycleno == thatchildp->cycleno) { /* * that is a call within a cycle */ return (GREATERTHAN); } else { /* * neither is a call within a cycle */ thistime = thisp->arc_time + thisp->arc_childtime; thattime = thatp->arc_time + thatp->arc_childtime; if (thistime < thattime) return (LESSTHAN); if (thistime > thattime) return (GREATERTHAN); if (thisp->arc_count < thatp->arc_count) return (LESSTHAN); if (thisp->arc_count > thatp->arc_count) return (GREATERTHAN); return (EQUALTO); } } } void printblurb(char *blurbname) { FILE *blurbfile; int input; blurbfile = fopen(blurbname, "r"); if (blurbfile == NULL) { perror(blurbname); return; } while ((input = getc(blurbfile)) != EOF) (void) putchar(input); (void) fclose(blurbfile); } static int namecmp(const void *arg1, const void *arg2) { nltype **npp1 = (nltype **)arg1; nltype **npp2 = (nltype **)arg2; if (!Cflag) return (strcmp((*npp1)->name, (*npp2)->name)); else { static char *s1 = NULL, *s2 = NULL; static size_t s1len = 0, s2len = 0; stripped_name(&s1, &s1len, npp1); stripped_name(&s2, &s2len, npp2); return (strcmp(s1, s2)); } } #define NAME_CHUNK 512 #define ROUNDLEN(x) (((x) + NAME_CHUNK - 1) / NAME_CHUNK * NAME_CHUNK) static void adjust_size(char **pp, size_t *lenp, const char *name) { void *newp; size_t nlen = strlen(name); size_t buflen; if (*lenp > nlen) { (void) memset(*pp, '\0', *lenp); return; } buflen = ROUNDLEN(nlen + 1); if ((newp = realloc(*pp, buflen)) == NULL) { (void) fprintf(stderr, "gprof: out of memory comparing names\n"); exit(EXIT_FAILURE); } (void) memset(newp, '\0', buflen); *lenp = buflen; *pp = newp; } static void stripped_name(char **sp, size_t *slenp, nltype **npp) { const char *name, *d; char *c; name = d = demangled_name(*npp); adjust_size(sp, slenp, name); c = *sp; while ((*d != '(') && (*d != '\0')) { if (*d != ':') *c++ = *d++; else d++; } *c = '\0'; if ((*npp)->name != name) free((void *)name); } /* * Checks if the current symbol name is the same as its neighbour and * returns TRUE if it is. */ static bool does_clash(nltype **nlp, int ndx, int nnames) { /* * same as previous (if there's one) ? */ if (ndx && (strcmp(nlp[ndx]->name, nlp[ndx-1]->name) == 0)) return (TRUE); /* * same as next (if there's one) ? */ if ((ndx < (nnames - 1)) && (strcmp(nlp[ndx]->name, nlp[ndx+1]->name) == 0)) { return (TRUE); } return (FALSE); } void printmodules() { mod_info_t *mi; (void) printf("\f\nObject modules\n\n"); for (mi = &modules; mi; mi = mi->next) (void) printf(" %d: %s\n", mi->id, mi->name); } #define IDFMT(id) ((id) < 10 ? 1 : 2) #define NMFMT(id) ((id) < 10 ? 17 : 16) void printindex() { nltype **namesortnlp; nltype *nlp; int index, nnames, todo, i, j; char peterbuffer[BUFSIZ]; mod_info_t *mi; /* * Now, sort regular function name alphabetically * to create an index. */ namesortnlp = calloc(total_names + ncycle, sizeof (nltype *)); if (namesortnlp == NULL) (void) fprintf(stderr, "%s: ran out of memory for sorting\n", whoami); nnames = 0; for (mi = &modules; mi; mi = mi->next) { for (index = 0; index < mi->nname; index++) { if (zflag == 0 && (mi->nl[index]).ncall == 0 && (mi->nl[index]).time == 0) { continue; } /* * Do not print certain special symbols, like * PRF_EXTSYM, etc. even if zflag was on. */ if (is_special_sym(&(mi->nl[index]))) continue; namesortnlp[nnames++] = &(mi->nl[index]); } } qsort(namesortnlp, nnames, sizeof (nltype *), namecmp); for (index = 1, todo = nnames; index <= ncycle; index++) namesortnlp[todo++] = &cyclenl[index]; (void) printf("\f\nIndex by function name\n\n"); if (!Cflag) index = (todo + 2) / 3; else index = todo; for (i = 0; i < index; i++) { if (!Cflag) { for (j = i; j < todo; j += index) { nlp = namesortnlp[j]; if (nlp->printflag) { (void) sprintf(peterbuffer, "[%d]", nlp->index); } else { (void) sprintf(peterbuffer, "(%d)", nlp->index); } if (j < nnames) { if (does_clash(namesortnlp, j, nnames)) { (void) printf( "%6.6s %*d:%-*.*s", peterbuffer, IDFMT(nlp->module->id), nlp->module->id, NMFMT(nlp->module->id), NMFMT(nlp->module->id), nlp->name); } else { (void) printf("%6.6s %-19.19s", peterbuffer, nlp->name); } } else { (void) printf("%6.6s ", peterbuffer); (void) sprintf(peterbuffer, "", nlp->cycleno); (void) printf("%-19.19s", peterbuffer); } } } else { nlp = namesortnlp[i]; if (nlp->printflag) (void) sprintf(peterbuffer, "[%d]", nlp->index); else (void) sprintf(peterbuffer, "(%d)", nlp->index); if (i < nnames) { const char *d = demangled_name(nlp); if (does_clash(namesortnlp, i, nnames)) { (void) printf("%6.6s %d:%s\n", peterbuffer, nlp->module->id, d); } else { (void) printf("%6.6s %s\n", peterbuffer, d); } if (d != nlp->name) { (void) printf("%6.6s [%s]", "", nlp->name); free((void *)d); } } else { (void) printf("%6.6s ", peterbuffer); (void) sprintf(peterbuffer, "", nlp->cycleno); (void) printf("%-33.33s", peterbuffer); } } (void) printf("\n"); } free(namesortnlp); } /* * 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. */ #include "gprof.h" /* * these are the lists of names: * there is the list head and then the listname * is a pointer to the list head * (for ease of passing to stringlist functions). */ struct stringlist fhead = { 0, 0 }; struct stringlist *flist = &fhead; struct stringlist Fhead = { 0, 0 }; struct stringlist *Flist = &Fhead; struct stringlist ehead = { 0, 0 }; struct stringlist *elist = &ehead; struct stringlist Ehead = { 0, 0 }; struct stringlist *Elist = &Ehead; void addlist(struct stringlist *listp, char *funcname) { struct stringlist *slp; slp = malloc(sizeof (struct stringlist)); if (slp == NULL) { (void) fprintf(stderr, "gprof: ran out room for printlist\n"); exit(1); } slp->next = listp->next; slp->string = funcname; listp->next = slp; } bool onlist(struct stringlist *listp, char *funcname) { struct stringlist *slp; for (slp = listp->next; slp; slp = slp->next) { if (strcmp(slp->string, funcname) == 0) return (TRUE); if (funcname[0] == '_' && strcmp(slp->string, &funcname[1]) == 0) return (TRUE); } return (FALSE); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "gprof.h" #include #include #include #include #include #include #include #include "gelf.h" #ifdef DEBUG static void debug_dup_del(nltype *, nltype *); #define DPRINTF(msg, file) if (debug & ELFDEBUG) \ (void) printf(msg, file); #define PRINTF(msg) if (debug & ELFDEBUG) \ (void) printf(msg); #define DEBUG_DUP_DEL(keeper, louser) if (debug & ELFDEBUG) \ debug_dup_del(keeper, louser); #else #define DPRINTF(msg, file) #define PRINTF(msg) #define DEBUG_DUP_DEL(keeper, louser) #endif size_t textbegin, textsize; /* Prototype definitions first */ static void process(char *filename, int fd); static void get_symtab(Elf *elf, mod_info_t *module); static void get_textseg(Elf *elf, int fd); static void save_aout_info(char *); static void fatal_error(char *error) { (void) fprintf(stderr, "Fatal ELF error: %s (%s)\n", error, elf_errmsg(-1)); exit(EX_SOFTWARE); } bool is_shared_obj(char *name) { int fd; Elf *elf; GElf_Ehdr ehdr; if ((fd = open(name, O_RDONLY)) == -1) { (void) fprintf(stderr, "%s: can't open `%s'\n", whoami, name); exit(EX_NOINPUT); } if (elf_version(EV_CURRENT) == EV_NONE) fatal_error("libelf is out of date"); if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) fatal_error("can't read as ELF file"); if (gelf_getehdr(elf, &ehdr) == NULL) fatal_error("can't read ehdr"); (void) elf_end(elf); (void) close(fd); if (ehdr.e_type == ET_DYN) return (TRUE); else return (FALSE); } static void save_aout_info(char *aoutname) { struct stat buf; extern fl_info_t aout_info; if (stat(aoutname, &buf) == -1) { (void) fprintf(stderr, "%s: can't get info on `%s'\n", whoami, aoutname); exit(EX_NOINPUT); } aout_info.dev = buf.st_dev; aout_info.ino = buf.st_ino; aout_info.mtime = buf.st_mtime; aout_info.size = buf.st_size; } void getnfile(char *aoutname) { int fd; DPRINTF(" Attempting to open %s \n", aoutname); if ((fd = open((aoutname), O_RDONLY)) == -1) { (void) fprintf(stderr, "%s: can't open `%s'\n", whoami, aoutname); exit(EX_NOINPUT); } process(aoutname, fd); save_aout_info(aoutname); (void) close(fd); } static GElf_Addr get_txtorigin(Elf *elf) { GElf_Ehdr ehdr; GElf_Phdr phdr; GElf_Half ndx; GElf_Addr txt_origin = 0; bool first_load_seg = TRUE; if (gelf_getehdr(elf, &ehdr) == NULL) fatal_error("can't read ehdr"); for (ndx = 0; ndx < ehdr.e_phnum; ndx++) { if (gelf_getphdr(elf, ndx, &phdr) == NULL) continue; if ((phdr.p_type == PT_LOAD) && !(phdr.p_flags & PF_W)) { if (first_load_seg || phdr.p_vaddr < txt_origin) txt_origin = phdr.p_vaddr; if (first_load_seg) first_load_seg = FALSE; } } return (txt_origin); } void process_namelist(mod_info_t *module) { int fd; Elf *elf; if ((fd = open(module->name, O_RDONLY)) == -1) { (void) fprintf(stderr, "%s: can't read %s\n", whoami, module->name); (void) fprintf(stderr, "Exiting due to error(s)...\n"); exit(EX_NOINPUT); } /* * libelf's version already verified in processing a.out, * so directly do elf_begin() */ if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) fatal_error("can't read as ELF file"); module->next = NULL; module->txt_origin = get_txtorigin(elf); get_symtab(elf, module); module->active = TRUE; } /* * Get the ELF header and, if it exists, call get_symtab() * to begin processing of the file; otherwise, return from * processing the file with a warning. */ static void process(char *filename, int fd) { Elf *elf; extern bool cflag; extern bool Bflag; if (elf_version(EV_CURRENT) == EV_NONE) fatal_error("libelf is out of date"); if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) fatal_error("can't read as ELF file"); if (gelf_getclass(elf) == ELFCLASS64) Bflag = TRUE; /* * Initialize active modules list. Note that we set the end * address while reading the symbol table, in get_symtab */ modules.id = 1; modules.next = NULL; modules.txt_origin = get_txtorigin(elf); modules.load_base = modules.txt_origin; if ((modules.name = malloc(strlen(filename) + 1)) == NULL) { (void) fprintf(stderr, "%s: can't malloc %d bytes", whoami, strlen(filename) + 1); exit(EX_UNAVAILABLE); } (void) strcpy(modules.name, filename); get_symtab(elf, &modules); modules.load_end = modules.data_end; modules.active = TRUE; n_modules = 1; if (cflag) get_textseg(elf, fd); } static void get_textseg(Elf *elf, int fd) { GElf_Ehdr ehdr; GElf_Phdr phdr; GElf_Half i; if (gelf_getehdr(elf, &ehdr) == NULL) fatal_error("can't read ehdr"); for (i = 0; i < ehdr.e_phnum; i++) { if (gelf_getphdr(elf, i, &phdr) == NULL) continue; if (!(phdr.p_flags & PF_W) && (phdr.p_filesz > textsize)) { size_t chk; /* * We could have multiple loadable text segments; * keep the largest we find. */ if (textspace) free(textspace); /* * gprof is a 32-bit program; if this text segment * has a > 32-bit offset or length, it's too big. */ chk = (size_t)phdr.p_vaddr + (size_t)phdr.p_filesz; if (phdr.p_vaddr + phdr.p_filesz != (GElf_Xword)chk) fatal_error("text segment too large for -c"); textbegin = (size_t)phdr.p_vaddr; textsize = (size_t)phdr.p_filesz; textspace = malloc(textsize); if (lseek(fd, (off_t)phdr.p_offset, SEEK_SET) != (off_t)phdr.p_offset) fatal_error("cannot seek to text section"); if (read(fd, textspace, textsize) != textsize) fatal_error("cannot read text"); } } if (textsize == 0) fatal_error("can't find text segment"); } #ifdef DEBUG static void debug_dup_del(nltype * keeper, nltype * louser) { (void) printf("remove_dup_syms: discarding sym %s over sym %s\n", louser->name, keeper->name); } #endif /* DEBUG */ static void remove_dup_syms(nltype *nl, sztype *sym_count) { int i; int index; int nextsym; nltype *orig_list; if ((orig_list = malloc(sizeof (nltype) * *sym_count)) == NULL) { (void) fprintf(stderr, "gprof: remove_dup_syms: malloc failed\n"); (void) fprintf(stderr, "Exiting due to error(s)...\n"); exit(EX_UNAVAILABLE); } (void) memcpy(orig_list, nl, sizeof (nltype) * *sym_count); for (i = 0, index = 0, nextsym = 1; nextsym < *sym_count; nextsym++) { int i_type; int n_bind; int n_type; /* * If orig_list[nextsym] points to a new symvalue, then we * will copy our keeper and move on to the next symbol. */ if ((orig_list + i)->value < (orig_list + nextsym)->value) { *(nl + index++) = *(orig_list +i); i = nextsym; continue; } /* * If these two symbols have the same info, then we * keep the first and keep checking for dups. */ if ((orig_list + i)->syminfo == (orig_list + nextsym)->syminfo) { DEBUG_DUP_DEL(orig_list + i, orig_list + nextsym); continue; } n_bind = ELF32_ST_BIND((orig_list + nextsym)->syminfo); i_type = ELF32_ST_TYPE((orig_list + i)->syminfo); n_type = ELF32_ST_TYPE((orig_list + nextsym)->syminfo); /* * If they have the same type we take the stronger * bound function. */ if (i_type == n_type) { if (n_bind == STB_WEAK) { DEBUG_DUP_DEL((orig_list + i), (orig_list + nextsym)); continue; } DEBUG_DUP_DEL((orig_list + nextsym), (orig_list + i)); i = nextsym; continue; } /* * If the first symbol isn't of type NOTYPE then it must * be the keeper. */ if (i_type != STT_NOTYPE) { DEBUG_DUP_DEL((orig_list + i), (orig_list + nextsym)); continue; } /* * Throw away the first one and take the new * symbol */ DEBUG_DUP_DEL((orig_list + nextsym), (orig_list + i)); i = nextsym; } if (i < *sym_count) { if ((orig_list + i)->value > (nl + index - 1)->value) *(nl + index++) = *(orig_list + i); } *sym_count = index; } /* * compare either by name or by value for sorting. * This is the comparison function called by qsort to * sort the symbols either by name or value when requested. */ static int compare(const void *arg1, const void *arg2) { nltype *a = (nltype *)arg1; nltype *b = (nltype *)arg2; if (a->value > b->value) return (1); else return ((a->value == b->value) - 1); } static int is_function(Elf *elf, GElf_Sym *sym) { Elf_Scn *scn; GElf_Shdr shdr; /* * With shared objects, it is possible we come across a function * that's global, but is undefined. The definition is probably * elsewhere, so we'll have to skip it as far as this object is * concerned. */ if (sym->st_shndx == SHN_UNDEF) return (0); if (GELF_ST_TYPE(sym->st_info) == STT_FUNC) { if (GELF_ST_BIND(sym->st_info) == STB_GLOBAL) return (1); if (GELF_ST_BIND(sym->st_info) == STB_WEAK) return (1); if (!aflag && GELF_ST_BIND(sym->st_info) == STB_LOCAL) return (1); } /* * It's not a function; determine if it's in an executable section. */ if (GELF_ST_TYPE(sym->st_info) != STT_NOTYPE) return (0); /* * If it isn't global, and it isn't weak, and it either isn't * local or the "all flag" isn't set, then get out. */ if (GELF_ST_BIND(sym->st_info) != STB_GLOBAL && GELF_ST_BIND(sym->st_info) != STB_WEAK && (GELF_ST_BIND(sym->st_info) != STB_LOCAL || aflag)) return (0); if (sym->st_shndx >= SHN_LORESERVE) return (0); scn = elf_getscn(elf, sym->st_shndx); (void) gelf_getshdr(scn, &shdr); if (!(shdr.sh_flags & SHF_EXECINSTR)) return (0); return (1); } static void get_symtab(Elf *elf, mod_info_t *module) { Elf_Scn *scn = NULL, *sym_pri = NULL, *sym_aux = NULL; GElf_Word strndx = 0; sztype nsyms, i; Elf_Data *symdata_pri; Elf_Data *symdata_aux = NULL; GElf_Xword nsyms_pri = 0, nsyms_aux = 0; nltype *etext = NULL; nltype *l_nl, *l_npe; sztype l_nname; extern sztype total_names; int symtab_found = 0; /* * Scan the section headers looking for a symbol table. Our * preference is to use .symtab, because it contains the full * set of symbols. If we find it, we stop looking immediately * and use it. In the absence of a .symtab section, we are * willing to use the dynamic symbol table (.dynsym), possibly * augmented by the .SUNW_ldynsym, which contains local symbols. */ while ((symtab_found == 0) && ((scn = elf_nextscn(elf, scn)) != NULL)) { GElf_Shdr shdr; if (gelf_getshdr(scn, &shdr) == NULL) continue; switch (shdr.sh_type) { case SHT_SYMTAB: nsyms_pri = shdr.sh_size / shdr.sh_entsize; strndx = shdr.sh_link; sym_pri = scn; /* Throw away .SUNW_ldynsym. It is for .dynsym only */ nsyms_aux = 0; sym_aux = NULL; /* We have found the best symbol table. Stop looking */ symtab_found = 1; break; case SHT_DYNSYM: /* We will use .dynsym if no .symtab is found */ nsyms_pri = shdr.sh_size / shdr.sh_entsize; strndx = shdr.sh_link; sym_pri = scn; break; case SHT_SUNW_LDYNSYM: /* Auxiliary table, used with .dynsym */ nsyms_aux = shdr.sh_size / shdr.sh_entsize; sym_aux = scn; break; } } if (sym_pri == NULL || strndx == 0) fatal_error("can't find symbol table.\n"); nsyms = (sztype)(nsyms_pri + nsyms_aux); if ((nsyms_pri + nsyms_aux) != (GElf_Xword)nsyms) fatal_error( "32-bit gprof cannot handle more than 2^32 symbols"); if ((symdata_pri = elf_getdata(sym_pri, NULL)) == NULL) fatal_error("can't read symbol data.\n"); if ((sym_aux != NULL) && ((symdata_aux = elf_getdata(sym_aux, NULL)) == NULL)) fatal_error("can't read .SUNW_ldynsym symbol data.\n"); if ((l_nl = l_npe = (nltype *)calloc(nsyms + PRF_SYMCNT, sizeof (nltype))) == NULL) fatal_error("cannot allocate symbol data.\n"); /* * Now we need to cruise through the symbol table eliminating * all non-functions from consideration, and making strings * real. */ l_nname = 0; for (i = 1; i < nsyms; i++) { GElf_Sym gsym; char *name; /* * Look up the symbol. In the case where we have a * .SUNW_ldynsym/.dynsym pair, we treat them as a single * logical table, with the data from .SUNW_ldynsym coming * before the data in .dynsym. */ if (i >= nsyms_aux) (void) gelf_getsym(symdata_pri, i - nsyms_aux, &gsym); else (void) gelf_getsym(symdata_aux, i, &gsym); name = elf_strptr(elf, strndx, gsym.st_name); /* * We're interested in this symbol if it's a function or * if it's the symbol "_etext" */ if (is_function(elf, &gsym) || strcmp(name, PRF_ETEXT) == 0) { l_npe->name = name; l_npe->value = gsym.st_value; l_npe->sz = gsym.st_size; l_npe->syminfo = gsym.st_info; l_npe->module = module; if (strcmp(name, PRF_ETEXT) == 0) etext = l_npe; if (lflag == TRUE && GELF_ST_BIND(gsym.st_info) == STB_LOCAL) { /* * If the "locals only" flag is on, then * we add the local symbols to the * exclusion lists. */ addlist(Elist, name); addlist(elist, name); } DPRINTF("Index %lld:", l_nname); DPRINTF("\tValue: 0x%llx\t", l_npe->value); DPRINTF("Name: %s \n", l_npe->name); l_npe++; l_nname++; } if (strcmp(name, PRF_END) == 0) module->data_end = gsym.st_value; } if (l_npe == l_nl) fatal_error("no valid functions found"); /* * Finally, we need to construct some dummy entries. */ if (etext) { l_npe->name = PRF_EXTSYM; l_npe->value = etext->value + 1; l_npe->syminfo = GELF_ST_INFO(STB_GLOBAL, STT_FUNC); l_npe->module = module; l_npe++; l_nname++; } l_npe->name = PRF_MEMTERM; l_npe->value = (pctype)-1; l_npe->syminfo = GELF_ST_INFO(STB_GLOBAL, STT_FUNC); l_npe->module = module; l_npe++; l_nname++; /* * We're almost done; all we need to do is sort the symbols * and then remove the duplicates. */ qsort(l_nl, (size_t)l_nname, sizeof (nltype), compare); remove_dup_syms(l_nl, &l_nname); module->nl = l_nl; module->npe = l_npe; module->nname = l_nname; total_names += l_nname; } /* * 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 (c) 1993 by Sun Microsystems, Inc. */ /* * opcodes of the call instructions */ /* * offset (in bytes) of the code from the entry address of a routine. * (see asgnsamples for use and explanation.) */ #define OFFSET_OF_CODE 0 /* there is no mask on a SPARC */ #define UNITS_TO_CODE (OFFSET_OF_CODE / sizeof(UNIT)) /* * address at which text begins */ extern size_t textbegin; #define TORIGIN (unsigned int) textbegin /* * Macros for manipulating instruction fields. These use the * structures defined below */ #define OP(x) (((union instruct *) (x))->f_1.op) #define DISP30(x) (((union instruct *) (x))->f_1.disp30) #define OP3(x) (((union instruct *) (x))->f_3c.op3) #define RD(x) (((union instruct *) (x))->f_3c.rd) #define IMMED(x) (((union instruct *) (x))->f_3c.i) #define SIMM13(x) (((((union instruct *) (x))->f_3d.simm13) << 19) >> 19) #define RS1(x) (((union instruct *) (x))->f_3c.rs1) #define RS2(x) (((union instruct *) (x))->f_3c.rs2) /* * a few values for operand and register fields */ #define CALL 0x1 #define FMT3_0x10 0x2 #define JMPL 0x38 #define R_G0 0x0 #define R_O7 0xF #define R_I7 0x1F /* * A macro for converting from instructp to the appropriate address in * the program */ #define PC_VAL(x) ((x) - (unsigned long) textspace + TORIGIN) /* * structures for decoding instructions */ struct f_1 { unsigned long op:2, disp30:30; }; struct f_2a { unsigned long op:2, rd:5, op2:3, imm22:22; }; struct f_2b { unsigned long op:2, a:1, cond:4, op2:3, disp22:22; }; struct f_3a { unsigned long op:2, ign1:1, cond:4, op3:6, rs1:5, i:1, ign2:8, rs2:5; }; struct f_3b { unsigned long op:2, ign1:1, cond:4, op3:6, rs1:5, i:1, simm13:13; }; struct f_3c { unsigned long op:2, rd:5, op3:6, rs1:5, i:1, asi:8, rs2:5; }; struct f_3d { unsigned long op:2, rd:5, op3:6, rs1:5, i:1, simm13:13; }; struct f_3e { unsigned long op:2, rd:5, op3:6, rs1:5, opf:9, rs2:5; }; union instruct { struct f_1 f_1; struct f_2a f_2a; struct f_2b f_2b; struct f_3a f_3a; struct f_3b f_3b; struct f_3c f_3c; struct f_3d f_3d; struct f_3e f_3e; };