
A skin which generates gprof-style profiling output files.  This
allows profiling of multithreaded programs using shared libraries.
Requires a patched version of gprof to interpret the output files
(available at http://www.goop.org/~jeremy/valgrind/vgprof.html)


 Makefile.am           |    1 
 configure.in          |    1 
 coregrind/vg_mylibc.c |    4 
 include/vg_skin.h     |    3 
 valgrind.spec.in      |    1 
 vgprof/Makefile.am    |   26 +
 vgprof/gmon_out.h     |   96 +++++
 vgprof/gp_main.c      |  940 ++++++++++++++++++++++++++++++++++++++++++++++++++
 vgprof/vgprof.h       |   89 ++++
 9 files changed, 1161 insertions(+)

diff -puN /dev/null vgprof/Makefile.am
--- /dev/null	2003-01-30 02:24:37.000000000 -0800
+++ local-jeremy/vgprof/Makefile.am	2003-10-13 21:18:24.277974336 -0700
@@ -0,0 +1,26 @@
+
+SUBDIRS = . 
+
+CFLAGS = $(WERROR) -DVG_LIBDIR="\"$(libdir)"\" \
+		-Winline -Wall -Wshadow -O -fomit-frame-pointer @PREFERRED_STACK_BOUNDARY@ -g
+
+valdir = $(libdir)/valgrind
+
+INCLUDES = -I$(top_srcdir)/include
+
+val_PROGRAMS = vgskin_vgprof.so
+
+vgskin_vgprof_so_SOURCES	= gp_main.c
+vgskin_vgprof_so_LDFLAGS	= -shared
+
+gpincludedir = $(includedir)/valgrind
+
+gpinclude_HEADERS = \
+	vgprof.h
+
+noinst_HEADERS =	\
+	gmon_out.h
+
+##vgskin_none.so$(EXEEXT): $(vgskin_none_so_OBJECTS)
+##	$(CC) $(CFLAGS) $(LDFLAGS) -shared -o vgskin_none.so \
+##		$(vgskin_none_so_OBJECTS)
diff -puN /dev/null vgprof/gmon_out.h
--- /dev/null	2003-01-30 02:24:37.000000000 -0800
+++ local-jeremy/vgprof/gmon_out.h	2003-10-13 21:18:24.278974184 -0700
@@ -0,0 +1,96 @@
+/* Copyright (C) 1996, 1997 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by David Mosberger <davidm@cs.arizona.edu>.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+/* This file specifies the format of gmon.out files.  It should have
+   as few external dependencies as possible as it is going to be included
+   in many different programs.  That is, minimize the number of #include's.
+
+   A gmon.out file consists of a header (defined by gmon_hdr) followed by
+   a sequence of records.  Each record starts with a one-byte tag
+   identifying the type of records, followed by records specific data. */
+
+#ifndef _SYS_GMON_OUT_H
+#define _SYS_GMON_OUT_H	1
+
+#include <features.h>
+
+#define	GMON_MAGIC	"gmon"	/* magic cookie */
+#define GMON_VERSION	1	/* version number */
+
+/* For profiling shared object we need a new format.  */
+#define GMON_SHOBJ_VERSION	0x1ffff
+
+__BEGIN_DECLS
+
+/*
+ * Raw header as it appears on file (without padding).  This header
+ * always comes first in gmon.out and is then followed by a series
+ * records defined below.
+ */
+struct gmon_hdr
+  {
+    char cookie[4];
+    char version[4];
+    char spare[3 * 4];
+  };
+
+/* types of records in this file: */
+typedef enum
+  {
+    GMON_TAG_TIME_HIST = 0,
+    GMON_TAG_CG_ARC = 1,
+    GMON_TAG_BB_COUNT = 2,
+    GMON_TAG_SOMAP = 3,			/* version 2 */
+  } GMON_Record_Tag;
+
+struct gmon_hist_hdr
+  {
+    char low_pc[sizeof (char *)];	/* base pc address of sample buffer */
+    char high_pc[sizeof (char *)];	/* max pc address of sampled buffer */
+    char hist_size[4];			/* size of sample buffer */
+    char prof_rate[4];			/* profiling clock rate */
+    char dimen[15];			/* phys. dim., usually "seconds" */
+    char dimen_abbrev;			/* usually 's' for "seconds" */
+  };
+
+struct gmon_cg_arc_record
+  {
+    char from_pc[sizeof (char *)];	/* address within caller's body */
+    char self_pc[sizeof (char *)];	/* address within callee's body */
+    char count[4];			/* number of arc traversals */
+  };
+
+struct gmon_somap_record
+{
+  char start[sizeof(char *)];		/* mapped address */
+  char length[sizeof(unsigned long)];	/* length of mapping */
+  char offset[sizeof(char *)];		/* symbol table offset */
+  char namelen;				/* length of name */
+  char name[0];				/* filename */
+};
+
+__END_DECLS
+
+#endif /* sys/gmon_out.h */
+/*
+ * Local variables:
+ *  c-basic-offset: 2
+ *  c-style: gnu
+ * End:
+ */
diff -puN /dev/null vgprof/gp_main.c
--- /dev/null	2003-01-30 02:24:37.000000000 -0800
+++ local-jeremy/vgprof/gp_main.c	2003-10-13 21:18:24.285973120 -0700
@@ -0,0 +1,940 @@
+
+/*--------------------------------------------------------------------*/
+/*--- The vgprof skin.                                   gp_main.c ---*/
+/*--------------------------------------------------------------------*/
+
+/*
+   This file is part of Valgrind, an x86 protected-mode emulator 
+   designed for debugging and profiling binaries on x86-Unixes.
+
+   Copyright (C) 2002 Jeremy Fitzhardinge
+      jeremy@goop.org
+
+   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 2 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., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307, USA.
+
+   The GNU General Public License is contained in the file COPYING.
+*/
+
+#include "vg_skin.h"
+#include "vgprof.h"
+#include "gmon_out.h"
+
+VG_DETERMINE_INTERFACE_VERSION
+
+/*
+ * Generate a gprof-compatible output file as a result of running the
+ * instrumented program.
+ */
+
+/* 
+ * With --calls-only=yes (the default), only call jmp instructions are
+ * instrumented; the problem with this is that calls into shared
+ * libraries are intercepted and actually implemented with a jmp.
+ * Since we want to see the actual call and don't really care about
+ * the dynamic linker, we should do something about it.  But what?
+ * Look to see if the eip is in an object's PLT (appears as _init) and
+ * not instrument those basic blocks, thereby pretending the call went
+ * directly. 
+ */
+
+/*
+ * TODO:
+ *
+ * - Use suppression mechanism to be able to exclude particular
+ *   functions and files
+ * - units other than instructions
+ */
+
+
+#define GMON_OUT	"vgmon.out"
+
+/* Options */
+static Char *output = GMON_OUT;	/* output file name */
+
+static Bool histo = True;		/* include histogram */
+static Bool bbcount = True;		/* include basic-block count */
+static Bool call_graph = True;		/* include call-graph */
+static Bool somap = True;		/* include shared object map in output */
+
+static Bool text_only = True;		/* only instrument things in the text segment */
+static Bool calls_only = True;		/* only instrument call instructions */
+
+static UInt histo_scale = 16;
+static UInt histo_scale_shift = 4;
+
+static Bool auto_scale = True;
+static UInt unit_scale = 1;
+
+typedef enum Units {
+   Instructions,
+   ElapsedTime,
+   CPUTime,
+} Units;
+Units units = Instructions;
+
+static const struct unit_names {
+   const Char *name;
+   const Char abbr;
+} unit_names[] = {
+   [Instructions] { "instructions", 'i' },
+   [ElapsedTime] { "milliseconds", 's' },
+   [CPUTime] { "seconds", 's' },
+};
+
+//typedef ULong	Sample;
+typedef UInt	Sample;
+
+/* Stats */
+static UInt num_BB;
+static UInt num_edges;
+static UInt num_instrumented;
+static UInt num_skipped;
+
+#define uInstr0   VG_(new_UInstr0)
+#define uInstr1   VG_(new_UInstr1)
+#define uInstr2   VG_(new_UInstr2)
+#define uLiteral  VG_(set_lit_field)
+#define uCCall    VG_(set_ccall_fields)
+#define newTemp   VG_(get_new_temp)
+
+
+#define BB_HASHSIZE	1023
+#define EDGE_HASHSIZE	1023
+
+typedef struct BBCount {
+   ULong	count;
+   Addr		addr;
+   struct BBCount *next;
+} BBCount;
+
+static BBCount *bbhash[BB_HASHSIZE];
+
+/* allocate new BBCount and insert into hash */
+static BBCount *newBBCount(Addr addr)
+{
+   UInt bucket = addr % BB_HASHSIZE;
+   BBCount *bbp;
+
+   /* not found - insert new */
+   bbp = VG_(malloc)(sizeof(*bbp));
+   bbp->addr = addr;
+   bbp->count = 0;
+   bbp->next = bbhash[bucket];
+   bbhash[bucket] = bbp;
+   num_BB++;
+
+   return bbp;
+}
+
+static inline BBCount *getBBCount(Addr addr)
+{
+   UInt bucket = addr % BB_HASHSIZE;
+   BBCount *bbp;
+
+   for(bbp = bbhash[bucket]; bbp != 0; bbp = bbp->next)
+      if (bbp->addr == addr)
+	 return bbp;
+
+   return NULL;
+}
+
+typedef struct Edge {
+   Addr		from;
+   Addr		to;
+   ULong	count;
+   struct Edge *next;
+} Edge;
+
+static Edge *edgehash[EDGE_HASHSIZE];
+
+/* move uncommon case out of line */
+static Edge *newEdge(Addr from, Addr to, UInt bucket)
+{
+   Edge *ep;
+
+   /* not found - insert new */
+   ep = VG_(malloc)(sizeof(*ep));
+   ep->from = from;
+   ep->to = to;
+   ep->count = 0;
+   ep->next = edgehash[bucket];
+   edgehash[bucket] = ep;
+   num_edges++;
+
+   return ep;
+}
+
+static inline Edge *getEdge(Addr from, Addr to)
+{
+   UInt bucket = (from ^ ((to << 17) | (to >> (32-17)))) % EDGE_HASHSIZE;
+   Edge *ep;
+
+   for(ep = edgehash[bucket]; ep != 0; ep = ep->next)
+      if (ep->from == from && ep->to == to)
+	 return ep;
+
+   return newEdge(from, to, bucket);
+}
+
+/* one-level page table which has HPG_SIZE entries pointing to pages
+   of HPG_IDX_SIZE histo buckets */
+#define HPG_BITS	20
+#define HPG_SIZE	(1 << HPG_BITS)
+
+#define HPG_IDX		(32 - HPG_BITS)
+#define HPG_IDX_SIZE	(1 << HPG_IDX)
+#define HPG_IDX_MASK	(HPG_IDX_SIZE - 1)
+
+static Sample** histoTab;
+
+static inline UInt histo_page(Addr addr)
+{
+   addr >>= histo_scale_shift;
+   return addr / HPG_IDX_SIZE;
+}
+
+static inline UInt histo_idx(Addr addr)
+{
+   addr >>= histo_scale_shift;
+   return addr & HPG_IDX_MASK;
+}
+
+static void alloc_histo(Addr addr)
+{
+   UInt page = histo_page(addr);
+
+   if (histoTab[page] == 0) {
+      histoTab[page] = VG_(malloc)(sizeof(**histoTab) * HPG_IDX_SIZE);
+      VG_(memset)(histoTab[page], 0, sizeof(**histoTab) * HPG_IDX_SIZE);
+   }
+}
+
+static inline void inc_histo(Addr addr, Int nInstr)
+{
+   UInt page = histo_page(addr);
+   UInt idx = histo_idx(addr);
+
+   histoTab[page][idx] += nInstr;
+
+   //VG_(printf)("addr %p %d/%d -> %d\n", addr, page, idx, histoTab[page][idx]);
+}
+
+#if 1
+#define REGPARM(n)	n
+#else
+#define REGPARM(n)	0
+#endif
+
+static ULong time_start;
+
+static inline ULong rdtsc(void)
+{
+   ULong now;
+
+   asm volatile ("rdtsc" : "=A" (now));
+   return now;
+}
+
+static void timer_bb_start(void) __attribute__((regparm (REGPARM(0))));
+static void timer_bb_stop(Addr) __attribute__((regparm (REGPARM(1))));
+
+static void timer_bb_start(void)
+{
+   time_start = rdtsc();
+}
+
+static void timer_bb_stop(Addr addr)
+{
+   ULong now = rdtsc();
+   ULong delta;
+
+   delta = now - time_start;
+   time_start = now;
+   inc_histo(addr, delta);
+}
+
+typedef void helper_t(Addr, UInt);
+
+static helper_t helper_none __attribute__((regparm (REGPARM(2))));
+static helper_t helper_histo __attribute__((regparm (REGPARM(2))));
+static helper_t helper_cg __attribute__((regparm (REGPARM(2))));
+static helper_t helper_cg_histo __attribute__((regparm (REGPARM(2))));
+static helper_t helper_bbc __attribute__((regparm (REGPARM(2))));
+static helper_t helper_bbc_histo __attribute__((regparm (REGPARM(2))));
+static helper_t helper_bbc_cg __attribute__((regparm (REGPARM(2))));
+static helper_t helper_bbc_cg_histo __attribute__((regparm (REGPARM(2))));
+
+static void helper_none(Addr addr, UInt nInstr)
+{
+}
+
+static inline void helper_histo(Addr addr, UInt nInstr)
+{
+   inc_histo(addr, nInstr);
+}
+
+/* Previous basic block address */
+static Addr bb_from;
+
+static inline void helper_cg(Addr addr, UInt nInstr)
+{
+   if (bb_from) {
+      Edge *edge = getEdge(bb_from, addr);
+      edge->count++;
+      bb_from = 0;
+   }
+}
+
+static void helper_cg_histo(Addr addr, UInt nInstr)
+{
+   helper_histo(addr, nInstr);
+   helper_cg(addr, nInstr);
+}
+
+static inline void helper_bbc(Addr addr, UInt nInstr)
+{
+   BBCount *bb = getBBCount(addr);
+   bb->count++;
+}
+
+static void helper_bbc_histo(Addr addr, UInt nInstr)
+{
+   helper_histo(addr, nInstr);
+   helper_bbc(addr, nInstr);
+}
+
+static void helper_bbc_cg(Addr addr, UInt nInstr)
+{
+   helper_cg(addr, nInstr);
+   helper_bbc(addr, nInstr);
+}
+
+static void helper_bbc_cg_histo(Addr addr, UInt nInstr)
+{
+   helper_histo(addr, nInstr);
+   helper_cg(addr, nInstr);
+   helper_bbc(addr, nInstr);
+}
+
+/* bits are bbcount, call_graph, histo */
+static helper_t *helpers[] = {
+   helper_none,
+   helper_histo,
+   helper_cg,
+   helper_cg_histo,
+   helper_bbc,
+   helper_bbc_histo,
+   helper_bbc_cg,
+   helper_bbc_cg_histo,
+};
+
+static helper_t *bb_helper = helper_none;
+
+void SK_(pre_clo_init)(void)
+{
+   VG_(details_name)                ("vgprof");
+   VG_(details_version)		    (NULL);
+   VG_(details_description)         ("gprof profiling from within valgrind");
+   VG_(details_bug_reports_to)	    ("jeremy@goop.org");
+   VG_(details_copyright_author)    ("Copyright (C) 2002 Jeremy Fitzhardinge");
+   VG_(details_avg_translation_sizeB) ( 100 );
+
+   VG_(needs_command_line_options)();
+   //needs->basic_block_discards = True;
+   VG_(needs_client_requests)();
+}
+
+void SK_(post_clo_init)(void)
+{
+   UInt idx;
+
+   if (histo) {
+      histoTab = VG_(malloc)(sizeof(*histoTab) * (HPG_SIZE / histo_scale));
+      VG_(memset)(histoTab, 0, sizeof(*histoTab) * (HPG_SIZE / histo_scale));
+   }
+
+   idx = 0;
+   if (histo && units == Instructions)
+      idx += 1;
+   if (call_graph)
+      idx += 2;
+   if (bbcount)
+      idx += 4;
+   bb_helper = helpers[idx];
+
+   VG_(register_compact_helper)((Addr) bb_helper);
+
+   if (histo && (units == ElapsedTime || units == CPUTime)) {
+      VG_(register_compact_helper)((Addr) timer_bb_start);
+      VG_(register_compact_helper)((Addr) timer_bb_stop);
+   }
+}
+
+/* Relative weightings of various instruction types */
+static struct inst_weights
+{
+   UInt	arith;
+   UInt mem;
+   UInt mult;
+   UInt div;
+   UInt fp;
+   UInt seg;
+   UInt jump;
+} inst_weights = 
+{
+   .arith = 1,
+   .mem   = 2,
+   .mult  = 4,
+   .div   = 15,
+   .fp    = 10,
+   .seg   = 4,
+   .jump  = 2,
+};
+
+static inline UInt max(UInt a, UInt b)
+{
+   return a > b ? a : b;
+}
+
+UCodeBlock* SK_(instrument)(UCodeBlock* cb_in, Addr orig_eip)
+{
+   UCodeBlock* cb;
+   Int i;
+   Int nInstr = 1;
+   Addr eip = orig_eip;
+
+   //VG_(printf)("%p -> %c\n", orig_eip, "?TGP"[VG_(seg_sect_kind)(orig_eip)]);
+   if (text_only && VG_(seg_sect_kind)(orig_eip) != Vg_SectText) {
+      num_skipped++;
+      return cb_in;
+   }
+   num_instrumented++;
+
+   for (i = 0; i < VG_(get_num_instrs)(cb_in); i++) {
+      UInstr *u = VG_(get_instr)(cb_in, i);
+      UInt weight = 0;
+
+      switch(u->opcode) {
+      case LOAD:
+      case STORE:
+      case FPU_R: case FPU_W:
+	 weight = max(weight, inst_weights.mem);
+	 break;
+
+      case MUL:
+      case UMUL:
+	 weight = max(weight, inst_weights.mult);
+	 break;
+
+      case FPU:
+	 weight = max(weight, inst_weights.fp);
+	 break;
+
+      case GETSEG:
+      case PUTSEG:
+      case USESEG:
+	 weight = max(weight, inst_weights.seg);
+	 break;
+
+      case JMP:
+	 weight = max(weight, inst_weights.jump);
+	 break;
+
+      case INCEIP:
+	 if (weight == 0)
+	    weight = inst_weights.arith;
+	 nInstr += weight;
+	 break;
+      }
+   }
+
+   cb = VG_(setup_UCodeBlock)(cb_in);
+
+   /* create BB count node here (if it hasn't already been created) */
+   if (bbcount && getBBCount(orig_eip) == NULL)
+      newBBCount(orig_eip);
+
+   /* also allocate histogram page */
+   if (histo)
+      alloc_histo(orig_eip);
+
+   /* insert instrumentation for this BB */
+   if (bb_helper != helper_none)
+      VG_(call_helper_2_0)(cb, (Addr) bb_helper, orig_eip, nInstr, REGPARM(2));
+
+   if (histo && (units == ElapsedTime || units == CPUTime))
+      VG_(call_helper_0_0)(cb, (Addr) timer_bb_start);
+
+   for (i = 0; i < VG_(get_num_instrs)(cb_in); i++) {
+      UInstr *u = VG_(get_instr)(cb_in, i);
+
+      switch (u->opcode) {
+      case JMP:
+	 if (call_graph && (!calls_only || u->jmpkind == JmpCall))
+	    VG_(set_global_var)(cb, (Addr)&bb_from, eip);
+
+	 if (histo && (units == ElapsedTime || units == CPUTime))
+	    VG_(call_helper_1_0)(cb, (Addr) timer_bb_stop, orig_eip, REGPARM(1));
+
+	 eip += u->extra4b;
+	 VG_(copy_UInstr)(cb, u);
+	 break;
+
+      case INCEIP:
+	 eip += u->val1;
+	 VG_(copy_UInstr)(cb, u);
+	 break;
+
+      default:
+	 VG_(copy_UInstr)(cb, u);
+      }
+  }
+
+   return cb;
+}
+
+/* set all accumulated results to 0 */
+static void zero_stats(void)
+{
+   if (VG_(clo_verbosity))
+      VG_(message)(Vg_UserMsg, "Zeroing all statistics");
+
+   if (histo && histoTab) {
+      UInt i;
+
+      for(i = 0; i < (HPG_SIZE/histo_scale); i++) {
+	 Int j;
+
+	 if (histoTab[i] == 0)
+	    continue;
+
+	 for(j = 0; j < HPG_IDX_SIZE; j++)
+	    histoTab[i][j] = 0;
+      }
+   }
+
+   if (call_graph) {
+      UInt i;
+
+      for(i = 0; i < EDGE_HASHSIZE; i++) {
+	 Edge *e;
+
+	 for(e = edgehash[i]; e != 0; e = e->next)
+	    e->count = 0;
+      }
+   }
+
+   if (bbcount) {
+      UInt i;
+
+      for(i = 0; i < BB_HASHSIZE; i++) {
+	 BBCount *bb;
+
+	 for(bb = bbhash[i]; bb != 0; bb = bb->next)
+	    bb->count = 0;
+      }
+   }
+}
+
+
+/* Dump currently accumulated output into a new file */
+static void dump_output(void)
+{
+   Int fd;
+   UChar b;
+   Int i;
+   struct gmon_hdr hdr;
+   struct gmon_cg_arc_record arc;
+   struct gmon_bb_count_record {
+      char addr[sizeof(char *)];
+      char count[sizeof(UInt)];
+   } bbcountrec;
+   struct gmon_hist_hdr hist;
+   Int count;
+
+   {
+      Char *outfile = VG_(malloc)(VG_(strlen)(output) + 1 + 5 + 1 + 4 + 1);
+      struct vki_stat st;
+
+      fd = -1;
+      VG_(sprintf)(outfile, "%s.%d", output, VG_(getpid)());
+
+      if (VG_(stat)(outfile, &st) != -1)
+	 for(count = 1; count < 10000; count++) {
+	    VG_(sprintf)(outfile, "%s.%d.%d", output, VG_(getpid)(), count);
+	    if (VG_(stat)(outfile, &st) == -1)
+	       break;
+	 }
+
+      fd = VG_(open)(outfile, VKI_O_WRONLY|VKI_O_CREAT|VKI_O_EXCL, 0666);
+
+      if (fd == -1) {
+	 VG_(message)(Vg_UserMsg, "Couldn't create output file %s", outfile);
+	 VG_(free)(outfile);
+	 return;
+      }
+      if (VG_(clo_verbosity))
+	 VG_(message)(Vg_UserMsg, "writing output to %s",
+		      outfile);
+      VG_(free)(outfile);
+   }
+
+   if (VG_(clo_verbosity) > 1)
+      VG_(message)(Vg_UserMsg, "writing out %d basic blocks; %d edges", 
+		   num_BB, num_edges);
+
+   VG_(memset)(&hdr, 0, sizeof(hdr));
+   VG_(memcpy)(hdr.cookie, GMON_MAGIC, sizeof(hdr.cookie));
+   *(Int *)hdr.version = somap ? 2 : 1;
+
+   VG_(write)(fd, &hdr, sizeof(hdr));
+
+   if (somap) {
+      /* Put this first so that gprof has symbols for everything which follows */
+      const SegInfo *seg;
+
+      b = GMON_TAG_SOMAP;
+
+      for(seg = VG_(next_seginfo)(NULL); 
+	  seg != NULL; 
+	  seg = VG_(next_seginfo)(seg)) {
+	 Addr base = VG_(seg_start)(seg);
+	 UInt size = VG_(seg_size)(seg);
+	 const UChar *filename = VG_(seg_filename)(seg);
+	 UInt offset = VG_(seg_sym_offset)(seg);
+	 struct gmon_somap_record somap_rec;
+
+	 if (offset == 0) {
+	    /* don't include main executable */
+	    continue;
+	 }
+
+	 //VG_(printf)("%8p %8d off=%08x name=%s\n", base, size, offset, filename);
+	 *(Addr *)&somap_rec.start = base;
+	 *(UInt *)&somap_rec.length = size;
+	 *(UInt *)&somap_rec.offset = offset;
+	 somap_rec.namelen = VG_(strlen)(filename);
+
+	 VG_(write)(fd, &b, sizeof(b));
+	 VG_(write)(fd, &somap_rec, sizeof(somap_rec));
+	 VG_(write)(fd, (void *)filename, VG_(strlen)(filename));
+      }
+   }
+
+   if (histo) {
+      Sample maxsample = 0;
+
+      for(i = 0; i < (HPG_SIZE/histo_scale); i++) {
+	 Int j;
+
+	 if (histoTab == 0 || histoTab[i] == 0)
+	    continue;
+
+	 for(j = 0; j < HPG_IDX_SIZE; j++) {
+	    Sample sample = histoTab[i][j];
+	    
+	    if (sample > maxsample)
+	       maxsample = sample;
+	 }
+      }
+
+      {
+	 UInt limit = 65536;
+	 ULong tsc_per_ms = VG_(get_tsc_ticks_per_millisecond)();
+
+	 if (units == ElapsedTime)
+	    limit *= tsc_per_ms;
+
+	 if ((maxsample / unit_scale) >= limit) {
+	    if (auto_scale) {
+	       unit_scale = 1;
+	       while((maxsample / unit_scale) >= limit)
+		  unit_scale *= 10;
+	       VG_(message)(Vg_UserMsg, "Max sample is %ld; Using --unit-scale=%d", 
+			    (ULong)maxsample, unit_scale);
+	       auto_scale = False;	/* only the first time */
+	    } else {
+	       VG_(message)(Vg_UserMsg, "Warning: max sample is %d; recommend unit-scale of %d",
+			    maxsample, (maxsample + 65535) / 65535);
+	    }
+	 }
+	 if (units == ElapsedTime)
+	    unit_scale *= tsc_per_ms;
+      }
+
+      b = GMON_TAG_TIME_HIST;
+   
+      for(i = 0; i < (HPG_SIZE/histo_scale); i++) {
+	 UShort *buf;
+	 Int j;
+	 /* if True, keep at least a count of 1 after division */
+	 static const Bool residual = False;
+
+	 if (histoTab == 0 || histoTab[i] == 0)
+	    continue;
+
+	 for(j = 0; j < HPG_IDX_SIZE; j++) {
+	    if (residual) {
+	       if (histoTab[i][j] != 0)
+		  break;
+	    } else {
+	       if ((histoTab[i][j] / unit_scale) != 0)
+		  break;
+	    }
+	 }
+	 if (j == HPG_IDX_SIZE)
+	    continue;		/* all 0 (!?) */
+
+	 *(Addr *)hist.low_pc = (i * HPG_IDX_SIZE) * histo_scale;
+	 *(Addr *)hist.high_pc = ((i+1) * HPG_IDX_SIZE) * histo_scale;
+	 *(UInt *)hist.hist_size = HPG_IDX_SIZE;
+	 *(UInt *)hist.prof_rate = 1;
+	 VG_(strncpy)(hist.dimen, unit_names[units].name, sizeof(hist.dimen));
+	 hist.dimen_abbrev = unit_names[units].abbr;
+
+	 if (0)
+	    VG_(printf)("hist[%d] -> low_pc=%p high_pc=%p\n", 
+			i, *(Addr *)&hist.low_pc, *(Addr *)&hist.high_pc);
+
+	 buf = VG_(malloc)(HPG_IDX_SIZE * sizeof(*buf));
+	 for(j = 0; j < HPG_IDX_SIZE; j++) {
+	    Sample sample = histoTab[i][j] / unit_scale;
+	 
+	    if (residual && sample == 0 && histoTab[i][j] != 0)
+	       sample = 1;	/* don't make sample completely disappear */
+
+	    if (sample != (UShort)sample)
+	       buf[j] = 0xffff;
+	    else
+	       buf[j] = (UShort)sample;
+	 }
+
+	 VG_(write)(fd, &b, sizeof(b));
+	 VG_(write)(fd, &hist, sizeof(hist));
+	 VG_(write)(fd, buf, sizeof(*buf)*HPG_IDX_SIZE);
+
+	 VG_(free)(buf);
+      }
+   }
+
+   if (bbcount) {
+      b = GMON_TAG_BB_COUNT;
+      VG_(write)(fd, &b, sizeof(b));
+      i = num_BB;
+      VG_(write)(fd, &i, sizeof(i));
+
+      for(i = 0; i < BB_HASHSIZE; i++) {
+	 BBCount *bb;
+
+	 for(bb = bbhash[i]; bb != 0; bb = bb->next) {
+	    //VG_(printf)("bb %p = %d\n", bb->addr, bb->count);
+
+	    *((Addr *)&bbcountrec.addr) = bb->addr;
+	    *((UInt *)&bbcountrec.count) = bb->count;
+
+	    VG_(write)(fd, &bbcountrec, sizeof(bbcountrec));
+	 }
+      }
+   }
+
+   if (call_graph) {
+      b = GMON_TAG_CG_ARC;
+
+      for(i = 0; i < EDGE_HASHSIZE; i++) {
+	 Edge *e = edgehash[i];
+	 for(; e != 0; e = e->next) {
+	    //VG_(printf)("arc %p-%p = %d\n", e->from, e->to, e->count);
+
+	    if (e->count == 0)
+	       continue;
+
+	    *(Addr *)&arc.from_pc = e->from;
+	    *(Addr *)&arc.self_pc = e->to;
+	    *(UInt *)&arc.count = e->count;
+
+	    VG_(write)(fd, &b, sizeof(b));
+	    VG_(write)(fd, &arc, sizeof(arc));
+	 }
+      }
+   }
+   VG_(close)(fd);
+}
+
+void SK_(fini)(Int exitcode)
+{
+   dump_output();
+}
+
+Bool SK_(handle_client_request)(ThreadId tid, UInt *args, UInt *ret)
+{
+   if (!VG_IS_SKIN_USERREQ('G','P',args[0]))
+      return False;
+
+   switch(args[0]) {
+   case VG_USERREQ__DUMP_PROFILE:
+      dump_output();
+      if (args[1])
+	 zero_stats();
+      *ret = 0;			/* meaningless */
+      break;
+
+   case VG_USERREQ__ZERO_STATS:
+      zero_stats();
+      *ret = 0;			/* meaningless */
+      break;
+
+   default:
+      return False;
+   }
+
+   return True;
+}
+
+static Bool match_Bool(Char *arg, Char *argstr, Bool *ret)
+{
+   Int len = VG_(strlen)(argstr);
+
+   if (VG_(strncmp)(arg, argstr, len) == 0) {
+      if (VG_(strcmp)(arg+len, "yes") == 0) {
+	 *ret = True;
+	 return True;
+      } else if (VG_(strcmp)(arg+len, "no") == 0) {
+	 *ret = False;
+	 return True;
+      } else
+	 VG_(bad_option)(arg);
+   }
+   return False;
+}
+
+static Bool match_Int(Char *arg, Char *argstr, Int *ret)
+{
+   Int len = VG_(strlen)(argstr);
+
+   if (VG_(strncmp)(arg, argstr, len) == 0) {
+      *ret = (Int)VG_(atoll)(arg+len);
+	 return True;
+   }
+   return False;
+}
+
+static Bool match_str(Char *arg, Char *argstr, Char **ret)
+{
+   Int len = VG_(strlen)(argstr);
+
+   if (VG_(strncmp)(arg, argstr, len) == 0) {
+      *ret = VG_(strdup)(arg+len);
+      return True;
+   }
+
+   return False;
+}
+
+Bool SK_(process_cmd_line_option)(Char* arg)
+{
+   if (match_Bool(arg, "--histo=", &histo))
+      return True;
+
+   if (match_Bool(arg, "--call-graph=", &call_graph))
+      return True;
+
+   if (match_Bool(arg, "--bbcount=", &bbcount))
+      return True;
+
+   if (match_str(arg, "--prof-output=", &output))
+      return True;
+
+   {
+      Char *u;
+      if (match_str(arg, "--units=", &u)) {
+	 Bool OK = True;
+	 if (VG_(strcmp)(u, "instructions") == 0)
+	    units = Instructions;
+	 else if (VG_(strcmp)(u, "walltime") == 0)
+	    units = ElapsedTime;
+	 else if (VG_(strcmp)(u, "cputime") == 0)
+	    units = CPUTime;
+	 else {
+	    OK = False;
+	    VG_(bad_option)(arg);
+	 }
+	 VG_(free)(u);
+	 if (OK)
+	    return True;
+      }
+   }
+
+   if (match_Bool(arg, "--somap=", &somap))
+      return True;
+
+   if (match_Bool(arg, "--calls-only=", &calls_only))
+      return True;
+
+   if (match_Bool(arg, "--text-only=", &text_only))
+      return True;
+
+   if (match_Int(arg, "--histo-scale=", &histo_scale)) {
+      if (histo_scale == 0 || (histo_scale & (histo_scale-1)))
+	 VG_(bad_option)(arg);
+      else {
+	 histo_scale_shift = 0;
+	 while((1 << histo_scale_shift) != histo_scale)
+	    histo_scale_shift++;
+      }
+      return True;
+   }
+
+   if (match_Int(arg, "--unit-scale=", &unit_scale)) {
+      if (unit_scale == 0) {
+	 if (VG_(strcmp)(arg+13, "auto") ==0) {
+	    unit_scale = 1;
+	    auto_scale = True;
+	 } else
+	    VG_(bad_option)(arg);
+      } else
+	 auto_scale = False;
+      return True;
+   }
+
+   return False;
+}
+
+Char *SK_(usage)(void)
+{
+   return
+"    --histo=yes|no        include a histogram [yes]\n"
+"    --histo-scale=N       bucket size for histogram [16]\n"
+"    --units=instructions|walltime|cputime\n"
+"                          units measured in the histogram [instructions]\n"
+"    --unit-scale=N|auto   scaling for accumulated units [auto]\n\n"
+
+"    --call-graph=yes|no   include call graph [yes]\n"
+"    --calls-only=yes|no   only include function calls in call-graph output [yes]\n"
+"    --text-only=yes|no    only instrument code in a text segment [yes]\n\n"
+"    --bbcount=yes|no      include basic-block counts [yes]\n\n"
+"    --prof-output=file    profile output file name ["GMON_OUT"]\n"
+"    --somap=yes|no        include mapping for shared objects within gmon file [yes]\n"
+      ;
+}
+
+/*--------------------------------------------------------------------*/
+/*--- end                                                gp_main.c ---*/
+/*--------------------------------------------------------------------*/
+/*
+ * Local variables:
+ *  c-basic-offset: 3
+ * End:
+ */
diff -puN /dev/null vgprof/vgprof.h
--- /dev/null	2003-01-30 02:24:37.000000000 -0800
+++ local-jeremy/vgprof/vgprof.h	2003-10-13 21:18:24.286972968 -0700
@@ -0,0 +1,89 @@
+
+/*
+   ----------------------------------------------------------------
+
+   Notice that the following BSD-style license applies to this one
+   file (vg_memcheck.h) only.  The entire rest of Valgrind is licensed
+   under the terms of the GNU General Public License, version 2.  See
+   the COPYING file in the source distribution for details.
+
+   ----------------------------------------------------------------
+
+   This file is part of gprof, a valgrindn skin for generating profiles.
+
+   Copyright (C) 2000-2002 Jeremy Fitzhardinge.  All rights reserved.
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   1. Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+   2. The origin of this software must not be misrepresented; you must 
+      not claim that you wrote the original software.  If you use this 
+      software in a product, an acknowledgment in the product 
+      documentation would be appreciated but is not required.
+
+   3. Altered source versions must be plainly marked as such, and must
+      not be misrepresented as being the original software.
+
+   4. The name of the author may not be used to endorse or promote 
+      products derived from this software without specific prior written 
+      permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+   ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+   ----------------------------------------------------------------
+
+   Notice that the above BSD-style license applies to this one file
+   (vgprof.h) only.  The entire rest of Valgrind is licensed under
+   the terms of the GNU General Public License, version 2.  See the
+   COPYING file in the source distribution for details.
+
+   ---------------------------------------------------------------- 
+*/
+
+#ifndef __VGPROF_H
+#define __VGPROF_H
+
+#define __VALGRIND_SOMESKIN_H
+
+#include "valgrind.h"
+
+typedef
+   enum {
+      VG_USERREQ__DUMP_PROFILE = VG_USERREQ_SKIN_BASE('G','P'),
+      VG_USERREQ__ZERO_STATS,
+   } Vg_GProfClientRequest;
+
+/* Dump current state of statistics gathered; if passed 1 argument, it
+   will also atomically zero the counts */
+#define VALGRIND_DUMP_PROFILE(_zqq_zero)				\
+   do {									\
+     unsigned int _qzz_res;						\
+     VALGRIND_MAGIC_SEQUENCE(_qzz_res, 0, VG_USERREQ__DUMP_PROFILE,	\
+			     _zqq_zero, 0, 0, 0);			\
+     (void)0;								\
+   } while(0)
+
+/* Zero statistics */
+#define VALGRIND_ZERO_STATS()						\
+   do {									\
+     unsigned int _qzz_res;						\
+     VALGRIND_MAGIC_SEQUENCE(_qzz_res, 0, VG_USERREQ__ZERO_STATS,	\
+			     0, 0, 0, 0);				\
+     (void)0;								\
+   } while(0)
+
+#endif /* __VGPROF_H */
diff -puN configure.in~12-vgprof configure.in
--- local/configure.in~12-vgprof	2003-10-13 21:18:24.260976920 -0700
+++ local-jeremy/configure.in	2003-10-13 21:18:24.288972664 -0700
@@ -390,6 +390,7 @@ AC_OUTPUT(
    lackey/Makefile
    lackey/tests/Makefile
    lackey/docs/Makefile
+   vgprof/Makefile
    none/Makefile
    none/tests/Makefile
    none/docs/Makefile
diff -puN Makefile.am~12-vgprof Makefile.am
--- local/Makefile.am~12-vgprof	2003-10-13 21:18:24.263976464 -0700
+++ local-jeremy/Makefile.am	2003-10-13 21:18:24.288972664 -0700
@@ -11,6 +11,7 @@ SUBDIRS = 	coregrind . docs tests includ
 		corecheck \
 		helgrind \
 		lackey \
+		vgprof \
 		none
 
 AM_CPPFLAGS = -DVG_LIBDIR="\"$(libdir)"\"
diff -puN coregrind/vg_mylibc.c~12-vgprof coregrind/vg_mylibc.c
--- local/coregrind/vg_mylibc.c~12-vgprof	2003-10-13 21:18:24.266976008 -0700
+++ local-jeremy/coregrind/vg_mylibc.c	2003-10-13 21:18:24.292972056 -0700
@@ -1350,6 +1350,10 @@ UInt VG_(read_millisecond_timer) ( void 
    return (UInt)rdtsc_now;
 }
 
+ULong VG_(get_tsc_ticks_per_millisecond)(void)
+{
+   return rdtsc_ticks_per_millisecond;
+}
 
 void VG_(start_rdtsc_calibration) ( void )
 {
diff -puN include/vg_skin.h~12-vgprof include/vg_skin.h
--- local/include/vg_skin.h~12-vgprof	2003-10-13 21:18:24.270975400 -0700
+++ local-jeremy/include/vg_skin.h	2003-10-13 21:18:24.298971144 -0700
@@ -498,6 +498,9 @@ extern Int VG_(ksigaltstack) ( const vki
 extern Int VG_(kkill)        ( Int pid, Int signo );
 extern Int VG_(ksigpending)  ( vki_ksigset_t* set );
 
+/* --- Timers --- */
+extern ULong VG_(get_tsc_ticks_per_millisecond) (void);
+extern UInt  VG_(read_millisecond_timer)        (void);
 
 /* ------------------------------------------------------------------ */
 /* other, randomly useful functions */
diff -puN valgrind.spec.in~12-vgprof valgrind.spec.in
--- local/valgrind.spec.in~12-vgprof	2003-10-13 21:18:24.273974944 -0700
+++ local-jeremy/valgrind.spec.in	2003-10-13 21:18:24.299970992 -0700
@@ -41,6 +41,7 @@ make install prefix=$RPM_BUILD_ROOT/usr
 /usr/include/valgrind/valgrind.h
 /usr/include/valgrind/memcheck.h
 /usr/include/valgrind/helgrind.h
+/usr/include/valgrind/vgprof.h
 /usr/bin/valgrind
 /usr/bin/cg_annotate
 /usr/lib/valgrind/*

_
