| /* |
| * Copyright (c) 2000-2001 Red Hat, Inc. All rights reserved. |
| * |
| * This copyrighted material is made available to anyone wishing to use, modify, |
| * copy, or redistribute it subject to the terms and conditions of the BSD |
| * License. This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY expressed or implied, including the implied |
| * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. A copy |
| * of this license is available at http://www.opensource.org/licenses. Any |
| * Red Hat trademarks that are incorporated in the source code or documentation |
| * are not subject to the BSD License and may only be used or replicated with |
| * the express permission of Red Hat, Inc. |
| */ |
| |
| /* Structure emitted by -a */ |
| struct bb |
| { |
| long zero_word; |
| const char *filename; |
| long *counts; |
| long ncounts; |
| struct bb *next; |
| const unsigned long *addresses; |
| |
| /* Older GCC's did not emit these fields. */ |
| long nwords; |
| const char **functions; |
| const long *line_nums; |
| const char **filenames; |
| char *flags; |
| }; |
| |
| /* Simple minded basic block profiling output dumper for |
| systems that don't provide tcov support. At present, |
| it requires atexit and stdio. */ |
| |
| #undef NULL /* Avoid errors if stdio.h and our stddef.h mismatch. */ |
| #include <stdio.h> |
| #include <time.h> |
| char *ctime (const time_t *); |
| |
| /*#include "gbl-ctors.h"*/ |
| #include "gcov-io.h" |
| #include <string.h> |
| |
| static struct bb *bb_head; |
| |
| static int num_digits (long value, int base) __attribute__ ((const)); |
| |
| /* Return the number of digits needed to print a value */ |
| /* __inline__ */ static int num_digits (long value, int base) |
| { |
| int minus = (value < 0 && base != 16); |
| unsigned long v = (minus) ? -value : value; |
| int ret = minus; |
| |
| do |
| { |
| v /= base; |
| ret++; |
| } |
| while (v); |
| |
| return ret; |
| } |
| |
| void |
| __bb_exit_func (void) |
| { |
| FILE *da_file, *file; |
| long time_value; |
| int i; |
| |
| if (bb_head == 0) |
| return; |
| |
| i = strlen (bb_head->filename) - 3; |
| |
| if (!strcmp (bb_head->filename+i, ".da")) |
| { |
| /* Must be -fprofile-arcs not -a. |
| Dump data in a form that gcov expects. */ |
| |
| struct bb *ptr; |
| |
| for (ptr = bb_head; ptr != (struct bb *) 0; ptr = ptr->next) |
| { |
| int firstchar; |
| |
| /* Make sure the output file exists - |
| but don't clobber exiting data. */ |
| if ((da_file = fopen (ptr->filename, "a")) != 0) |
| fclose (da_file); |
| |
| /* Need to re-open in order to be able to write from the start. */ |
| da_file = fopen (ptr->filename, "r+b"); |
| /* Some old systems might not allow the 'b' mode modifier. |
| Therefore, try to open without it. This can lead to a race |
| condition so that when you delete and re-create the file, the |
| file might be opened in text mode, but then, you shouldn't |
| delete the file in the first place. */ |
| if (da_file == 0) |
| da_file = fopen (ptr->filename, "r+"); |
| if (da_file == 0) |
| { |
| fprintf (stderr, "arc profiling: Can't open output file %s.\n", |
| ptr->filename); |
| continue; |
| } |
| |
| /* After a fork, another process might try to read and/or write |
| the same file simultanously. So if we can, lock the file to |
| avoid race conditions. */ |
| |
| /* If the file is not empty, and the number of counts in it is the |
| same, then merge them in. */ |
| firstchar = fgetc (da_file); |
| if (firstchar == EOF) |
| { |
| if (ferror (da_file)) |
| { |
| fprintf (stderr, "arc profiling: Can't read output file "); |
| perror (ptr->filename); |
| } |
| } |
| else |
| { |
| long n_counts = 0; |
| |
| if (ungetc (firstchar, da_file) == EOF) |
| rewind (da_file); |
| if (__read_long (&n_counts, da_file, 8) != 0) |
| { |
| fprintf (stderr, "arc profiling: Can't read output file %s.\n", |
| ptr->filename); |
| continue; |
| } |
| |
| if (n_counts == ptr->ncounts) |
| { |
| int i; |
| |
| for (i = 0; i < n_counts; i++) |
| { |
| long v = 0; |
| |
| if (__read_long (&v, da_file, 8) != 0) |
| { |
| fprintf (stderr, "arc profiling: Can't read output file %s.\n", |
| ptr->filename); |
| break; |
| } |
| ptr->counts[i] += v; |
| } |
| } |
| |
| } |
| |
| rewind (da_file); |
| |
| /* ??? Should first write a header to the file. Preferably, a 4 byte |
| magic number, 4 bytes containing the time the program was |
| compiled, 4 bytes containing the last modification time of the |
| source file, and 4 bytes indicating the compiler options used. |
| |
| That way we can easily verify that the proper source/executable/ |
| data file combination is being used from gcov. */ |
| |
| if (__write_long (ptr->ncounts, da_file, 8) != 0) |
| { |
| |
| fprintf (stderr, "arc profiling: Error writing output file %s.\n", |
| ptr->filename); |
| } |
| else |
| { |
| int j; |
| long *count_ptr = ptr->counts; |
| int ret = 0; |
| for (j = ptr->ncounts; j > 0; j--) |
| { |
| if (__write_long (*count_ptr, da_file, 8) != 0) |
| { |
| ret=1; |
| break; |
| } |
| count_ptr++; |
| } |
| if (ret) |
| fprintf (stderr, "arc profiling: Error writing output file %s.\n", |
| ptr->filename); |
| } |
| |
| if (fclose (da_file) == EOF) |
| fprintf (stderr, "arc profiling: Error closing output file %s.\n", |
| ptr->filename); |
| } |
| |
| return; |
| } |
| |
| /* Must be basic block profiling. Emit a human readable output file. */ |
| |
| file = fopen ("bb.out", "a"); |
| |
| if (!file) |
| perror ("bb.out"); |
| |
| else |
| { |
| struct bb *ptr; |
| |
| /* This is somewhat type incorrect, but it avoids worrying about |
| exactly where time.h is included from. It should be ok unless |
| a void * differs from other pointer formats, or if sizeof (long) |
| is < sizeof (time_t). It would be nice if we could assume the |
| use of rationale standards here. */ |
| |
| time ((void *) &time_value); |
| fprintf (file, "Basic block profiling finished on %s\n", ctime ((void *) &time_value)); |
| |
| /* We check the length field explicitly in order to allow compatibility |
| with older GCC's which did not provide it. */ |
| |
| for (ptr = bb_head; ptr != (struct bb *) 0; ptr = ptr->next) |
| { |
| int i; |
| int func_p = (ptr->nwords >= (long) sizeof (struct bb) |
| && ptr->nwords <= 1000 |
| && ptr->functions); |
| int line_p = (func_p && ptr->line_nums); |
| int file_p = (func_p && ptr->filenames); |
| int addr_p = (ptr->addresses != 0); |
| long ncounts = ptr->ncounts; |
| long cnt_max = 0; |
| long line_max = 0; |
| long addr_max = 0; |
| int file_len = 0; |
| int func_len = 0; |
| int blk_len = num_digits (ncounts, 10); |
| int cnt_len; |
| int line_len; |
| int addr_len; |
| |
| fprintf (file, "File %s, %ld basic blocks \n\n", |
| ptr->filename, ncounts); |
| |
| /* Get max values for each field. */ |
| for (i = 0; i < ncounts; i++) |
| { |
| const char *p; |
| int len; |
| |
| if (cnt_max < ptr->counts[i]) |
| cnt_max = ptr->counts[i]; |
| |
| if (addr_p && (unsigned long) addr_max < ptr->addresses[i]) |
| addr_max = ptr->addresses[i]; |
| |
| if (line_p && line_max < ptr->line_nums[i]) |
| line_max = ptr->line_nums[i]; |
| |
| if (func_p) |
| { |
| p = (ptr->functions[i]) ? (ptr->functions[i]) : "<none>"; |
| len = strlen (p); |
| if (func_len < len) |
| func_len = len; |
| } |
| |
| if (file_p) |
| { |
| p = (ptr->filenames[i]) ? (ptr->filenames[i]) : "<none>"; |
| len = strlen (p); |
| if (file_len < len) |
| file_len = len; |
| } |
| } |
| |
| addr_len = num_digits (addr_max, 16); |
| cnt_len = num_digits (cnt_max, 10); |
| line_len = num_digits (line_max, 10); |
| |
| /* Now print out the basic block information. */ |
| for (i = 0; i < ncounts; i++) |
| { |
| fprintf (file, |
| " Block #%*d: executed %*ld time(s)", |
| blk_len, i+1, |
| cnt_len, ptr->counts[i]); |
| |
| if (addr_p) |
| fprintf (file, " address= 0x%.*lx", addr_len, |
| ptr->addresses[i]); |
| |
| if (func_p) |
| fprintf (file, " function= %-*s", func_len, |
| (ptr->functions[i]) ? ptr->functions[i] : "<none>"); |
| |
| if (line_p) |
| fprintf (file, " line= %*ld", line_len, ptr->line_nums[i]); |
| |
| if (file_p) |
| fprintf (file, " file= %s", |
| (ptr->filenames[i]) ? ptr->filenames[i] : "<none>"); |
| |
| fprintf (file, "\n"); |
| } |
| |
| fprintf (file, "\n"); |
| fflush (file); |
| } |
| |
| fprintf (file, "\n\n"); |
| fclose (file); |
| } |
| } |
| |
| void |
| __bb_init_func (struct bb *blocks) |
| { |
| /* User is supposed to check whether the first word is non-0, |
| but just in case.... */ |
| |
| if (blocks->zero_word) |
| return; |
| |
| /* Initialize destructor. */ |
| if (!bb_head) |
| atexit (__bb_exit_func); |
| |
| /* Set up linked list. */ |
| blocks->zero_word = 1; |
| blocks->next = bb_head; |
| bb_head = blocks; |
| } |
| |
| /* Called before fork or exec - write out profile information gathered so |
| far and reset it to zero. This avoids duplication or loss of the |
| profile information gathered so far. */ |
| void |
| __bb_fork_func (void) |
| { |
| struct bb *ptr; |
| |
| __bb_exit_func (); |
| for (ptr = bb_head; ptr != (struct bb *) 0; ptr = ptr->next) |
| { |
| long i; |
| for (i = ptr->ncounts - 1; i >= 0; i--) |
| ptr->counts[i] = 0; |
| } |
| } |
| |
| #ifndef MACHINE_STATE_SAVE |
| #define MACHINE_STATE_SAVE(ID) |
| #endif |
| #ifndef MACHINE_STATE_RESTORE |
| #define MACHINE_STATE_RESTORE(ID) |
| #endif |
| |
| /* Number of buckets in hashtable of basic block addresses. */ |
| |
| #define BB_BUCKETS 311 |
| |
| /* Maximum length of string in file bb.in. */ |
| |
| #define BBINBUFSIZE 500 |
| |
| struct bb_edge |
| { |
| struct bb_edge *next; |
| unsigned long src_addr; |
| unsigned long dst_addr; |
| unsigned long count; |
| }; |
| |
| enum bb_func_mode |
| { |
| TRACE_KEEP = 0, TRACE_ON = 1, TRACE_OFF = 2 |
| }; |
| |
| struct bb_func |
| { |
| struct bb_func *next; |
| char *funcname; |
| char *filename; |
| enum bb_func_mode mode; |
| }; |
| |
| /* This is the connection to the outside world. |
| The BLOCK_PROFILER macro must set __bb.blocks |
| and __bb.blockno. */ |
| |
| struct { |
| unsigned long blockno; |
| struct bb *blocks; |
| } __bb; |
| |
| /* Vars to store addrs of source and destination basic blocks |
| of a jump. */ |
| |
| static unsigned long bb_src = 0; |
| static unsigned long bb_dst = 0; |
| |
| static FILE *bb_tracefile = (FILE *) 0; |
| static struct bb_edge **bb_hashbuckets = (struct bb_edge **) 0; |
| static struct bb_func *bb_func_head = (struct bb_func *) 0; |
| static unsigned long bb_callcount = 0; |
| static int bb_mode = 0; |
| |
| static unsigned long *bb_stack = (unsigned long *) 0; |
| static size_t bb_stacksize = 0; |
| |
| static int reported = 0; |
| |
| /* Trace modes: |
| Always : Print execution frequencies of basic blocks |
| to file bb.out. |
| bb_mode & 1 != 0 : Dump trace of basic blocks to file bbtrace[.gz] |
| bb_mode & 2 != 0 : Print jump frequencies to file bb.out. |
| bb_mode & 4 != 0 : Cut call instructions from basic block flow. |
| bb_mode & 8 != 0 : Insert return instructions in basic block flow. |
| */ |
| |
| #ifdef HAVE_POPEN |
| |
| /*#include <sys/types.h>*/ |
| #include <sys/stat.h> |
| /*#include <malloc.h>*/ |
| |
| /* Commands executed by gopen. */ |
| |
| #define GOPENDECOMPRESS "gzip -cd " |
| #define GOPENCOMPRESS "gzip -c >" |
| |
| /* Like fopen but pipes through gzip. mode may only be "r" or "w". |
| If it does not compile, simply replace gopen by fopen and delete |
| '.gz' from any first parameter to gopen. */ |
| |
| static FILE * |
| gopen (char *fn, char *mode) |
| { |
| int use_gzip; |
| char *p; |
| |
| if (mode[1]) |
| return (FILE *) 0; |
| |
| if (mode[0] != 'r' && mode[0] != 'w') |
| return (FILE *) 0; |
| |
| p = fn + strlen (fn)-1; |
| use_gzip = ((p[-1] == '.' && (p[0] == 'Z' || p[0] == 'z')) |
| || (p[-2] == '.' && p[-1] == 'g' && p[0] == 'z')); |
| |
| if (use_gzip) |
| { |
| if (mode[0]=='r') |
| { |
| FILE *f; |
| char *s = (char *) malloc (sizeof (char) * strlen (fn) |
| + sizeof (GOPENDECOMPRESS)); |
| strcpy (s, GOPENDECOMPRESS); |
| strcpy (s + (sizeof (GOPENDECOMPRESS)-1), fn); |
| f = popen (s, mode); |
| free (s); |
| return f; |
| } |
| |
| else |
| { |
| FILE *f; |
| char *s = (char *) malloc (sizeof (char) * strlen (fn) |
| + sizeof (GOPENCOMPRESS)); |
| strcpy (s, GOPENCOMPRESS); |
| strcpy (s + (sizeof (GOPENCOMPRESS)-1), fn); |
| if (!(f = popen (s, mode))) |
| f = fopen (s, mode); |
| free (s); |
| return f; |
| } |
| } |
| |
| else |
| return fopen (fn, mode); |
| } |
| |
| static int |
| gclose (FILE *f) |
| { |
| struct stat buf; |
| |
| if (f != 0) |
| { |
| if (!fstat (fileno (f), &buf) && S_ISFIFO (buf.st_mode)) |
| return pclose (f); |
| |
| return fclose (f); |
| } |
| return 0; |
| } |
| |
| #endif /* HAVE_POPEN */ |
| |
| /* Called once per program. */ |
| |
| static void |
| __bb_exit_trace_func (void) |
| { |
| FILE *file = fopen ("bb.out", "a"); |
| struct bb_func *f; |
| struct bb *b; |
| |
| if (!file) |
| perror ("bb.out"); |
| |
| if (bb_mode & 1) |
| { |
| if (!bb_tracefile) |
| perror ("bbtrace"); |
| else |
| #ifdef HAVE_POPEN |
| gclose (bb_tracefile); |
| #else |
| fclose (bb_tracefile); |
| #endif /* HAVE_POPEN */ |
| } |
| |
| /* Check functions in `bb.in'. */ |
| |
| if (file) |
| { |
| long time_value; |
| const struct bb_func *p; |
| int printed_something = 0; |
| struct bb *ptr; |
| long blk; |
| |
| /* This is somewhat type incorrect. */ |
| time ((void *) &time_value); |
| |
| for (p = bb_func_head; p != (struct bb_func *) 0; p = p->next) |
| { |
| for (ptr = bb_head; ptr != (struct bb *) 0; ptr = ptr->next) |
| { |
| if (!ptr->filename || (p->filename != (char *) 0 && strcmp (p->filename, ptr->filename))) |
| continue; |
| for (blk = 0; blk < ptr->ncounts; blk++) |
| { |
| if (!strcmp (p->funcname, ptr->functions[blk])) |
| goto found; |
| } |
| } |
| |
| if (!printed_something) |
| { |
| fprintf (file, "Functions in `bb.in' not executed during basic block profiling on %s\n", ctime ((void *) &time_value)); |
| printed_something = 1; |
| } |
| |
| fprintf (file, "\tFunction %s", p->funcname); |
| if (p->filename) |
| fprintf (file, " of file %s", p->filename); |
| fprintf (file, "\n" ); |
| |
| found: ; |
| } |
| |
| if (printed_something) |
| fprintf (file, "\n"); |
| |
| } |
| |
| if (bb_mode & 2) |
| { |
| if (!bb_hashbuckets) |
| { |
| if (!reported) |
| { |
| fprintf (stderr, "Profiler: out of memory\n"); |
| reported = 1; |
| } |
| return; |
| } |
| |
| else if (file) |
| { |
| long time_value; |
| int i; |
| unsigned long addr_max = 0; |
| unsigned long cnt_max = 0; |
| int cnt_len; |
| int addr_len; |
| |
| /* This is somewhat type incorrect, but it avoids worrying about |
| exactly where time.h is included from. It should be ok unless |
| a void * differs from other pointer formats, or if sizeof (long) |
| is < sizeof (time_t). It would be nice if we could assume the |
| use of rationale standards here. */ |
| |
| time ((void *) &time_value); |
| fprintf (file, "Basic block jump tracing"); |
| |
| switch (bb_mode & 12) |
| { |
| case 0: |
| fprintf (file, " (with call)"); |
| break; |
| |
| case 4: |
| /* Print nothing. */ |
| break; |
| |
| case 8: |
| fprintf (file, " (with call & ret)"); |
| break; |
| |
| case 12: |
| fprintf (file, " (with ret)"); |
| break; |
| } |
| |
| fprintf (file, " finished on %s\n", ctime ((void *) &time_value)); |
| |
| for (i = 0; i < BB_BUCKETS; i++) |
| { |
| struct bb_edge *bucket = bb_hashbuckets[i]; |
| for ( ; bucket; bucket = bucket->next ) |
| { |
| if (addr_max < bucket->src_addr) |
| addr_max = bucket->src_addr; |
| if (addr_max < bucket->dst_addr) |
| addr_max = bucket->dst_addr; |
| if (cnt_max < bucket->count) |
| cnt_max = bucket->count; |
| } |
| } |
| addr_len = num_digits (addr_max, 16); |
| cnt_len = num_digits (cnt_max, 10); |
| |
| for ( i = 0; i < BB_BUCKETS; i++) |
| { |
| struct bb_edge *bucket = bb_hashbuckets[i]; |
| for ( ; bucket; bucket = bucket->next ) |
| { |
| fprintf (file, |
| "Jump from block 0x%.*lx to block 0x%.*lx executed %*lu time(s)\n", |
| addr_len, bucket->src_addr, |
| addr_len, bucket->dst_addr, |
| cnt_len, bucket->count); |
| } |
| } |
| |
| fprintf (file, "\n"); |
| |
| } |
| } |
| |
| if (file) |
| fclose (file); |
| |
| /* Free allocated memory. */ |
| |
| f = bb_func_head; |
| while (f) |
| { |
| struct bb_func *old = f; |
| |
| f = f->next; |
| if (old->funcname) free (old->funcname); |
| if (old->filename) free (old->filename); |
| free (old); |
| } |
| |
| if (bb_stack) |
| free (bb_stack); |
| |
| if (bb_hashbuckets) |
| { |
| int i; |
| |
| for (i = 0; i < BB_BUCKETS; i++) |
| { |
| struct bb_edge *old, *bucket = bb_hashbuckets[i]; |
| |
| while (bucket) |
| { |
| old = bucket; |
| bucket = bucket->next; |
| free (old); |
| } |
| } |
| free (bb_hashbuckets); |
| } |
| |
| for (b = bb_head; b; b = b->next) |
| if (b->flags) free (b->flags); |
| } |
| |
| /* Called once per program. */ |
| |
| static void |
| __bb_init_prg (void) |
| { |
| FILE *file; |
| char buf[BBINBUFSIZE]; |
| const char *p; |
| const char *pos; |
| enum bb_func_mode m; |
| int i; |
| |
| /* Initialize destructor. */ |
| atexit (__bb_exit_func); |
| |
| if (!(file = fopen ("bb.in", "r"))) |
| return; |
| |
| while(fgets (buf, BBINBUFSIZE, file) != 0) |
| { |
| i = strlen (buf); |
| if (buf[i-1] == '\n') |
| buf[--i] = '\0'; |
| |
| p = buf; |
| if (*p == '-') |
| { |
| m = TRACE_OFF; |
| p++; |
| } |
| else |
| { |
| m = TRACE_ON; |
| } |
| if (!strcmp (p, "__bb_trace__")) |
| bb_mode |= 1; |
| else if (!strcmp (p, "__bb_jumps__")) |
| bb_mode |= 2; |
| else if (!strcmp (p, "__bb_hidecall__")) |
| bb_mode |= 4; |
| else if (!strcmp (p, "__bb_showret__")) |
| bb_mode |= 8; |
| else |
| { |
| struct bb_func *f = (struct bb_func *) malloc (sizeof (struct bb_func)); |
| if (f) |
| { |
| unsigned long l; |
| f->next = bb_func_head; |
| if ((pos = strchr (p, ':'))) |
| { |
| if (!(f->funcname = (char *) malloc (strlen (pos+1)+1))) |
| continue; |
| strcpy (f->funcname, pos+1); |
| l = pos-p; |
| if ((f->filename = (char *) malloc (l+1))) |
| { |
| strncpy (f->filename, p, l); |
| f->filename[l] = '\0'; |
| } |
| else |
| f->filename = (char *) 0; |
| } |
| else |
| { |
| if (!(f->funcname = (char *) malloc (strlen (p)+1))) |
| continue; |
| strcpy (f->funcname, p); |
| f->filename = (char *) 0; |
| } |
| f->mode = m; |
| bb_func_head = f; |
| } |
| } |
| } |
| fclose (file); |
| |
| #ifdef HAVE_POPEN |
| |
| if (bb_mode & 1) |
| bb_tracefile = gopen ("bbtrace.gz", "w"); |
| |
| #else |
| |
| if (bb_mode & 1) |
| bb_tracefile = fopen ("bbtrace", "w"); |
| |
| #endif /* HAVE_POPEN */ |
| |
| if (bb_mode & 2) |
| { |
| bb_hashbuckets = (struct bb_edge **) |
| malloc (BB_BUCKETS * sizeof (struct bb_edge *)); |
| if (bb_hashbuckets) |
| /* Use a loop here rather than calling bzero to avoid having to |
| conditionalize its existance. */ |
| for (i = 0; i < BB_BUCKETS; i++) |
| bb_hashbuckets[i] = 0; |
| } |
| |
| if (bb_mode & 12) |
| { |
| bb_stacksize = 10; |
| bb_stack = (unsigned long *) malloc (bb_stacksize * sizeof (*bb_stack)); |
| } |
| |
| /* Initialize destructor. */ |
| atexit (__bb_exit_trace_func); |
| } |
| |
| /* Called upon entering a basic block. */ |
| |
| void |
| __bb_trace_func (void) |
| { |
| struct bb_edge *bucket; |
| |
| MACHINE_STATE_SAVE("1") |
| |
| if (!bb_callcount || (__bb.blocks->flags && (__bb.blocks->flags[__bb.blockno] & TRACE_OFF))) |
| goto skip; |
| |
| bb_dst = __bb.blocks->addresses[__bb.blockno]; |
| __bb.blocks->counts[__bb.blockno]++; |
| |
| if (bb_tracefile) |
| { |
| fwrite (&bb_dst, sizeof (unsigned long), 1, bb_tracefile); |
| } |
| |
| if (bb_hashbuckets) |
| { |
| struct bb_edge **startbucket, **oldnext; |
| |
| oldnext = startbucket |
| = & bb_hashbuckets[ (((int) bb_src*8) ^ (int) bb_dst) % BB_BUCKETS ]; |
| bucket = *startbucket; |
| |
| for (bucket = *startbucket; bucket; |
| oldnext = &(bucket->next), bucket = *oldnext) |
| { |
| if (bucket->src_addr == bb_src |
| && bucket->dst_addr == bb_dst) |
| { |
| bucket->count++; |
| *oldnext = bucket->next; |
| bucket->next = *startbucket; |
| *startbucket = bucket; |
| goto ret; |
| } |
| } |
| |
| bucket = (struct bb_edge *) malloc (sizeof (struct bb_edge)); |
| |
| if (!bucket) |
| { |
| if (!reported) |
| { |
| fprintf (stderr, "Profiler: out of memory\n"); |
| reported = 1; |
| } |
| } |
| |
| else |
| { |
| bucket->src_addr = bb_src; |
| bucket->dst_addr = bb_dst; |
| bucket->next = *startbucket; |
| *startbucket = bucket; |
| bucket->count = 1; |
| } |
| } |
| |
| ret: |
| bb_src = bb_dst; |
| |
| skip: |
| ; |
| |
| MACHINE_STATE_RESTORE("1") |
| |
| } |
| |
| /* Called when returning from a function and `__bb_showret__' is set. */ |
| |
| static void |
| __bb_trace_func_ret (void) |
| { |
| struct bb_edge *bucket; |
| |
| if (!bb_callcount || (__bb.blocks->flags && (__bb.blocks->flags[__bb.blockno] & TRACE_OFF))) |
| goto skip; |
| |
| if (bb_hashbuckets) |
| { |
| struct bb_edge **startbucket, **oldnext; |
| |
| oldnext = startbucket |
| = & bb_hashbuckets[ (((int) bb_dst * 8) ^ (int) bb_src) % BB_BUCKETS ]; |
| bucket = *startbucket; |
| |
| for (bucket = *startbucket; bucket; |
| oldnext = &(bucket->next), bucket = *oldnext) |
| { |
| if (bucket->src_addr == bb_dst |
| && bucket->dst_addr == bb_src) |
| { |
| bucket->count++; |
| *oldnext = bucket->next; |
| bucket->next = *startbucket; |
| *startbucket = bucket; |
| goto ret; |
| } |
| } |
| |
| bucket = (struct bb_edge *) malloc (sizeof (struct bb_edge)); |
| |
| if (!bucket) |
| { |
| if (!reported) |
| { |
| fprintf (stderr, "Profiler: out of memory\n"); |
| reported = 1; |
| } |
| } |
| |
| else |
| { |
| bucket->src_addr = bb_dst; |
| bucket->dst_addr = bb_src; |
| bucket->next = *startbucket; |
| *startbucket = bucket; |
| bucket->count = 1; |
| } |
| } |
| |
| ret: |
| bb_dst = bb_src; |
| |
| skip: |
| ; |
| |
| } |
| |
| /* Called upon entering the first function of a file. */ |
| |
| static void |
| __bb_init_file (struct bb *blocks) |
| { |
| |
| const struct bb_func *p; |
| long blk, ncounts = blocks->ncounts; |
| const char **functions = blocks->functions; |
| |
| /* Set up linked list. */ |
| blocks->zero_word = 1; |
| blocks->next = bb_head; |
| bb_head = blocks; |
| |
| blocks->flags = 0; |
| if (!bb_func_head |
| || !(blocks->flags = (char *) malloc (sizeof (char) * blocks->ncounts))) |
| return; |
| |
| for (blk = 0; blk < ncounts; blk++) |
| blocks->flags[blk] = 0; |
| |
| for (blk = 0; blk < ncounts; blk++) |
| { |
| for (p = bb_func_head; p; p = p->next) |
| { |
| if (!strcmp (p->funcname, functions[blk]) |
| && (!p->filename || !strcmp (p->filename, blocks->filename))) |
| { |
| blocks->flags[blk] |= p->mode; |
| } |
| } |
| } |
| |
| } |
| |
| /* Called when exiting from a function. */ |
| |
| void |
| __bb_trace_ret (void) |
| { |
| |
| MACHINE_STATE_SAVE("2") |
| |
| if (bb_callcount) |
| { |
| if ((bb_mode & 12) && bb_stacksize > bb_callcount) |
| { |
| bb_src = bb_stack[bb_callcount]; |
| if (bb_mode & 8) |
| __bb_trace_func_ret (); |
| } |
| |
| bb_callcount -= 1; |
| } |
| |
| MACHINE_STATE_RESTORE("2") |
| |
| } |
| |
| /* Called when entering a function. */ |
| |
| void |
| __bb_init_trace_func (struct bb *blocks, unsigned long blockno) |
| { |
| static int trace_init = 0; |
| |
| MACHINE_STATE_SAVE("3") |
| |
| if (!blocks->zero_word) |
| { |
| if (!trace_init) |
| { |
| trace_init = 1; |
| __bb_init_prg (); |
| } |
| __bb_init_file (blocks); |
| } |
| |
| if (bb_callcount) |
| { |
| |
| bb_callcount += 1; |
| |
| if (bb_mode & 12) |
| { |
| if (bb_callcount >= bb_stacksize) |
| { |
| size_t newsize = bb_callcount + 100; |
| |
| bb_stack = (unsigned long *) realloc (bb_stack, newsize); |
| if (! bb_stack) |
| { |
| if (!reported) |
| { |
| fprintf (stderr, "Profiler: out of memory\n"); |
| reported = 1; |
| } |
| bb_stacksize = 0; |
| goto stack_overflow; |
| } |
| bb_stacksize = newsize; |
| } |
| bb_stack[bb_callcount] = bb_src; |
| |
| if (bb_mode & 4) |
| bb_src = 0; |
| |
| } |
| |
| stack_overflow:; |
| |
| } |
| |
| else if (blocks->flags && (blocks->flags[blockno] & TRACE_ON)) |
| { |
| bb_callcount = 1; |
| bb_src = 0; |
| |
| if (bb_stack) |
| bb_stack[bb_callcount] = bb_src; |
| } |
| |
| MACHINE_STATE_RESTORE("3") |
| } |
| |