diff mbox

[RFC] libgcov.c re-factoring and offline profile-tool

Message ID CAF1bQ=REYRgX4zn2pwP0HZhg5P0nO3XgXVK=RQVBpFv-o5zoig@mail.gmail.com
State New
Headers show

Commit Message

Rong Xu Nov. 7, 2013, 5:19 p.m. UTC
Thanks Joseph for these detailed comments/suggestions.
The fixed patch is attached to this email.
The only thing left out is the Texinfo manual. Do you mean this tool
should have its it's own texi file in gcc/doc?
I prefer to do the documentation later as I'm sure the final version
will from current one.

-Rong

On Wed, Nov 6, 2013 at 5:17 PM, Joseph S. Myers <joseph@codesourcery.com> wrote:
> On Wed, 6 Nov 2013, Rong Xu wrote:
>
>> In current implementation, if you use 'profile-tool help', it will
>> print out the usage and exit.
>
> Please make sure you follow the GNU Coding Standards.  Any installed tool
> needs to support --help, with output following the GNU Coding Standards,
> to standard output, with exit status 0 and with the output including the
> configured bug-reporting URL.  Any installed tool also needs to support
> --version, reporting the version number (with any configured package
> version) in the same format as other GCC tools, with copyright / licensing
> information.
>
> This is in addition to documenting the tool in the Texinfo manuals.
>
> You should also use the common GCC diagnostic functions rather than
> fprintf to stderr (and in any case make sure diagnostics follow the GNU
> Coding Standards for formatting - do not start with a capital letter or
> end with ".").
>
> All English messages need to be appropriately marked for translation.
> Look at the initialization in gcov.c including
>
>   gcc_init_libintl ();
>
>   diagnostic_initialize (global_dc, 0);
>
> and if you use standard diagnostic functions then translation is handled
> automatically; for random usage messages, fnotice may be helpful to avoid
> needing explicit _() markings.
>
> It is always wrong to include system headers before config.h, as config.h
> may define feature test macros that only work if defined before any system
> header is included.
>
> --
> Joseph S. Myers
> joseph@codesourcery.com

Comments

Joseph Myers Nov. 7, 2013, 5:40 p.m. UTC | #1
On Thu, 7 Nov 2013, Rong Xu wrote:

> Thanks Joseph for these detailed comments/suggestions.
> The fixed patch is attached to this email.
> The only thing left out is the Texinfo manual. Do you mean this tool
> should have its it's own texi file in gcc/doc?

Its own texi file, probably included as a chapter by gcc.texi (like 
gcov.texi is).
diff mbox

Patch

Index: libgcc/libgcov-tool.c
===================================================================
--- libgcc/libgcov-tool.c	(revision 0)
+++ libgcc/libgcov-tool.c	(revision 0)
@@ -0,0 +1,799 @@ 
+/* GCC Profile-tool support.
+   Contributed by Rong Xu <xur@google.com>.
+   Copyright (C) 2013 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC 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, or (at your option) any later
+version.
+
+GCC 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 GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* work around the poisoned malloc/calloc in system.h.  */
+#ifndef xmalloc
+#define xmalloc malloc 
+#define xcalloc calloc 
+#endif
+
+#define PROFILE_TOOL 1
+#define L_gcov 1
+#define L_gcov_merge_add 1
+#define L_gcov_merge_single 1
+#define L_gcov_merge_delta 1
+#define L_gcov_merge_ior 1
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "version.h"
+#include "demangle.h"
+
+/* We need the dumping and merge part of code in libgcov.  */
+#include "libgcov-driver.c"
+#include "libgcov-merge.c"
+
+/* Verbose mode for debug.  */
+static int verbose;
+
+/* Set verbose flag.  */
+void gcov_tool_set_verbose (void)
+{
+  verbose = 1;
+}
+
+/* -------- Read Gcda and Reconstruct GCOV_INFO ----------- */
+
+#include "obstack.h"
+#include <unistd.h>
+#include <ftw.h>
+
+static void tag_function (const char *, unsigned, unsigned);
+static void tag_blocks (const char *, unsigned, unsigned);
+static void tag_arcs (const char *, unsigned, unsigned);
+static void tag_lines (const char *, unsigned, unsigned);
+static void tag_counters (const char *, unsigned, unsigned);
+static void tag_summary (const char *, unsigned, unsigned);
+
+/* The gcov_info for the first module.  */
+static struct gcov_info *curr_gcov_info;
+/* The gcov_info being processed.  */
+static struct gcov_info *gcov_info_head;
+/* This variable contains all the functions in current module.  */
+static struct obstack fn_info;
+/* The function being processed.  */
+static struct gcov_fn_info *curr_fn_info;
+/* The number of functions seen so far.  */
+static unsigned num_fn_info;
+/* This variable contains all the counters for current module.  */
+static int k_ctrs_mask[GCOV_COUNTERS];
+/* The kind of counters that have been seen.  */
+static struct gcov_ctr_info k_ctrs[GCOV_COUNTERS];
+/* Number of kind of counters that have been seen.  */
+static int k_ctrs_types;
+/* The longest length of all the filenames.  */
+static int max_filename_len;
+
+/* Merge functions for counters.  */
+static gcov_merge_fn ctr_merge_functions[GCOV_COUNTERS] = {
+    __gcov_merge_add,
+    __gcov_merge_add,
+    __gcov_merge_add,
+    __gcov_merge_single,
+    __gcov_merge_delta,
+    __gcov_merge_single,
+    __gcov_merge_add,
+    __gcov_merge_ior,
+};
+
+/* Set the fn_ctrs structure in fn_info.  */
+
+static void
+set_fn_ctrs (struct gcov_fn_info *fn_info)
+{
+  int j = 0, i;
+
+  for (i = 0; i < GCOV_COUNTERS; i++)
+    {
+      if (k_ctrs_mask[i] == 0)
+        continue;
+      fn_info->ctrs[j].num = k_ctrs[i].num;
+      fn_info->ctrs[j].values = k_ctrs[i].values;
+      j++;
+    }
+  if (k_ctrs_types == 0)
+    k_ctrs_types = j;
+  else
+    gcc_assert (j == k_ctrs_types);
+}
+
+typedef struct tag_format
+{
+    unsigned tag;
+    char const *name;
+    void (*proc) (const char *, unsigned, unsigned);
+} tag_format_t;
+
+static const tag_format_t tag_table[] =
+{
+  {0, "NOP", NULL},
+  {0, "UNKNOWN", NULL},
+  {0, "COUNTERS", tag_counters},
+  {GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
+  {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
+  {GCOV_TAG_ARCS, "ARCS", tag_arcs},
+  {GCOV_TAG_LINES, "LINES", tag_lines},
+  {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
+  {GCOV_TAG_PROGRAM_SUMMARY, "PROGRAM_SUMMARY", tag_summary},
+  {0, NULL, NULL}
+};
+
+/* Handler for reading funtion tag.  */
+
+static void
+tag_function (const char *filename ATTRIBUTE_UNUSED,
+	      unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)
+{
+  int i;
+
+  /* write out previous fn_info. */
+  if (num_fn_info)
+    {
+      set_fn_ctrs (curr_fn_info);
+      /* obstack_grow (&fn_info, &curr_fn_info, sizeof (struct gcov_fn_info*)); */
+      obstack_ptr_grow (&fn_info, curr_fn_info);
+    }
+  /* first time.  */
+    {
+      /* Here we over allocate a bit, using GCOV_COUNTERS instead of the actual active
+         counter types.  */
+      curr_fn_info = (struct gcov_fn_info *) xcalloc (sizeof (struct gcov_fn_info)
+                              + GCOV_COUNTERS * sizeof (struct gcov_ctr_info), 1);
+    }
+
+  for (i = 0; i < GCOV_COUNTERS; i++)
+     k_ctrs[i].num = 0;
+  k_ctrs_types = 0;
+
+  curr_fn_info->key = curr_gcov_info;
+  curr_fn_info->ident = gcov_read_unsigned ();
+  curr_fn_info->lineno_checksum = gcov_read_unsigned ();
+  curr_fn_info->cfg_checksum = gcov_read_unsigned ();
+  num_fn_info++;
+
+  if (verbose)
+    fprintf (stdout, "tag one function id=%d\n", curr_fn_info->ident);
+}
+
+/* Handler for reading block tag.  */
+
+static void
+tag_blocks (const char *filename ATTRIBUTE_UNUSED,
+	    unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)
+{
+  gcc_assert (0);
+}
+
+/* Handler for reading flow arc tag.  */
+
+static void
+tag_arcs (const char *filename ATTRIBUTE_UNUSED,
+	  unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)
+{
+  gcc_assert (0);
+}
+
+/* Handler for reading line tag.  */
+
+static void
+tag_lines (const char *filename ATTRIBUTE_UNUSED,
+	   unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)
+{
+  gcc_assert (0);
+}
+
+/* Handler for reading counters array tag.  */
+
+static void
+tag_counters (const char *filename ATTRIBUTE_UNUSED,
+	      unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)
+{
+  unsigned n_counts = GCOV_TAG_COUNTER_NUM (length);
+  gcov_type *values;
+  unsigned ix;
+  unsigned tag_ix;
+
+  tag_ix = GCOV_COUNTER_FOR_TAG (tag);
+  gcc_assert (tag_ix < GCOV_COUNTERS);
+  k_ctrs_mask [tag_ix] = 1;
+  gcc_assert (k_ctrs[tag_ix].num == 0);
+  k_ctrs[tag_ix].num = n_counts;
+
+  k_ctrs[tag_ix].values = values = (gcov_type *)xmalloc (n_counts * sizeof (gcov_type));
+  gcc_assert (values);
+
+  for (ix = 0; ix != n_counts; ix++)
+    values[ix] = gcov_read_counter ();
+}
+
+/* Handler for reading summary tag.  */
+
+static void
+tag_summary (const char *filename ATTRIBUTE_UNUSED,
+	     unsigned tag ATTRIBUTE_UNUSED, unsigned length ATTRIBUTE_UNUSED)
+{
+  struct gcov_summary summary;
+
+  gcov_read_summary (&summary);
+  if (!saved_summary_checksum)
+    saved_summary_checksum = summary.checksum;
+}
+
+/* This function is called at the end of reading a gcda file.
+   It flushes the contents in curr_fn_info to the gcov_info.  */
+
+static void
+read_gcda_finalize (struct gcov_info *obj_info)
+{
+  int i;
+
+  set_fn_ctrs (curr_fn_info);
+  obstack_ptr_grow (&fn_info, curr_fn_info);
+
+  /* We set the following fields: merge, n_functions, and functions.  */
+  obj_info->n_functions = num_fn_info;
+  obj_info->functions = (const struct gcov_fn_info**) obstack_finish (&fn_info);
+
+  /* wrap all the counter array. */
+  for (i=0; i< GCOV_COUNTERS; i++)
+    {
+      if (k_ctrs_mask[i])
+        obj_info->merge[i] = ctr_merge_functions[i];
+    }
+}
+
+/* Read the content of a gcda file, and return a gcov_info data structure.
+   Program level summary CURRENT_SUMMARY will also be updated.  */
+
+static struct gcov_info *
+read_gcda_file (const char *filename)
+{
+  unsigned tags[4];
+  unsigned depth = 0;
+  unsigned magic, version;
+  struct gcov_info *obj_info;
+  int i;
+
+  for (i=0; i< GCOV_COUNTERS; i++)
+    k_ctrs_mask[i] = 0;
+  k_ctrs_types = 0;
+
+  if (!gcov_open (filename))
+    {
+      fprintf (stderr, "%s:cannot open\n", filename);
+      return NULL;
+    }
+
+  /* read magic.  */
+  magic = gcov_read_unsigned ();
+  if (magic != GCOV_DATA_MAGIC)
+    {
+      fprintf (stderr, "%s:not a gcov data file\n", filename);
+      gcov_close ();
+      return NULL;
+    }
+
+  /* read version.  */
+  version = gcov_read_unsigned ();
+  if (version != GCOV_VERSION)
+    {
+      fprintf (stderr, "%s:incorrect gcov version %d vs %d \n", filename, version, GCOV_VERSION);
+      gcov_close ();
+      return NULL;
+    }
+
+  /* Instantiate a gcov_info object.  */
+  curr_gcov_info = obj_info = (struct gcov_info *) xcalloc (sizeof (struct gcov_info) +
+             sizeof (struct gcov_ctr_info) * GCOV_COUNTERS, 1);
+
+  obj_info->version = version;
+  obstack_init (&fn_info);
+  num_fn_info = 0;
+  curr_fn_info = 0;
+  {
+    char *str_dup = (char*) xmalloc (strlen (filename) + 1);
+    int len;
+
+    strcpy (str_dup, filename);
+    obj_info->filename = str_dup;
+    if ((len = strlen (filename)) > max_filename_len)
+      max_filename_len = len;
+  }
+
+  /* read stamp.  */
+  obj_info->stamp = gcov_read_unsigned ();
+
+  while (1)
+    {
+      gcov_position_t base;
+      unsigned tag, length;
+      tag_format_t const *format;
+      unsigned tag_depth;
+      int error;
+      unsigned mask;
+
+      tag = gcov_read_unsigned ();
+      if (!tag)
+	break;
+      length = gcov_read_unsigned ();
+      base = gcov_position ();
+      mask = GCOV_TAG_MASK (tag) >> 1;
+      for (tag_depth = 4; mask; mask >>= 8)
+	{
+	  if (((mask & 0xff) != 0xff))
+	    {
+	      fprintf (stderr, "warning: %s:tag `%08x' is invalid\n", filename, tag);
+	      break;
+	    }
+	  tag_depth--;
+	}
+      for (format = tag_table; format->name; format++)
+	if (format->tag == tag)
+	  goto found;
+      format = &tag_table[GCOV_TAG_IS_COUNTER (tag) ? 2 : 1];
+    found:;
+      if (tag)
+	{
+	  if (depth && depth < tag_depth)
+	    {
+	      if (!GCOV_TAG_IS_SUBTAG (tags[depth - 1], tag))
+		fprintf (stderr, "warning: %s:tag `%08x' is incorrectly nested\n",
+			filename, tag);
+	    }
+	  depth = tag_depth;
+	  tags[depth - 1] = tag;
+	}
+
+      if (format->proc)
+	(*format->proc) (filename, tag, length);
+
+      if (format->proc)
+	{
+	  unsigned long actual_length = gcov_position () - base;
+
+	  if (actual_length > length)
+	    fprintf (stderr,"warning: %s:record size mismatch %lu bytes overread\n",
+		    filename, actual_length - length);
+	  else if (length > actual_length)
+	    fprintf (stderr,"warning: %s:record size mismatch %lu bytes unread\n",
+		    filename, length - actual_length);
+	}
+      gcov_sync (base, length);
+      if ((error = gcov_is_error ()))
+	{
+	  fprintf (stderr,error < 0 ? "warning:%s:counter overflow at %lu\n" :
+		  "Warning:%s:read error at %lu\n", filename,
+		  (long unsigned) gcov_position ());
+	  break;
+	}
+    }
+
+  read_gcda_finalize (obj_info);
+  gcov_close ();
+
+  return obj_info;
+}
+
+#define GCOV_SUFFIX ".gcda"
+
+/* This will be called by ftw. Return a non-zero value
+   to stop the tree walk.  */
+
+static int
+ftw_read_file ( const char *filename,
+                const struct stat *status ATTRIBUTE_UNUSED,
+                int type)
+{
+  int filename_len;
+  int suffix_len;
+  struct gcov_info *obj_info;
+
+  /* Only read regular files.  */
+  if (type != FTW_F)
+    return 0;
+
+  filename_len = strlen (filename);
+  suffix_len = strlen (GCOV_SUFFIX);
+
+  if (filename_len <= suffix_len)
+    return 0;
+
+  if (strcmp(filename + filename_len - suffix_len, GCOV_SUFFIX))
+    return 0;
+
+   if (verbose)
+    fprintf (stderr, "reading file: %s\n", filename);
+
+  obj_info = read_gcda_file (filename);
+
+  obj_info->next = gcov_info_head;
+  gcov_info_head = obj_info;
+
+  return 0;
+}
+
+static inline void
+read_profile_dir_init (void)
+{
+  gcov_info_head = 0;
+}
+
+/* Driver for read a profile directory and convert into gcvo_info list in memory.
+   Return NULL on error,
+   Return the head of gcov_info list on success.
+   Note the file static variable GCOV_MAX_FILENAME is also set.  */
+
+struct gcov_info *
+gcov_read_profile_dir (const char* dir_name, int recompute_summary ATTRIBUTE_UNUSED)
+{
+  char *pwd;
+  int ret;
+
+  read_profile_dir_init ();
+
+  if (access (dir_name, R_OK) != 0)
+    {
+      fprintf (stderr, "cannot access directory %s\n", dir_name);
+      return NULL;
+    }
+  pwd = get_current_dir_name ();
+  gcc_assert (pwd);
+  ret = chdir (dir_name);
+  if (ret !=0)
+    {
+      fprintf (stderr, "%s is not a directory\n", dir_name);
+      return NULL;
+    }
+  ftw (".", ftw_read_file, 50);
+  ret = chdir (pwd);
+  free (pwd);
+
+
+  /* gcov_max_filename is defined in libgcov.c that recored
+     max filename len. We need to set it here to allocate the
+     array for dumping.  */
+  gcov_max_filename = max_filename_len;
+
+  return gcov_info_head;;
+}
+
+/* -------------- Merge Profile Counters ------------------ */
+
+/* Offline tool to manipulate profile data.
+   This tool targets on matched profiles. But it has some tolerance on
+   unmatched profiles.
+   When merging p1 to p2 (p2 is the dst),
+   * m.gcda in p1 but not in p2: append m.gcda to p2 with specified weight; emit warning
+   * m.gcda in p2 but not in p1: keep m.gcda in p2 and multiplying specified weight; emit warning.
+   * m.gcda in both p1 and p2:
+   ** p1->m.gcda->f checksum matches p2->m.gcda->f: simple merge.
+   ** p1->m.gcda->f checksum does not matches p2->m.gcda->f: keep p2->m.gcda->f and
+      drop p1->m.gcda->f. A warning is emitted.  */
+
+/* Add INFO2's counter to INFO1, multiplying weight W.  */
+
+static int
+gcov_merge (struct gcov_info *info1, struct gcov_info *info2, int w)
+{
+  unsigned f_ix;
+  unsigned n_functions = info1->n_functions;
+  int has_mismatch = 0;
+
+  gcc_assert (info2->n_functions == n_functions);
+  for (f_ix = 0; f_ix < n_functions; f_ix++)
+    {
+      unsigned t_ix;
+      const struct gcov_fn_info *gfi_ptr1 = info1->functions[f_ix];
+      const struct gcov_fn_info *gfi_ptr2 = info2->functions[f_ix];
+      const struct gcov_ctr_info *ci_ptr1, *ci_ptr2;
+
+      if (!gfi_ptr1 || gfi_ptr1->key != info1)
+        continue;
+      if (!gfi_ptr2 || gfi_ptr2->key != info2)
+        continue;
+
+      if (gfi_ptr1->cfg_checksum != gfi_ptr2->cfg_checksum)
+        {
+          fprintf (stderr, "in %s, cfg_checksum mismatch, skipping\n",
+                  info1->filename);
+          has_mismatch = 1;
+          continue;
+        }
+      ci_ptr1 = gfi_ptr1->ctrs;
+      ci_ptr2 = gfi_ptr2->ctrs;
+      for (t_ix = 0; t_ix != GCOV_COUNTERS; t_ix++)
+        {
+          gcov_merge_fn merge1 = info1->merge[t_ix];
+          gcov_merge_fn merge2 = info2->merge[t_ix];
+
+          gcc_assert (merge1 == merge2);
+          if (!merge1)
+            continue;
+          gcc_assert (ci_ptr1->num == ci_ptr2->num);
+          (*merge1) (ci_ptr1->values, ci_ptr1->num, ci_ptr2->values, w);
+          ci_ptr1++;
+          ci_ptr2++;
+        }
+    }
+
+  return has_mismatch;
+}
+
+static struct gcov_info *
+find_match_gcov_info (struct gcov_info **array, int size, struct gcov_info *info)
+{
+  struct gcov_info *gi_ptr;
+  struct gcov_info *ret = NULL;
+  int i;
+
+  for (i = 0; i < size; i++)
+    {
+      gi_ptr = array[i];
+      if (gi_ptr == 0)
+        continue;
+      if (!strcmp (gi_ptr->filename, info->filename))
+        {
+          ret = gi_ptr;
+          array[i] = 0;
+          break;
+        }
+    }
+
+  if (ret && ret->n_functions != info->n_functions)
+    {
+      fprintf (stderr, "mismatched profiles in %s (%d functions"
+                       " vs %d functions)\n",
+                       ret->filename,
+                       ret->n_functions,
+                       info->n_functions);
+      ret = NULL;
+    }
+  return ret;
+}
+
+/* return 0 on success: without mismatch.
+   reutrn 1 on error.  */
+
+int
+gcov_profile_merge (struct gcov_info *tgt_profile, struct gcov_info *src_profile,
+                    int w1, int w2)
+{
+  struct gcov_info *gi_ptr;
+  struct gcov_info **tgt_infos;
+  struct gcov_info *tgt_tail;
+  struct gcov_info **in_src_not_tgt;
+  unsigned tgt_cnt = 0, src_cnt = 0;
+  unsigned unmatch_info_cnt = 0;
+  unsigned int i;
+
+  for (gi_ptr = tgt_profile; gi_ptr; gi_ptr = gi_ptr->next)
+    tgt_cnt++;
+  for (gi_ptr = src_profile; gi_ptr; gi_ptr = gi_ptr->next)
+    src_cnt++;
+  tgt_infos = (struct gcov_info **) xmalloc (sizeof (struct gcov_info *) * tgt_cnt);
+  gcc_assert (tgt_infos);
+  in_src_not_tgt = (struct gcov_info **) xmalloc (sizeof (struct gcov_info *) * src_cnt);
+  gcc_assert (in_src_not_tgt);
+
+  for (gi_ptr = tgt_profile, i = 0; gi_ptr; gi_ptr = gi_ptr->next, i++)
+    tgt_infos[i] = gi_ptr;
+
+  tgt_tail = tgt_infos[tgt_cnt - 1];
+
+  /* First pass on tgt_profile, we multiply w1 to all counters.  */
+  if (w1 > 1)
+    {
+       for (i = 0; i < tgt_cnt; i++)
+         gcov_merge (tgt_infos[i], tgt_infos[i], w1-1);
+    }
+
+  /* Second pass, add src_profile to the tgt_profile.  */
+  for (gi_ptr = src_profile; gi_ptr; gi_ptr = gi_ptr->next)
+    {
+      struct gcov_info *gi_ptr1;
+
+      gi_ptr1 = find_match_gcov_info (tgt_infos, tgt_cnt, gi_ptr);
+      if (gi_ptr1 == NULL)
+        {
+          in_src_not_tgt[unmatch_info_cnt++] = gi_ptr;
+          continue;
+        }
+      gcov_merge (gi_ptr1, gi_ptr, w2);
+    }
+
+#if 0
+  /* For modules left in the array. They are not in src_prfile.  */
+  for (i = 0; i < tgt_cnt; i++)
+    {
+      gi_ptr = tgt_infos[i];
+      if (!gi_ptr)
+        continue;
+      gcov_merge (gi_ptr, gi_ptr, w2);
+    }
+
+  /* For modules in src but not in tgt. We adjust the counter and append.  */
+  for (i = 0; i < unmatch_info_cnt; i++)
+    {
+      gi_ptr = in_src_not_tgt[i];
+      gcov_merge (gi_ptr, gi_ptr, w1+w2-1);
+    }
+#else
+  /* For modules in src but not in tgt. We adjust the counter and append.  */
+  for (i = 0; i < unmatch_info_cnt; i++)
+    {
+      gi_ptr = in_src_not_tgt[i];
+      gcov_merge (gi_ptr, gi_ptr, w2 - 1);
+      tgt_tail->next = gi_ptr;
+      tgt_tail = gi_ptr;
+    }
+#endif
+
+  return 0;
+}
+
+/* -------------- Scale Profile Counters ------------------ */
+
+/* Type of function used to normalize counters.  */
+typedef void (*gcov_scale_fn) (gcov_type *, gcov_unsigned_t, double);
+
+static void
+__gcov_scale_add (gcov_type *counters, unsigned n_counters, double f)
+{
+  for (; n_counters; counters++, n_counters--)
+    {
+      gcov_type val = *counters;
+      *counters = val * f;
+    }
+}
+
+/* Scale ior counters.  */
+
+static void
+__gcov_scale_ior (gcov_type *counters ATTRIBUTE_UNUSED,
+                      unsigned n_counters ATTRIBUTE_UNUSED,
+                      double f ATTRIBUTE_UNUSED)
+{
+  /* do nothing.  */
+}
+
+/* Scale delta counters.  */
+
+static void
+__gcov_scale_delta (gcov_type *counters, unsigned n_counters, double f)
+{
+  unsigned i, n_measures;
+
+  gcc_assert (!(n_counters % 4));
+  n_measures = n_counters / 4;
+  for (i = 0; i < n_measures; i++, counters += 4)
+    {
+      counters[2] *= f;
+      counters[3] *= f;
+    }
+}
+
+/* Scale single counters.  */
+
+static void
+__gcov_scale_single (gcov_type *counters, unsigned n_counters, double f)
+{
+  unsigned i, n_measures;
+
+  gcc_assert (!(n_counters % 3));
+  n_measures = n_counters / 3;
+  for (i = 0; i < n_measures; i++, counters += 3)
+    {
+      counters[1] *= f;
+      counters[2] *= f;
+    }
+}
+
+/* Scaling functions for counters.  */
+static gcov_scale_fn ctr_scale_functions[GCOV_COUNTERS] = {
+    __gcov_scale_add,
+    __gcov_scale_add,
+    __gcov_scale_add,
+    __gcov_scale_single,
+    __gcov_scale_delta,
+    __gcov_scale_single,
+    __gcov_scale_add,
+    __gcov_scale_ior,
+};
+
+
+/* Driver for scale profile counters.  */
+
+int
+gcov_profile_scale (struct gcov_info *profile, float scale_factor)
+{
+  struct gcov_info *gi_ptr;
+  unsigned f_ix;
+
+  if (verbose)
+    fprintf (stdout, "scale_factor is %f\n", scale_factor);
+
+  /* scale the counters.  */
+  for (gi_ptr = profile; gi_ptr; gi_ptr = gi_ptr->next)
+    for (f_ix = 0; f_ix < gi_ptr->n_functions; f_ix++)
+      {
+        unsigned t_ix;
+        const struct gcov_fn_info *gfi_ptr = gi_ptr->functions[f_ix];
+        const struct gcov_ctr_info *ci_ptr;
+
+        if (!gfi_ptr || gfi_ptr->key != gi_ptr)
+          continue;
+
+        ci_ptr = gfi_ptr->ctrs;
+        for (t_ix = 0; t_ix != GCOV_COUNTERS; t_ix++)
+          {
+            gcov_merge_fn merge = gi_ptr->merge[t_ix];
+
+            if (!merge)
+              continue;
+            (*ctr_scale_functions[t_ix]) (ci_ptr->values, ci_ptr->num, scale_factor);
+            ci_ptr++;
+          }
+      }
+
+  return 0;
+}
+
+/* Driver for normalize profile counters.  */
+
+int
+gcov_profile_normalize (struct gcov_info *profile, gcov_type max_val)
+{
+  struct gcov_info *gi_ptr;
+  gcov_type curr_max_val = 0;
+  unsigned f_ix;
+  unsigned int i;
+  float scale_factor;
+
+  /* get the larest count value.  */
+  for (gi_ptr = profile; gi_ptr; gi_ptr = gi_ptr->next)
+    for (f_ix = 0; f_ix < gi_ptr->n_functions; f_ix++)
+      {
+        unsigned t_ix;
+        const struct gcov_fn_info *gfi_ptr = gi_ptr->functions[f_ix];
+        const struct gcov_ctr_info *ci_ptr;
+
+        if (!gfi_ptr || gfi_ptr->key != gi_ptr)
+          continue;
+
+        ci_ptr = gfi_ptr->ctrs;
+        for (t_ix = 0; t_ix < 1; t_ix++)
+          {
+            for (i = 0; i < ci_ptr->num; i++)
+              if (ci_ptr->values[i] > curr_max_val)
+                curr_max_val = ci_ptr->values[i];
+            ci_ptr++;
+          }
+      }
+
+  scale_factor = (float)max_val / curr_max_val;
+  if (verbose)
+    fprintf (stdout, "max_val is %lld\n", (long long) curr_max_val);
+
+  return gcov_profile_scale (profile, scale_factor);
+}
Index: gcc/profile-tool.c
===================================================================
--- gcc/profile-tool.c	(revision 0)
+++ gcc/profile-tool.c	(revision 0)
@@ -0,0 +1,404 @@ 
+/* GCC Profile-tool suuport.
+   Contributed by Rong Xu <xur@google.com>.
+   Copyright (C) 2013 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC 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, or (at your option) any later
+version.
+
+GCC 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 GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#define PROFILE_TOOL 1
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "version.h"
+#include "gcov-io.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <ftw.h>
+
+extern int gcov_profile_merge (struct gcov_info*, struct gcov_info*, int, int);
+extern int gcov_profile_normalize (struct gcov_info*, gcov_type);
+extern int gcov_profile_scale (struct gcov_info*, float);
+extern struct gcov_info* gcov_read_profile_dir (const char*, int);
+extern void gcov_exit (void);
+extern void set_gcov_list (struct gcov_info *);
+extern void gcov_tool_set_verbose (void);
+
+
+static int verbose;
+
+static int
+unlink_file (const char *name,
+             const struct stat *status ATTRIBUTE_UNUSED,
+             int type ATTRIBUTE_UNUSED,
+             struct FTW *ftwbuf ATTRIBUTE_UNUSED)
+{
+  int ret = remove (name);
+
+  if (ret)
+    {
+      fnotice (stderr, "error in removing %s\n", name);
+      exit (FATAL_EXIT_CODE);
+    }
+
+  return ret;
+}
+
+static int
+unlink_dir (const char *path)
+{
+    return nftw(path, unlink_file, 64, FTW_DEPTH | FTW_PHYS);
+}
+
+static int
+profile_merge (const char *d1, const char *d2, const char *out, int w1, int w2)
+{
+  char *pwd;
+  int ret;
+  struct gcov_info * d1_profile;
+  struct gcov_info * d2_profile;
+
+
+  d1_profile = gcov_read_profile_dir (d1, 0);
+  if (!d1_profile)
+    return 1;
+
+  if (d2)
+    {
+      d2_profile = gcov_read_profile_dir (d2, 0);
+      if (!d2_profile)
+        return 1;
+
+      /* the actual merge: we overwrite to d1_profile.  */
+      ret = gcov_profile_merge (d1_profile, d2_profile, w1, w2);
+
+      if (ret)
+        return ret;
+    }
+
+  /* output */
+  unlink_dir (out);
+  mkdir (out, 0755);
+  pwd = get_current_dir_name ();
+  gcc_assert (pwd);
+  ret = chdir (out);
+  gcc_assert (ret == 0);
+
+  set_gcov_list (d1_profile);
+  gcov_exit ();
+
+  ret = chdir (pwd);
+  free (pwd);
+  return 0;
+}
+
+static void
+print_merge_usage_message (int error_p)
+{
+  FILE *file = error_p ? stderr : stdout;
+
+  fnotice (file, "  merge [options] <dir1> <dir2>              Merge coverage file contents\n");
+  fnotice (file, "    -v, --verbose                            Verbose mode\n");
+  fnotice (file, "    -o <dir>, --output <dir>                 Output directory\n");
+  fnotice (file, "    -w <w1,w2>, --weight <w1,w2>             Set weights (float point values)\n");
+}
+
+static const struct option merge_options[] =
+{
+  { "verbose",                no_argument,       NULL, 'v' },
+  { "output",                 required_argument, NULL, 'o' },
+  { "weight",                 required_argument, NULL, 'w' },
+  { 0, 0, 0, 0 }
+};
+
+static void
+merge_usage (void)
+{
+  fnotice (stderr, "Merge subcomand usage:");
+  print_merge_usage_message (true);
+  exit (FATAL_EXIT_CODE);
+}
+
+static int
+do_merge (int argc, char **argv)
+{
+  int opt;
+  int ret;
+  const char *output_dir = 0;
+  int w1 = 1, w2 = 1;
+
+  while ((opt = getopt_long (argc, argv, "vo:w:", merge_options, NULL)) != -1)
+    {
+      switch (opt)
+        {
+        case 'v':
+          verbose = 1;
+          gcov_tool_set_verbose ();
+          break;
+        case 'o':
+          output_dir = optarg;
+          break;
+        case 'w':
+          sscanf (optarg, "%d,%d", &w1, &w2);
+          if (w1 < 0 || w2 < 0)
+            {
+              fnotice (stderr, "weights need to be non-negative\n");
+              exit (FATAL_EXIT_CODE);
+            }
+          break;
+        default:
+          merge_usage ();
+        }
+    }
+
+  if (output_dir == NULL)
+    output_dir = "merged_profile";
+
+  if (argc - optind == 2)
+    ret = profile_merge (argv[optind], argv[optind+1], output_dir, w1, w2);
+  else
+    merge_usage ();
+
+  return ret;
+}
+
+static int
+profile_rewrite (const char *d1, const char *out, long long n_val, float scale)
+{
+  char *pwd;
+  int ret;
+  struct gcov_info * d1_profile;
+
+
+  d1_profile = gcov_read_profile_dir (d1, 0);
+  if (!d1_profile)
+    return 1;
+
+  /* output */
+  unlink_dir (out);
+  mkdir (out, 0755);
+  pwd = get_current_dir_name ();
+  gcc_assert (pwd);
+  ret = chdir (out);
+  gcc_assert (ret == 0);
+
+  if (n_val)
+    gcov_profile_normalize (d1_profile, (gcov_type) n_val);
+  else
+    gcov_profile_scale (d1_profile, scale);
+
+  set_gcov_list (d1_profile);
+  gcov_exit ();
+
+  ret = chdir (pwd);
+  free (pwd);
+  return 0;
+}
+
+static void
+print_rewrite_usage_message (int error_p)
+{
+  FILE *file = error_p ? stderr : stdout;
+
+  fnotice (file, "  rewrite [options] <dir>                    Rewrite coverage file contents\n");
+  fnotice (file, "    -v, --verbose                            Verbose mode\n");
+  fnotice (file, "    -o <dir>, --output <dir>                 Output directory\n");
+  fnotice (file, "    -s <float>, --scale <float>              Scale the profile counters\n");
+  fnotice (file, "    -n <long long>, --normalize <long long>  Normalize the profile\n");
+}
+
+static const struct option rewrite_options[] =
+{
+  { "verbose",                no_argument,       NULL, 'v' },
+  { "output",                 required_argument, NULL, 'o' },
+  { "scale",                  required_argument, NULL, 's' },
+  { "normalize",              required_argument, NULL, 'n' },
+  { 0, 0, 0, 0 }
+};
+
+static void
+rewrite_usage (void)
+{
+  fnotice (stderr, "Rewrite subcommand usage:");
+  print_rewrite_usage_message (true);
+  exit (FATAL_EXIT_CODE);
+}
+
+static int
+do_rewrite (int argc, char **argv)
+{
+  int opt;
+  int ret;
+  const char *output_dir = 0;
+  long long normalize_val = 0;
+  float scale = 1.0;
+
+  while ((opt = getopt_long (argc, argv, "vo:s:n:", rewrite_options, NULL)) != -1)
+    {
+      switch (opt)
+        {
+        case 'v':
+          verbose = 1;
+          gcov_tool_set_verbose ();
+          break;
+        case 'o':
+          output_dir = optarg;
+          break;
+        case 'n':
+          if (scale != 1.0)
+            {
+              fnotice (stderr, "scaling cannot co-exist with normalization\n");
+              scale = 1.0;
+            }
+          normalize_val = atoll (optarg);
+          break;
+        case 's':
+          sscanf (optarg, "%f", &scale);
+          if (scale < 0.0)
+            {
+              fnotice (stderr, "scale needs to be non-negative\n");
+              exit (FATAL_EXIT_CODE);
+            }
+          if (normalize_val != 0)
+            {
+              fnotice (stderr, "normalization cannot co-exist with scaling\n");
+              normalize_val = 0;
+            }
+          break;
+        default:
+          rewrite_usage ();
+        }
+    }
+
+  if (output_dir == NULL)
+    output_dir = "rewrite_profile";
+
+  if (argc - optind == 1)
+    ret = profile_rewrite (argv[optind],  output_dir, normalize_val, scale);
+  else
+    rewrite_usage ();
+
+  return ret;
+}
+
+/* Print a usage message and exit.  If ERROR_P is nonzero, this is an error,
+   otherwise the output of --help.  */
+
+static void
+print_usage (int error_p)
+{
+  FILE *file = error_p ? stderr : stdout;
+  int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
+
+  fnotice (file, "Usage: %s [OPTION]... SUB_COMMAND [OPTION]...\n\n", progname);
+  fnotice (file, "Offline tool to handle gcda counts.\n\n");
+  fnotice (file, "  -h, --help                                 Print this help, then exit\n");
+  fnotice (file, "  -v, --version                              Print version number, then exit\n");
+  print_merge_usage_message (error_p);
+  print_rewrite_usage_message (error_p);
+  fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
+           bug_report_url);
+  exit (status);
+}
+
+/* Print version information and exit.  */
+
+static void
+print_version (void)
+{
+  fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
+  fprintf (stdout, "Copyright %s 2013 Free Software Foundation, Inc.\n",
+           _("(C)"));
+  fnotice (stdout,
+           _("This is free software; see the source for copying conditions.\n"
+             "There is NO warranty; not even for MERCHANTABILITY or \n"
+             "FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
+  exit (SUCCESS_EXIT_CODE);
+}
+
+static const struct option options[] =
+{
+  { "help",                 no_argument,       NULL, 'h' },
+  { "version",              no_argument,       NULL, 'v' },
+  { 0, 0, 0, 0 }
+};
+
+/* Process args, return index to first non-arg.  */
+
+static int
+process_args (int argc, char **argv)
+{
+  int opt;
+
+  while ((opt = getopt_long (argc, argv, "hv", options, NULL)) != -1)  
+    {    
+      switch (opt)
+        {    
+        case 'h': 
+          print_usage (false);
+          /* print_usage will exit.  */
+        case 'v':
+          print_version ();
+          /* print_version will exit.  */
+        default:
+          print_usage (true);
+          /* print_usage will exit.  */
+        }
+    }
+
+  return optind;
+}
+
+int
+main (int argc, char **argv)
+{
+  const char *p;
+  const char *sub_command;
+
+  p = argv[0] + strlen (argv[0]);
+  while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
+    --p;
+  progname = p;
+
+  xmalloc_set_program_name (progname);
+
+  /* Unlock the stdio streams.  */
+  unlock_std_streams ();
+
+  gcc_init_libintl ();
+
+  diagnostic_initialize (global_dc, 0);
+
+  /* Handle response files.  */
+  expandargv (&argc, &argv);
+
+  process_args (argc, argv);
+  if (optind < argc)
+    print_usage (true);
+
+  sub_command = argv[1];
+
+  if (!strcmp (sub_command, "merge"))
+    return do_merge (argc - 1, argv + 1);
+  else if (!strcmp (sub_command, "rewrite"))
+    return do_rewrite (argc - 1, argv + 1);
+
+  print_usage (true);
+}
+
Index: gcc/gcov-io.h
===================================================================
--- gcc/gcov-io.h	(revision 204285)
+++ gcc/gcov-io.h	(working copy)
@@ -164,7 +164,7 @@  see the files COPYING3 and COPYING.RUNTIME respect
 #ifndef GCC_GCOV_IO_H
 #define GCC_GCOV_IO_H
 
-#if IN_LIBGCOV
+#if (IN_LIBGCOV && !PROFILE_TOOL)
 /* About the target */
 
 #if BITS_PER_UNIT == 8
@@ -214,7 +214,7 @@  typedef unsigned gcov_type_unsigned __attribute__
 typedef unsigned gcov_unsigned_t;
 typedef unsigned gcov_position_t;
 /* gcov_type is typedef'd elsewhere for the compiler */
-#if IN_GCOV
+#if IN_GCOV || PROfILE_TOOL
 #define GCOV_LINKAGE static
 typedef HOST_WIDEST_INT gcov_type;
 typedef unsigned HOST_WIDEST_INT gcov_type_unsigned;
@@ -259,7 +259,9 @@  typedef unsigned HOST_WIDEST_INT gcov_type_unsigne
 
 /* Poison these, so they don't accidentally slip in.  */
 #pragma GCC poison gcov_write_string gcov_write_tag gcov_write_length
+#ifndef PROFILE_TOOL
 #pragma GCC poison gcov_read_string gcov_sync gcov_time gcov_magic
+#endif
 
 #ifdef HAVE_GAS_HIDDEN
 #define ATTRIBUTE_HIDDEN  __attribute__ ((__visibility__ ("hidden")))
@@ -467,7 +469,8 @@  struct gcov_fn_info
 };
 
 /* Type of function used to merge counters.  */
-typedef void (*gcov_merge_fn) (gcov_type *, gcov_unsigned_t);
+typedef void (*gcov_merge_fn) (gcov_type *, gcov_unsigned_t,
+                               gcov_type *, unsigned);
 
 /* Information about a single object file.  */
 struct gcov_info
@@ -482,8 +485,12 @@  struct gcov_info
 					  unused) */
   
   unsigned n_functions;		/* number of functions */
+#ifndef PROFILE_TOOL
   const struct gcov_fn_info *const *functions; /* pointer to pointers
 					          to function information  */
+#else
+  const struct gcov_fn_info **functions;
+#endif
 };
 
 /* Register a new object file module.  */
@@ -499,22 +506,28 @@  extern void __gcov_reset (void);
 extern void __gcov_dump (void);
 
 /* The merge function that just sums the counters.  */
-extern void __gcov_merge_add (gcov_type *, unsigned) ATTRIBUTE_HIDDEN;
+extern void __gcov_merge_add (gcov_type *, unsigned, gcov_type *,
+                              unsigned) ATTRIBUTE_HIDDEN;
 
 /* The merge function to choose the most common value.  */
-extern void __gcov_merge_single (gcov_type *, unsigned) ATTRIBUTE_HIDDEN;
+extern void __gcov_merge_single (gcov_type *, unsigned, gcov_type *,
+                              unsigned) ATTRIBUTE_HIDDEN;
 
 /* The merge function to choose the most common difference between
    consecutive values.  */
-extern void __gcov_merge_delta (gcov_type *, unsigned) ATTRIBUTE_HIDDEN;
+extern void __gcov_merge_delta (gcov_type *, unsigned, gcov_type *,
+                              unsigned) ATTRIBUTE_HIDDEN;
 
 /* The merge function that just ors the counters together.  */
-extern void __gcov_merge_ior (gcov_type *, unsigned) ATTRIBUTE_HIDDEN;
+extern void __gcov_merge_ior (gcov_type *, unsigned, gcov_type *,
+                              unsigned) ATTRIBUTE_HIDDEN;
 
 /* The profiler functions.  */
 extern void __gcov_interval_profiler (gcov_type *, gcov_type, int, unsigned);
 extern void __gcov_pow2_profiler (gcov_type *, gcov_type);
 extern void __gcov_one_value_profiler (gcov_type *, gcov_type);
+extern void __gcov_indirect_call_profiler (gcov_type*, gcov_type,
+                                           void*, void*);
 extern void __gcov_indirect_call_profiler_v2 (gcov_type, void *);
 extern void __gcov_average_profiler (gcov_type *, gcov_type);
 extern void __gcov_ior_profiler (gcov_type *, gcov_type);
@@ -538,7 +551,7 @@  extern int __gcov_execve (const char *, char  *con
 /* Optimum number of gcov_unsigned_t's read from or written to disk.  */
 #define GCOV_BLOCK_SIZE (1 << 10)
 
-GCOV_LINKAGE struct gcov_var
+struct gcov_var
 {
   FILE *file;
   gcov_position_t start;	/* Position of first byte of block */
@@ -560,7 +573,8 @@  extern int __gcov_execve (const char *, char  *con
   size_t alloc;
   gcov_unsigned_t *buffer;
 #endif
-} gcov_var ATTRIBUTE_HIDDEN;
+};
+extern struct gcov_var gcov_var ATTRIBUTE_HIDDEN;
 
 /* Functions for reading and writing gcov files. In libgcov you can
    open the file for reading then writing. Elsewhere you can open the
@@ -587,7 +601,7 @@  GCOV_LINKAGE gcov_unsigned_t gcov_read_unsigned (v
 GCOV_LINKAGE gcov_type gcov_read_counter (void) ATTRIBUTE_HIDDEN;
 GCOV_LINKAGE void gcov_read_summary (struct gcov_summary *) ATTRIBUTE_HIDDEN;
 
-#if IN_LIBGCOV
+#if (IN_LIBGCOV)
 /* Available only in libgcov */
 GCOV_LINKAGE void gcov_write_counter (gcov_type) ATTRIBUTE_HIDDEN;
 GCOV_LINKAGE void gcov_write_tag_length (gcov_unsigned_t, gcov_unsigned_t)
@@ -597,7 +611,9 @@  GCOV_LINKAGE void gcov_write_summary (gcov_unsigne
     ATTRIBUTE_HIDDEN;
 static void gcov_rewrite (void);
 GCOV_LINKAGE void gcov_seek (gcov_position_t /*position*/) ATTRIBUTE_HIDDEN;
-#else
+#endif
+
+#if (!IN_LIBGCOV || PROFILE_TOOL)
 /* Available outside libgcov */
 GCOV_LINKAGE const char *gcov_read_string (void);
 GCOV_LINKAGE void gcov_sync (gcov_position_t /*base*/,
Index: gcc/gcov-io.c
===================================================================
--- gcc/gcov-io.c	(revision 204285)
+++ gcc/gcov-io.c	(working copy)
@@ -27,7 +27,7 @@  see the files COPYING3 and COPYING.RUNTIME respect
 /* Routines declared in gcov-io.h.  This file should be #included by
    another source file, after having #included gcov-io.h.  */
 
-#if !IN_GCOV
+#if (!IN_GCOV)
 static void gcov_write_block (unsigned);
 static gcov_unsigned_t *gcov_write_words (unsigned);
 #endif
@@ -36,6 +36,10 @@  static const gcov_unsigned_t *gcov_read_words (uns
 static void gcov_allocate (unsigned);
 #endif
 
+#if IN_LIBGCOV >= 0
+/*GCOV_LINKAGE*/ struct gcov_var gcov_var ATTRIBUTE_HIDDEN;
+#endif
+
 static inline gcov_unsigned_t from_file (gcov_unsigned_t value)
 {
 #if !IN_LIBGCOV
@@ -236,7 +240,9 @@  gcov_write_words (unsigned words)
 {
   gcov_unsigned_t *result;
 
+#ifndef PROFILE_TOOL
   gcc_assert (gcov_var.mode < 0);
+#endif
 #if IN_LIBGCOV
   if (gcov_var.offset >= GCOV_BLOCK_SIZE)
     {
@@ -503,7 +509,7 @@  gcov_read_counter (void)
    buffer, or NULL on empty string. You must copy the string before
    calling another gcov function.  */
 
-#if !IN_LIBGCOV
+#if !IN_LIBGCOV || PROFILE_TOOL
 GCOV_LINKAGE const char *
 gcov_read_string (void)
 {
@@ -580,7 +586,7 @@  gcov_read_summary (struct gcov_summary *summary)
     }
 }
 
-#if !IN_LIBGCOV
+#if !IN_LIBGCOV || PROFILE_TOOL
 /* Reset to a known position.  BASE should have been obtained from
    gcov_position, LENGTH should be a record length.  */
 
@@ -606,7 +612,9 @@  gcov_sync (gcov_position_t base, gcov_unsigned_t l
 GCOV_LINKAGE void
 gcov_seek (gcov_position_t base)
 {
+#ifndef PROFILE_TOOL
   gcc_assert (gcov_var.mode < 0);
+#endif
   if (gcov_var.offset)
     gcov_write_block (gcov_var.offset);
   fseek (gcov_var.file, base << 2, SEEK_SET);
Index: gcc/Makefile.in
===================================================================
--- gcc/Makefile.in	(revision 204285)
+++ gcc/Makefile.in	(working copy)
@@ -123,7 +123,8 @@  SUBDIRS =@subdirs@ build
 
 # Selection of languages to be made.
 CONFIG_LANGUAGES = @all_selected_languages@
-LANGUAGES = c gcov$(exeext) gcov-dump$(exeext) $(CONFIG_LANGUAGES)
+LANGUAGES = c gcov$(exeext) gcov-dump$(exeext) profile-tool$(exeext) \
+	    $(CONFIG_LANGUAGES)
 
 # Default values for variables overridden in Makefile fragments.
 # CFLAGS is for the user to override to, e.g., do a cross build with -O2.
@@ -196,6 +197,7 @@  GCC_WARN_CXXFLAGS = $(LOOSE_WARN) $($(@D)-warn) $(
 # flex output may yield harmless "no previous prototype" warnings
 build/gengtype-lex.o-warn = -Wno-error
 gengtype-lex.o-warn = -Wno-error
+libgcov-tool.o-warn = -Wno-error
 
 # All warnings have to be shut off in stage1 if the compiler used then
 # isn't gcc; configure determines that.  WARN_CFLAGS will be either
@@ -1480,7 +1482,7 @@  ALL_HOST_FRONTEND_OBJS = $(foreach v,$(CONFIG_LANG
 ALL_HOST_BACKEND_OBJS = $(GCC_OBJS) $(OBJS) $(OBJS-libcommon) \
   $(OBJS-libcommon-target) @TREEBROWSER@ main.o c-family/cppspec.o \
   $(COLLECT2_OBJS) $(EXTRA_GCC_OBJS) $(GCOV_OBJS) $(GCOV_DUMP_OBJS) \
-  lto-wrapper.o
+  $(PROFILE_TOOL_OBJS) libgcov-tool.o lto-wrapper.o
 
 # This lists all host object files, whether they are included in this
 # compilation or not.
@@ -1505,6 +1507,7 @@  MOSTLYCLEANFILES = insn-flags.h insn-config.h insn
  $(SPECS) collect2$(exeext) gcc-ar$(exeext) gcc-nm$(exeext) \
  gcc-ranlib$(exeext) \
  gcov-iov$(build_exeext) gcov$(exeext) gcov-dump$(exeext) \
+ profile-tool$(exeect) \
  gengtype$(exeext) *.[0-9][0-9].* *.[si] *-checksum.c libbackend.a \
  libcommon-target.a libcommon.a libgcc.mk
 
@@ -2560,6 +2563,16 @@  GCOV_DUMP_OBJS = gcov-dump.o
 gcov-dump$(exeext): $(GCOV_DUMP_OBJS) $(LIBDEPS)
 	+$(LINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) $(GCOV_DUMP_OBJS) \
 		$(LIBS) -o $@
+libgcov-tool.o: $(srcdir)/../libgcc/libgcov-tool.c gcov-io.c $(GCOV_IO_H) \
+  $(srcdir)/../libgcc/libgcov-driver.c $(srcdir)/../libgcc/libgcov-driver-system.c \
+  $(srcdir)/../libgcc/libgcov-merge.c \
+  $(SYSTEM_H) coretypes.h $(TM_H) $(CONFIG_H) version.h intl.h $(DIAGNOSTIC_H)
+	+$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) -o $@ $<
+
+PROFILE_TOOL_OBJS = profile-tool.o libgcov-tool.o
+profile-tool$(exeext): $(PROFILE_TOOL_OBJS) $(LIBDEPS)
+	+$(LINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) $(PROFILE_TOOL_OBJS) \
+	  $(LIBS) -o $@
 #
 # Build the include directories.  The stamp files are stmp-* rather than
 # s-* so that mostlyclean does not force the include directory to
@@ -3183,6 +3196,13 @@  install-common: native lang.install-common install
 	    rm -f $(DESTDIR)$(bindir)/$(GCOV_INSTALL_NAME)$(exeext); \
 	    $(INSTALL_PROGRAM) gcov$(exeext) $(DESTDIR)$(bindir)/$(GCOV_INSTALL_NAME)$(exeext); \
 	fi
+# Install profile-tool if it was compiled.
+	-if [ -f profile-tool$(exeext) ]; \
+	then \
+	    rm -f $(DESTDIR)$(bindir)/$(PROFILE_TOOL_INSTALL_NAME)$(exeext); \
+	    $(INSTALL_PROGRAM) \
+	    profile-tool$(exeext) $(DESTDIR)$(bindir)/$(PROFILE_TOOL_INSTALL_NAME)$(exeext); \
+	fi
 
 # Install the driver program as $(target_noncanonical)-gcc,
 # $(target_noncanonical)-gcc-$(version), and also as gcc if native.