summaryrefslogtreecommitdiffstats
path: root/qemu/roms/ipxe/src/core/fnrec.c
blob: 0430817f876bb751d56c32d7b12fa3ff29cf1e10 (plain)
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * You can also choose to distribute this program under the terms of
 * the Unmodified Binary Distribution Licence (as given in the file
 * COPYING.UBDL), provided that you have satisfied its requirements.
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ipxe/init.h>
#include <ipxe/uaccess.h>
#include <ipxe/io.h>

/** @file
 *
 * Function trace recorder for crash and hang debugging
 *
 */

/** Constant for identifying valid trace buffers */
#define FNREC_MAGIC ( 'f' << 24 | 'n' << 16 | 'r' << 8 | 'e' )

/** Number of trace buffer entries */
#define FNREC_NUM_ENTRIES 4096

/** Trace buffer physical address
 *
 * Fixed at 17MB
 */
#define FNREC_PHYS_ADDRESS ( 17 * 1024 * 1024 )

/** A trace buffer entry */
struct fnrec_entry {
	/** Called function address */
	void *called_fn;
	/** Call site */
	void *call_site;
	/** Entry count */
	uint16_t entry_count;
	/** Exit count */
	uint16_t exit_count;
	/** Checksum */
	unsigned long checksum;
};

/** A trace buffer */
struct fnrec_buffer {
	/** Constant for identifying valid trace buffers */
	uint32_t magic;

	/** Next trace buffer entry to fill */
	unsigned int idx;

	/** Trace buffer */
	struct fnrec_entry data[FNREC_NUM_ENTRIES]
		__attribute__ (( aligned ( 64 ) ));
};

/** 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 ) );
}

/**
 * Invalidate the trace buffer
 *
 */
static void fnrec_invalidate ( void ) {
	fnrec_buffer->magic = 0;
}

/**
 * Reset the trace buffer and clear entries
 */
static void fnrec_reset ( void ) {
	memset ( fnrec_buffer, 0, sizeof ( *fnrec_buffer ) );
	fnrec_buffer->magic = FNREC_MAGIC;
}

/**
 * Append an entry to the trace buffer
 *
 * @v called_fn		Called function
 * @v call_site		Call site
 * @ret entry		Trace buffer entry
 */
static struct fnrec_entry * fnrec_append ( void *called_fn, void *call_site ) {
	struct fnrec_entry *entry;

	/* Re-use existing entry, if possible */
	entry = &fnrec_buffer->data[ fnrec_buffer->idx ];
	if ( ( entry->called_fn == called_fn ) &&
	     ( entry->call_site == call_site ) &&
	     ( entry->entry_count >= entry->exit_count ) ) {
		return entry;
	}

	/* Otherwise, create a new entry */
	fnrec_buffer->idx = ( ( fnrec_buffer->idx + 1 ) % FNREC_NUM_ENTRIES );
	entry = &fnrec_buffer->data[ fnrec_buffer->idx ];
	entry->called_fn = called_fn;
	entry->call_site = call_site;
	entry->entry_count = 0;
	entry->exit_count = 0;
	entry->checksum = ( ( ( unsigned long ) called_fn ) ^
			    ( ( unsigned long ) call_site ) );
	return entry;
}

/**
 * Print the contents of the trace buffer in chronological order
 */
static void fnrec_dump ( void ) {
	struct fnrec_entry *entry;
	unsigned int i;
	unsigned int idx;
	unsigned long checksum;

	printf ( "fnrec buffer dump:\n" );
	for ( i = 1 ; i <= FNREC_NUM_ENTRIES ; i++ ) {
		idx = ( ( fnrec_buffer->idx + i ) % FNREC_NUM_ENTRIES );
		entry = &fnrec_buffer->data[idx];
		if ( ( entry->entry_count == 0 ) && ( entry->exit_count == 0 ) )
			continue;
		checksum = ( ( ( ( unsigned long ) entry->called_fn ) ^
			       ( ( unsigned long ) entry->call_site ) ) +
			     entry->entry_count + entry->exit_count );
		printf ( "%p %p %d %d", entry->called_fn, entry->call_site,
			 entry->entry_count, entry->exit_count );
		if ( entry->checksum != checksum ) {
			printf ( " (checksum wrong at phys %08lx)",
				 virt_to_phys ( entry ) );
		}
		printf ( "\n");
	}
}

/**
 * Function tracer initialisation function
 */
static void fnrec_init ( void ) {

	fnrec_buffer = phys_to_virt ( FNREC_PHYS_ADDRESS );
	if ( fnrec_is_valid() ) {
		fnrec_invalidate();
		fnrec_dump();
	} else {
		printf ( "fnrec buffer not found\n" );
	}
	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 ) {
	struct fnrec_entry *entry;

	if ( fnrec_is_valid() ) {
		entry = fnrec_append ( called_fn, call_site );
		entry->entry_count++;
		entry->checksum++;
		mb();
	}
}

void __cyg_profile_func_exit ( void *called_fn, void *call_site ) {
	struct fnrec_entry *entry;

	if ( fnrec_is_valid() ) {
		entry = fnrec_append ( called_fn, call_site );
		entry->exit_count++;
		entry->checksum++;
		mb();
	}
}