/* basic_blocks.c - Basic-block level related code: reading/writing of basic-block info to/from gmon.out; computing and formatting of basic-block related statistics. Copyright 1999, 2000, 2001, 2002, 2004, 2005, 2007 Free Software Foundation, Inc. This file is part of GNU Binutils. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gprof.h" #include "libiberty.h" #include "filenames.h" #include "basic_blocks.h" #include "corefile.h" #include "gmon_io.h" #include "gmon_out.h" #include "search_list.h" #include "source.h" #include "symtab.h" #include "sym_ids.h" static int cmp_bb (const PTR, const PTR); static int cmp_ncalls (const PTR, const PTR); static void fskip_string (FILE *); static void annotate_with_count (char *, unsigned int, int, PTR); /* Default option values: */ bfd_boolean bb_annotate_all_lines = FALSE; unsigned long bb_min_calls = 1; int bb_table_length = 10; /* Variables used to compute annotated source listing stats: */ static long num_executable_lines; static long num_lines_executed; /* Helper for sorting. Compares two symbols and returns result such that sorting will be increasing according to filename, line number, and address (in that order). */ static int cmp_bb (const PTR lp, const PTR rp) { int r; const Sym *left = *(const Sym **) lp; const Sym *right = *(const Sym **) rp; if (left->file && right->file) { r = filename_cmp (left->file->name, right->file->name); if (r) return r; if (left->line_num != right->line_num) return left->line_num - right->line_num; } if (left->addr < right->addr) return -1; else if (left->addr > right->addr) return 1; else return 0; } /* Helper for sorting. Order basic blocks in decreasing number of calls, ties are broken in increasing order of line numbers. */ static int cmp_ncalls (const PTR lp, const PTR rp) { const Sym *left = *(const Sym **) lp; const Sym *right = *(const Sym **) rp; if (!left) return 1; else if (!right) return -1; if (left->ncalls < right->ncalls) return 1; else if (left->ncalls > right->ncalls) return -1; return left->line_num - right->line_num; } /* Skip over variable length string. */ static void fskip_string (FILE *fp) { int ch; while ((ch = fgetc (fp)) != EOF) { if (ch == '\0') break; } } /* Read a basic-block record from file IFP. FILENAME is the name of file IFP and is provided for formatting error-messages only. */ void bb_read_rec (FILE *ifp, const char *filename) { unsigned int nblocks, b; bfd_vma addr, ncalls; Sym *sym; if (gmon_io_read_32 (ifp, &nblocks)) { fprintf (stderr, _("%s: %s: unexpected end of file\n"), whoami, filename); done (1); } nblocks = bfd_get_32 (core_bfd, (bfd_byte *) & nblocks); if (gmon_file_version == 0) fskip_string (ifp); for (b = 0; b < nblocks; ++b) { if (gmon_file_version == 0) { int line_num; /* Version 0 had lots of extra stuff that we don't care about anymore. */ if ((fread (&ncalls, sizeof (ncalls), 1, ifp) != 1) || (fread (&addr, sizeof (addr), 1, ifp) != 1) || (fskip_string (ifp), FALSE) || (fskip_string (ifp), FALSE) || (fread (&line_num, sizeof (line_num), 1, ifp) != 1)) { perror (filename); done (1); } } else if (gmon_io_read_vma (ifp, &addr) || gmon_io_read_vma (ifp, &ncalls)) { perror (filename); done (1); } /* Basic-block execution counts are meaningful only if we're profiling at the line-by-line level: */ if (line_granularity) { sym = sym_lookup (&symtab, addr); if (sym) { int i; DBG (BBDEBUG, printf ("[bb_read_rec] 0x%lx->0x%lx (%s:%d) cnt=%lu\n", (unsigned long) addr, (unsigned long) sym->addr, sym->name, sym->line_num, (unsigned long) ncalls)); for (i = 0; i < NBBS; i++) { if (! sym->bb_addr[i] || sym->bb_addr[i] == addr) { sym->bb_addr[i] = addr; sym->bb_calls[i] += ncalls; break; } } } } else { static bfd_boolean user_warned = FALSE; if (!user_warned) { user_warned = TRUE; fprintf (stderr, _("%s: warning: ignoring basic-block exec counts (use -l or --line)\n"), whoami); } } } return; } /* Write all basic-blocks with non-zero counts to file OFP. FILENAME is the name of OFP and is provided for producing error-messages only. */ void bb_write_blocks (FILE *ofp, const char *filename) { unsigned int nblocks = 0; Sym *sym; int i; /* Count how many non-zero blocks with have: */ for (sym = symtab.base; sym < symtab.limit; ++sym) { for (i = 0; i < NBBS && sym->bb_addr[i]; i++) ; nblocks += i; } /* Write header: */ if (gmon_io_write_8 (ofp, GMON_TAG_BB_COUNT) || gmon_io_write_32 (ofp, nblocks)) { perror (filename); done (1); } /* Write counts: */ for (sym = symtab.base; sym < symtab.limit; ++sym) { for (i = 0; i < NBBS && sym->bb_addr[i]; i++) { if (gmon_io_write_vma (ofp, sym->bb_addr[i]) || gmon_io_write_vma (ofp, (bfd_vma) sym->bb_calls[i])) { perror (filename); done (1); } } } } /* Output basic-block statistics in a format that is easily parseable. Current the format is: :: (: */ void print_exec_counts () { Sym **sorted_bbs, *sym; unsigned int i, j, len; if (first_output) first_output = FALSE; else printf ("\f\n"); /* Sort basic-blocks according to function name and line number: */ sorted_bbs = (Sym **) xmalloc (symtab.len * sizeof (sorted_bbs[0])); len = 0; for (sym = symtab.base; sym < symtab.limit; ++sym) { /* Accept symbol if it's in the INCL_EXEC table or there is no INCL_EXEC table and it does not appear in the EXCL_EXEC table. */ if (sym_lookup (&syms[INCL_EXEC], sym->addr) || (syms[INCL_EXEC].len == 0 && !sym_lookup (&syms[EXCL_EXEC], sym->addr))) { sorted_bbs[len++] = sym; } } qsort (sorted_bbs, len, sizeof (sorted_bbs[0]), cmp_bb); /* Output basic-blocks: */ for (i = 0; i < len; ++i) { sym = sorted_bbs [i]; if (sym->ncalls > 0 || ! ignore_zeros) { /* FIXME: This only works if bfd_vma is unsigned long. */ printf (_("%s:%d: (%s:0x%lx) %lu executions\n"), sym->file ? sym->file->name : _(""), sym->line_num, sym->name, (unsigned long) sym->addr, sym->ncalls); } for (j = 0; j < NBBS && sym->bb_addr[j]; j ++) { if (sym->bb_calls[j] > 0 || ! ignore_zeros) { /* FIXME: This only works if bfd_vma is unsigned long. */ printf (_("%s:%d: (%s:0x%lx) %lu executions\n"), sym->file ? sym->file->name : _(""), sym->line_num, sym->name, (unsigned long) sym->bb_addr[j], sym->bb_calls[j]); } } } free (sorted_bbs); } /* Helper for bb_annotated_source: format annotation containing number of line executions. Depends on being called on each line of a file in sequential order. Global variable bb_annotate_all_lines enables execution count compression (counts are supressed if identical to the last one) and prints counts on all executed lines. Otherwise, print all basic-block execution counts exactly once on the line that starts the basic-block. */ static void annotate_with_count (char *buf, unsigned int width, int line_num, PTR arg) { Source_File *sf = (Source_File *) arg; Sym *b; unsigned int i; static unsigned long last_count; unsigned long last_print = (unsigned long) -1; b = NULL; if (line_num <= sf->num_lines) b = (Sym *) sf->line[line_num - 1]; if (!b) { for (i = 0; i < width; i++) buf[i] = ' '; buf[width] = '\0'; } else { char tmpbuf[NBBS * 30]; char *p; unsigned long ncalls; int ncalls_set; unsigned int len; ++num_executable_lines; p = tmpbuf; *p = '\0'; ncalls = 0; ncalls_set = 0; /* If this is a function entry point, label the line no matter what. Otherwise, we're in the middle of a function, so check to see if the first basic-block address is larger than the starting address of the line. If so, then this line begins with a a portion of the previous basic-block, so print that prior execution count (if bb_annotate_all_lines is set). */ if (b->is_func) { sprintf (p, "%lu", b->ncalls); p += strlen (p); last_count = b->ncalls; last_print = last_count; ncalls = b->ncalls; ncalls_set = 1; } else if (bb_annotate_all_lines && b->bb_addr[0] && b->bb_addr[0] > b->addr) { sprintf (p, "%lu", last_count); p += strlen (p); last_print = last_count; ncalls = last_count; ncalls_set = 1; } /* Loop through all of this line's basic-blocks. For each one, update last_count, then compress sequential identical counts (if bb_annotate_all_lines) and print the execution count. */ for (i = 0; i < NBBS && b->bb_addr[i]; i++) { last_count = b->bb_calls[i]; if (! ncalls_set) { ncalls = 0; ncalls_set = 1; } ncalls += last_count; if (bb_annotate_all_lines && last_count == last_print) continue; if (p > tmpbuf) *p++ = ','; sprintf (p, "%lu", last_count); p += strlen (p); last_print = last_count; } /* We're done. If nothing has been printed on this line, print the last execution count (bb_annotate_all_lines), which could be from either a previous line (if there were no BBs on this line), or from this line (if all our BB counts were compressed out because they were identical). */ if (bb_annotate_all_lines && p == tmpbuf) { sprintf (p, "%lu", last_count); p += strlen (p); ncalls = last_count; ncalls_set = 1; } if (! ncalls_set) { unsigned int c; for (c = 0; c < width; c++) buf[c] = ' '; buf[width] = '\0'; return; } ++num_lines_executed; if (ncalls < bb_min_calls) { strcpy (tmpbuf, "#####"); p = tmpbuf + 5; } strcpy (p, " -> "); p += 4; len = p - tmpbuf; if (len >= width) { strncpy (buf, tmpbuf, width); buf[width] = '\0'; } else { unsigned int c; strcpy (buf + width - len, tmpbuf); for (c = 0; c < width - len; ++c) buf[c] = ' '; } } } /* Annotate the files named in SOURCE_FILES with basic-block statistics (execution counts). After each source files, a few statistics regarding that source file are printed. */ void print_annotated_source () { Sym *sym, *line_stats, *new_line; Source_File *sf; int i, table_len; FILE *ofp; /* Find maximum line number for each source file that user is interested in: */ for (sym = symtab.base; sym < symtab.limit; ++sym) { /* Accept symbol if it's file is known, its line number is bigger than anything we have seen for that file so far and if it's in the INCL_ANNO table or there is no INCL_ANNO table and it does not appear in the EXCL_ANNO table. */ if (sym->file && sym->line_num > sym->file->num_lines && (sym_lookup (&syms[INCL_ANNO], sym->addr) || (syms[INCL_ANNO].len == 0 && !sym_lookup (&syms[EXCL_ANNO], sym->addr)))) { sym->file->num_lines = sym->line_num; } } /* Allocate line descriptors: */ for (sf = first_src_file; sf; sf = sf->next) { if (sf->num_lines > 0) { sf->line = (void **) xmalloc (sf->num_lines * sizeof (sf->line[0])); memset (sf->line, 0, sf->num_lines * sizeof (sf->line[0])); } } /* Count executions per line: */ for (sym = symtab.base; sym < symtab.limit; ++sym) { if (sym->file && sym->file->num_lines && (sym_lookup (&syms[INCL_ANNO], sym->addr) || (syms[INCL_ANNO].len == 0 && !sym_lookup (&syms[EXCL_ANNO], sym->addr)))) { sym->file->ncalls += sym->ncalls; line_stats = (Sym *) sym->file->line[sym->line_num - 1]; if (!line_stats) { /* Common case has at most one basic-block per source line: */ sym->file->line[sym->line_num - 1] = sym; } else if (!line_stats->addr) { /* sym is the 3rd .. nth basic block for this line: */ line_stats->ncalls += sym->ncalls; } else { /* sym is the second basic block for this line. */ new_line = (Sym *) xmalloc (sizeof (*new_line)); *new_line = *line_stats; new_line->addr = 0; new_line->ncalls += sym->ncalls; sym->file->line[sym->line_num - 1] = new_line; } } } /* Plod over source files, annotating them: */ for (sf = first_src_file; sf; sf = sf->next) { if (!sf->num_lines || (ignore_zeros && sf->ncalls == 0)) continue; num_executable_lines = num_lines_executed = 0; ofp = annotate_source (sf, 16, annotate_with_count, sf); if (!ofp) continue; if (bb_table_length > 0) { fprintf (ofp, _("\n\nTop %d Lines:\n\n Line Count\n\n"), bb_table_length); /* Abuse line arrays---it's not needed anymore: */ qsort (sf->line, sf->num_lines, sizeof (sf->line[0]), cmp_ncalls); table_len = bb_table_length; if (table_len > sf->num_lines) table_len = sf->num_lines; for (i = 0; i < table_len; ++i) { sym = (Sym *) sf->line[i]; if (!sym || sym->ncalls == 0) break; fprintf (ofp, "%9d %10lu\n", sym->line_num, sym->ncalls); } } free (sf->line); sf->line = 0; fprintf (ofp, _("\nExecution Summary:\n\n")); fprintf (ofp, _("%9ld Executable lines in this file\n"), num_executable_lines); fprintf (ofp, _("%9ld Lines executed\n"), num_lines_executed); fprintf (ofp, _("%9.2f Percent of the file executed\n"), num_executable_lines ? 100.0 * num_lines_executed / (double) num_executable_lines : 100.0); fprintf (ofp, _("\n%9lu Total number of line executions\n"), sf->ncalls); fprintf (ofp, _("%9.2f Average executions per line\n"), num_executable_lines ? (double) sf->ncalls / (double) num_executable_lines : 0.0); if (ofp != stdout) fclose (ofp); } }