/* File format for coverage information Copyright (C) 1996, 1997, 1998, 2000, 2002, 2003 Free Software Foundation, Inc. Contributed by Bob Manson . Completely remangled by Nathan Sidwell . 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 2, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Routines declared in gcov-io.h. This file should be #included by another source file, after having #included gcov-io.h. */ #if !IN_GCOV static void gcov_write_block (unsigned); static unsigned char *gcov_write_bytes (unsigned); #endif static const unsigned char *gcov_read_bytes (unsigned); #if !IN_LIBGCOV static void gcov_allocate (unsigned); #endif /* 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 for modification. 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. Return zero on failure, >0 on opening an existing file and <0 on creating a new one. */ GCOV_LINKAGE int gcov_open (const char *name, int mode) { int result = 1; #if GCOV_LOCKED struct flock s_flock; s_flock.l_type = F_WRLCK; s_flock.l_whence = SEEK_SET; s_flock.l_start = 0; s_flock.l_len = 0; /* Until EOF. */ s_flock.l_pid = getpid (); #endif if (gcov_var.file) abort (); gcov_var.start = 0; gcov_var.offset = gcov_var.length = 0; gcov_var.overread = -4u; gcov_var.error = 0; if (mode >= 0) gcov_var.file = fopen (name, "r+b"); if (gcov_var.file) gcov_var.mode = 1; else if (mode <= 0) { result = -1; gcov_var.file = fopen (name, "w+b"); if (gcov_var.file) gcov_var.mode = -1; } if (!gcov_var.file) return 0; setbuf (gcov_var.file, (char *)0); #if GCOV_LOCKED while (fcntl (fileno (gcov_var.file), F_SETLKW, &s_flock) && errno == EINTR) continue; #endif return result; } /* 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 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 = xrealloc (gcov_var.buffer, new_size); } #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, 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 unsigned char * gcov_write_bytes (unsigned bytes) { char unsigned *result; GCOV_CHECK_WRITING (); #if IN_LIBGCOV if (gcov_var.offset >= GCOV_BLOCK_SIZE) { gcov_write_block (GCOV_BLOCK_SIZE); if (gcov_var.offset) { GCOV_CHECK (gcov_var.offset == 4); memcpy (gcov_var.buffer, gcov_var.buffer + GCOV_BLOCK_SIZE, 4); } } #else if (gcov_var.offset + bytes > gcov_var.alloc) gcov_allocate (gcov_var.offset + bytes); #endif result = &gcov_var.buffer[gcov_var.offset]; gcov_var.offset += bytes; return result; } /* Write unsigned VALUE to coverage file. Sets error flag appropriately. */ GCOV_LINKAGE void gcov_write_unsigned (gcov_unsigned_t value) { unsigned char *buffer = gcov_write_bytes (4); unsigned ix; for (ix = 4; ix--; ) { buffer[ix] = value; value >>= 8; } if (sizeof (value) > 4 && value) gcov_var.error = -1; return; } /* Write counter VALUE to coverage file. Sets error flag appropriately. */ #if IN_LIBGCOV GCOV_LINKAGE void gcov_write_counter (gcov_type value) { unsigned char *buffer = gcov_write_bytes (8); unsigned ix; for (ix = 8; ix--; ) { buffer[ix] = value; value >>= 8; } if ((sizeof (value) > 8 && value) || value < 0) gcov_var.error = -1; return; } #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 pad = 0; unsigned rem = 0; unsigned char *buffer; unsigned ix; unsigned value; if (string) { length = strlen (string); rem = 4 - (length & 3); } buffer = gcov_write_bytes (4 + length + rem); value = length; for (ix = 4; ix--; ) { buffer[ix] = value; value >>= 8; } memcpy (buffer + 4, string, length); memcpy (buffer + 4 + length, &pad, rem); } #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; unsigned char *buffer = gcov_write_bytes (8); unsigned ix; for (ix = 4; ix--; ) { buffer[ix] = tag; tag >>= 8; } memset (buffer + 4, 0, 4); 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; unsigned char *buffer; unsigned ix; GCOV_CHECK_WRITING (); GCOV_CHECK (position + 8 <= gcov_var.start + gcov_var.offset); GCOV_CHECK (position >= gcov_var.start); offset = position - gcov_var.start; length = gcov_var.offset - offset - 8; buffer = &gcov_var.buffer[offset + 4]; for (ix = 4; ix--; ) { buffer[ix] = length; length >>= 8; } 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) { unsigned char *buffer = gcov_write_bytes (8); unsigned ix; for (ix = 4; ix--; ) { buffer[ix] = tag; tag >>= 8; } for (ix = 4; ix--; ) { buffer[ix + 4] = length; length >>= 8; } return; } /* 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 unsigned char * gcov_read_bytes (unsigned bytes) { const unsigned char *result; unsigned excess = gcov_var.length - gcov_var.offset; GCOV_CHECK_READING (); if (excess < bytes) { gcov_var.start += gcov_var.offset; #if IN_LIBGCOV if (excess) { GCOV_CHECK (excess == 4); memcpy (gcov_var.buffer, gcov_var.buffer + gcov_var.offset, 4); } #else memmove (gcov_var.buffer, gcov_var.buffer + gcov_var.offset, excess); #endif gcov_var.offset = 0; gcov_var.length = excess; #if IN_LIBGCOV GCOV_CHECK (!gcov_var.length || gcov_var.length == 4); excess = GCOV_BLOCK_SIZE; #else if (gcov_var.length + bytes > gcov_var.alloc) gcov_allocate (gcov_var.length + bytes); excess = gcov_var.alloc - gcov_var.length; #endif excess = fread (gcov_var.buffer + gcov_var.length, 1, excess, gcov_var.file); gcov_var.length += excess; if (gcov_var.length < bytes) { gcov_var.overread += bytes - gcov_var.length; gcov_var.length = 0; return 0; } } result = &gcov_var.buffer[gcov_var.offset]; gcov_var.offset += bytes; 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 = 0; unsigned ix; const unsigned char *buffer = gcov_read_bytes (4); if (!buffer) return 0; for (ix = sizeof (value); ix < 4; ix++) if (buffer[ix]) gcov_var.error = -1; for (ix = 0; ix != 4; ix++) { value <<= 8; value |= buffer[ix]; } 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 = 0; unsigned ix; const unsigned char *buffer = gcov_read_bytes (8); if (!buffer) return 0; for (ix = sizeof (value); ix < 8; ix++) if (buffer[ix]) gcov_var.error = -1; for (ix = 0; ix != 8; ix++) { value <<= 8; value |= buffer[ix]; } if (value < 0) 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; length += 4 - (length & 3); return (const char *) gcov_read_bytes (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) { GCOV_CHECK_READING (); 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, SEEK_SET); gcov_var.start = ftell (gcov_var.file); } } #endif #if IN_LIBGCOV /* Move to the a set position in a gcov file. BASE is zero to move to the end, and nonzero to move to that position. */ GCOV_LINKAGE void gcov_seek (gcov_position_t base) { GCOV_CHECK_WRITING (); if (gcov_var.offset) gcov_write_block (gcov_var.offset); fseek (gcov_var.file, base, base ? SEEK_SET : SEEK_END); gcov_var.start = ftell (gcov_var.file); } #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 */