Stefan Reinauer (stefan.reinauer@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@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@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 *)¤t_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@cygnus.com. + Completely remangled by Nathan Sidwell nathan@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@cygnus.com. + Completely remangled by Nathan Sidwell nathan@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.