about summary refs log tree commit diff
path: root/elf
diff options
context:
space:
mode:
authorUlrich Drepper <drepper@redhat.com>1998-06-07 14:06:56 +0000
committerUlrich Drepper <drepper@redhat.com>1998-06-07 14:06:56 +0000
commitc0fb8a563c1c49e5fbec9bc22deac618910a3ff4 (patch)
treee7db886672cef990feba6c4357ebece5a820d7cf /elf
parent737547be99fd9afffbdf3f7ac817da0a06fffc9e (diff)
downloadglibc-c0fb8a563c1c49e5fbec9bc22deac618910a3ff4.tar.gz
glibc-c0fb8a563c1c49e5fbec9bc22deac618910a3ff4.tar.xz
glibc-c0fb8a563c1c49e5fbec9bc22deac618910a3ff4.zip
Update.
1998-06-07 13:32  Ulrich Drepper  <drepper@cygnus.com>

	* libc.map: Add _dl_profile.
	* elf/dl-reloc.c (_dl_relocate_object): Take extra argument, pass
	this to ELF_DYNAMIC_RELOCATE.
	Always allocate array for relocation result if LD_PROFILE is defined.
	* elf/ldsodefs.h: Adjust prototypes.
	* elf/dl-open.c (_dl_open): Call relocation function with extra
	argument.
	* elf/rtld.c: Likewise.
	* elf/dl-profile.c (_dl_mcount): Don't mark as internal function.
	Correct loop condition.
	* elf/dynamic-link.h: Don't examine _dl_profile variable, pass
	consider_profile to runtime setup function.
	* sysdeps/i386/dl-machine.h (elf_machine_runtime_setup): Use
	_dl_runtime_profile for all shared objects if LD_PROFILE is defined.

	* elf/dl-support.c: Define __libc_stack_end.
	* elf/rtld.c: Likewise.
	* sysdeps/generic/libc-start.c: Store last stack address in
	__libc_stack_end.
	* sysdeps/i386/dl-machine.h (_dl_start_user): Store stack address.
	* sysdeps/i386/elf/start.s: Call __libc_start_main with extra argument.

	* elf/elf.h: Include <features.h>, not <sys/cdefs.h>.
	Include <stdint.h>, not <inttypes.h>.

	* elf/sprof.c: Implement flat profiling.

	* libio/fgetc.c: Call _IO_cleanup_region_end with 0 and call
	_IO_funlockfile explicitly.
	* libio/fileops.c: Likewise.
	* libio/fputc.c: Likewise.
	* libio/freopen.c: Likewise.
	* libio/freopen64.c: Likewise.
	* libio/fseek.c: Likewise.
	* libio/fseeko.c: Likewise.
	* libio/fseeko64.c: Likewise.
	* libio/ftello.c: Likewise.
	* libio/ftello64.c: Likewise.
	* libio/getc.c: Likewise.
	* libio/getchar.c: Likewise.
	* libio/iofclose.c: Likewise.
	* libio/iofflush.c: Likewise.
	* libio/iofgetpos.c: Likewise.
	* libio/iofgetpos64.c: Likewise.
	* libio/iofgets.c: Likewise.
	* libio/iofputs.c: Likewise.
	* libio/iofread.c: Likewise.
	* libio/iofsetpos.c: Likewise.
	* libio/iofsetpos64.c: Likewise.
	* libio/ioftell.c: Likewise.
	* libio/iofwrite.c: Likewise.
	* libio/iogetdelim.c: Likewise.
	* libio/iogets.c: Likewise.
	* libio/ioputs.c: Likewise.
	* libio/ioseekoff.c: Likewise.
	* libio/ioseekpos.c: Likewise.
	* libio/iosetbuffer.c: Likewise.
	* libio/iosetvbuf.c: Likewise.
	* libio/ioungetc.c: Likewise.
	* libio/iovsprintf.c: Likewise.
	* libio/iovsscanf.c: Likewise.
	* libio/oldfileops.c: Likewise.
	* libio/oldiofclose.c: Likewise.
	* libio/peekc.c: Likewise.
	* libio/putc.c: Likewise.
	* libio/putchar.c: Likewise.
	* libio/rewind.c: Likewise.

	* malloc/mtrace.c: Pretty print.

	* misc/mntent.h (struct mentent): Make string elements const char *.

	* nis/nis_printf.c: Optimize I/O a little bit.

	* signal/Makefile (distribute): Add sigset-cvt-mask.h.
	* sysdeps/generic/sigset-cvt-mask.h: New file.
	* sysdeps/unix/sysv/linux/sigset-cvt-mask.h: New file.
	* sysdeps/unix/sysv/sysv4/sigset-cvt-mask.h: New file.
	* sysdeps/posix/sigvec.c: Rewrite the use definitions from
	sigset-cvt-mask.h to do the dirty work.
	Patches by Joe Keane.

	* sysdeps/posix/mkstemp.c: Save one precious byte of rodata.

	* sysdeps/unix/sysv/linux/i386/sysdep.h: Rewrite PSEUDO etc to make
	syscall_error label in case of PIC anonymous.
	* sysdeps/unix/sysv/linux/i386/i686/sysdep.h: Likewise.
	* sysdeps/unix/sysv/linux/i386/clone.S: Adapt for this change.
	* sysdeps/unix/sysv/linux/i386/mmap.S: Adapt for this change.
	* sysdeps/unix/sysv/linux/i386/s_pread64.S: Adapt for this change.
	* sysdeps/unix/sysv/linux/i386/s_pwrite64.S: Adapt for this change.
	* sysdeps/unix/sysv/linux/i386/socket.S: Adapt for this change.
	* sysdeps/unix/sysv/linux/i386/syscall.S: Adapt for this change.
Diffstat (limited to 'elf')
-rw-r--r--elf/dl-open.c3
-rw-r--r--elf/dl-profile.c6
-rw-r--r--elf/dl-reloc.c7
-rw-r--r--elf/dl-support.c3
-rw-r--r--elf/dynamic-link.h8
-rw-r--r--elf/elf.h6
-rw-r--r--elf/ldsodefs.h6
-rw-r--r--elf/rtld.c15
-rw-r--r--elf/sprof.c220
9 files changed, 215 insertions, 59 deletions
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 308175a8ae..4c4c8abdc5 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -102,7 +102,8 @@ _dl_open (const char *file, int mode)
 	  asm ("" : "=r" (reloc) : "0" (reloc));
 
 	  (*reloc) (l, _dl_object_relocation_scope (l),
-		    (mode & RTLD_BINDING_MASK) == RTLD_LAZY);
+		    ((mode & RTLD_BINDING_MASK) == RTLD_LAZY
+		     || _dl_profile != NULL), _dl_profile != NULL);
 	  *_dl_global_scope_end = NULL;
 	}
 
diff --git a/elf/dl-profile.c b/elf/dl-profile.c
index c7ac360811..a9f8dd6226 100644
--- a/elf/dl-profile.c
+++ b/elf/dl-profile.c
@@ -436,7 +436,6 @@ _dl_start_profile (struct link_map *map, const char *output_dir)
 
 
 void
-internal_function
 _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc)
 {
   uint16_t *topcindex;
@@ -479,7 +478,7 @@ _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc)
 
   /* We have to look through the chain of arcs whether there is already
      an entry for our arc.  */
-  while (fromp->here->from_pc == frompc)
+  while (fromp->here->from_pc != frompc)
     {
       if (fromp->link != 0)
 	do
@@ -523,8 +522,7 @@ _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc)
 	      data[newarc].self_pc = selfpc;
 	      data[newarc].count = 0;
 	      fromp->link = 0;
-
-	      narcs++;
+	      ++narcs;
 
 	      break;
 	    }
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 898fb48e42..e0eae3c2ae 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -28,7 +28,8 @@
 
 void
 internal_function
-_dl_relocate_object (struct link_map *l, struct link_map *scope[], int lazy)
+_dl_relocate_object (struct link_map *l, struct link_map *scope[], int lazy,
+		     int consider_profiling)
 {
   if (l->l_relocated)
     return;
@@ -72,9 +73,9 @@ _dl_relocate_object (struct link_map *l, struct link_map *scope[], int lazy)
 			  l->l_name, (flags)))
 
 #include "dynamic-link.h"
-    ELF_DYNAMIC_RELOCATE (l, lazy, 1);
+    ELF_DYNAMIC_RELOCATE (l, lazy, consider_profiling);
 
-    if (_dl_profile_map == l)
+    if (_dl_profile != NULL)
       {
 	/* Allocate the array which will contain the already found
 	   relocations.  */
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 9012a0c2a6..85f656c2fd 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -58,6 +58,9 @@ struct link_map *_dl_profile_map;
 /* Names of shared object for which the RPATHs should be ignored.  */
 const char *_dl_inhibit_rpath;
 
+/* This is the address of the last stack address ever used.  */
+void *__libc_stack_end;
+
 
 static void non_dynamic_init (void) __attribute__ ((unused));
 
diff --git a/elf/dynamic-link.h b/elf/dynamic-link.h
index aedee20700..9d7ae3d3fa 100644
--- a/elf/dynamic-link.h
+++ b/elf/dynamic-link.h
@@ -1,5 +1,5 @@
 /* Inline functions for dynamic linking.
-   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -161,10 +161,8 @@ elf_get_dynamic_info (ElfW(Dyn) *dyn,
    to inline functions containing inlines themselves.  */
 #define ELF_DYNAMIC_RELOCATE(map, lazy, consider_profile) \
   do {									      \
-    int profile = (consider_profile && _dl_profile != NULL		      \
-		   && _dl_name_match_p (_dl_profile, (map)));		      \
-    int edr_lazy = elf_machine_runtime_setup ((map), (lazy) || profile,	      \
-					      profile);			      \
+    int edr_lazy = elf_machine_runtime_setup ((map), (lazy),		      \
+					      (consider_profile));	      \
     ELF_DYNAMIC_DO_REL ((map), edr_lazy);				      \
     ELF_DYNAMIC_DO_RELA ((map), edr_lazy);				      \
   } while (0)
diff --git a/elf/elf.h b/elf/elf.h
index fcc8a5384a..80f10d6472 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -1,5 +1,5 @@
 /* This file defines standard ELF types, structures, and macros.
-   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ian Lance Taylor <ian@cygnus.com>.
 
@@ -21,13 +21,13 @@
 #ifndef _ELF_H
 #define	_ELF_H 1
 
-#include <sys/cdefs.h>
+#include <features.h>
 
 __BEGIN_DECLS
 
 /* Standard ELF types.  */
 
-#include <inttypes.h>
+#include <stdint.h>
 
 /* Type for a 16-bit quantity.  */
 typedef uint16_t Elf32_Half;
diff --git a/elf/ldsodefs.h b/elf/ldsodefs.h
index a2bfc0b210..45c2a5ec64 100644
--- a/elf/ldsodefs.h
+++ b/elf/ldsodefs.h
@@ -363,7 +363,8 @@ extern struct link_map *_dl_new_object (char *realname, const char *libname,
    If LAZY is nonzero, don't relocate its PLT.  */
 extern void _dl_relocate_object (struct link_map *map,
 				 struct link_map *scope[],
-				 int lazy) internal_function;
+				 int lazy, int consider_profiling)
+     internal_function;
 
 /* Check the version dependencies of all objects available through
    MAP.  If VERBOSE print some more diagnostics.  */
@@ -406,8 +407,7 @@ extern void _dl_start_profile (struct link_map *map, const char *output_dir)
      internal_function;
 
 /* The actual functions used to keep book on the calls.  */
-extern void _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc)
-     internal_function;
+extern void _dl_mcount (ElfW(Addr) frompc, ElfW(Addr) selfpc);
 
 
 /* Show the members of the auxiliary array passed up from the kernel.  */
diff --git a/elf/rtld.c b/elf/rtld.c
index fe676f7005..58f9da8ef8 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -93,6 +93,9 @@ const char *_dl_inhibit_rpath;		/* RPATH values which should be
    never be called.  */
 int _dl_starting_up;
 
+/* This variable contains the lowest stack address ever used.  */
+void *__libc_stack_end;
+
 static void dl_main (const ElfW(Phdr) *phdr,
 		     ElfW(Half) phent,
 		     ElfW(Addr) *user_entry);
@@ -201,7 +204,7 @@ relocate_doit (void *a)
   struct relocate_args *args = (struct relocate_args *) a;
 
   _dl_relocate_object (args->l, _dl_object_relocation_scope (args->l),
-		       args->lazy);
+		       args->lazy, 0);
 }
 
 static void
@@ -852,6 +855,11 @@ of this helper program; chances are you did not intend to run this program.\n\
        know that because it is self-contained).  */
 
     struct link_map *l;
+    int consider_profiling = _dl_profile != NULL;
+
+    /* If we are profiling we also must do lazy reloaction.  */
+    lazy |= consider_profiling;
+
     l = _dl_loaded;
     while (l->l_next)
       l = l->l_next;
@@ -859,7 +867,8 @@ of this helper program; chances are you did not intend to run this program.\n\
       {
 	if (l != &_dl_rtld_map)
 	  {
-	    _dl_relocate_object (l, _dl_object_relocation_scope (l), lazy);
+	    _dl_relocate_object (l, _dl_object_relocation_scope (l), lazy,
+				 consider_profiling);
 	    *_dl_global_scope_end = NULL;
 	  }
 	l = l->l_prev;
@@ -875,7 +884,7 @@ of this helper program; chances are you did not intend to run this program.\n\
     if (_dl_rtld_map.l_opencount > 0)
       /* There was an explicit ref to the dynamic linker as a shared lib.
 	 Re-relocate ourselves with user-controlled symbol definitions.  */
-      _dl_relocate_object (&_dl_rtld_map, &_dl_default_scope[2], 0);
+      _dl_relocate_object (&_dl_rtld_map, &_dl_default_scope[2], 0, 0);
   }
 
   {
diff --git a/elf/sprof.c b/elf/sprof.c
index 95e96646d1..477d95a283 100644
--- a/elf/sprof.c
+++ b/elf/sprof.c
@@ -68,15 +68,15 @@ extern int __profile_frequency __P ((void));
 static void print_version (FILE *stream, struct argp_state *state);
 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
 
-#define OPT_COUNT_TOTAL	1
-#define OPT_TEST 2
+#define OPT_TEST	1
 
 /* Definitions of arguments for argp functions.  */
 static const struct argp_option options[] =
 {
   { NULL, 0, NULL, 0, N_("Output selection:") },
-  { "count-total", OPT_COUNT_TOTAL, NULL, 0,
-    N_("print number of invocations for each function") },
+  { "flat-profile", 'p', NULL, 0,
+    N_("generate flat profile with counts and ticks") },
+
   { "test", OPT_TEST, NULL, OPTION_HIDDEN, NULL },
   { NULL, 0, NULL, 0, NULL }
 };
@@ -101,7 +101,9 @@ static struct argp argp =
 static enum
 {
   NONE = 0,
-  COUNT_TOTAL
+  FLAT_MODE = 1 << 0,
+
+  DEFAULT_MODE = FLAT_MODE
 } mode;
 
 /* If nonzero the total number of invocations of a function is emitted.  */
@@ -135,6 +137,7 @@ struct known_symbol
   size_t size;
 
   uintmax_t ticks;
+  uintmax_t calls;
 };
 
 
@@ -173,6 +176,7 @@ struct profdata
   off_t size;
 
   char *hist;
+  struct gmon_hist_hdr *hist_hdr;
   uint16_t *kcount;
   uint32_t narcs;		/* Number of arcs in toset.  */
   struct here_cg_arc_record *data;
@@ -192,7 +196,9 @@ static void unload_shobj (struct shobj *shobj);
 static struct profdata *load_profdata (const char *name, struct shobj *shobj);
 static void unload_profdata (struct profdata *profdata);
 static void count_total_ticks (struct shobj *shobj, struct profdata *profdata);
+static void count_calls (struct shobj *shobj, struct profdata *profdata);
 static void read_symbols (struct shobj *shobj);
+static void generate_flat_profile (struct profdata *profdata);
 
 
 int
@@ -266,26 +272,19 @@ no filename for profiling data given and shared object `%s' has no soname"),
 
   read_symbols (shobj_handle);
 
+  /* Count the ticks.  */
+  count_total_ticks (shobj_handle, profdata_handle);
+
+  /* Count the calls.  */
+  count_calls (shobj_handle, profdata_handle);
+
+  /* If no mode is specified fall back to the default mode.  */
+  if (mode == NONE)
+    mode = DEFAULT_MODE;
+
   /* Do some work.  */
-  switch (mode)
-    {
-    case COUNT_TOTAL:
-      count_total_ticks (shobj_handle, profdata_handle);
-      {
-	size_t n;
-	for (n = 0; n < symidx; ++n)
-	  if (sortsym[n]->ticks != 0)
-	    printf ("Name: %-30s, Ticks: %" PRIdMAX "\n", sortsym[n]->name,
-		    sortsym[n]->ticks);
-	printf ("Total ticks: %" PRIdMAX "\n", total_ticks);
-      }
-      break;
-    case NONE:
-      /* Do nothing.  */
-      break;
-    default:
-      assert (! "Internal error");
-    }
+  if (mode & FLAT_MODE)
+    generate_flat_profile (profdata_handle);
 
   /* Free the resources.  */
   unload_shobj (shobj_handle);
@@ -301,9 +300,6 @@ parse_opt (int key, char *arg, struct argp_state *state)
 {
   switch (key)
     {
-    case OPT_COUNT_TOTAL:
-      mode = COUNT_TOTAL;
-      break;
     case OPT_TEST:
       do_test = 1;
       break;
@@ -689,6 +685,8 @@ load_profdata (const char *name, struct shobj *shobj)
 
   /* Pointer to data after the header.  */
   result->hist = (char *) ((struct gmon_hdr *) addr + 1);
+  result->hist_hdr = (struct gmon_hist_hdr *) ((char *) result->hist
+					       + sizeof (uint32_t));
   result->kcount = (uint16_t *) ((char *) result->hist + sizeof (uint32_t)
 				 + sizeof (struct gmon_hist_hdr));
 
@@ -709,7 +707,7 @@ load_profdata (const char *name, struct shobj *shobj)
   *(char **) hist_hdr.high_pc = (char *) shobj->highpc - shobj->map->l_addr;
   if (do_test)
     printf ("low_pc = %p\nhigh_pc = %p\n",
-	    hist_hdr.low_pc, hist_hdr.high_pc);
+	    *(char **) hist_hdr.low_pc, *(char **) hist_hdr.high_pc);
   *(int32_t *) hist_hdr.hist_size = shobj->kcountsize / sizeof (HISTCOUNTER);
   *(int32_t *) hist_hdr.prof_rate = __profile_frequency ();
   strncpy (hist_hdr.dimen, "seconds", sizeof (hist_hdr.dimen));
@@ -718,7 +716,7 @@ load_profdata (const char *name, struct shobj *shobj)
   /* Test whether the header of the profiling data is ok.  */
   if (memcmp (addr, &gmon_hdr, sizeof (struct gmon_hdr)) != 0
       || *(uint32_t *) result->hist != GMON_TAG_TIME_HIST
-      || memcmp (result->hist + sizeof (uint32_t), &hist_hdr,
+      || memcmp (result->hist_hdr, &hist_hdr,
 		 sizeof (struct gmon_hist_hdr)) != 0
       || narcsp[-1] != GMON_TAG_CG_ARC)
     {
@@ -802,6 +800,49 @@ count_total_ticks (struct shobj *shobj, struct profdata *profdata)
 }
 
 
+static struct known_symbol *
+find_symbol (uintptr_t addr)
+{
+  size_t sidx = 0;
+
+  while (sidx < symidx)
+    {
+      uintptr_t start = sortsym[sidx]->addr;
+      uintptr_t end = start + sortsym[sidx]->size;
+
+      if (addr >= start && addr < end)
+	return sortsym[sidx];
+
+      if (addr < start)
+	break;
+
+      ++sidx;
+    }
+
+  return NULL;
+}
+
+
+static void
+count_calls (struct shobj *shobj, struct profdata *profdata)
+{
+  struct here_cg_arc_record *data = profdata->data;
+  uint32_t narcs = profdata->narcs;
+  uint32_t cnt;
+
+  for (cnt = 0; cnt < narcs; ++cnt)
+    {
+      uintptr_t here = data[cnt].self_pc;
+      struct known_symbol *symbol;
+
+      /* Find the symbol for this address.  */
+      symbol = find_symbol (here);
+      if (symbol != NULL)
+	symbol->calls += data[cnt].count;
+    }
+}
+
+
 static int
 symorder (const void *o1, const void *o2)
 {
@@ -843,6 +884,7 @@ read_symbols (struct shobj *shobj)
 	     || ELFW(ST_TYPE) (sym->st_info) == STT_NOTYPE)
 	    && sym->st_size != 0)
 	  {
+	    struct known_symbol **existp;
 	    struct known_symbol *newsym
 	      = (struct known_symbol *) obstack_alloc (&shobj->ob_sym,
 						       sizeof (*newsym));
@@ -853,9 +895,25 @@ read_symbols (struct shobj *shobj)
 	    newsym->addr = sym->st_value;
 	    newsym->size = sym->st_size;
 	    newsym->ticks = 0;
-
-	    tsearch (newsym, &symroot, symorder);
-	    ++n;
+	    newsym->calls = 0;
+
+	    existp = tfind (newsym, &symroot, symorder);
+	    if (existp == NULL)
+	      {
+		/* New function.  */
+		tsearch (newsym, &symroot, symorder);
+		++n;
+	      }
+	    else
+	      {
+		/* The function is already defined.  See whether we have
+		   a better name here.  */
+		if ((*existp)->name[0] == '_' && newsym->name[0] != '_')
+		  *existp = newsym;
+		else
+		  /* We don't need the allocated memory.  */
+		  obstack_free (&shobj->ob_sym, newsym);
+	      }
 	  }
     }
   else
@@ -872,11 +930,12 @@ read_symbols (struct shobj *shobj)
 	 dynamic symbol table!!  */
       while ((void *) symtab < (void *) strtab)
 	{
-	  if (/*(ELFW(ST_TYPE)(symtab->st_info) == STT_FUNC
-		|| ELFW(ST_TYPE)(symtab->st_info) == STT_NOTYPE)
-		&&*/ symtab->st_size != 0)
+	  if ((ELFW(ST_TYPE)(symtab->st_info) == STT_FUNC
+	       || ELFW(ST_TYPE)(symtab->st_info) == STT_NOTYPE)
+	      && symtab->st_size != 0)
 	    {
 	      struct known_symbol *newsym;
+	      struct known_symbol **existp;
 
 	      newsym =
 		(struct known_symbol *) obstack_alloc (&shobj->ob_sym,
@@ -889,8 +948,23 @@ read_symbols (struct shobj *shobj)
 	      newsym->size = symtab->st_size;
 	      newsym->ticks = 0;
 
-	      tsearch (newsym, &symroot, symorder);
-	      ++n;
+	      existp = tfind (newsym, &symroot, symorder);
+	      if (existp == NULL)
+		{
+		  /* New function.  */
+		  tsearch (newsym, &symroot, symorder);
+		  ++n;
+		}
+	      else
+		{
+		  /* The function is already defined.  See whether we have
+		     a better name here.  */
+		  if ((*existp)->name[0] == '_' && newsym->name[0] != '_')
+		    *existp = newsym;
+		  else
+		    /* We don't need the allocated memory.  */
+		    obstack_free (&shobj->ob_sym, newsym);
+		}
 	    }
 	}
 
@@ -903,3 +977,75 @@ read_symbols (struct shobj *shobj)
 
   twalk (symroot, printsym);
 }
+
+
+static int
+countorder (const void *p1, const void *p2)
+{
+  struct known_symbol *s1 = (struct known_symbol *) p1;
+  struct known_symbol *s2 = (struct known_symbol *) p2;
+
+  if (s1->ticks != s2->ticks)
+    return (int) (s2->ticks - s1->ticks);
+
+  if (s1->calls != s2->calls)
+    return (int) (s2->calls - s1->calls);
+
+  return strcmp (s1->name, s2->name);
+}
+
+
+static double tick_unit;
+static uintmax_t cumu_ticks;
+
+static void
+printflat (const void *node, VISIT value, int level)
+{
+  if (value == leaf || value == postorder)
+    {
+      struct known_symbol *s = *(struct known_symbol **) node;
+
+      cumu_ticks += s->ticks;
+
+      printf ("%6.2f%10.2f%9.2f%9" PRIdMAX "%9.2f%9.2f  %s\n",
+	      total_ticks ? (100.0 * s->ticks) / total_ticks : 0.0,
+	      tick_unit * cumu_ticks,
+	      tick_unit * s->ticks,
+	      s->calls,
+	      s->calls ? (s->ticks * 1000000) * tick_unit / s->calls : 0,
+	      0.0,	/* FIXME: don't know about called functions.  */
+	      s->name);
+    }
+}
+
+
+/* ARGUSED */
+static void
+freenoop (void *p)
+{
+}
+
+
+static void
+generate_flat_profile (struct profdata *profdata)
+{
+  size_t n;
+  void *data = NULL;
+
+  tick_unit = 1.0 / *(uint32_t *) profdata->hist_hdr->prof_rate;
+
+  printf ("Flat profile:\n\n"
+	  "Each sample counts as %g %s.\n",
+	  tick_unit, profdata->hist_hdr->dimen);
+  fputs ("  %   cumulative   self              self     total\n"
+	 " time   seconds   seconds    calls  us/call  us/call  name\n",
+	 stdout);
+
+  for (n = 0; n < symidx; ++n)
+    if (sortsym[n]->calls != 0 || sortsym[n]->ticks != 0)
+      tsearch (sortsym[n], &data, countorder);
+
+  twalk (data, printflat);
+
+  tdestroy (data, freenoop);
+}