]>
Commit | Line | Data |
---|---|---|
189935b1 | 1 | /* |
2 | ** Copyright (C) 2002 by Kevin L. Mitchell <klmitch@mit.edu> | |
3 | ** | |
4 | ** This program is free software; you can redistribute it and/or modify | |
5 | ** it under the terms of the GNU General Public License as published by | |
6 | ** the Free Software Foundation; either version 2 of the License, or | |
7 | ** (at your option) any later version. | |
8 | ** | |
9 | ** This program is distributed in the hope that it will be useful, | |
10 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | ** GNU General Public License for more details. | |
13 | ** | |
14 | ** You should have received a copy of the GNU General Public License | |
15 | ** along with this program; if not, write to the Free Software | |
16 | ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
17 | ** | |
18 | ** @(#)$Id: ringlog.c,v 1.4 2004/07/01 12:38:28 entrope Exp $ | |
19 | */ | |
20 | /* | |
21 | * This file contains two separate pieces, along with some common | |
22 | * gunk. If RINGLOG_INSTRUMENT is defined, the two special functions | |
23 | * __cyg_profile_func_enter() and __cyg_profile_func_exit() are | |
24 | * defined; otherwise a main function is defined. The common gunk is | |
25 | * init_log(), which opens and, if necessary, initializes a special | |
26 | * binary log file that is treated as a ring buffer (to prevent the | |
27 | * file from growing unboundedly). | |
28 | * | |
29 | * The object produced when compiled with RINGLOG_INSTRUMENT is | |
30 | * designed to work with the special gcc option | |
31 | * -finstrument-functions; this option causes the | |
32 | * __cyg_profile_func_*() functions mentioned above to be called when | |
33 | * a function is entered or exited. (Of course, ringlog.o should | |
34 | * *not* be compiled with this option.) These functions will in turn | |
35 | * call store_entry(), which will call init_log() as needed to open | |
36 | * the log file, ensure that a start record is output, and then will | |
37 | * store records for the function calls. The log file used is | |
38 | * "call.ringlog" in the directory from which the program was | |
39 | * started. | |
40 | * | |
41 | * When RINGLOG_INSTRUMENT is *not* defined while building, a main | |
42 | * function is defined, and the result is an executable for | |
43 | * interpretation of a ringlog. Usage is very simple: All arguments | |
44 | * not beginning with '-' are treated as files to open, and all | |
45 | * arguments beginning with '-' are treated as a specification for the | |
46 | * number of entries new files should be created with. If this | |
47 | * specification is 0 (which it is by default), files will not be | |
48 | * created if they do not already exist. | |
49 | * | |
50 | * For every filename argument, at least one line will be printed | |
51 | * out. If the file is not empty, the entries in the file will be | |
52 | * printed out, one to a line. Each entry is numbered with a logical | |
53 | * number. The entry numbers are followed by a two word description | |
54 | * of the entry type ("Log start," "Function entry," "Function exit," | |
55 | * and "Invalid entry"), followed by a colon (":"), followed by the | |
56 | * word "addr" and the address of the function, followed by the word | |
57 | * "call" and the address from which the function was called. The | |
58 | * ringlog program is not able to convert these addresses to symbols | |
59 | * or file and line numbers--that can be done with a program like | |
60 | * addr2line (part of the binutils package). The output has been | |
61 | * carefully contrived to be parsable by a script. | |
62 | * | |
63 | * The file format is documented below. Note that data is stored in | |
64 | * host byte order. | |
65 | * | |
66 | * 1 2 3 | |
67 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
68 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
69 | * | Magic number | | |
70 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
71 | * | First entry | Last entry | | |
72 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
73 | * | Records | | |
74 | * \ \ | |
75 | * \ \ | |
76 | * | Records | | |
77 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
78 | * | |
79 | * Record format: | |
80 | * 1 2 3 | |
81 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
82 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
83 | * | Type code | | |
84 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
85 | * | Function address | | |
86 | * / / | |
87 | * / / | |
88 | * | Function address | | |
89 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
90 | * | Calling location | | |
91 | * / / | |
92 | * / / | |
93 | * | Calling location | | |
94 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
95 | * | |
96 | * Some systems may have pointers larger than 32 bits, which is why these | |
97 | * fields are allowed to be variable width. | |
98 | */ | |
99 | #include <assert.h> | |
100 | #include <errno.h> | |
101 | #include <fcntl.h> | |
102 | #include <stdio.h> | |
103 | #include <stdlib.h> | |
104 | #include <string.h> | |
105 | #include <sys/mman.h> | |
106 | #include <sys/stat.h> | |
107 | #include <sys/types.h> | |
108 | #include <unistd.h> | |
109 | ||
110 | /* /etc/magic rules: | |
111 | * | |
112 | * 0 belong 0x52e45fd4 RingLog call trace file, big endian | |
113 | * >4 beshort x (First entry %u, | |
114 | * >>6 beshort x last entry %u) | |
115 | * 0 lelong 0x52e45fd4 RingLog call trace file, little endian | |
116 | * >4 leshort x (First entry %u, | |
117 | * >>6 leshort x last entry %u) | |
118 | */ | |
119 | #define RINGLOG_MAGIC 0x52e45fd4 /* verify file format */ | |
120 | ||
121 | #define RINGLOG_INIT 0x00000000 /* mark when a session was initiated */ | |
122 | #define RINGLOG_ENTER 0x01010101 /* record function entry */ | |
123 | #define RINGLOG_EXIT 0x02020202 /* record function exit */ | |
124 | ||
125 | #define RINGLOG_FNAME "call.ringlog" /* default file name */ | |
126 | #define RINGLOG_ILEN 2000 /* default number of entries */ | |
127 | ||
128 | /* length of the header and of individual entries */ | |
129 | #define HEAD_LEN (sizeof(u_int32_t) + 2 * sizeof(u_int16_t)) | |
130 | #define ENTRY_LEN (sizeof(u_int32_t) + 2 * sizeof(void *)) | |
131 | ||
132 | /* return an lvalue to the specified type stored at the specified location */ | |
133 | #define rl_ref(log, loc, type) (*((type *)((log) + (loc)))) | |
134 | ||
135 | /* access particular header fields */ | |
136 | #define rl_magic(log) rl_ref((log), 0, u_int32_t) | |
137 | #define rl_first(log) rl_ref((log), 4, u_int16_t) | |
138 | #define rl_last(log) rl_ref((log), 6, u_int16_t) | |
139 | ||
140 | /* translate physical entry number to a file location */ | |
141 | #define rl_addr(loc) ((loc) * ENTRY_LEN + HEAD_LEN) | |
142 | ||
143 | /* extract the type, function, and call fields of an entry */ | |
144 | #define rl_type(log, loc) rl_ref((log), rl_addr(loc), u_int32_t) | |
145 | #define rl_func(log, loc) rl_ref((log), rl_addr(loc) + sizeof(u_int32_t), \ | |
146 | void *) | |
147 | #define rl_call(log, loc) rl_ref((log), rl_addr(loc) + sizeof(u_int32_t) + \ | |
148 | sizeof(void *), void *) | |
149 | ||
150 | static char *log = 0; /* the log has to be global data */ | |
151 | static size_t log_size = 0; /* remember the size of the log */ | |
152 | static int log_length = 0; /* remember how many entries it'll hold */ | |
153 | ||
154 | /* Open and initialize the log file */ | |
155 | static int | |
156 | init_log(char *fname, size_t init_len) | |
157 | { | |
158 | char c = 0; | |
159 | int fd, err = 0, size = -1; | |
160 | struct stat buf; | |
161 | ||
162 | /* open file */ | |
163 | if ((fd = open(fname, O_RDWR | (init_len > 0 ? O_CREAT : 0), | |
164 | S_IRUSR | S_IWUSR)) < 0) | |
165 | return errno; /* return error */ | |
166 | ||
167 | if (fstat(fd, &buf)) { /* get size */ | |
168 | err = errno; /* save errno... */ | |
169 | close(fd); /* close file descriptor */ | |
170 | return err; /* return error */ | |
171 | } | |
172 | ||
173 | if (buf.st_size <= 8) /* too small */ | |
174 | size = HEAD_LEN + ENTRY_LEN * init_len; | |
175 | else if ((buf.st_size - 8) % ENTRY_LEN) /* not a multiple of entry length */ | |
176 | size = ((buf.st_size - 8) / ENTRY_LEN + 1) * ENTRY_LEN + 8; /* round up */ | |
177 | ||
178 | if (size >= 0) { /* need to set the size */ | |
179 | if (lseek(fd, size - 1, SEEK_SET) < 0) { /* seek to the end of our file */ | |
180 | err = errno; /* save errno... */ | |
181 | close(fd); /* close file descriptor */ | |
182 | return err; /* return error */ | |
183 | } | |
184 | ||
185 | if (write(fd, &c, 1) < 0) { /* write a zero to set the new size */ | |
186 | err = errno; /* save errno... */ | |
187 | close(fd); /* close file descriptor */ | |
188 | return err; /* return error */ | |
189 | } | |
190 | ||
191 | log_size = size; /* record log size */ | |
192 | } else | |
193 | log_size = buf.st_size; /* record log size */ | |
194 | ||
195 | /* map the file to memory */ | |
196 | if ((log = (char *)mmap(0, log_size, PROT_READ | PROT_WRITE, | |
197 | MAP_SHARED, fd, 0)) == MAP_FAILED) | |
198 | err = errno; /* save errno... */ | |
199 | ||
200 | close(fd); /* don't need the file descriptor anymore */ | |
201 | ||
202 | if (err) /* an error occurred while mapping the file; return it */ | |
203 | return err; | |
204 | ||
205 | log_length = (log_size - HEAD_LEN) / ENTRY_LEN; /* store number of entries */ | |
206 | ||
207 | if (rl_magic(log) == 0) { /* initialize if necessary */ | |
208 | rl_magic(log) = RINGLOG_MAGIC; | |
209 | rl_first(log) = -1; | |
210 | rl_last(log) = -1; | |
211 | } | |
212 | ||
213 | if (rl_magic(log) != RINGLOG_MAGIC) { /* verify file format */ | |
214 | munmap(log, log_size); /* unmap file */ | |
215 | return -1; /* -1 indicates file format error */ | |
216 | } | |
217 | ||
218 | return 0; /* return success */ | |
219 | } | |
220 | ||
221 | #ifdef RINGLOG_INSTRUMENT | |
222 | ||
223 | /* store an entry in the log file */ | |
224 | static void | |
225 | store_entry(u_int32_t type, void *this_fn, void *call_site) | |
226 | { | |
227 | if (!log) { /* open the log file if necessary; die if unable */ | |
228 | assert(init_log(RINGLOG_FNAME, RINGLOG_ILEN) == 0); | |
229 | store_entry(RINGLOG_INIT, 0, 0); /* mark start of logging */ | |
230 | } | |
231 | ||
232 | if (++(rl_last(log)) >= log_length) /* select next entry to fill */ | |
233 | rl_last(log) = 0; /* wrap if needed */ | |
234 | ||
235 | if (rl_first(log) == rl_last(log)) { /* advance start pointer if collision */ | |
236 | if (++(rl_first(log)) >= log_length) /* wrap if necessary */ | |
237 | rl_first(log) = 0; | |
238 | } else if (rl_first(log) == (u_int16_t)-1) /* no entries yet; enter one */ | |
239 | rl_first(log) = 0; | |
240 | ||
241 | rl_type(log, rl_last(log)) = type; /* record the entry */ | |
242 | rl_func(log, rl_last(log)) = this_fn; | |
243 | rl_call(log, rl_last(log)) = call_site; | |
244 | } | |
245 | ||
246 | /* called upon function entry */ | |
247 | void | |
248 | __cyg_profile_func_enter(void *this_fn, void *call_site) | |
249 | { | |
250 | store_entry(RINGLOG_ENTER, this_fn, call_site); | |
251 | } | |
252 | ||
253 | /* called upon function exit */ | |
254 | void | |
255 | __cyg_profile_func_exit(void *this_fn, void *call_site) | |
256 | { | |
257 | store_entry(RINGLOG_EXIT, this_fn, call_site); | |
258 | } | |
259 | ||
260 | #else /* !defined(RINGLOG_INSTRUMENT) */ | |
261 | ||
262 | /* converts a type to a printable string */ | |
263 | static char * | |
264 | get_type(u_int32_t type) | |
265 | { | |
266 | switch (type) { | |
267 | case RINGLOG_INIT: | |
268 | return " Logging start"; | |
269 | break; | |
270 | case RINGLOG_ENTER: | |
271 | return "Function entry"; | |
272 | break; | |
273 | case RINGLOG_EXIT: | |
274 | return " Function exit"; | |
275 | break; | |
276 | } | |
277 | ||
278 | return " Invalid entry"; | |
279 | } | |
280 | ||
281 | /* Print out entries from a starting point to an end point */ | |
282 | static void | |
283 | extract(int *count, u_int16_t start, u_int16_t end) | |
284 | { | |
285 | for (; start <= end; start++) | |
286 | printf("% 4d %s: addr %p call %p\n", (*count)++, | |
287 | get_type(rl_type(log, start)), rl_func(log, start), | |
288 | rl_call(log, start)); | |
289 | } | |
290 | ||
291 | int | |
292 | main(int argc, char **argv) | |
293 | { | |
294 | char *arg; | |
295 | int i, err, size = 0; | |
296 | ||
297 | while ((arg = *++argv)) { | |
298 | if (arg[0] == '-') { /* -<number> turns into log file size */ | |
299 | size = atoi(arg + 1); | |
300 | continue; | |
301 | } | |
302 | ||
303 | log = 0; /* initialize our data */ | |
304 | log_size = 0; | |
305 | log_length = 0; | |
306 | ||
307 | switch ((err = init_log(arg, size))) { /* initialize the log */ | |
308 | case -1: /* file is in an invalid format */ | |
309 | printf("File %s not a valid ringlog file\n", arg); | |
310 | continue; | |
311 | break; | |
312 | ||
313 | case 0: /* file has opened and is ready to be read */ | |
314 | break; | |
315 | ||
316 | default: /* some error occurred */ | |
317 | printf("Error %d opening file %s: %s\n", err, arg, strerror(err)); | |
318 | continue; | |
319 | break; | |
320 | } | |
321 | ||
322 | if (rl_first(log) == (u_int16_t)-1) /* it's an empty file */ | |
323 | printf("File %s is empty\n", arg); | |
324 | else { /* print out file contents */ | |
325 | printf("File %s contents:\n", arg); | |
326 | ||
327 | i = 0; /* initialize counter */ | |
328 | if (rl_last(log) <= rl_first(log)) { /* print out log file */ | |
329 | extract(&i, rl_first(log), log_length - 1); /* end of buffer... */ | |
330 | extract(&i, 0, rl_last(log)); /* then beginning of buffer */ | |
331 | } else | |
332 | extract(&i, rl_first(log), rl_last(log)); | |
333 | } | |
334 | ||
335 | munmap(log, log_size); /* unmap the file */ | |
336 | } | |
337 | ||
338 | return 0; | |
339 | } | |
340 | ||
341 | #endif /* !RINGLOG_INSTRUMENT */ |