summaryrefslogtreecommitdiffstats
path: root/src/core/fnrec.c
blob: 888a4a2772e3023f3f14e23fe0982b4a80c6ed7c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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 <ipxe/init.h>
#include <ipxe/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 ) {
}