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