/*
* 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
* 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 (c) 1988 AT&T */
/* All Rights Reserved */
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Program profiling report generator.
*
* Usage:
*
* prof [-ChsVz] [-a | c | n | t] [-o | x] [-g | l]
* [-m mdata] [prog]
*
* Where "prog" is the program that was profiled; "a.out" by default.
* Options are:
*
* -n Sort by symbol name.
* -t Sort by decreasing time.
* -c Sort by decreasing number of calls.
* -a Sort by increasing symbol address.
*
* The options that determine the type of sorting are mutually exclusive.
* Additional options are:
*
* -o Include symbol addresses in output (in octal).
* -x Include symbol addresses in output (in hexadecimal).
* -g Include non-global T-type symbols in output.
* -l Do NOT include local T-type symbols in output (default).
* -z Include all symbols in profiling range, even if zero
* number of calls or time.
* -h Suppress table header.
* -s Follow report with additional statistical information.
* -m mdata Use file "mdata" instead of MON_OUT for profiling data.
* -V print version information for prof (and exit, if only V spec'd)
* -C call C++ demangle routine to demangle names before printing.
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <ctype.h>
#include "conv.h"
#include "symint.h"
#include "mon.h"
#include "debug.h"
#define OLD_DEBUG(x)
#if vax
/* Max positive difference between a fnpc and sl_addr for match */
/* Type if n_type field in file symbol table entry. */
#endif
/* Max positive difference between a fnpc and sl_addr for match */
/* For u3b, the "type" is storage class + section number (no type_t) */
#endif
#if (sparc)
#endif
/* Title fragment used if symbol addresses in output ("-o" or "-x"). */
/* Format for addresses in output */
/* Make sure something we are set up for. Else lay egg. */
#include "### No code for processor type ###"
#endif
/* Shorthand to gimme the Precise #of addresses per cells */
/* Used for unsigned fixed-point fraction with binary scale at */
/* the left of 15'th bit (0 as least significant bit) . */
/*
* TS1 insures that the symbols section is executable.
*/
/*
* TS2 insures that the symbol should be reported. We want
* to report only those symbols that are functions (STT_FUNC)
* or "notype" (STT_NOTYPE... "printf", for example). Also,
* unless the gflag is set, the symbol must be global.
*/
#define TS2(i) \
(((ELF32_ST_TYPE(i) == STT_FUNC) || \
(ELF32_ST_TYPE(i) == STT_NOTYPE)) && \
((ELF32_ST_BIND(i) == STB_GLOBAL) || \
int Cflag = 0;
/* May be changed by "-m file". */
extern void profver(void);
/* For symbol table entries read from program file. */
/* Compare routines called from qsort() */
/* Other stuff. */
/* Return size of open file (arg is file descriptor) */
static void snh(void);
static void Perror(char *s);
static void usage(void);
/* Memory allocation. Like malloc(), but no return if error. */
/* Scan past path part (if any) in the ... */
static char *basename(char *s);
/* command name, for error messages. */
char *cmdname;
/* Structure of subroutine call counters (cnt) is defined in mon.h. */
/* Structure for header of mon.out (hdr) is defined in mon.h. */
struct slist {
/* converted to secs. */
};
/* local structure for tracking synonyms in our symbol list */
struct snymEntry {
/* flag, */
/* > 0 report line printed for these syns. */
/* == 0 not printed yet. */
};
/* (space by _prof_Malloc) */
/* symbols. Set by "-[acnt]". */
/*
* Bit macro and flag bit definitions. These need to be identical to the
*/
/* synonym entries. */
/* for scanning entries. */
static int fprecision(long count);
/*
* Sort flags. Mutually exclusive. These need to be identical to the ones
* defined in profv.h
*/
extern unsigned char sort_flag; /* what type of sort ? */
/*
* printSnymNames - print a comma-seperated list of snym names.
* This routine hunts down all the synonyms for the given
* symbol, and prints them as a comma-seperated list.
* NB we assume that all the synonyms _Follow_ this one,
* since they are only printed when the First one
* is seen.
*/
void
{
/* how many snyms for this addr, total, and their shared address */
/* put out first name - it counts as one, so decr count */
i--;
/* for the others: find each, print each. */
while (--i >= 0) {
;
}
/* finally.. the trailing newline */
(void) putchar('\n');
}
/*
* getSnymEntry - see if addr was noted as a aliased address
* (i.e. a synonym symbol) and return the address of the
* snym entry if it was.
*/
struct snymEntry *
{
struct snymEntry *p;
int i;
return (p);
return ((struct snymEntry *)0);
}
int
{
/* pcounts: PC clock hit counts */
/* structures: subr PC-call counts. */
/* file (later # ones used). */
/* that fill in range of profiling. */
/* because nonzero time or # calls. */
int i;
int n, symct;
/* i(pc) = ((pc - pc_l) * sf)/bias. */
/* LINTED: set but not used */
/* {pc00, pc00+1, ... pc00+s_inv-1}. */
float t, t0;
int callTotal = 0;
DEBUG_LOC("main: top");
switch (n) {
int (*fcn)(); /* For function to sort results. */
case 'm': /* Specify data file: -m file */
break;
#ifdef ddt
case 'T': /* Set trace flags: -T(octnum) */
break;
#endif
case 'n': /* Sort by symbol name. */
goto check;
case 't': /* Sort by decreasing time. */
goto check;
case 'c': /* Sort by decreasing # calls. */
goto check;
case 'a': /* Sort by increasing symbol address */
/* (don't have to -- it will be) */
sort_flag |= BY_ADDRESS;
check: /* Here to check sort option conflicts. */
" previous specification\n", cmdname, n);
}
break;
case 'o': /* Include symbol addresses in output. */
case 'x': /* Include symbol addresses in output. */
break;
case 'g': /* Include local T symbols as well as global */
gflag = 1;
break;
case 'l': /* Do NOT include local T symbols */
gflag = 0;
break;
case 'z': /* Print all symbols in profiling range, */
/* even if no time or # calls. */
break;
case 'h': /* Suppress table header. */
break;
case 's': /* Follow normal output with extra summary. */
break;
case 'V':
VwasSpecified = 1;
break;
case 'C': /* demangle C++ names before printing. */
Cflag = 1;
break;
case '?': /* But no good. */
usage();
} /* End switch (n) */
} /* End while (getopt) */
DEBUG_LOC("main: following getopt");
/* if -V the only argument, just exit. */
exit(0);
/* If have not specified sort mode ... */
/*
* profver() checks to see if the mon.out was "versioned" and if
* yes, processes it and exits; otherwise, we have an *old-style*
* mon.out and we process it the old way.
*/
profver();
/* Open monitor data file (has counts). */
DEBUG_LOC("main: before _symintOpen");
Perror("_symintOpen failed");
}
DEBUG_LOC("main: after _symintOpen");
{
exit(1);
}
}
/* Compute the file address of symbol table. Machine-dependent. */
/* Number of symbols in file symbol table. */
if (symttl == 0) { /* This is possible. */
exit(0); /* Note zero exit code. */
}
/* Get size of file containing profiling data. Read header part. */
/* Get # cnt structures (they follow header), */
/* and allocate space for them. */
/* Read the call addr-count pairs. */
/*
* Compute # PC counters (pcounts), which occupy whatever is left
* of the file after the header and call counts.
*/
do { /* and scan backward until find highest one used. */
break; /* Stop when find nonzero count. */
} while (--n_cc > 0); /* Or all are zero. */
if (n_cc > 0) {
/* If less than all cnt entries are used, return unused space. */
snh(); /* Should not fail when reducing size. */
}
/* If more than 250 cnt entries used set verbose for warning */
/* Space for PC counts. */
/* Read the PC counts from rest of MON_OUT file. */
/*
*
* Having gotten preliminaries out of the way, get down to business.
* The range pc_m of addresses over which profiling was done is
* computed from the low (pc_l) and high (pc_h) addresses, gotten
* from the MON_OUT header. From this and the number of clock
* tick counters, n_pc, is computed the so-called "scale", sf, used
* in the mapping of addresses to indices, as follows:
*
* (pc - pc_l) * sf
* i(pc) = ----------------
* 0200000
*
* Also, the N-to-one value, s_inv, such that
*
* i(pc_l + K * s_inv + d) = K, for 0 <= d < s_inv
*
* Following this, the symbol table is scanned, and those symbols
* that qualify are counted. These are T-type symbols, excluding
* local (nonglobal) unless the "-g" option was given. Having thus
* is allocated, and the symbol table re-read, this time keeping
* qualified symbols.
*
* NB s_inv, as actually computed, is not sufficiently accurate
* (since it is truncated) for many calculations. Since it is
* more accurate, therefore the latter will often appear in
* the code when 's_inv' is mentioned. dween
*
*/
/* BEGIN CSTYLED */
"low pc = %#o, high pc = %#o, range = %#o = %u\n\
call counts: %u, %u used; pc counters: %u\n",
/* END CSTYLED */
/*LINTED: E_ASSIGMENT_CAUSE_LOSS_PREC*/
/*
* Now adjust bias and sf so that there is no overflow
* when calculating indices.
*/
sf >>= 1;
bias >>= 1;
}
/* BEGIN CSTYLED */
if (debug_value) {
"sf = %d, s_inv = %d bias = %d\n",
}
);
/* END CSTYLED */
/* Prepare to read symbols from "a.out" (or whatever). */
n_syms = 0; /* Init count of qualified symbols. */
n = symttl; /* Total symbols. */
while (--n >= 0) /* Scan symbol table. */
if (readnl(n)) /* Read and examine symbol, count qualifiers */
n_syms++;
/* BEGIN CSTYLED */
if (debug_value) {
}
);
/* END CSTYLED */
/* Allocate space for qualified symbols. */
/*
* Allocate space for synonym symbols
* (i.e. symbols that refer to the same address).
* NB there can be no more than n_syms/2 addresses
* with symbols, That Have Aliases, that refer to them!
*/
n_snyms = 0;
/* OLD_DEBUG(debug_value &= ~020); */
/* Loop on number of qualified symbols. */
/* Is qualified. Move name ... */
/* and address into slist structure. */
/* set other slist fields to zero. */
/* BEGIN CSTYLED */
if (debug_value & 02)
);
/* END CSTYLED */
slp++;
--n;
}
}
/*
*
* Now attempt to match call counts with symbols. To do this, it
* pairs by ascending address, since they are generally not, to
* begin with. The addresses associated with the counts are not,
* of course, the subroutine addresses associated with the symbols,
* but some address slightly past these. Therefore a given count
* address (in the fnpc field) is matched with the closest symbol
* address (sl_addr) that is:
* (1) less than the fnpc value but,
* (2) not more than the length of the function
* In other words, unreasonable matchups are avoided.
* Situations such as this could arise when static procedures are
* counted but the "-g" option was not given to this program,
* causing the symbol to fail to qualify. Without this limitation,
* unmatched counts could be erroneously charged.
*
*/
/* Sort call counters and ... */
/* symbols by increasing address. */
/* Loop to match up call counts & symbols. */
if (sz == 0)
/* got a candidate: find Closest. */
do {
closest_symp = slp;
slp++;
--n;
/* BEGIN CSTYLED */
if (debug_value & 04) {
"Routine %-8.8s @ %#8x+%-2d matches count address %#8x\n",
}
);
/* END CSTYLED */
++ccp;
--vn_cc;
++ccp;
--vn_cc;
} else {
++slp;
--n;
}
}
/*
*
* The distribution of times to addresses is done on a proportional
* basis as follows: The t counts in pcounts[i] correspond to clock
* ticks for values of pc in the range pc, pc+1, ..., pc+s_inv-1
* (odd addresses excluded for PDP11s). Without more detailed info,
* it must be assumed that there is no greater probability
* of the clock ticking for any particular pc in this range than for
* any other. Thus the t counts are considered to be equally
* distributed over the addresses in the range, and that the time for
* any given address in the range is pcounts[i]/s_inv.
*
* The values of the symbols that qualify, bounded below and above
* by pc_l and pc_h, respectively, partition the profiling range into
* regions to which are assigned the total times associated with the
* addresses they contain in the following way:
*
* The sum of all pcounts[i] for which the corresponding addresses are
* wholly within the partition are charged to the partition (the
* subroutine whose address is the lower bound of the partition).
*
* If the range of addresses corresponding to a given t = pcounts[i]
* lies astraddle the boundary of a partition, e.g., for some k such
* that 0 < k < s_inv-1, the addresses pc, pc+1, ..., pc+k-1 are in
* the lower partition, and the addresses pc+k, pc+k+1, ..., pc+s_inv-1
* are in the next partition, then k*pcounts[i]/s_inv time is charged
* to the lower partition, and (s_inv-k) * pcounts[i]/s_inv time to the
* upper. It is conceivable, in cases of large granularity or small
* subroutines, for a range corresponding to a given pcounts[i] to
* overlap three regions, completely containing the (small) middle one.
* The algorithm is adjusted appropriately in this case.
*
*/
for (n = 0; n < n_syms; n++) { /* Loop on symbols. */
/* Start addr of region, low addr of overlap. */
/* Start addr of next region, low addr of overlap. */
/* First index into pcounts for this region and next region. */
long ticks;
/* Address of symbol (subroutine). */
/* Address of next symbol, if any or top */
/* of profile range, if not */
/* Lower bound of indices into pcounts for this range */
/* Upper bound (least or least + 1) of indices. */
/* Lowest addr for which count maps to pcounts[i0]; */
/* Lowest addr for which count maps to pcounts[i1]. */
/* BEGIN CSTYLED */
"%-8.8s\ti0 = %4d, pc00 = %#6o, pc0 = %#6o\n\
\t\ti1 = %4d, pc10 = %#6o, pc1 = %#6o\n\t\t",
/* END CSTYLED */
t = 0; /* Init time for this symbol. */
/* Counter overlaps two areas? (unlikely */
/* unless large granularity). */
/* Time less that which overlaps adjacent areas */
/* BEGIN CSTYLED */
);
/* END CSTYLED */
} else {
/* Overlap with previous region? */
/* BEGIN CSTYLED */
/* Get time of overlapping area and */
/* subtract proportion for lower region. */
t += PROFSEC(
/* Do not count this time when summing times */
/* wholly within the region. */
i0++;
/* BEGIN CSTYLED */
/* END CSTYLED */
}
/* routines. */
ticks = 0;
/* Stop at first count that overlaps following */
/* routine. */
/* Some overlap with low addresses of next routine? */
/* Yes. Get total count ... */
/* and accumulate proportion for addresses in */
/* range of this routine */
/* BEGIN CSTYLED */
);
/* END CSTYLED */
}
} /* End if (i0 == i1) ... else ... */
t0 += t; /* Accumulate total time. */
} /* End for (n = 0; n < n_syms; n++) */
/* Final pass to total up time. */
/* Sum ticks, then convert to seconds. */
;
/*
* Now, whilst we still have the symbols sorted
* in address order..
* Loop to record duplicates, so we can display
* synonym symbols correctly.
* Synonym symbols, or symbols with the same address,
* are to be displayed by prof on the same line, with
* one statistics line, as below:
* ... 255 ldaopen, ldaopen
* The way this will be implemented, is as follows:
*
* Pass 1 - while the symbols are in address order, we
* do a pre-pass through them, to determine for which
* addresses there are more than one symbol (i.e. synonyms).
* During this prepass we collect summary statistics in
* the synonym entry, for all the synonyms.
*
* 'Pass' 2 - while printing a report, for each report line,
* if the current symbol is a synonym symbol (i.e. in the
* snymList) then we scan forward and pick up all the names
* which map to this address, and print them too.
* If the address' synonyms have already been printed, then
* we just skip this symbol and go on to process the next.
*
*/
{
/* pass 1 */
char *thisaddr;
int thisIsSnym;
/* BEGIN CSTYLED */
);
/* END CSTYLED */
/* NB loop starts with 2nd symbol, loops over n_syms-1 symbols! */
if (thisIsSnym) {
/* gotta synonym */
if (!lastWasSnym) {
/* BEGIN CSTYLED */
if (debug_value) {
"Synonym series:\n1st->\t%s at address %x, ct=%ld, time=%f\n",
totseries++;
totsnyms++;
}
);
/* END CSTYLED */
/* this is the Second! of a series */
/* zero summary statistics */
snymp->tot_sl_count = 0;
/* Offen the Reported flag */
snymp->snymReported = 0;
}
/* BEGIN CSTYLED */
if (debug_value) {
"\t%s at address %x, ct=%ld, time=%f\n",
totsnyms++;
}
);
/* END CSTYLED */
/* ok - bump count for snym, and note its Finding */
/* and update the summary statistics */
}
/* BEGIN CSTYLED */
);
/* END CSTYLED */
}
/* BEGIN CSTYLED */
if (debug_value) {
}
);
/* END CSTYLED */
}
/*
* Most of the heavy work is done now. Only minor stuff remains.
* The symbols are currently in address order and must be re-sorted
* if desired in a different order. Report generating options
* include "-o" or "-x": Include symbol address, which causes
* another column
* in the output; and "-z": Include symbols in report even if zero
* time and call count. Symbols not in profiling range are excluded
* in any case. Following the main body of the report, the "-s"
* option causes certain additional information to be printed.
*/
if (sort) /* If comparison routine given then use it. */
}
t = 0.0; /* Init cumulative time. */
n_nonzero = 0; /* Number of symbols with nonzero time or # calls. */
/* t0, time in seconds. */
/* if a snym symbol, use summarized stats, else use indiv. */
} else {
}
/* if a snym and already reported, skip this entry */
continue;
/* Don't do entries with no action. */
continue;
}
/* count number of entries (i.e. symbols) printed */
if (snymp)
else
n_nonzero++;
/* LINTED: variable format */
}
t += t0; /* move here; compiler bug !! */
fdigits = 0;
if (count) { /* Any calls recorded? */
/* Get reasonable number of fractional digits to print. */
} else {
}
/*
* now print the name (or comma-seperate list of names,
* for synonym symbols).
*/
if (snymp) {
}
else
}
", %d had zero time and zero call-counts\n",
else
}
} else {
}
return (0);
}
/* Return size of file associated with file descriptor fd. */
static off_t
{
Perror("stat");
}
/* Read symbol entry. Return TRUE if satisfies conditions. */
static int
{
/* BEGIN CSTYLED */
if (debug_value & 020) {
"`%-8.8s'\tst_info=%#4o, value=%#8.6o\n",
}
);
/* END CSTYLED */
/*
* TXTSYM accepts global (and local, if "-g" given) T-type symbols.
* Only those in the profiling range are useful.
*/
}
/*
* Error-checking memory allocators -
* Guarantees good return (else none at all).
*/
static void *
{
void *p;
exit(1);
}
return (p);
}
/*
* Given the quotient Q = N/D, where entier(N) == N and D > 0, an
* approximation of the "best" number of fractional digits to use
* in printing Q is f = entier(log10(D)), which is crudely produced
* by the following routine.
*/
static int
{
}
/*
* Return pointer to base name(name less path) of string s.
* Handles case of superfluous trailing '/'s, and unlikely
* case of s == "/".
*/
static char *
basename(char *s)
{
char *p;
p = &s[strlen(s)]; /* End (+1) of string. */
while (p > s && *--p == '/') /* Trim trailing '/'s. */
*p = '\0';
p++; /* New end (+1) of string. */
while (p > s && *--p != '/') /* Break backward on '/'. */
;
if (*p == '/') /* If found '/', point to 1st following. */
p++;
if (*p == '\0')
p = "/"; /* If NULL, must be "/". (?) */
return (p);
}
/* Here if unexpected read problem. */
static void
{
exit(1);
}
/* Version of perror() that prints cmdname first. */
static void
Perror(char *s)
{ /* Print system error message & exit. */
perror(s); /* Print message. */
}
/* Here for things that "Should Never Happen". */
static void
snh(void)
{
(void) abort();
}
/*
* Various comparison routines for qsort. Uses:
*
* c_ccaddr - Compare fnpc fields of cnt structs to put
* call counters in increasing address order.
* c_sladdr - Sort slist structures on increasing address.
* c_time - " " " " decreasing time.
* c_ncalls - " " " " decreasing # calls.
* c_name - " " " " increasing symbol name
*/
int
{
}
int
{
}
int
{
}
int
{
/* Decreasing # calls. */
}
int
{
int diff;
/* flex names has variable length strings for names */
}
char *format_buf;
static char *
demangled_name(char *s)
{
const char *name;
name = conv_demangle_name(s);
return (s);
if (format_buf != NULL)
if (format_buf == NULL)
return (s);
return (format_buf);
}
/* getname - get the name of a symbol in a permanent fashion */
static char *
{
return ("<<bad symbol name>>");
if (Cflag)
/* just in case very long name */
sp_used = 0;
}
return (name);
}
static void
usage(void)
{
"usage: %s [-ChsVz] [-a | c | n | t] [-o | x] [-g | l]\n"
"\t[-m mdata] [prog]\n",
cmdname);
exit(1);
}