[fnrec] Add function recorder for debugging
authorStefan Hajnoczi <stefanha@gmail.com>
Fri, 5 Feb 2010 22:15:18 +0000 (22:15 +0000)
committerMarty Connor <mdc@etherboot.org>
Thu, 4 Mar 2010 16:38:29 +0000 (11:38 -0500)
The function recorder is a crash and hang debugging tool.  It logs each
function call into a memory buffer while gPXE runs.  After the machine
is reset, and if the contents of memory have not been overwritten, gPXE
will detect the memory buffer and print out its contents.

This allows developers to see a trace of the last functions called
before a crash or hang.  The util/fnrec.sh script can be used to convert
the function addresses back into symbol names.

To build with fnrec:

    make FNREC=1

Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
Signed-off-by: Marty Connor <mdc@etherboot.org>
src/Makefile.housekeeping
src/core/fnrec.c [new file with mode: 0644]
src/util/fnrec.sh [new file with mode: 0755]

index 8ba7e44..7e7ad76 100644 (file)
@@ -375,6 +375,35 @@ CFLAGS             += -Werror
 ASFLAGS                += --fatal-warnings
 endif
 
+# Function trace recorder state in the last build.  This is needed
+# in order to correctly rebuild whenever the function recorder is
+# enabled/disabled.
+#
+FNREC_STATE    := $(BIN)/.fnrec.state
+ifeq ($(wildcard $(FNREC_STATE)),)
+FNREC_STATE_OLD := <invalid>
+else
+FNREC_STATE_OLD        := $(shell cat $(FNREC_STATE))
+endif
+ifeq ($(FNREC_STATE_OLD),$(FNREC))
+$(FNREC_STATE) :
+else
+$(FNREC_STATE) : clean
+$(shell $(ECHO) "$(FNREC)" > $(FNREC_STATE))
+endif
+
+VERYCLEANUP    += $(FNREC_STATE)
+MAKEDEPS       += $(FNREC_STATE)
+
+ifeq ($(FNREC),1)
+# Enabling -finstrument-functions affects gcc's analysis and leads to spurious
+# warnings about use of uninitialised variables.
+#
+CFLAGS         += -Wno-uninitialized
+CFLAGS         += -finstrument-functions
+CFLAGS         += -finstrument-functions-exclude-file-list=core/fnrec.c
+endif
+
 # compiler.h is needed for our linking and debugging system
 #
 CFLAGS         += -include compiler.h
diff --git a/src/core/fnrec.c b/src/core/fnrec.c
new file mode 100644 (file)
index 0000000..c768c91
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2010 Stefan Hajnoczi <stefanha@gmail.com>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <gpxe/init.h>
+#include <gpxe/uaccess.h>
+
+/** @file
+ *
+ * Function trace recorder for crash and hang debugging
+ *
+ */
+
+enum {
+       /** Constant for identifying valid trace buffers */
+       fnrec_magic = 'f' << 24 | 'n' << 16 | 'r' << 8 | 'e',
+
+       /** Trace buffer length */
+       fnrec_buffer_length = 4096 / sizeof ( unsigned long ),
+};
+
+/** A trace buffer */
+struct fnrec_buffer {
+       /** Constant for identifying valid trace buffers */
+       uint32_t magic;
+
+       /** Next trace buffer entry to fill */
+       uint32_t idx;
+
+       /** Function address trace buffer */
+       unsigned long data[fnrec_buffer_length];
+};
+
+/** The trace buffer */
+static struct fnrec_buffer *fnrec_buffer;
+
+/**
+ * Test whether the trace buffer is valid
+ *
+ * @ret is_valid       Buffer is valid
+ */
+static int fnrec_is_valid ( void ) {
+       return fnrec_buffer && fnrec_buffer->magic == fnrec_magic;
+}
+
+/**
+ * Reset the trace buffer and clear entries
+ */
+static void fnrec_reset ( void ) {
+       memset ( fnrec_buffer, 0, sizeof ( *fnrec_buffer ) );
+       fnrec_buffer->magic = fnrec_magic;
+}
+
+/**
+ * Write a value to the end of the buffer if it is not a repetition
+ *
+ * @v l                        Value to append
+ */
+static void fnrec_append_unique ( unsigned long l ) {
+       static unsigned long lastval;
+       uint32_t idx = fnrec_buffer->idx;
+
+       /* Avoid recording the same value repeatedly */
+       if ( l == lastval )
+               return;
+
+       fnrec_buffer->data[idx] = l;
+       fnrec_buffer->idx = ( idx + 1 ) % fnrec_buffer_length;
+       lastval = l;
+}
+
+/**
+ * Print the contents of the trace buffer in chronological order
+ */
+static void fnrec_dump ( void ) {
+       size_t i;
+
+       if ( !fnrec_is_valid() ) {
+               printf ( "fnrec buffer not found\n" );
+               return;
+       }
+
+       printf ( "fnrec buffer dump:\n" );
+       for ( i = 0; i < fnrec_buffer_length; i++ ) {
+               unsigned long l = fnrec_buffer->data[
+                       ( fnrec_buffer->idx + i ) % fnrec_buffer_length];
+               printf ( "%08lx%c", l, i % 8 == 7 ? '\n' : ' ' );
+       }
+}
+
+/**
+ * Function tracer initialisation function
+ */
+static void fnrec_init ( void ) {
+       /* Hardcoded to 17 MB */
+       fnrec_buffer = phys_to_virt ( 17 * 1024 * 1024 );
+       fnrec_dump();
+       fnrec_reset();
+}
+
+struct init_fn fnrec_init_fn __init_fn ( INIT_NORMAL ) = {
+       .initialise = fnrec_init,
+};
+
+/*
+ * These functions are called from every C function.  The compiler inserts
+ * these calls when -finstrument-functions is used.
+ */
+void __cyg_profile_func_enter ( void *called_fn, void *call_site __unused ) {
+       if ( fnrec_is_valid() )
+               fnrec_append_unique ( ( unsigned long ) called_fn );
+}
+
+void __cyg_profile_func_exit ( void *called_fn __unused, void *call_site __unused ) {
+}
diff --git a/src/util/fnrec.sh b/src/util/fnrec.sh
new file mode 100755 (executable)
index 0000000..00784ac
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Copyright (C) 2010 Stefan Hajnoczi <stefanha@gmail.com>.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+if [ $# != 2 ]
+then
+       cat >&2 <<EOF
+usage: $0 <elf-binary> <addresses-file>
+Look up symbol names in <elf-binary> for function addresses from
+<addresses-file>.
+
+Example:
+$0 bin/gpxe.hd.tmp fnrec.dat
+EOF
+       exit 1
+fi
+
+tr ' ' '\n' <"$2" | addr2line -fe "$1" | awk '(NR % 2) { print }'