[coreboot] New patch to review for coreboot: 07248ee Implement GCC code coverage analysis

Stefan Reinauer (stefan.reinauer@coreboot.org) gerrit at coreboot.org
Wed Dec 19 01:24:09 CET 2012


Stefan Reinauer (stefan.reinauer at coreboot.org) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/2052

-gerrit

commit 07248eefd01d66fe006cb16ab778af4c4e837217
Author: Stefan Reinauer <reinauer at chromium.org>
Date:   Tue Dec 18 16:23:28 2012 -0800

    Implement GCC code coverage analysis
    
    Change-Id: Ib287d8309878a1f5c4be770c38b1bc0bb3aa6ec7
    Signed-off-by: Stefan Reinauer <reinauer at google.com>
---
 Makefile.inc                 |    3 +
 src/Kconfig                  |    9 +
 src/arch/x86/Makefile.inc    |    2 +-
 src/arch/x86/boot/acpi.c     |    4 +
 src/arch/x86/coreboot_ram.ld |    3 +
 src/include/cbmem.h          |    1 +
 src/include/coverage.h       |   21 +
 src/lib/Makefile.inc         |    1 +
 src/lib/gcov-glue.c          |  135 +++++
 src/lib/gcov-io.c            |  556 ++++++++++++++++++++
 src/lib/gcov-io.h            |  639 +++++++++++++++++++++++
 src/lib/gcov-iov.h           |    4 +
 src/lib/hardwaremain.c       |    5 +
 src/lib/libgcov.c            | 1158 ++++++++++++++++++++++++++++++++++++++++++
 src/lib/selfboot.c           |    4 +
 15 files changed, 2544 insertions(+), 1 deletion(-)

diff --git a/Makefile.inc b/Makefile.inc
index b0a5341..c40ab6c 100644
--- a/Makefile.inc
+++ b/Makefile.inc
@@ -99,6 +99,9 @@ romstage-S-ccopts:=-D__PRE_RAM__
 ifeq ($(CONFIG_TRACE),y)
 ramstage-c-ccopts:= -finstrument-functions
 endif
+ifeq ($(CONFIG_COVERAGE),y)
+ramstage-c-ccopts+=-fprofile-arcs -ftest-coverage
+endif
 
 ifeq ($(CONFIG_USE_BLOBS),y)
 forgetthis:=$(shell git submodule update --init --checkout 3rdparty)
diff --git a/src/Kconfig b/src/Kconfig
index 0a94ed8..b7bafbb 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -194,6 +194,15 @@ config REQUIRES_BLOB
 	  coreboot build for such a board can override this manually, but
 	  this option serves as warning that it might fail.
 
+config COVERAGE
+	bool "Code coverage support"
+	depends on COMPILER_GCC
+	default n
+	help
+	  Add code coverage support for coreboot. This will store code
+	  coverage information in CBMEM for extraction from user space.
+	  If unsure, say N.
+
 endmenu
 
 source src/mainboard/Kconfig
diff --git a/src/arch/x86/Makefile.inc b/src/arch/x86/Makefile.inc
index 4afa7d5..bc5a0be 100644
--- a/src/arch/x86/Makefile.inc
+++ b/src/arch/x86/Makefile.inc
@@ -166,7 +166,7 @@ $(objgenerated)/coreboot_ram.o: $$(ramstage-objs) $(LIBGCC_FILE_NAME)
 ifeq ($(CONFIG_COMPILER_LLVM_CLANG),y)
 	$(LD) -m elf_i386 -r -o $@ --wrap __divdi3 --wrap __udivdi3 --wrap __moddi3 --wrap __umoddi3 --start-group $(ramstage-objs) $(LIBGCC_FILE_NAME) --end-group
 else
-	$(CC) -nostdlib -r -o $@ -Wl,--wrap,__divdi3 -Wl,--wrap,__udivdi3 -Wl,--wrap,__moddi3 -Wl,--wrap,__umoddi3 -Wl,--start-group $(ramstage-objs) $(LIBGCC_FILE_NAME) -Wl,--end-group
+	$(CC) $(CFLAGS) -nostdlib -r -o $@ -Wl,--wrap,__divdi3 -Wl,--wrap,__udivdi3 -Wl,--wrap,__moddi3 -Wl,--wrap,__umoddi3 -Wl,--start-group $(ramstage-objs) $(LIBGCC_FILE_NAME) -Wl,--end-group
 endif
 
 ################################################################################
diff --git a/src/arch/x86/boot/acpi.c b/src/arch/x86/boot/acpi.c
index 1d7dbf8..730e53e 100644
--- a/src/arch/x86/boot/acpi.c
+++ b/src/arch/x86/boot/acpi.c
@@ -36,6 +36,7 @@
 #if CONFIG_COLLECT_TIMESTAMPS
 #include <timestamp.h>
 #endif
+#include <coverage.h>
 
 /* FIXME: Kconfig doesn't support overridable defaults :-( */
 #ifndef CONFIG_HPET_MIN_TICKS
@@ -642,6 +643,9 @@ void suspend_resume(void)
 		/* Call mainboard resume handler first, if defined. */
 		if (mainboard_suspend_resume)
 			mainboard_suspend_resume();
+#if CONFIG_COVERAGE
+		coverage_exit();
+#endif
 		post_code(POST_OS_RESUME);
 		acpi_jump_to_wakeup(wake_vec);
 	}
diff --git a/src/arch/x86/coreboot_ram.ld b/src/arch/x86/coreboot_ram.ld
index a87a0e7..289d6f7 100644
--- a/src/arch/x86/coreboot_ram.ld
+++ b/src/arch/x86/coreboot_ram.ld
@@ -35,6 +35,9 @@ SECTIONS
 		*(.textfirst);
 		*(.text);
 		*(.text.*);
+		__CTOR_LIST__ = .;
+		*(.ctors)
+		__CTOR_END__ = .;
 		. = ALIGN(16);
 		_etext = .;
 	}
diff --git a/src/include/cbmem.h b/src/include/cbmem.h
index 21efe77..08e913a 100644
--- a/src/include/cbmem.h
+++ b/src/include/cbmem.h
@@ -55,6 +55,7 @@
 #define CBMEM_ID_MRCDATA	0x4d524344
 #define CBMEM_ID_CONSOLE	0x434f4e53
 #define CBMEM_ID_ELOG		0x454c4f47
+#define CBMEM_ID_COVERAGE	0x47434f56
 #define CBMEM_ID_NONE		0x00000000
 
 #ifndef __ASSEMBLER__
diff --git a/src/include/coverage.h b/src/include/coverage.h
new file mode 100644
index 0000000..e1c50c5
--- /dev/null
+++ b/src/include/coverage.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
+ */
+
+void coverage_init(void);
+void coverage_exit(void);
diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc
index 6b3f0d8..6796448 100644
--- a/src/lib/Makefile.inc
+++ b/src/lib/Makefile.inc
@@ -70,6 +70,7 @@ ramstage-$(CONFIG_USBDEBUG) += usbdebug.c
 ramstage-$(CONFIG_BOOTSPLASH) += jpeg.c
 ramstage-$(CONFIG_TRACE) += trace.c
 ramstage-$(CONFIG_COLLECT_TIMESTAMPS) += timestamp.c
+ramstage-$(CONFIG_COVERAGE) += libgcov.c
 
 ramstage-$(CONFIG_CONSOLE_NE2K) += ne2k.c
 
diff --git a/src/lib/gcov-glue.c b/src/lib/gcov-glue.c
new file mode 100644
index 0000000..b1f431d
--- /dev/null
+++ b/src/lib/gcov-glue.c
@@ -0,0 +1,135 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2012 Google, Inc.  All rights reserved.
+ *
+ * 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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
+ */
+
+#include <stdint.h>
+#include <cbmem.h>
+#include <coverage.h>
+
+typedef struct file {
+	uint32_t magic;
+	struct file *next;
+	char *filename;
+	char *data;
+	int offset;
+	int len;
+} FILE;
+
+#define SEEK_SET       0       /* Seek from beginning of file.  */
+
+#define DIR_SEPARATOR '/'
+#define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
+#define HAS_DRIVE_SPEC(f) (0)
+
+#define COVERAGE_SIZE (32*1024)
+
+static FILE *current_file = NULL;
+static FILE *previous_file = NULL;
+
+static FILE *fopen(const char *path, const char *mode)
+{
+	printk(BIOS_DEBUG, "Opening file %s with mode %s\n",
+		path, mode);
+	if (!current_file) {
+		current_file = cbmem_add(CBMEM_ID_COVERAGE, 32*1024);
+	} else {
+		previous_file = current_file;
+		current_file = (FILE *)(ALIGN(((unsigned long)previous_file->data + previous_file->len), 16));
+	}
+
+	// TODO check if we're at the end of the CBMEM region
+	if (current_file) {
+		current_file->magic = 0x584d4153;
+		current_file->next = NULL;
+		if (previous_file)
+			previous_file->next = current_file;
+		current_file->filename = (char *)&current_file[1];
+		strcpy(current_file->filename, path);
+		current_file->data = (char *)ALIGN(((unsigned long)current_file->filename + strlen(path) + 1), 16);
+		current_file->offset = 0;
+		current_file->len = 0;
+	}
+
+	return current_file;
+}
+
+static int fclose(FILE *fp)
+{
+	return 0;
+}
+
+static int fseek(FILE *stream, long offset, int whence)
+{
+	/* fseek should only be called with offset==0 and whence==0
+	 * to a freshly opened file. */
+	gcc_assert (offset == 0 && whence == 0);
+	return 0;
+}
+
+static long ftell(FILE *stream)
+{
+	/* ftell should currently not be called */
+	gcc_assert(0);
+	return 0;
+}
+
+static size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+	/* fread should currently not be called */
+	gcc_assert(0);
+	return 0;
+}
+
+static size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+	printk(BIOS_DEBUG, "Writing %zd * 0x%zx bytes to file %s\n",
+		nmemb, size, stream->filename);
+	// TODO check if file is last opened file and fail otherwise.
+	
+	memcpy(stream->data + stream->offset, ptr, size * nmemb);
+	stream->len += (nmemb * size) - (stream->len - stream->offset);
+	stream->offset += nmemb * size;
+	return 0;
+}
+
+static void setbuf(FILE *stream, char *buf)
+{
+	gcc_assert(buf == 0);
+}
+
+void coverage_init(void)
+{
+	extern long __CTOR_LIST__;
+	typedef void (*func_ptr)(void) ;
+	func_ptr *ctor = (func_ptr*) &__CTOR_LIST__;
+	if (ctor == NULL)
+		return;
+
+	for ( ; *ctor != (func_ptr) 0; ctor++) {
+		(*ctor)();
+	}
+}
+
+void __gcov_flush(void);
+void coverage_exit(void)
+{
+	printk(BIOS_DEBUG, "Syncing coverage data.\n");
+	__gcov_flush();
+}
+
+
diff --git a/src/lib/gcov-io.c b/src/lib/gcov-io.c
new file mode 100644
index 0000000..d2fee03
--- /dev/null
+++ b/src/lib/gcov-io.c
@@ -0,0 +1,556 @@
+/* File format for coverage information
+   Copyright (C) 1996, 1997, 1998, 2000, 2002, 2003, 2004, 2005, 2007,
+   2008  Free Software Foundation, Inc.
+   Contributed by Bob Manson <manson at cygnus.com>.
+   Completely remangled by Nathan Sidwell <nathan at codesourcery.com>.
+
+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.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+<http://www.gnu.org/licenses/>.  */
+
+/* Routines declared in gcov-io.h.  This file should be #included by
+   another source file, after having #included gcov-io.h.  */
+
+#ifdef __COREBOOT__
+
+#endif
+
+#if !IN_GCOV
+static void gcov_write_block (unsigned);
+static gcov_unsigned_t *gcov_write_words (unsigned);
+#endif
+static const gcov_unsigned_t *gcov_read_words (unsigned);
+#if !IN_LIBGCOV
+static void gcov_allocate (unsigned);
+#endif
+
+static inline gcov_unsigned_t from_file (gcov_unsigned_t value)
+{
+#if !IN_LIBGCOV
+  if (gcov_var.endian)
+    {
+      value = (value >> 16) | (value << 16);
+      value = ((value & 0xff00ff) << 8) | ((value >> 8) & 0xff00ff);
+    }
+#endif
+  return value;
+}
+
+/* Open a gcov file. NAME is the name of the file to open and MODE
+   indicates whether a new file should be created, or an existing file
+   opened. If MODE is >= 0 an existing file will be opened, if
+   possible, and if MODE is <= 0, a new file will be created. Use
+   MODE=0 to attempt to reopen an existing file and then fall back on
+   creating a new one.  If MODE < 0, the file will be opened in
+   read-only mode.  Otherwise it will be opened for modification.
+   Return zero on failure, >0 on opening an existing file and <0 on
+   creating a new one.  */
+
+GCOV_LINKAGE int
+#if IN_LIBGCOV
+gcov_open (const char *name)
+#else
+gcov_open (const char *name, int mode)
+#endif
+{
+#if IN_LIBGCOV
+  const int mode = 0;
+#endif
+#if GCOV_LOCKED
+  struct flock s_flock;
+  int fd;
+
+  s_flock.l_whence = SEEK_SET;
+  s_flock.l_start = 0;
+  s_flock.l_len = 0; /* Until EOF.  */
+  s_flock.l_pid = getpid ();
+#endif
+
+  gcc_assert (!gcov_var.file);
+  gcov_var.start = 0;
+  gcov_var.offset = gcov_var.length = 0;
+  gcov_var.overread = -1u;
+  gcov_var.error = 0;
+#if !IN_LIBGCOV
+  gcov_var.endian = 0;
+#endif
+#if GCOV_LOCKED
+  if (mode > 0)
+    {
+      /* Read-only mode - acquire a read-lock.  */
+      s_flock.l_type = F_RDLCK;
+      fd = open (name, O_RDONLY);
+    }
+  else
+    {
+      /* Write mode - acquire a write-lock.  */
+      s_flock.l_type = F_WRLCK;
+      fd = open (name, O_RDWR | O_CREAT, 0666);
+    }
+  if (fd < 0)
+    return 0;
+
+  while (fcntl (fd, F_SETLKW, &s_flock) && errno == EINTR)
+    continue;
+
+  gcov_var.file = fdopen (fd, (mode > 0) ? "rb" : "r+b");
+
+  if (!gcov_var.file)
+    {
+      close (fd);
+      return 0;
+    }
+
+  if (mode > 0)
+    gcov_var.mode = 1;
+  else if (mode == 0)
+    {
+      struct stat st;
+
+      if (fstat (fd, &st) < 0)
+	{
+	  fclose (gcov_var.file);
+	  gcov_var.file = 0;
+	  return 0;
+	}
+      if (st.st_size != 0)
+	gcov_var.mode = 1;
+      else
+	gcov_var.mode = mode * 2 + 1;
+    }
+  else
+    gcov_var.mode = mode * 2 + 1;
+#else
+  if (mode >= 0)
+    gcov_var.file = fopen (name, (mode > 0) ? "rb" : "r+b");
+
+  if (gcov_var.file)
+    gcov_var.mode = 1;
+  else if (mode <= 0)
+    {
+      gcov_var.file = fopen (name, "w+b");
+      if (gcov_var.file)
+	gcov_var.mode = mode * 2 + 1;
+    }
+  if (!gcov_var.file)
+    return 0;
+#endif
+
+  setbuf (gcov_var.file, (char *)0);
+
+  return 1;
+}
+
+/* Close the current gcov file. Flushes data to disk. Returns nonzero
+   on failure or error flag set.  */
+
+GCOV_LINKAGE int
+gcov_close (void)
+{
+  if (gcov_var.file)
+    {
+#if !IN_GCOV
+      if (gcov_var.offset && gcov_var.mode < 0)
+	gcov_write_block (gcov_var.offset);
+#endif
+      fclose (gcov_var.file);
+      gcov_var.file = 0;
+      gcov_var.length = 0;
+    }
+#if !IN_LIBGCOV
+  free (gcov_var.buffer);
+  gcov_var.alloc = 0;
+  gcov_var.buffer = 0;
+#endif
+  gcov_var.mode = 0;
+  return gcov_var.error;
+}
+
+#if !IN_LIBGCOV
+/* Check if MAGIC is EXPECTED. Use it to determine endianness of the
+   file. Returns +1 for same endian, -1 for other endian and zero for
+   not EXPECTED.  */
+
+GCOV_LINKAGE int
+gcov_magic (gcov_unsigned_t magic, gcov_unsigned_t expected)
+{
+  if (magic == expected)
+    return 1;
+  magic = (magic >> 16) | (magic << 16);
+  magic = ((magic & 0xff00ff) << 8) | ((magic >> 8) & 0xff00ff);
+  if (magic == expected)
+    {
+      gcov_var.endian = 1;
+      return -1;
+    }
+  return 0;
+}
+#endif
+
+#if !IN_LIBGCOV
+static void
+gcov_allocate (unsigned length)
+{
+  size_t new_size = gcov_var.alloc;
+
+  if (!new_size)
+    new_size = GCOV_BLOCK_SIZE;
+  new_size += length;
+  new_size *= 2;
+
+  gcov_var.alloc = new_size;
+  gcov_var.buffer = XRESIZEVAR (gcov_unsigned_t, gcov_var.buffer, new_size << 2);
+}
+#endif
+
+#if !IN_GCOV
+/* Write out the current block, if needs be.  */
+
+static void
+gcov_write_block (unsigned size)
+{
+  if (fwrite (gcov_var.buffer, size << 2, 1, gcov_var.file) != 1)
+    gcov_var.error = 1;
+  gcov_var.start += size;
+  gcov_var.offset -= size;
+}
+
+/* Allocate space to write BYTES bytes to the gcov file. Return a
+   pointer to those bytes, or NULL on failure.  */
+
+static gcov_unsigned_t *
+gcov_write_words (unsigned words)
+{
+  gcov_unsigned_t *result;
+
+  gcc_assert (gcov_var.mode < 0);
+#if IN_LIBGCOV
+  if (gcov_var.offset >= GCOV_BLOCK_SIZE)
+    {
+      gcov_write_block (GCOV_BLOCK_SIZE);
+      if (gcov_var.offset)
+	{
+	  gcc_assert (gcov_var.offset == 1);
+	  memcpy (gcov_var.buffer, gcov_var.buffer + GCOV_BLOCK_SIZE, 4);
+	}
+    }
+#else
+  if (gcov_var.offset + words > gcov_var.alloc)
+    gcov_allocate (gcov_var.offset + words);
+#endif
+  result = &gcov_var.buffer[gcov_var.offset];
+  gcov_var.offset += words;
+
+  return result;
+}
+
+/* Write unsigned VALUE to coverage file.  Sets error flag
+   appropriately.  */
+
+GCOV_LINKAGE void
+gcov_write_unsigned (gcov_unsigned_t value)
+{
+  gcov_unsigned_t *buffer = gcov_write_words (1);
+
+  buffer[0] = value;
+}
+
+/* Write counter VALUE to coverage file.  Sets error flag
+   appropriately.  */
+
+#if IN_LIBGCOV
+GCOV_LINKAGE void
+gcov_write_counter (gcov_type value)
+{
+  gcov_unsigned_t *buffer = gcov_write_words (2);
+
+  buffer[0] = (gcov_unsigned_t) value;
+  if (sizeof (value) > sizeof (gcov_unsigned_t))
+    buffer[1] = (gcov_unsigned_t) (value >> 32);
+  else
+    buffer[1] = 0;
+}
+#endif /* IN_LIBGCOV */
+
+#if !IN_LIBGCOV
+/* Write STRING to coverage file.  Sets error flag on file
+   error, overflow flag on overflow */
+
+GCOV_LINKAGE void
+gcov_write_string (const char *string)
+{
+  unsigned length = 0;
+  unsigned alloc = 0;
+  gcov_unsigned_t *buffer;
+
+  if (string)
+    {
+      length = strlen (string);
+      alloc = (length + 4) >> 2;
+    }
+
+  buffer = gcov_write_words (1 + alloc);
+
+  buffer[0] = alloc;
+  buffer[alloc] = 0;
+  memcpy (&buffer[1], string, length);
+}
+#endif
+
+#if !IN_LIBGCOV
+/* Write a tag TAG and reserve space for the record length. Return a
+   value to be used for gcov_write_length.  */
+
+GCOV_LINKAGE gcov_position_t
+gcov_write_tag (gcov_unsigned_t tag)
+{
+  gcov_position_t result = gcov_var.start + gcov_var.offset;
+  gcov_unsigned_t *buffer = gcov_write_words (2);
+
+  buffer[0] = tag;
+  buffer[1] = 0;
+
+  return result;
+}
+
+/* Write a record length using POSITION, which was returned by
+   gcov_write_tag.  The current file position is the end of the
+   record, and is restored before returning.  Returns nonzero on
+   overflow.  */
+
+GCOV_LINKAGE void
+gcov_write_length (gcov_position_t position)
+{
+  unsigned offset;
+  gcov_unsigned_t length;
+  gcov_unsigned_t *buffer;
+
+  gcc_assert (gcov_var.mode < 0);
+  gcc_assert (position + 2 <= gcov_var.start + gcov_var.offset);
+  gcc_assert (position >= gcov_var.start);
+  offset = position - gcov_var.start;
+  length = gcov_var.offset - offset - 2;
+  buffer = (gcov_unsigned_t *) &gcov_var.buffer[offset];
+  buffer[1] = length;
+  if (gcov_var.offset >= GCOV_BLOCK_SIZE)
+    gcov_write_block (gcov_var.offset);
+}
+
+#else /* IN_LIBGCOV */
+
+/* Write a tag TAG and length LENGTH.  */
+
+GCOV_LINKAGE void
+gcov_write_tag_length (gcov_unsigned_t tag, gcov_unsigned_t length)
+{
+  gcov_unsigned_t *buffer = gcov_write_words (2);
+
+  buffer[0] = tag;
+  buffer[1] = length;
+}
+
+/* Write a summary structure to the gcov file.  Return nonzero on
+   overflow.  */
+
+GCOV_LINKAGE void
+gcov_write_summary (gcov_unsigned_t tag, const struct gcov_summary *summary)
+{
+  unsigned ix;
+  const struct gcov_ctr_summary *csum;
+
+  gcov_write_tag_length (tag, GCOV_TAG_SUMMARY_LENGTH);
+  gcov_write_unsigned (summary->checksum);
+  for (csum = summary->ctrs, ix = GCOV_COUNTERS_SUMMABLE; ix--; csum++)
+    {
+      gcov_write_unsigned (csum->num);
+      gcov_write_unsigned (csum->runs);
+      gcov_write_counter (csum->sum_all);
+      gcov_write_counter (csum->run_max);
+      gcov_write_counter (csum->sum_max);
+    }
+}
+#endif /* IN_LIBGCOV */
+
+#endif /*!IN_GCOV */
+
+/* Return a pointer to read BYTES bytes from the gcov file. Returns
+   NULL on failure (read past EOF).  */
+
+static const gcov_unsigned_t *
+gcov_read_words (unsigned words)
+{
+  const gcov_unsigned_t *result;
+  unsigned excess = gcov_var.length - gcov_var.offset;
+
+  gcc_assert (gcov_var.mode > 0);
+  if (excess < words)
+    {
+      gcov_var.start += gcov_var.offset;
+#if IN_LIBGCOV
+      if (excess)
+	{
+	  gcc_assert (excess == 1);
+	  memcpy (gcov_var.buffer, gcov_var.buffer + gcov_var.offset, 4);
+	}
+#else
+      memmove (gcov_var.buffer, gcov_var.buffer + gcov_var.offset, excess * 4);
+#endif
+      gcov_var.offset = 0;
+      gcov_var.length = excess;
+#if IN_LIBGCOV
+      gcc_assert (!gcov_var.length || gcov_var.length == 1);
+      excess = GCOV_BLOCK_SIZE;
+#else
+      if (gcov_var.length + words > gcov_var.alloc)
+	gcov_allocate (gcov_var.length + words);
+      excess = gcov_var.alloc - gcov_var.length;
+#endif
+      excess = fread (gcov_var.buffer + gcov_var.length,
+		      1, excess << 2, gcov_var.file) >> 2;
+      gcov_var.length += excess;
+      if (gcov_var.length < words)
+	{
+	  gcov_var.overread += words - gcov_var.length;
+	  gcov_var.length = 0;
+	  return 0;
+	}
+    }
+  result = &gcov_var.buffer[gcov_var.offset];
+  gcov_var.offset += words;
+  return result;
+}
+
+/* Read unsigned value from a coverage file. Sets error flag on file
+   error, overflow flag on overflow */
+
+GCOV_LINKAGE gcov_unsigned_t
+gcov_read_unsigned (void)
+{
+  gcov_unsigned_t value;
+  const gcov_unsigned_t *buffer = gcov_read_words (1);
+
+  if (!buffer)
+    return 0;
+  value = from_file (buffer[0]);
+  return value;
+}
+
+/* Read counter value from a coverage file. Sets error flag on file
+   error, overflow flag on overflow */
+
+GCOV_LINKAGE gcov_type
+gcov_read_counter (void)
+{
+  gcov_type value;
+  const gcov_unsigned_t *buffer = gcov_read_words (2);
+
+  if (!buffer)
+    return 0;
+  value = from_file (buffer[0]);
+  if (sizeof (value) > sizeof (gcov_unsigned_t))
+    value |= ((gcov_type) from_file (buffer[1])) << 32;
+  else if (buffer[1])
+    gcov_var.error = -1;
+
+  return value;
+}
+
+/* Read string from coverage file. Returns a pointer to a static
+   buffer, or NULL on empty string. You must copy the string before
+   calling another gcov function.  */
+
+#if !IN_LIBGCOV
+GCOV_LINKAGE const char *
+gcov_read_string (void)
+{
+  unsigned length = gcov_read_unsigned ();
+
+  if (!length)
+    return 0;
+
+  return (const char *) gcov_read_words (length);
+}
+#endif
+
+GCOV_LINKAGE void
+gcov_read_summary (struct gcov_summary *summary)
+{
+  unsigned ix;
+  struct gcov_ctr_summary *csum;
+
+  summary->checksum = gcov_read_unsigned ();
+  for (csum = summary->ctrs, ix = GCOV_COUNTERS_SUMMABLE; ix--; csum++)
+    {
+      csum->num = gcov_read_unsigned ();
+      csum->runs = gcov_read_unsigned ();
+      csum->sum_all = gcov_read_counter ();
+      csum->run_max = gcov_read_counter ();
+      csum->sum_max = gcov_read_counter ();
+    }
+}
+
+#if !IN_LIBGCOV
+/* Reset to a known position.  BASE should have been obtained from
+   gcov_position, LENGTH should be a record length.  */
+
+GCOV_LINKAGE void
+gcov_sync (gcov_position_t base, gcov_unsigned_t length)
+{
+  gcc_assert (gcov_var.mode > 0);
+  base += length;
+  if (base - gcov_var.start <= gcov_var.length)
+    gcov_var.offset = base - gcov_var.start;
+  else
+    {
+      gcov_var.offset = gcov_var.length = 0;
+      fseek (gcov_var.file, base << 2, SEEK_SET);
+      gcov_var.start = ftell (gcov_var.file) >> 2;
+    }
+}
+#endif
+
+#if IN_LIBGCOV
+/* Move to a given position in a gcov file.  */
+
+GCOV_LINKAGE void
+gcov_seek (gcov_position_t base)
+{
+  gcc_assert (gcov_var.mode < 0);
+  if (gcov_var.offset)
+    gcov_write_block (gcov_var.offset);
+  fseek (gcov_var.file, base << 2, SEEK_SET);
+  gcov_var.start = ftell (gcov_var.file) >> 2;
+}
+#endif
+
+#if IN_GCOV > 0
+/* Return the modification time of the current gcov file.  */
+
+GCOV_LINKAGE time_t
+gcov_time (void)
+{
+  struct stat status;
+
+  if (fstat (fileno (gcov_var.file), &status))
+    return 0;
+  else
+    return status.st_mtime;
+}
+#endif /* IN_GCOV */
diff --git a/src/lib/gcov-io.h b/src/lib/gcov-io.h
new file mode 100644
index 0000000..9f3644a
--- /dev/null
+++ b/src/lib/gcov-io.h
@@ -0,0 +1,639 @@
+/* File format for coverage information
+   Copyright (C) 1996, 1997, 1998, 2000, 2002,
+   2003, 2004, 2005, 2008, 2009 Free Software Foundation, Inc.
+   Contributed by Bob Manson <manson at cygnus.com>.
+   Completely remangled by Nathan Sidwell <nathan at codesourcery.com>.
+
+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.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+<http://www.gnu.org/licenses/>.  */
+
+
+/* Coverage information is held in two files.  A notes file, which is
+   generated by the compiler, and a data file, which is generated by
+   the program under test.  Both files use a similar structure.  We do
+   not attempt to make these files backwards compatible with previous
+   versions, as you only need coverage information when developing a
+   program.  We do hold version information, so that mismatches can be
+   detected, and we use a format that allows tools to skip information
+   they do not understand or are not interested in.
+
+   Numbers are recorded in the 32 bit unsigned binary form of the
+   endianness of the machine generating the file. 64 bit numbers are
+   stored as two 32 bit numbers, the low part first.  Strings are
+   padded with 1 to 4 NUL bytes, to bring the length up to a multiple
+   of 4. The number of 4 bytes is stored, followed by the padded
+   string. Zero length and NULL strings are simply stored as a length
+   of zero (they have no trailing NUL or padding).
+
+   	int32:  byte3 byte2 byte1 byte0 | byte0 byte1 byte2 byte3
+	int64:  int32:low int32:high
+	string: int32:0 | int32:length char* char:0 padding
+	padding: | char:0 | char:0 char:0 | char:0 char:0 char:0
+	item: int32 | int64 | string
+
+   The basic format of the files is
+
+   	file : int32:magic int32:version int32:stamp record*
+
+   The magic ident is different for the notes and the data files.  The
+   magic ident is used to determine the endianness of the file, when
+   reading.  The version is the same for both files and is derived
+   from gcc's version number. The stamp value is used to synchronize
+   note and data files and to synchronize merging within a data
+   file. It need not be an absolute time stamp, merely a ticker that
+   increments fast enough and cycles slow enough to distinguish
+   different compile/run/compile cycles.
+
+   Although the ident and version are formally 32 bit numbers, they
+   are derived from 4 character ASCII strings.  The version number
+   consists of the single character major version number, a two
+   character minor version number (leading zero for versions less than
+   10), and a single character indicating the status of the release.
+   That will be 'e' experimental, 'p' prerelease and 'r' for release.
+   Because, by good fortune, these are in alphabetical order, string
+   collating can be used to compare version strings.  Be aware that
+   the 'e' designation will (naturally) be unstable and might be
+   incompatible with itself.  For gcc 3.4 experimental, it would be
+   '304e' (0x33303465).  When the major version reaches 10, the
+   letters A-Z will be used.  Assuming minor increments releases every
+   6 months, we have to make a major increment every 50 years.
+   Assuming major increments releases every 5 years, we're ok for the
+   next 155 years -- good enough for me.
+
+   A record has a tag, length and variable amount of data.
+
+   	record: header data
+	header: int32:tag int32:length
+	data: item*
+
+   Records are not nested, but there is a record hierarchy.  Tag
+   numbers reflect this hierarchy.  Tags are unique across note and
+   data files.  Some record types have a varying amount of data.  The
+   LENGTH is the number of 4bytes that follow and is usually used to
+   determine how much data.  The tag value is split into 4 8-bit
+   fields, one for each of four possible levels.  The most significant
+   is allocated first.  Unused levels are zero.  Active levels are
+   odd-valued, so that the LSB of the level is one.  A sub-level
+   incorporates the values of its superlevels.  This formatting allows
+   you to determine the tag hierarchy, without understanding the tags
+   themselves, and is similar to the standard section numbering used
+   in technical documents.  Level values [1..3f] are used for common
+   tags, values [41..9f] for the notes file and [a1..ff] for the data
+   file.
+
+   The basic block graph file contains the following records
+   	note: unit function-graph*
+	unit: header int32:checksum string:source
+	function-graph: announce_function basic_blocks {arcs | lines}*
+	announce_function: header int32:ident
+		int32:lineno_checksum int32:cfg_checksum
+		string:name string:source int32:lineno
+	basic_block: header int32:flags*
+	arcs: header int32:block_no arc*
+	arc:  int32:dest_block int32:flags
+        lines: header int32:block_no line*
+               int32:0 string:NULL
+	line:  int32:line_no | int32:0 string:filename
+
+   The BASIC_BLOCK record holds per-bb flags.  The number of blocks
+   can be inferred from its data length.  There is one ARCS record per
+   basic block.  The number of arcs from a bb is implicit from the
+   data length.  It enumerates the destination bb and per-arc flags.
+   There is one LINES record per basic block, it enumerates the source
+   lines which belong to that basic block.  Source file names are
+   introduced by a line number of 0, following lines are from the new
+   source file.  The initial source file for the function is NULL, but
+   the current source file should be remembered from one LINES record
+   to the next.  The end of a block is indicated by an empty filename
+   - this does not reset the current source file.  Note there is no
+   ordering of the ARCS and LINES records: they may be in any order,
+   interleaved in any manner.  The current filename follows the order
+   the LINES records are stored in the file, *not* the ordering of the
+   blocks they are for.
+
+   The data file contains the following records.
+        data: {unit summary:object summary:program* function-data*}*
+	unit: header int32:checksum
+        function-data:	announce_function present counts
+	announce_function: header int32:ident
+		int32:lineno_checksum int32:cfg_checksum
+	present: header int32:present
+	counts: header int64:count*
+	summary: int32:checksum {count-summary}GCOV_COUNTERS_SUMMABLE
+	count-summary:	int32:num int32:runs int64:sum
+			int64:max int64:sum_max
+
+   The ANNOUNCE_FUNCTION record is the same as that in the note file,
+   but without the source location.  The COUNTS gives the
+   counter values for instrumented features.  The about the whole
+   program.  The checksum is used for whole program summaries, and
+   disambiguates different programs which include the same
+   instrumented object file.  There may be several program summaries,
+   each with a unique checksum.  The object summary's checksum is
+   zero.  Note that the data file might contain information from
+   several runs concatenated, or the data might be merged.
+
+   This file is included by both the compiler, gcov tools and the
+   runtime support library libgcov. IN_LIBGCOV and IN_GCOV are used to
+   distinguish which case is which.  If IN_LIBGCOV is nonzero,
+   libgcov is being built. If IN_GCOV is nonzero, the gcov tools are
+   being built. Otherwise the compiler is being built. IN_GCOV may be
+   positive or negative. If positive, we are compiling a tool that
+   requires additional functions (see the code for knowledge of what
+   those functions are).  */
+
+#ifndef GCC_GCOV_IO_H
+#define GCC_GCOV_IO_H
+
+#ifdef __COREBOOT__
+#define GCOV_LINKAGE /* nothing */
+/* We need the definitions for
+    BITS_PER_UNIT and
+    LONG_LONG_TYPE_SIZE
+   They are defined in gcc/defaults.h and gcc/config/<arch_depend_files>
+   (like, gcc/config/i386/i386.h). And it can be overridden by setting
+   in build scripts. Here I hardcoded the value for x86. */
+#define BITS_PER_UNIT 8
+#define LONG_LONG_TYPE_SIZE 64
+
+/* There are many gcc_assertions. Set the vaule to 1 if we want a warning
+   message if the assertion fails.  */
+#ifndef ENABLE_ASSERT_CHECKING
+#define ENABLE_ASSERT_CHECKING 1
+#endif
+#endif /* __COREBOOT__ */
+
+#if IN_LIBGCOV
+/* About the target */
+
+#if BITS_PER_UNIT == 8
+typedef unsigned gcov_unsigned_t __attribute__ ((mode (SI)));
+typedef unsigned gcov_position_t __attribute__ ((mode (SI)));
+#if LONG_LONG_TYPE_SIZE > 32
+typedef signed gcov_type __attribute__ ((mode (DI)));
+#else
+typedef signed gcov_type __attribute__ ((mode (SI)));
+#endif
+#else
+#if BITS_PER_UNIT == 16
+typedef unsigned gcov_unsigned_t __attribute__ ((mode (HI)));
+typedef unsigned gcov_position_t __attribute__ ((mode (HI)));
+#if LONG_LONG_TYPE_SIZE > 32
+typedef signed gcov_type __attribute__ ((mode (SI)));
+#else
+typedef signed gcov_type __attribute__ ((mode (HI)));
+#endif
+#else
+typedef unsigned gcov_unsigned_t __attribute__ ((mode (QI)));
+typedef unsigned gcov_position_t __attribute__ ((mode (QI)));
+#if LONG_LONG_TYPE_SIZE > 32
+typedef signed gcov_type __attribute__ ((mode (HI)));
+#else
+typedef signed gcov_type __attribute__ ((mode (QI)));
+#endif
+#endif
+#endif
+
+
+#if defined (TARGET_POSIX_IO)
+#define GCOV_LOCKED 1
+#else
+#define GCOV_LOCKED 0
+#endif
+
+#else /* !IN_LIBGCOV */
+/* About the host */
+
+typedef unsigned gcov_unsigned_t;
+typedef unsigned gcov_position_t;
+/* gcov_type is typedef'd elsewhere for the compiler */
+#if IN_GCOV
+#define GCOV_LINKAGE static
+typedef HOST_WIDEST_INT gcov_type;
+#if IN_GCOV > 0
+#include <sys/types.h>
+#endif
+#else /*!IN_GCOV */
+#define GCOV_TYPE_SIZE (LONG_LONG_TYPE_SIZE > 32 ? 64 : 32)
+#endif
+
+#if defined (HOST_HAS_F_SETLKW)
+#define GCOV_LOCKED 1
+#else
+#define GCOV_LOCKED 0
+#endif
+
+#endif /* !IN_LIBGCOV */
+
+/* In gcov we want function linkage to be static.  In the compiler we want
+   it extern, so that they can be accessed from elsewhere.  In libgcov we
+   need these functions to be extern, so prefix them with __gcov.  In
+   libgcov they must also be hidden so that the instance in the executable
+   is not also used in a DSO.  */
+#if IN_LIBGCOV
+
+#ifndef __COREBOOT__
+#include "tconfig.h"
+#endif /* __COREBOOT__ */
+
+#define gcov_var __gcov_var
+#define gcov_open __gcov_open
+#define gcov_close __gcov_close
+#define gcov_write_tag_length __gcov_write_tag_length
+#define gcov_position __gcov_position
+#define gcov_seek __gcov_seek
+#define gcov_rewrite __gcov_rewrite
+#define gcov_is_error __gcov_is_error
+#define gcov_write_unsigned __gcov_write_unsigned
+#define gcov_write_counter __gcov_write_counter
+#define gcov_write_summary __gcov_write_summary
+#define gcov_read_unsigned __gcov_read_unsigned
+#define gcov_read_counter __gcov_read_counter
+#define gcov_read_summary __gcov_read_summary
+
+/* Poison these, so they don't accidentally slip in.  */
+#pragma GCC poison gcov_write_string gcov_write_tag gcov_write_length
+#pragma GCC poison gcov_read_string gcov_sync gcov_time gcov_magic
+
+#ifdef HAVE_GAS_HIDDEN
+#define ATTRIBUTE_HIDDEN  __attribute__ ((__visibility__ ("hidden")))
+#else
+#define ATTRIBUTE_HIDDEN
+#endif
+
+#else
+
+#define ATTRIBUTE_HIDDEN
+
+#endif
+
+#ifndef GCOV_LINKAGE
+#define GCOV_LINKAGE extern
+#endif
+
+/* File suffixes.  */
+#define GCOV_DATA_SUFFIX ".gcda"
+#define GCOV_NOTE_SUFFIX ".gcno"
+
+/* File magic. Must not be palindromes.  */
+#define GCOV_DATA_MAGIC ((gcov_unsigned_t)0x67636461) /* "gcda" */
+#define GCOV_NOTE_MAGIC ((gcov_unsigned_t)0x67636e6f) /* "gcno" */
+
+/* gcov-iov.h is automatically generated by the makefile from
+   version.c, it looks like
+   	#define GCOV_VERSION ((gcov_unsigned_t)0x89abcdef)
+*/
+#include "gcov-iov.h"
+
+/* Convert a magic or version number to a 4 character string.  */
+#define GCOV_UNSIGNED2STRING(ARRAY,VALUE)	\
+  ((ARRAY)[0] = (char)((VALUE) >> 24),		\
+   (ARRAY)[1] = (char)((VALUE) >> 16),		\
+   (ARRAY)[2] = (char)((VALUE) >> 8),		\
+   (ARRAY)[3] = (char)((VALUE) >> 0))
+
+/* The record tags.  Values [1..3f] are for tags which may be in either
+   file.  Values [41..9f] for those in the note file and [a1..ff] for
+   the data file.  The tag value zero is used as an explicit end of
+   file marker -- it is not required to be present.  */
+
+#define GCOV_TAG_FUNCTION	 ((gcov_unsigned_t)0x01000000)
+#define GCOV_TAG_FUNCTION_LENGTH (3)
+#define GCOV_TAG_BLOCKS		 ((gcov_unsigned_t)0x01410000)
+#define GCOV_TAG_BLOCKS_LENGTH(NUM) (NUM)
+#define GCOV_TAG_BLOCKS_NUM(LENGTH) (LENGTH)
+#define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
+#define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2)
+#define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH) - 1) / 2)
+#define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
+#define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
+#define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2)
+#define GCOV_TAG_COUNTER_NUM(LENGTH) ((LENGTH) / 2)
+#define GCOV_TAG_OBJECT_SUMMARY  ((gcov_unsigned_t)0xa1000000) /* Obsolete */
+#define GCOV_TAG_PROGRAM_SUMMARY ((gcov_unsigned_t)0xa3000000)
+#define GCOV_TAG_SUMMARY_LENGTH  \
+	(1 + GCOV_COUNTERS_SUMMABLE * (2 + 3 * 2))
+
+/* Counters that are collected.  */
+#define GCOV_COUNTER_ARCS 	0  /* Arc transitions.  */
+#define GCOV_COUNTERS_SUMMABLE	1  /* Counters which can be
+				      summaried.  */
+#define GCOV_FIRST_VALUE_COUNTER 1 /* The first of counters used for value
+				      profiling.  They must form a consecutive
+				      interval and their order must match
+				      the order of HIST_TYPEs in
+				      value-prof.h.  */
+#define GCOV_COUNTER_V_INTERVAL	1  /* Histogram of value inside an interval.  */
+#define GCOV_COUNTER_V_POW2	2  /* Histogram of exact power2 logarithm
+				      of a value.  */
+#define GCOV_COUNTER_V_SINGLE	3  /* The most common value of expression.  */
+#define GCOV_COUNTER_V_DELTA	4  /* The most common difference between
+				      consecutive values of expression.  */
+
+#define GCOV_COUNTER_V_INDIR	5  /* The most common indirect address */
+#define GCOV_COUNTER_AVERAGE	6  /* Compute average value passed to the
+				      counter.  */
+#define GCOV_COUNTER_IOR	7  /* IOR of the all values passed to
+				      counter.  */
+#define GCOV_LAST_VALUE_COUNTER 7  /* The last of counters used for value
+				      profiling.  */
+#define GCOV_COUNTERS		8
+
+/* Number of counters used for value profiling.  */
+#define GCOV_N_VALUE_COUNTERS \
+  (GCOV_LAST_VALUE_COUNTER - GCOV_FIRST_VALUE_COUNTER + 1)
+
+  /* A list of human readable names of the counters */
+#define GCOV_COUNTER_NAMES	{"arcs", "interval", "pow2", "single", \
+      				 "delta", "indirect_call", "average", "ior"}
+
+  /* Names of merge functions for counters.  */
+#define GCOV_MERGE_FUNCTIONS	{"__gcov_merge_add",	\
+				 "__gcov_merge_add",	\
+				 "__gcov_merge_add",	\
+				 "__gcov_merge_single",	\
+				 "__gcov_merge_delta",  \
+				 "__gcov_merge_single", \
+				 "__gcov_merge_add",	\
+				 "__gcov_merge_ior"}
+
+/* Convert a counter index to a tag.  */
+#define GCOV_TAG_FOR_COUNTER(COUNT)				\
+	(GCOV_TAG_COUNTER_BASE + ((gcov_unsigned_t)(COUNT) << 17))
+/* Convert a tag to a counter.  */
+#define GCOV_COUNTER_FOR_TAG(TAG)					\
+	((unsigned)(((TAG) - GCOV_TAG_COUNTER_BASE) >> 17))
+/* Check whether a tag is a counter tag.  */
+#define GCOV_TAG_IS_COUNTER(TAG)				\
+	(!((TAG) & 0xFFFF) && GCOV_COUNTER_FOR_TAG (TAG) < GCOV_COUNTERS)
+
+/* The tag level mask has 1's in the position of the inner levels, &
+   the lsb of the current level, and zero on the current and outer
+   levels.  */
+#define GCOV_TAG_MASK(TAG) (((TAG) - 1) ^ (TAG))
+
+/* Return nonzero if SUB is an immediate subtag of TAG.  */
+#define GCOV_TAG_IS_SUBTAG(TAG,SUB)				\
+	(GCOV_TAG_MASK (TAG) >> 8 == GCOV_TAG_MASK (SUB) 	\
+	 && !(((SUB) ^ (TAG)) & ~GCOV_TAG_MASK(TAG)))
+
+/* Return nonzero if SUB is at a sublevel to TAG.  */
+#define GCOV_TAG_IS_SUBLEVEL(TAG,SUB)				\
+     	(GCOV_TAG_MASK (TAG) > GCOV_TAG_MASK (SUB))
+
+/* Basic block flags.  */
+#define GCOV_BLOCK_UNEXPECTED	(1 << 1)
+
+/* Arc flags.  */
+#define GCOV_ARC_ON_TREE 	(1 << 0)
+#define GCOV_ARC_FAKE		(1 << 1)
+#define GCOV_ARC_FALLTHROUGH	(1 << 2)
+
+/* Structured records.  */
+
+/* Cumulative counter data.  */
+struct gcov_ctr_summary
+{
+  gcov_unsigned_t num;		/* number of counters.  */
+  gcov_unsigned_t runs;		/* number of program runs */
+  gcov_type sum_all;		/* sum of all counters accumulated.  */
+  gcov_type run_max;		/* maximum value on a single run.  */
+  gcov_type sum_max;    	/* sum of individual run max values.  */
+};
+
+/* Object & program summary record.  */
+struct gcov_summary
+{
+  gcov_unsigned_t checksum;	/* checksum of program */
+  struct gcov_ctr_summary ctrs[GCOV_COUNTERS_SUMMABLE];
+};
+
+/* Structures embedded in coveraged program.  The structures generated
+   by write_profile must match these.  */
+
+#if IN_LIBGCOV
+/* Information about counters for a single function.  */
+struct gcov_ctr_info
+{
+  gcov_unsigned_t num;		/* number of counters.  */
+  gcov_type *values;		/* their values.  */
+};
+
+/* Information about a single function.  This uses the trailing array
+   idiom. The number of counters is determined from the merge pointer
+   array in gcov_info.  The key is used to detect which of a set of
+   comdat functions was selected -- it points to the gcov_info object
+   of the object file containing the selected comdat function.  */
+
+struct gcov_fn_info
+{
+  const struct gcov_info *key;		/* comdat key */
+  gcov_unsigned_t ident;		/* unique ident of function */
+  gcov_unsigned_t lineno_checksum;	/* function lineo_checksum */
+  gcov_unsigned_t cfg_checksum;		/* function cfg checksum */
+  struct gcov_ctr_info ctrs[0];		/* instrumented counters */
+};
+
+/* Type of function used to merge counters.  */
+typedef void (*gcov_merge_fn) (gcov_type *, gcov_unsigned_t);
+
+/* Information about a single object file.  */
+struct gcov_info
+{
+  gcov_unsigned_t version;	/* expected version number */
+  struct gcov_info *next;	/* link to next, used by libgcov */
+
+  gcov_unsigned_t stamp;	/* uniquifying time stamp */
+  const char *filename;		/* output file name */
+
+  gcov_merge_fn merge[GCOV_COUNTERS];  /* merge functions (null for
+					  unused) */
+  
+  unsigned n_functions;		/* number of functions */
+  const struct gcov_fn_info *const *functions; /* pointer to pointers
+					          to function information  */
+};
+
+/* Register a new object file module.  */
+extern void __gcov_init (struct gcov_info *) ATTRIBUTE_HIDDEN;
+
+#ifndef __COREBOOT__
+/* Called before fork, to avoid double counting.  */
+extern void __gcov_flush (void) ATTRIBUTE_HIDDEN;
+#endif
+
+/* The merge function that just sums the counters.  */
+extern void __gcov_merge_add (gcov_type *, unsigned) ATTRIBUTE_HIDDEN;
+
+/* The merge function to choose the most common value.  */
+extern void __gcov_merge_single (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;
+
+/* The merge function that just ors the counters together.  */
+extern void __gcov_merge_ior (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_average_profiler (gcov_type *, gcov_type);
+extern void __gcov_ior_profiler (gcov_type *, gcov_type);
+
+#ifndef inhibit_libc
+/* The wrappers around some library functions..  */
+extern pid_t __gcov_fork (void) ATTRIBUTE_HIDDEN;
+extern int __gcov_execl (const char *, char *, ...) ATTRIBUTE_HIDDEN;
+extern int __gcov_execlp (const char *, char *, ...) ATTRIBUTE_HIDDEN;
+extern int __gcov_execle (const char *, char *, ...) ATTRIBUTE_HIDDEN;
+extern int __gcov_execv (const char *, char *const []) ATTRIBUTE_HIDDEN;
+extern int __gcov_execvp (const char *, char *const []) ATTRIBUTE_HIDDEN;
+extern int __gcov_execve (const char *, char  *const [], char *const [])
+  ATTRIBUTE_HIDDEN;
+#endif
+
+#endif /* IN_LIBGCOV */
+
+#if IN_LIBGCOV >= 0
+
+/* Optimum number of gcov_unsigned_t's read from or written to disk.  */
+#define GCOV_BLOCK_SIZE (1 << 10)
+
+GCOV_LINKAGE struct gcov_var
+{
+  FILE *file;
+  gcov_position_t start;	/* Position of first byte of block */
+  unsigned offset;		/* Read/write position within the block.  */
+  unsigned length;		/* Read limit in the block.  */
+  unsigned overread;		/* Number of words overread.  */
+  int error;			/* < 0 overflow, > 0 disk error.  */
+  int mode;	                /* < 0 writing, > 0 reading */
+#if IN_LIBGCOV
+  /* Holds one block plus 4 bytes, thus all coverage reads & writes
+     fit within this buffer and we always can transfer GCOV_BLOCK_SIZE
+     to and from the disk. libgcov never backtracks and only writes 4
+     or 8 byte objects.  */
+  gcov_unsigned_t buffer[GCOV_BLOCK_SIZE + 1];
+#else
+  int endian;			/* Swap endianness.  */
+  /* Holds a variable length block, as the compiler can write
+     strings and needs to backtrack.  */
+  size_t alloc;
+  gcov_unsigned_t *buffer;
+#endif
+} 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
+   file either for reading or for writing. When reading a file you may
+   use the gcov_read_* functions, gcov_sync, gcov_position, &
+   gcov_error. When writing a file you may use the gcov_write
+   functions, gcov_seek & gcov_error. When a file is to be rewritten
+   you use the functions for reading, then gcov_rewrite then the
+   functions for writing.  Your file may become corrupted if you break
+   these invariants.  */
+#if IN_LIBGCOV
+GCOV_LINKAGE int gcov_open (const char */*name*/) ATTRIBUTE_HIDDEN;
+#else
+GCOV_LINKAGE int gcov_open (const char */*name*/, int /*direction*/);
+GCOV_LINKAGE int gcov_magic (gcov_unsigned_t, gcov_unsigned_t);
+#endif
+GCOV_LINKAGE int gcov_close (void) ATTRIBUTE_HIDDEN;
+
+/* Available everywhere.  */
+static gcov_position_t gcov_position (void);
+static int gcov_is_error (void);
+
+GCOV_LINKAGE gcov_unsigned_t gcov_read_unsigned (void) ATTRIBUTE_HIDDEN;
+GCOV_LINKAGE gcov_type gcov_read_counter (void) ATTRIBUTE_HIDDEN;
+GCOV_LINKAGE void gcov_read_summary (struct gcov_summary *) ATTRIBUTE_HIDDEN;
+
+#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)
+    ATTRIBUTE_HIDDEN;
+GCOV_LINKAGE void gcov_write_summary (gcov_unsigned_t /*tag*/,
+				      const struct gcov_summary *)
+    ATTRIBUTE_HIDDEN;
+static void gcov_rewrite (void);
+GCOV_LINKAGE void gcov_seek (gcov_position_t /*position*/) ATTRIBUTE_HIDDEN;
+#else
+/* Available outside libgcov */
+GCOV_LINKAGE const char *gcov_read_string (void);
+GCOV_LINKAGE void gcov_sync (gcov_position_t /*base*/,
+			     gcov_unsigned_t /*length */);
+#endif
+
+#if !IN_GCOV
+/* Available outside gcov */
+GCOV_LINKAGE void gcov_write_unsigned (gcov_unsigned_t) ATTRIBUTE_HIDDEN;
+#endif
+
+#if !IN_GCOV && !IN_LIBGCOV
+/* Available only in compiler */
+GCOV_LINKAGE void gcov_write_string (const char *);
+GCOV_LINKAGE gcov_position_t gcov_write_tag (gcov_unsigned_t);
+GCOV_LINKAGE void gcov_write_length (gcov_position_t /*position*/);
+#endif
+
+#if IN_GCOV > 0
+/* Available in gcov */
+GCOV_LINKAGE time_t gcov_time (void);
+#endif
+
+/* Save the current position in the gcov file.  */
+
+static inline gcov_position_t
+gcov_position (void)
+{
+  gcc_assert (gcov_var.mode > 0);
+  return gcov_var.start + gcov_var.offset;
+}
+
+/* Return nonzero if the error flag is set.  */
+
+static inline int
+gcov_is_error (void)
+{
+  return gcov_var.file ? gcov_var.error : 1;
+}
+
+#if IN_LIBGCOV
+/* Move to beginning of file and initialize for writing.  */
+
+static inline void
+gcov_rewrite (void)
+{
+  gcc_assert (gcov_var.mode > 0);
+  gcov_var.mode = -1;
+  gcov_var.start = 0;
+  gcov_var.offset = 0;
+  fseek (gcov_var.file, 0L, SEEK_SET);
+}
+#endif
+
+#endif /* IN_LIBGCOV >= 0 */
+
+#endif /* GCC_GCOV_IO_H */
diff --git a/src/lib/gcov-iov.h b/src/lib/gcov-iov.h
new file mode 100644
index 0000000..09951fb
--- /dev/null
+++ b/src/lib/gcov-iov.h
@@ -0,0 +1,4 @@
+/* Generated automatically by the program `build/gcov-iov'
+   from `4.7.2 (4 7) and  (*)'.  */
+
+#define GCOV_VERSION ((gcov_unsigned_t)0x3430372a)  /* 407* */
diff --git a/src/lib/hardwaremain.c b/src/lib/hardwaremain.c
index 206e82b..eed243a 100644
--- a/src/lib/hardwaremain.c
+++ b/src/lib/hardwaremain.c
@@ -42,6 +42,7 @@ it with the version available from LANL.
 #if CONFIG_WRITE_HIGH_TABLES
 #include <cbmem.h>
 #endif
+#include <coverage.h>
 #include <timestamp.h>
 
 /**
@@ -62,6 +63,10 @@ void hardwaremain(int boot_complete)
 	timestamp_stash(TS_START_RAMSTAGE);
 	post_code(POST_ENTRY_RAMSTAGE);
 
+#if CONFIG_COVERAGE
+	coverage_init();
+#endif
+
 	/* console_init() MUST PRECEDE ALL printk()! */
 	console_init();
 
diff --git a/src/lib/libgcov.c b/src/lib/libgcov.c
new file mode 100644
index 0000000..29a93a0
--- /dev/null
+++ b/src/lib/libgcov.c
@@ -0,0 +1,1158 @@
+/* Routines required for instrumenting a program.  */
+/* Compile this one with gcc.  */
+/* Copyright (C) 1989, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+   2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009, 2010, 2011
+   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.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+<http://www.gnu.org/licenses/>.  */
+
+#define __COREBOOT__
+#ifdef __COREBOOT__
+#include <stdlib.h>
+#include <string.h>
+#include <console/console.h>
+#include <assert.h>
+typedef s32 pid_t;
+#define gcc_assert(x) ASSERT(x)
+#define fprintf(file, x...) printk(BIOS_ERR, x)
+#define alloca(size)   __builtin_alloca (size)
+#include "gcov-glue.c"
+
+/* Define MACROs to be used by coreboot compilation.  */
+# define L_gcov
+# define L_gcov_interval_profiler
+# define L_gcov_pow2_profiler
+# define L_gcov_one_value_profiler
+# define L_gcov_indirect_call_profiler
+# define L_gcov_average_profiler
+# define L_gcov_ior_profiler
+
+# define HAVE_CC_TLS 0
+# define __GCOV_KERNEL__
+
+# define IN_LIBGCOV 1
+# define IN_GCOV 0
+#else /* __COREBOOT__ */
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#endif /* __COREBOOT__ */
+
+#ifndef __COREBOOT__
+#if defined(inhibit_libc)
+#define IN_LIBGCOV (-1)
+#else
+#undef NULL /* Avoid errors if stdio.h and our stddef.h mismatch.  */
+#include <stdio.h>
+#define IN_LIBGCOV 1
+#if defined(L_gcov)
+#define GCOV_LINKAGE /* nothing */
+#endif
+#endif
+#endif /* __COREBOOT__ */
+#include "gcov-io.h"
+
+#if defined(inhibit_libc)
+/* If libc and its header files are not available, provide dummy functions.  */
+
+#ifdef L_gcov
+void __gcov_init (struct gcov_info *p __attribute__ ((unused))) {}
+void __gcov_flush (void) {}
+#endif
+
+#ifdef L_gcov_merge_add
+void __gcov_merge_add (gcov_type *counters  __attribute__ ((unused)),
+		       unsigned n_counters __attribute__ ((unused))) {}
+#endif
+
+#ifdef L_gcov_merge_single
+void __gcov_merge_single (gcov_type *counters  __attribute__ ((unused)),
+			  unsigned n_counters __attribute__ ((unused))) {}
+#endif
+
+#ifdef L_gcov_merge_delta
+void __gcov_merge_delta (gcov_type *counters  __attribute__ ((unused)),
+			 unsigned n_counters __attribute__ ((unused))) {}
+#endif
+
+#else
+
+#ifndef __COREBOOT__
+#include <string.h>
+#if GCOV_LOCKED
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/stat.h>
+#endif
+#else
+void __gcov_merge_add(gcov_type *counters  __attribute__ ((unused)),
+			unsigned n_counters __attribute__ ((unused))) {}
+#endif /* __COREBOOT__ */
+
+#ifdef L_gcov
+#include "gcov-io.c"
+
+struct gcov_fn_buffer
+{
+  struct gcov_fn_buffer *next;
+  unsigned fn_ix;
+  struct gcov_fn_info info;
+  /* note gcov_fn_info ends in a trailing array.  */
+};
+
+/* Chain of per-object gcov structures.  */
+static struct gcov_info *gcov_list;
+
+/* Size of the longest file name. */
+static size_t gcov_max_filename = 0;
+
+/* Make sure path component of the given FILENAME exists, create
+   missing directories. FILENAME must be writable.
+   Returns zero on success, or -1 if an error occurred.  */
+
+static int
+create_file_directory (char *filename)
+{
+#ifdef __COREBOOT__
+  (void) filename;
+  return 0;
+#else
+#if !defined(TARGET_POSIX_IO) && !defined(_WIN32)
+  (void) filename;
+  return -1;
+#else
+  char *s;
+
+  s = filename;
+
+  if (HAS_DRIVE_SPEC(s))
+    s += 2;
+  if (IS_DIR_SEPARATOR(*s))
+    ++s;
+  for (; *s != '\0'; s++)
+    if (IS_DIR_SEPARATOR(*s))
+      {
+        char sep = *s;
+	*s  = '\0';
+
+        /* Try to make directory if it doesn't already exist.  */
+        if (access (filename, F_OK) == -1
+#ifdef TARGET_POSIX_IO
+            && mkdir (filename, 0755) == -1
+#else
+            && mkdir (filename) == -1
+#endif
+            /* The directory might have been made by another process.  */
+	    && errno != EEXIST)
+	  {
+            fprintf (stderr, "profiling:%s:Cannot create directory\n",
+		     filename);
+            *s = sep;
+	    return -1;
+	  };
+
+	*s = sep;
+      };
+  return 0;
+#endif
+#endif
+}
+
+static struct gcov_fn_buffer *
+free_fn_data (const struct gcov_info *gi_ptr, struct gcov_fn_buffer *buffer,
+	      unsigned limit)
+{
+  struct gcov_fn_buffer *next;
+  unsigned ix, n_ctr = 0;
+  
+  if (!buffer)
+    return 0;
+  next = buffer->next;
+
+  for (ix = 0; ix != limit; ix++)
+    if (gi_ptr->merge[ix])
+      free (buffer->info.ctrs[n_ctr++].values);
+  free (buffer);
+  return next;
+}
+  
+static struct gcov_fn_buffer **
+buffer_fn_data (const char *filename, const struct gcov_info *gi_ptr,
+		struct gcov_fn_buffer **end_ptr, unsigned fn_ix)
+{
+  unsigned n_ctrs = 0, ix = 0;
+  struct gcov_fn_buffer *fn_buffer;
+  unsigned len;
+
+  for (ix = GCOV_COUNTERS; ix--;)
+    if (gi_ptr->merge[ix])
+      n_ctrs++;
+
+  len = sizeof (*fn_buffer) + sizeof (fn_buffer->info.ctrs[0]) * n_ctrs;
+  fn_buffer = (struct gcov_fn_buffer *)malloc (len);
+
+  if (!fn_buffer)
+    goto fail;
+  
+  fn_buffer->next = 0;
+  fn_buffer->fn_ix = fn_ix;
+  fn_buffer->info.ident = gcov_read_unsigned ();
+  fn_buffer->info.lineno_checksum = gcov_read_unsigned ();
+  fn_buffer->info.cfg_checksum = gcov_read_unsigned ();
+
+  for (n_ctrs = ix = 0; ix != GCOV_COUNTERS; ix++)
+    {
+      gcov_unsigned_t length;
+      gcov_type *values;
+
+      if (!gi_ptr->merge[ix])
+	continue;
+      
+      if (gcov_read_unsigned () != GCOV_TAG_FOR_COUNTER (ix))
+	{
+	  len = 0;
+	  goto fail;
+	}
+
+      length = GCOV_TAG_COUNTER_NUM (gcov_read_unsigned ());
+      len = length * sizeof (gcov_type);
+      values = (gcov_type *)malloc (len);
+      if (!values)
+	goto fail;
+      
+      fn_buffer->info.ctrs[n_ctrs].num = length;
+      fn_buffer->info.ctrs[n_ctrs].values = values;
+
+      while (length--)
+	*values++ = gcov_read_counter ();
+      n_ctrs++;
+    }
+  
+  *end_ptr = fn_buffer;
+  return &fn_buffer->next;
+
+fail:
+  fprintf (stderr, "profiling:%s:Function %u %s %u \n", filename, fn_ix,
+	   len ? "cannot allocate" : "counter mismatch", len ? len : ix);
+
+  return (struct gcov_fn_buffer **)free_fn_data (gi_ptr, fn_buffer, ix);
+}
+
+/* Add an unsigned value to the current crc */
+
+static gcov_unsigned_t
+crc32_unsigned (gcov_unsigned_t crc32, gcov_unsigned_t value)
+{
+  unsigned ix;
+
+  for (ix = 32; ix--; value <<= 1)
+    {
+      unsigned feedback;
+
+      feedback = (value ^ crc32) & 0x80000000 ? 0x04c11db7 : 0;
+      crc32 <<= 1;
+      crc32 ^= feedback;
+    }
+
+  return crc32;
+}
+
+/* Check if VERSION of the info block PTR matches libgcov one.
+   Return 1 on success, or zero in case of versions mismatch.
+   If FILENAME is not NULL, its value used for reporting purposes
+   instead of value from the info block.  */
+
+static int
+gcov_version (struct gcov_info *ptr, gcov_unsigned_t version,
+	      const char *filename)
+{
+  if (version != GCOV_VERSION)
+    {
+      char v[4], e[4];
+
+      GCOV_UNSIGNED2STRING (v, version);
+      GCOV_UNSIGNED2STRING (e, GCOV_VERSION);
+
+      fprintf (stderr,
+	       "profiling:%s:Version mismatch - expected %.4s got %.4s\n",
+	       filename? filename : ptr->filename, e, v);
+      return 0;
+    }
+  return 1;
+}
+
+/* Dump the coverage counts. We merge with existing counts when
+   possible, to avoid growing the .da files ad infinitum. We use this
+   program's checksum to make sure we only accumulate whole program
+   statistics to the correct summary. An object file might be embedded
+   in two separate programs, and we must keep the two program
+   summaries separate.  */
+
+static void
+gcov_exit (void)
+{
+  struct gcov_info *gi_ptr;
+  const struct gcov_fn_info *gfi_ptr;
+  struct gcov_summary this_prg; /* summary for program.  */
+  struct gcov_summary all_prg;  /* summary for all instances of program.  */
+  struct gcov_ctr_summary *cs_ptr;
+  const struct gcov_ctr_info *ci_ptr;
+  unsigned t_ix;
+  int f_ix = 0;
+  gcov_unsigned_t c_num;
+  const char *gcov_prefix;
+  int gcov_prefix_strip = 0;
+  size_t prefix_length;
+  char *gi_filename, *gi_filename_up;
+  gcov_unsigned_t crc32 = 0;
+
+  memset (&all_prg, 0, sizeof (all_prg));
+  /* Find the totals for this execution.  */
+  memset (&this_prg, 0, sizeof (this_prg));
+  for (gi_ptr = gcov_list; gi_ptr; gi_ptr = gi_ptr->next)
+    {
+      crc32 = crc32_unsigned (crc32, gi_ptr->stamp);
+      crc32 = crc32_unsigned (crc32, gi_ptr->n_functions);
+      
+      for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; f_ix++)
+	{
+	  gfi_ptr = gi_ptr->functions[f_ix];
+
+	  if (gfi_ptr && gfi_ptr->key != gi_ptr)
+	    gfi_ptr = 0;
+	  
+	  crc32 = crc32_unsigned (crc32, gfi_ptr ? gfi_ptr->cfg_checksum : 0);
+	  crc32 = crc32_unsigned (crc32,
+				  gfi_ptr ? gfi_ptr->lineno_checksum : 0);
+	  if (!gfi_ptr)
+	    continue;
+
+	  ci_ptr = gfi_ptr->ctrs;
+	  for (t_ix = 0; t_ix != GCOV_COUNTERS_SUMMABLE; t_ix++)
+	    {
+	      if (!gi_ptr->merge[t_ix])
+		continue;
+
+	      cs_ptr = &this_prg.ctrs[t_ix];
+	      cs_ptr->num += ci_ptr->num;
+	      crc32 = crc32_unsigned (crc32, ci_ptr->num);
+	      
+	      for (c_num = 0; c_num < ci_ptr->num; c_num++)
+		{
+		  cs_ptr->sum_all += ci_ptr->values[c_num];
+		  if (cs_ptr->run_max < ci_ptr->values[c_num])
+		    cs_ptr->run_max = ci_ptr->values[c_num];
+		}
+	      ci_ptr++;
+	    }
+	}
+    }
+
+#ifndef __COREBOOT__
+  {
+    /* Check if the level of dirs to strip off specified. */
+    char *tmp = getenv("GCOV_PREFIX_STRIP");
+    if (tmp)
+      {
+	gcov_prefix_strip = atoi (tmp);
+	/* Do not consider negative values. */
+	if (gcov_prefix_strip < 0)
+	  gcov_prefix_strip = 0;
+      }
+  }
+
+  /* Get file name relocation prefix.  Non-absolute values are ignored. */
+  gcov_prefix = getenv("GCOV_PREFIX");
+  if (gcov_prefix)
+    {
+      prefix_length = strlen(gcov_prefix);
+
+      /* Remove an unnecessary trailing '/' */
+      if (IS_DIR_SEPARATOR (gcov_prefix[prefix_length - 1]))
+	prefix_length--;
+    }
+  else
+#endif
+    prefix_length = 0;
+
+  /* If no prefix was specified and a prefix stip, then we assume
+     relative.  */
+  if (gcov_prefix_strip != 0 && prefix_length == 0)
+    {
+      gcov_prefix = ".";
+      prefix_length = 1;
+    }
+  /* Allocate and initialize the filename scratch space plus one.  */
+  gi_filename = (char *) alloca (prefix_length + gcov_max_filename + 2);
+  if (prefix_length)
+    memcpy (gi_filename, gcov_prefix, prefix_length);
+  gi_filename_up = gi_filename + prefix_length;
+
+  /* Now merge each file.  */
+  for (gi_ptr = gcov_list; gi_ptr; gi_ptr = gi_ptr->next)
+    {
+      unsigned n_counts;
+      struct gcov_summary prg; /* summary for this object over all
+				  program.  */
+      struct gcov_ctr_summary *cs_prg, *cs_tprg, *cs_all;
+      int error = 0;
+      gcov_unsigned_t tag, length;
+      gcov_position_t summary_pos = 0;
+      gcov_position_t eof_pos = 0;
+      const char *fname, *s;
+      struct gcov_fn_buffer *fn_buffer = 0;
+      struct gcov_fn_buffer **fn_tail = &fn_buffer;
+
+      fname = gi_ptr->filename;
+
+      /* Avoid to add multiple drive letters into combined path.  */
+      if (prefix_length != 0 && HAS_DRIVE_SPEC(fname))
+        fname += 2;
+
+      /* Build relocated filename, stripping off leading
+         directories from the initial filename if requested. */
+      if (gcov_prefix_strip > 0)
+        {
+          int level = 0;
+          s = fname;
+          if (IS_DIR_SEPARATOR(*s))
+            ++s;
+
+          /* Skip selected directory levels. */
+	  for (; (*s != '\0') && (level < gcov_prefix_strip); s++)
+	    if (IS_DIR_SEPARATOR(*s))
+	      {
+		fname = s;
+		level++;
+	      }
+        }
+
+      /* Update complete filename with stripped original. */
+      if (prefix_length != 0 && !IS_DIR_SEPARATOR (*fname))
+        {
+          /* If prefix is given, add directory separator.  */
+	  strcpy (gi_filename_up, "/");
+	  strcpy (gi_filename_up + 1, fname);
+	}
+      else
+        strcpy (gi_filename_up, fname);
+
+      if (!gcov_open (gi_filename))
+	{
+	  /* Open failed likely due to missed directory.
+	     Create directory and retry to open file. */
+          if (create_file_directory (gi_filename))
+	    {
+	      fprintf (stderr, "profiling:%s:Skip\n", gi_filename);
+	      continue;
+	    }
+	  if (!gcov_open (gi_filename))
+	    {
+              fprintf (stderr, "profiling:%s:Cannot open\n", gi_filename);
+	      continue;
+	    }
+	}
+
+      tag = gcov_read_unsigned ();
+      if (tag)
+	{
+	  /* Merge data from file.  */
+	  if (tag != GCOV_DATA_MAGIC)
+	    {
+	      fprintf (stderr, "profiling:%s:Not a gcov data file\n",
+		       gi_filename);
+	      goto read_fatal;
+	    }
+	  length = gcov_read_unsigned ();
+	  if (!gcov_version (gi_ptr, length, gi_filename))
+	    goto read_fatal;
+
+	  length = gcov_read_unsigned ();
+	  if (length != gi_ptr->stamp)
+	    /* Read from a different compilation. Overwrite the file.  */
+	    goto rewrite;
+
+	  /* Look for program summary.  */
+	  for (f_ix = 0;;)
+	    {
+	      struct gcov_summary tmp;
+	      
+	      eof_pos = gcov_position ();
+	      tag = gcov_read_unsigned ();
+	      if (tag != GCOV_TAG_PROGRAM_SUMMARY)
+		break;
+
+	      f_ix--;
+	      length = gcov_read_unsigned ();
+	      if (length != GCOV_TAG_SUMMARY_LENGTH)
+		goto read_mismatch;
+	      gcov_read_summary (&tmp);
+	      if ((error = gcov_is_error ()))
+		goto read_error;
+	      if (summary_pos || tmp.checksum != crc32)
+		goto next_summary;
+	      
+	      for (t_ix = 0; t_ix != GCOV_COUNTERS_SUMMABLE; t_ix++)
+		if (tmp.ctrs[t_ix].num != this_prg.ctrs[t_ix].num)
+		  goto next_summary;
+	      prg = tmp;
+	      summary_pos = eof_pos;
+
+	    next_summary:;
+	    }
+	  
+	  /* Merge execution counts for each function.  */
+	  for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions;
+	       f_ix++, tag = gcov_read_unsigned ())
+	    {
+	      gfi_ptr = gi_ptr->functions[f_ix];
+
+	      if (tag != GCOV_TAG_FUNCTION)
+		goto read_mismatch;
+
+	      length = gcov_read_unsigned ();
+	      if (!length)
+		/* This function did not appear in the other program.
+		   We have nothing to merge.  */
+		continue;
+
+	      if (length != GCOV_TAG_FUNCTION_LENGTH)
+		goto read_mismatch;
+	      
+	      if (!gfi_ptr || gfi_ptr->key != gi_ptr)
+		{
+		  /* This function appears in the other program.  We
+		     need to buffer the information in order to write
+		     it back out -- we'll be inserting data before
+		     this point, so cannot simply keep the data in the
+		     file.  */
+		  fn_tail = buffer_fn_data (gi_filename,
+					    gi_ptr, fn_tail, f_ix);
+		  if (!fn_tail)
+		    goto read_mismatch;
+		  continue;
+		}
+
+	      length = gcov_read_unsigned ();
+	      if (length != gfi_ptr->ident)
+		goto read_mismatch;
+	      
+	      length = gcov_read_unsigned ();
+	      if (length != gfi_ptr->lineno_checksum)
+		goto read_mismatch;
+	      
+	      length = gcov_read_unsigned ();
+	      if (length != gfi_ptr->cfg_checksum)
+		goto read_mismatch;
+	      
+	      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;
+
+		  tag = gcov_read_unsigned ();
+		  length = gcov_read_unsigned ();
+		  if (tag != GCOV_TAG_FOR_COUNTER (t_ix)
+		      || length != GCOV_TAG_COUNTER_LENGTH (ci_ptr->num))
+		    goto read_mismatch;
+		  (*merge) (ci_ptr->values, ci_ptr->num);
+		  ci_ptr++;
+		}
+	      if ((error = gcov_is_error ()))
+		goto read_error;
+	    }
+
+	  if (tag)
+	    {
+	    read_mismatch:;
+	      fprintf (stderr, "profiling:%s:Merge mismatch for %s %u\n",
+		       gi_filename, f_ix >= 0 ? "function" : "summary",
+		       f_ix < 0 ? -1 - f_ix : f_ix);
+	      goto read_fatal;
+	    }
+	}
+      goto rewrite;
+
+    read_error:;
+      fprintf (stderr, "profiling:%s:%s merging\n", gi_filename,
+	       error < 0 ? "Overflow": "Error");
+
+      goto read_fatal;
+
+    rewrite:;
+      gcov_rewrite ();
+      if (!summary_pos)
+	{
+	  memset (&prg, 0, sizeof (prg));
+	  summary_pos = eof_pos;
+	}
+
+      /* Merge the summaries.  */
+      for (t_ix = 0; t_ix < GCOV_COUNTERS_SUMMABLE; t_ix++)
+	{
+	  cs_prg = &prg.ctrs[t_ix];
+	  cs_tprg = &this_prg.ctrs[t_ix];
+	  cs_all = &all_prg.ctrs[t_ix];
+
+	  if (gi_ptr->merge[t_ix])
+	    {
+	      if (!cs_prg->runs++)
+		cs_prg->num = cs_tprg->num;
+	      cs_prg->sum_all += cs_tprg->sum_all;
+	      if (cs_prg->run_max < cs_tprg->run_max)
+		cs_prg->run_max = cs_tprg->run_max;
+	      cs_prg->sum_max += cs_tprg->run_max;
+	    }
+	  else if (cs_prg->runs)
+	    goto read_mismatch;
+
+	  if (!cs_all->runs && cs_prg->runs)
+	    memcpy (cs_all, cs_prg, sizeof (*cs_all));
+	  else if (!all_prg.checksum
+		   && (!GCOV_LOCKED || cs_all->runs == cs_prg->runs)
+		   && memcmp (cs_all, cs_prg, sizeof (*cs_all)))
+	    {
+	      fprintf (stderr, "profiling:%s:Invocation mismatch - some data files may have been removed%s\n",
+		       gi_filename, GCOV_LOCKED
+		       ? "" : " or concurrently updated without locking support");
+	      all_prg.checksum = ~0u;
+	    }
+	}
+
+      prg.checksum = crc32;
+
+      /* Write out the data.  */
+      if (!eof_pos)
+	{
+	  gcov_write_tag_length (GCOV_DATA_MAGIC, GCOV_VERSION);
+	  gcov_write_unsigned (gi_ptr->stamp);
+	}
+
+      if (summary_pos)
+	gcov_seek (summary_pos);
+
+      /* Generate whole program statistics.  */
+      gcov_write_summary (GCOV_TAG_PROGRAM_SUMMARY, &prg);
+
+      if (summary_pos < eof_pos)
+	gcov_seek (eof_pos);
+
+      /* Write execution counts for each function.  */
+      for (f_ix = 0; (unsigned)f_ix != gi_ptr->n_functions; f_ix++)
+	{
+	  unsigned buffered = 0;
+
+	  if (fn_buffer && fn_buffer->fn_ix == (unsigned)f_ix)
+	    {
+	      /* Buffered data from another program.  */
+	      buffered = 1;
+	      gfi_ptr = &fn_buffer->info;
+	      length = GCOV_TAG_FUNCTION_LENGTH;
+	    }
+	  else
+	    {
+	      gfi_ptr = gi_ptr->functions[f_ix];
+	      if (gfi_ptr && gfi_ptr->key == gi_ptr)
+		length = GCOV_TAG_FUNCTION_LENGTH;
+	      else
+		length = 0;
+	    }
+	  
+	  gcov_write_tag_length (GCOV_TAG_FUNCTION, length);
+	  if (!length)
+	    continue;
+	  
+	  gcov_write_unsigned (gfi_ptr->ident);
+	  gcov_write_unsigned (gfi_ptr->lineno_checksum);
+	  gcov_write_unsigned (gfi_ptr->cfg_checksum);
+
+	  ci_ptr = gfi_ptr->ctrs;
+	  for (t_ix = 0; t_ix < GCOV_COUNTERS; t_ix++)
+	    {
+	      if (!gi_ptr->merge[t_ix])
+		continue;
+
+	      n_counts = ci_ptr->num;
+	      gcov_write_tag_length (GCOV_TAG_FOR_COUNTER (t_ix),
+				     GCOV_TAG_COUNTER_LENGTH (n_counts));
+	      gcov_type *c_ptr = ci_ptr->values;
+	      while (n_counts--)
+		gcov_write_counter (*c_ptr++);
+	      ci_ptr++;
+	    }
+	  if (buffered)
+	    fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS);
+	}
+
+      gcov_write_unsigned (0);
+
+    read_fatal:;
+      while (fn_buffer)
+	fn_buffer = free_fn_data (gi_ptr, fn_buffer, GCOV_COUNTERS);
+
+      if ((error = gcov_close ()))
+	  fprintf (stderr, error  < 0 ?
+		   "profiling:%s:Overflow writing\n" :
+		   "profiling:%s:Error writing\n",
+		   gi_filename);
+    }
+}
+
+/* Add a new object file onto the bb chain.  Invoked automatically
+   when running an object file's global ctors.  */
+
+void
+__gcov_init (struct gcov_info *info)
+{
+  if (!info->version || !info->n_functions)
+    return;
+  if (gcov_version (info, info->version, 0))
+    {
+      size_t filename_length = strlen(info->filename);
+
+      /* Refresh the longest file name information */
+      if (filename_length > gcov_max_filename)
+        gcov_max_filename = filename_length;
+
+#ifndef __COREBOOT__
+      if (!gcov_list)
+	atexit (gcov_exit);
+#endif
+
+      info->next = gcov_list;
+      gcov_list = info;
+    }
+  info->version = 0;
+}
+
+/* Called before fork or exec - write out profile information gathered so
+   far and reset it to zero.  This avoids duplication or loss of the
+   profile information gathered so far.  */
+
+void
+__gcov_flush (void)
+{
+  const struct gcov_info *gi_ptr;
+
+  gcov_exit ();
+  for (gi_ptr = gcov_list; gi_ptr; gi_ptr = gi_ptr->next)
+    {
+      unsigned f_ix;
+
+      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];
+
+	  if (!gfi_ptr || gfi_ptr->key != gi_ptr)
+	    continue;
+	  const struct gcov_ctr_info *ci_ptr = gfi_ptr->ctrs;
+	  for (t_ix = 0; t_ix != GCOV_COUNTERS; t_ix++)
+	    {
+	      if (!gi_ptr->merge[t_ix])
+		continue;
+	      
+	      memset (ci_ptr->values, 0, sizeof (gcov_type) * ci_ptr->num);
+	      ci_ptr++;
+	    }
+	}
+    }
+}
+
+#endif /* L_gcov */
+
+#ifdef L_gcov_merge_add
+/* The profile merging function that just adds the counters.  It is given
+   an array COUNTERS of N_COUNTERS old counters and it reads the same number
+   of counters from the gcov file.  */
+void
+__gcov_merge_add (gcov_type *counters, unsigned n_counters)
+{
+  for (; n_counters; counters++, n_counters--)
+    *counters += gcov_read_counter ();
+}
+#endif /* L_gcov_merge_add */
+
+#ifdef L_gcov_merge_ior
+/* The profile merging function that just adds the counters.  It is given
+   an array COUNTERS of N_COUNTERS old counters and it reads the same number
+   of counters from the gcov file.  */
+void
+__gcov_merge_ior (gcov_type *counters, unsigned n_counters)
+{
+  for (; n_counters; counters++, n_counters--)
+    *counters |= gcov_read_counter ();
+}
+#endif
+
+#ifdef L_gcov_merge_single
+/* The profile merging function for choosing the most common value.
+ * It is given an array COUNTERS of N_COUNTERS old counters and it
+ * reads the same number of counters from the gcov file.  The counters
+ * are split into 3-tuples where the members of the tuple have
+ * meanings:
+ *
+ * -- the stored candidate on the most common value of the measured entity
+ * -- counter
+ * -- total number of evaluations of the value
+ */
+void
+__gcov_merge_single (gcov_type *counters, unsigned n_counters)
+{
+  unsigned i, n_measures;
+  gcov_type value, counter, all;
+
+  gcc_assert (!(n_counters % 3));
+  n_measures = n_counters / 3;
+  for (i = 0; i < n_measures; i++, counters += 3)
+    {
+      value = gcov_read_counter ();
+      counter = gcov_read_counter ();
+      all = gcov_read_counter ();
+
+      if (counters[0] == value)
+	counters[1] += counter;
+      else if (counter > counters[1])
+	{
+	  counters[0] = value;
+	  counters[1] = counter - counters[1];
+	}
+      else
+	counters[1] -= counter;
+      counters[2] += all;
+    }
+}
+#endif /* L_gcov_merge_single */
+
+#ifdef L_gcov_merge_delta
+/* The profile merging function for choosing the most common
+   difference between two consecutive evaluations of the value.  It is
+   given an array COUNTERS of N_COUNTERS old counters and it reads the
+   same number of counters from the gcov file.  The counters are split
+   into 4-tuples where the members of the tuple have meanings:
+
+   -- the last value of the measured entity
+   -- the stored candidate on the most common difference
+   -- counter
+   -- total number of evaluations of the value  */
+void
+__gcov_merge_delta (gcov_type *counters, unsigned n_counters)
+{
+  unsigned i, n_measures;
+  gcov_type value, counter, all;
+
+  gcc_assert (!(n_counters % 4));
+  n_measures = n_counters / 4;
+  for (i = 0; i < n_measures; i++, counters += 4)
+    {
+      /* last = */ gcov_read_counter ();
+      value = gcov_read_counter ();
+      counter = gcov_read_counter ();
+      all = gcov_read_counter ();
+
+      if (counters[1] == value)
+	counters[2] += counter;
+      else if (counter > counters[2])
+	{
+	  counters[1] = value;
+	  counters[2] = counter - counters[2];
+	}
+      else
+	counters[2] -= counter;
+      counters[3] += all;
+    }
+}
+#endif /* L_gcov_merge_delta */
+
+#ifdef L_gcov_interval_profiler
+/* If VALUE is in interval <START, START + STEPS - 1>, then increases the
+   corresponding counter in COUNTERS.  If the VALUE is above or below
+   the interval, COUNTERS[STEPS] or COUNTERS[STEPS + 1] is increased
+   instead.  */
+
+void
+__gcov_interval_profiler (gcov_type *counters, gcov_type value,
+			  int start, unsigned steps)
+{
+  gcov_type delta = value - start;
+  if (delta < 0)
+    counters[steps + 1]++;
+  else if (delta >= steps)
+    counters[steps]++;
+  else
+    counters[delta]++;
+}
+#endif
+
+#ifdef L_gcov_pow2_profiler
+/* If VALUE is a power of two, COUNTERS[1] is incremented.  Otherwise
+   COUNTERS[0] is incremented.  */
+
+void
+__gcov_pow2_profiler (gcov_type *counters, gcov_type value)
+{
+  if (value & (value - 1))
+    counters[0]++;
+  else
+    counters[1]++;
+}
+#endif
+
+/* Tries to determine the most common value among its inputs.  Checks if the
+   value stored in COUNTERS[0] matches VALUE.  If this is the case, COUNTERS[1]
+   is incremented.  If this is not the case and COUNTERS[1] is not zero,
+   COUNTERS[1] is decremented.  Otherwise COUNTERS[1] is set to one and
+   VALUE is stored to COUNTERS[0].  This algorithm guarantees that if this
+   function is called more than 50% of the time with one value, this value
+   will be in COUNTERS[0] in the end.
+
+   In any case, COUNTERS[2] is incremented.  */
+
+static inline void
+__gcov_one_value_profiler_body (gcov_type *counters, gcov_type value)
+{
+  if (value == counters[0])
+    counters[1]++;
+  else if (counters[1] == 0)
+    {
+      counters[1] = 1;
+      counters[0] = value;
+    }
+  else
+    counters[1]--;
+  counters[2]++;
+}
+
+#ifdef L_gcov_one_value_profiler
+void
+__gcov_one_value_profiler (gcov_type *counters, gcov_type value)
+{
+  __gcov_one_value_profiler_body (counters, value);
+}
+#endif
+
+#ifdef L_gcov_indirect_call_profiler
+
+/* By default, the C++ compiler will use function addresses in the
+   vtable entries.  Setting TARGET_VTABLE_USES_DESCRIPTORS to nonzero
+   tells the compiler to use function descriptors instead.  The value
+   of this macro says how many words wide the descriptor is (normally 2),
+   but it may be dependent on target flags.  Since we do not have access
+   to the target flags here we just check to see if it is set and use
+   that to set VTABLE_USES_DESCRIPTORS to 0 or 1.
+
+   It is assumed that the address of a function descriptor may be treated
+   as a pointer to a function.  */
+
+#ifdef TARGET_VTABLE_USES_DESCRIPTORS
+#define VTABLE_USES_DESCRIPTORS 1
+#else
+#define VTABLE_USES_DESCRIPTORS 0
+#endif
+
+/* Tries to determine the most common value among its inputs. */
+void
+__gcov_indirect_call_profiler (gcov_type* counter, gcov_type value,
+			       void* cur_func, void* callee_func)
+{
+  /* If the C++ virtual tables contain function descriptors then one
+     function may have multiple descriptors and we need to dereference
+     the descriptors to see if they point to the same function.  */
+  if (cur_func == callee_func
+      || (VTABLE_USES_DESCRIPTORS && callee_func
+	  && *(void **) cur_func == *(void **) callee_func))
+    __gcov_one_value_profiler_body (counter, value);
+}
+#endif
+
+
+#ifdef L_gcov_average_profiler
+/* Increase corresponding COUNTER by VALUE.  FIXME: Perhaps we want
+   to saturate up.  */
+
+void
+__gcov_average_profiler (gcov_type *counters, gcov_type value)
+{
+  counters[0] += value;
+  counters[1] ++;
+}
+#endif
+
+#ifdef L_gcov_ior_profiler
+/* Increase corresponding COUNTER by VALUE.  FIXME: Perhaps we want
+   to saturate up.  */
+
+void
+__gcov_ior_profiler (gcov_type *counters, gcov_type value)
+{
+  *counters |= value;
+}
+#endif
+
+#ifdef L_gcov_fork
+/* A wrapper for the fork function.  Flushes the accumulated profiling data, so
+   that they are not counted twice.  */
+
+pid_t
+__gcov_fork (void)
+{
+  __gcov_flush ();
+  return fork ();
+}
+#endif
+
+#ifdef L_gcov_execl
+/* A wrapper for the execl function.  Flushes the accumulated profiling data, so
+   that they are not lost.  */
+
+int
+__gcov_execl (const char *path, char *arg, ...)
+{
+  va_list ap, aq;
+  unsigned i, length;
+  char **args;
+
+  __gcov_flush ();
+
+  va_start (ap, arg);
+  va_copy (aq, ap);
+
+  length = 2;
+  while (va_arg (ap, char *))
+    length++;
+  va_end (ap);
+
+  args = (char **) alloca (length * sizeof (void *));
+  args[0] = arg;
+  for (i = 1; i < length; i++)
+    args[i] = va_arg (aq, char *);
+  va_end (aq);
+
+  return execv (path, args);
+}
+#endif
+
+#ifdef L_gcov_execlp
+/* A wrapper for the execlp function.  Flushes the accumulated profiling data, so
+   that they are not lost.  */
+
+int
+__gcov_execlp (const char *path, char *arg, ...)
+{
+  va_list ap, aq;
+  unsigned i, length;
+  char **args;
+
+  __gcov_flush ();
+
+  va_start (ap, arg);
+  va_copy (aq, ap);
+
+  length = 2;
+  while (va_arg (ap, char *))
+    length++;
+  va_end (ap);
+
+  args = (char **) alloca (length * sizeof (void *));
+  args[0] = arg;
+  for (i = 1; i < length; i++)
+    args[i] = va_arg (aq, char *);
+  va_end (aq);
+
+  return execvp (path, args);
+}
+#endif
+
+#ifdef L_gcov_execle
+/* A wrapper for the execle function.  Flushes the accumulated profiling data, so
+   that they are not lost.  */
+
+int
+__gcov_execle (const char *path, char *arg, ...)
+{
+  va_list ap, aq;
+  unsigned i, length;
+  char **args;
+  char **envp;
+
+  __gcov_flush ();
+
+  va_start (ap, arg);
+  va_copy (aq, ap);
+
+  length = 2;
+  while (va_arg (ap, char *))
+    length++;
+  va_end (ap);
+
+  args = (char **) alloca (length * sizeof (void *));
+  args[0] = arg;
+  for (i = 1; i < length; i++)
+    args[i] = va_arg (aq, char *);
+  envp = va_arg (aq, char **);
+  va_end (aq);
+
+  return execve (path, args, envp);
+}
+#endif
+
+#ifdef L_gcov_execv
+/* A wrapper for the execv function.  Flushes the accumulated profiling data, so
+   that they are not lost.  */
+
+int
+__gcov_execv (const char *path, char *const argv[])
+{
+  __gcov_flush ();
+  return execv (path, argv);
+}
+#endif
+
+#ifdef L_gcov_execvp
+/* A wrapper for the execvp function.  Flushes the accumulated profiling data, so
+   that they are not lost.  */
+
+int
+__gcov_execvp (const char *path, char *const argv[])
+{
+  __gcov_flush ();
+  return execvp (path, argv);
+}
+#endif
+
+#ifdef L_gcov_execve
+/* A wrapper for the execve function.  Flushes the accumulated profiling data, so
+   that they are not lost.  */
+
+int
+__gcov_execve (const char *path, char *const argv[], char *const envp[])
+{
+  __gcov_flush ();
+  return execve (path, argv, envp);
+}
+#endif
+#endif /* inhibit_libc */
diff --git a/src/lib/selfboot.c b/src/lib/selfboot.c
index 2556a14..c5fb62a 100644
--- a/src/lib/selfboot.c
+++ b/src/lib/selfboot.c
@@ -32,6 +32,7 @@
 #if CONFIG_COLLECT_TIMESTAMPS
 #include <timestamp.h>
 #endif
+#include <coverage.h>
 
 /* Maximum physical address we can use for the coreboot bounce buffer. */
 #ifndef MAX_ADDR
@@ -518,6 +519,9 @@ int selfboot(struct lb_memory *mem, struct cbfs_payload *payload)
 #if CONFIG_COLLECT_TIMESTAMPS
 	timestamp_add_now(TS_SELFBOOT_JUMP);
 #endif
+#if CONFIG_COVERAGE
+	coverage_exit();
+#endif
 
 	/* Before we go off to run the payload, see if
 	 * we stayed within our bounds.



More information about the coreboot mailing list