Add to git.
[monolith.git] / apps / stats.c
1 /* Monolith statistics/debugging application.
2  * - by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: stats.c,v 1.10 2003/02/22 15:34:24 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #ifdef HAVE_SYS_TYPES_H
27 #include <sys/types.h>
28 #endif
29
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33
34 #ifdef HAVE_STRING_H
35 #include <string.h>
36 #endif
37
38 #ifdef HAVE_NETINET_IN_H
39 #include <netinet/in.h>
40 #endif
41
42 #ifdef HAVE_SYS_SOCKET_H
43 #include <sys/socket.h>
44 #endif
45
46 #ifdef HAVE_ARPA_INET_H
47 #include <arpa/inet.h>
48 #endif
49
50 #ifdef HAVE_DLFCN_H
51 #include <dlfcn.h>
52 #endif
53
54 #include <pool.h>
55 #include <pstring.h>
56
57 #include <pthr_pseudothread.h>
58 #include <pthr_cgi.h>
59 #include <pthr_dbi.h>
60
61 #include "monolith.h"
62 #include "ml_window.h"
63 #include "ml_widget.h"
64 #include "ml_form_layout.h"
65 #include "ml_table_layout.h"
66 #include "ml_multicol_layout.h"
67 #include "ml_flow_layout.h"
68 #include "ml_form.h"
69 #include "ml_form_select.h"
70 #include "ml_form_submit.h"
71 #include "ml_text_label.h"
72 #include "ml_button.h"
73 #include "ml_dialog.h"
74
75 /*----- The following standard boilerplate code must appear -----*/
76
77 /* Main entry point to the app. */
78 static void app_main (ml_session);
79
80 int
81 handle_request (rws_request rq)
82 {
83   return ml_entry_point (rq, app_main);
84 }
85
86 /*----- End of standard boilerplate code -----*/
87
88 /* Per-session data structure. */
89 struct data
90 {
91   pool pool;                    /* Session pool for allocations. */
92
93   /* This is the top-level window. */
94   ml_window std_win;
95   ml_table_layout std_tbl;
96   ml_form std_form;             /* Area selection form. */
97   ml_flow_layout std_flow;
98   ml_form_select std_choices;   /* Top-level choices. */
99   ml_form_submit std_submit;
100 };
101
102 /* Arguments to the show_session function. */
103 struct show_session_args
104 {
105   const char *sessionid;
106   struct data *data;
107 };
108
109 static void show_reactor (ml_session session, struct data *data);
110 static void list_sessions (ml_session session, struct data *data);
111 static void show_session (ml_session session, void *vargs);
112 static void list_threads (ml_session session, struct data *data);
113 static void jump_to (ml_session session, void *vdata);
114 static void pack (struct data *data, ml_widget widget);
115 static const char *resolve_addr (pool pool, unsigned long addr);
116 static const char *pr_time (pool pool, reactor_time_t time);
117
118 static struct {
119   const char *choice;
120   void (*fn) (ml_session, struct data *data);
121   int is_default;
122 } choices[] = {
123   { "Reactor",                   show_reactor,  0 },
124   { "Sessions",                  list_sessions, 1 },
125   { "Threads",                   list_threads,  0 },
126 };
127
128 #define nr_choices (sizeof choices / sizeof choices[0])
129
130 /* If this variable is set, then sessionids are not revealed in full. Set
131  * this using the 'monolith stats restricted: 1' flag in the configuration
132  * file.
133  */
134 static int restricted = 1;
135
136 /* If this variable is set, then the application is available to everyone.
137  * If not set, then it is only available to localhost. Set this using the
138  * 'monolith stats world readable: 1' flag in the configuration file.
139  */
140 static int world_readable = 0;
141
142 static void
143 app_main (ml_session session)
144 {
145   pool pool = ml_session_pool (session);
146   struct data *data;
147   int i;
148
149   /* Read configuration settings. */
150   restricted = ml_cfg_get_bool (session, "monolith stats restricted", 1);
151   world_readable =
152     ml_cfg_get_bool (session, "monolith stats world readable", 0);
153
154   /* Allocate per-session data structure. */
155   data = pmalloc (pool, sizeof *data);
156   data->pool = pool;
157
158   /* Lay out the window. */
159   data->std_win = new_ml_window (session, pool);
160   data->std_tbl = new_ml_table_layout (pool, 2, 1);
161   data->std_form = new_ml_form (pool);
162   data->std_flow = new_ml_flow_layout (pool);
163   data->std_choices = new_ml_form_select (pool, data->std_form);
164   ml_flow_layout_pack (data->std_flow, data->std_choices);
165   data->std_submit = new_ml_form_submit (pool, data->std_form, "Go");
166   ml_flow_layout_pack (data->std_flow, data->std_submit);
167   ml_form_pack (data->std_form, data->std_flow);
168   ml_table_layout_pack (data->std_tbl, data->std_form, 0, 0);
169   ml_window_pack (data->std_win, data->std_tbl);
170
171   /* Populate the list of choices. */
172   for (i = 0; i < nr_choices; ++i)
173     {
174       ml_form_select_push_back (data->std_choices, choices[i].choice);
175       if (choices[i].is_default)
176         ml_form_select_set_selection (data->std_choices, i);
177     }
178
179   /* Set the form callback so that we jump to the right page. */
180   ml_form_set_callback (data->std_form, jump_to, session, data);
181
182   /* Set the window title. */
183   ml_window_set_title (data->std_win,
184                        "Monolith statistics and debugging application");
185
186   /* Jump to the initial page (default choice). */
187   jump_to (session, data);
188 }
189
190 static void
191 jump_to (ml_session session, void *vdata)
192 {
193   struct data *data = (struct data *) vdata;
194   int i;
195
196   /* Find out which choice is selected. */
197   i = ml_form_select_get_selection (data->std_choices);
198   if (i >= 0 && i < nr_choices)
199     /* Run that function to update the page. */
200     choices[i].fn (session, data);
201 }
202
203 static void
204 pack (struct data *data, ml_widget widget)
205 {
206   ml_table_layout_pack (data->std_tbl, widget, 1, 0);
207 }
208
209 static const char *
210 disguise_sessionid (pool pool, const char *sessionid)
211 {
212   if (!restricted) return sessionid;
213   else
214     {
215       char *s = pstrndup (pool, sessionid, 6);
216       s = pstrcat (pool, s, "[...]");
217       return s;
218     }
219 }
220
221 static void
222 show_reactor (ml_session session, struct data *data)
223 {
224   /* XXX not impl. */
225 }
226
227 static void
228 list_sessions (ml_session session, struct data *data)
229 {
230   pool pool = data->pool;
231   vector sessionids;
232   ml_multicol_layout tbl;
233   ml_text_label lbl;
234   int i;
235
236   /* Pull out the list of session IDs and turn them into session objects. */
237   sessionids = _ml_get_sessions (pool);
238
239   tbl = new_ml_multicol_layout (pool, 5);
240   ml_widget_set_property (tbl, "class", "ml_stats_table");
241
242   /* Table headers. */
243   lbl = new_ml_text_label (pool, "sessionid");
244   ml_multicol_layout_set_header (tbl, 1);
245   ml_multicol_layout_pack (tbl, lbl);
246   lbl = new_ml_text_label (pool, "last access");
247   ml_multicol_layout_set_header (tbl, 1);
248   ml_multicol_layout_pack (tbl, lbl);
249   lbl = new_ml_text_label (pool, "address");
250   ml_multicol_layout_set_header (tbl, 1);
251   ml_multicol_layout_pack (tbl, lbl);
252   lbl = new_ml_text_label (pool, "size");
253   ml_multicol_layout_set_header (tbl, 1);
254   ml_multicol_layout_pack (tbl, lbl);
255   lbl = new_ml_text_label (pool, "path");
256   ml_multicol_layout_set_header (tbl, 1);
257   ml_multicol_layout_pack (tbl, lbl);
258
259   /* The sessions themselves. */
260   for (i = 0; i < vector_size (sessionids); ++i)
261     {
262       const char *sessionid;
263       ml_session s;
264       ml_button b;
265       struct sockaddr_in addr;
266       const char *host_header, *canonical_path;
267       struct pool *sp;
268       struct pool_stats pool_stats;
269       struct show_session_args *args;
270
271       vector_get (sessionids, i, sessionid);
272       s = _ml_get_session (sessionid);
273
274       b = new_ml_button (pool, disguise_sessionid (pool, sessionid));
275       ml_widget_set_property (b, "button.style", "link");
276       args = pmalloc (pool, sizeof *args);
277       args->sessionid = sessionid;
278       args->data = data;
279       ml_button_set_callback (b, show_session, session, args);
280       ml_button_set_popup (b, psprintf (pool, "session_%s", sessionid));
281       ml_button_set_popup_size (b, 640, 480);
282       ml_multicol_layout_pack (tbl, b);
283
284       lbl = new_ml_text_label (pool,
285                                pr_time (pool,
286                                         _ml_session_get_last_access (s)));
287       ml_multicol_layout_pack (tbl, lbl);
288
289       addr = _ml_session_get_original_ip (s);
290       lbl = new_ml_text_label (pool, pstrdup (pool,
291                                               inet_ntoa (addr.sin_addr)));
292       ml_multicol_layout_pack (tbl, lbl);
293
294       sp = ml_session_pool (s);
295       pool_get_stats (sp, &pool_stats, sizeof (pool_stats));
296       lbl = new_ml_text_label (pool, pitoa (pool, pool_stats.struct_size));
297       ml_multicol_layout_pack (tbl, lbl);
298
299       host_header = ml_session_host_header (s);
300       canonical_path = ml_session_canonical_path (s);
301       lbl = new_ml_text_label (pool,
302                                psprintf (pool, "http://%s%s",
303                                          host_header, canonical_path));
304       ml_multicol_layout_pack (tbl, lbl);
305     }
306
307   pack (data, tbl);
308 }
309
310 static void
311 show_session (ml_session session, void *vargs)
312 {
313   struct show_session_args *args = (struct show_session_args *) vargs;
314   struct data *data = args->data;
315   pool pool = data->pool;
316   const char *sessionid = args->sessionid;
317   ml_window win;
318   ml_form_layout tbl;
319   ml_text_label lbl;
320   ml_session s;
321
322   win = new_ml_window (session, pool);
323   tbl = new_ml_form_layout (pool);
324   ml_widget_set_property (tbl, "class", "ml_stats_table");
325
326   /* Get the session. */
327   s = _ml_get_session (sessionid);
328   if (!s)
329     {
330       ml_error_window (pool, session,
331                        "Session not found. It is likely that the session "
332                        "has timed out and been deleted.",
333                        ML_DIALOG_CLOSE_BUTTON);
334       return;
335     }
336
337   /* Get the fields from the session and display them. */
338
339   ml_form_layout_pack (tbl, "sessionid",
340                        new_ml_text_label (pool,
341                          disguise_sessionid (pool, sessionid)));
342   ml_form_layout_pack (tbl, "path",
343                        new_ml_text_label (pool,
344                          psprintf (pool, "http://%s%s",
345                                    ml_session_host_header (s),
346                                    ml_session_canonical_path (s))));
347
348   /* Pool and pool stats. */
349   {
350     struct pool *sp;
351     struct pool_stats pool_stats;
352
353     sp = ml_session_pool (s);
354     pool_get_stats (sp, &pool_stats, sizeof (pool_stats));
355
356     ml_form_layout_pack (tbl, "session pool",
357                          new_ml_text_label (pool, psprintf (pool, "%p", sp)));
358     ml_form_layout_pack (tbl, "session pool size",
359                          new_ml_text_label (pool,
360                            pitoa (pool, pool_stats.struct_size)));
361   }
362
363   ml_form_layout_pack (tbl, "access count",
364                        new_ml_text_label (pool,
365                          pitoa (pool, _ml_session_get_hits (s))));
366   ml_form_layout_pack (tbl, "last access",
367                        new_ml_text_label (pool,
368                          pr_time (pool, _ml_session_get_last_access (s))));
369   ml_form_layout_pack (tbl, "creation time",
370                        new_ml_text_label (pool,
371                          pr_time (pool, _ml_session_get_created (s))));
372
373   /* Original IP address. */
374   {
375     struct sockaddr_in addr;
376
377     addr = _ml_session_get_original_ip (s);
378     ml_form_layout_pack (tbl, "original ip",
379                          new_ml_text_label (pool,
380                            pstrdup (pool, inet_ntoa (addr.sin_addr))));
381   }
382
383   /* User-Agent header. */
384   {
385     const char *ua = ml_session_user_agent (s);
386
387     ml_form_layout_pack (tbl, "user agent",
388                          new_ml_text_label (pool, ua ? pstrdup (pool, ua)
389                                             : "(not set)"));
390   }
391
392   /* Initial args. */
393   {
394     cgi args;
395     ml_multicol_layout args_tbl;
396     int i;
397     vector keys;
398
399     args = ml_session_args (s);
400     /* XXX cgi_params_in_pool function */
401     keys = cgi_params (args);
402
403     args_tbl = new_ml_multicol_layout (pool, 2);
404     ml_widget_set_property (args_tbl, "class", "ml_stats_table");
405
406     lbl = new_ml_text_label (pool, "name");
407     ml_multicol_layout_set_header (args_tbl, 1);
408     ml_multicol_layout_pack (args_tbl, lbl);
409
410     lbl = new_ml_text_label (pool, "value");
411     ml_multicol_layout_set_header (args_tbl, 1);
412     ml_multicol_layout_pack (args_tbl, lbl);
413
414     for (i = 0; i < vector_size (keys); ++i)
415       {
416         const char *key, *value;
417
418         vector_get (keys, i, key);
419         value = cgi_param (args, key);
420
421         lbl = new_ml_text_label (pool, pstrdup (pool, key));
422         ml_multicol_layout_pack (args_tbl, lbl);
423         lbl = new_ml_text_label (pool, pstrdup (pool, value));
424         ml_multicol_layout_pack (args_tbl, lbl);
425       }
426
427     ml_form_layout_pack (tbl, "initial parameters", args_tbl);
428   }
429
430   ml_form_layout_pack (tbl, "app_main function",
431                        new_ml_text_label (pool,
432                          resolve_addr (pool,
433                            (unsigned long) _ml_session_get_app_main (s))));
434
435   /* Windows. */
436   {
437     vector windows;
438     ml_multicol_layout win_tbl;
439     int i;
440
441     windows = _ml_session_get_windows (s, pool);
442
443     win_tbl = new_ml_multicol_layout (pool, 2);
444     ml_widget_set_property (win_tbl, "class", "ml_stats_table");
445
446     lbl = new_ml_text_label (pool, "windowid");
447     ml_multicol_layout_set_header (win_tbl, 1);
448     ml_multicol_layout_pack (win_tbl, lbl);
449
450     lbl = new_ml_text_label (pool, "window");
451     ml_multicol_layout_set_header (win_tbl, 1);
452     ml_multicol_layout_pack (win_tbl, lbl);
453
454     for (i = 0; i < vector_size (windows); ++i)
455       {
456         const char *windowid;
457         ml_window window;
458
459         vector_get (windows, i, windowid);
460         window = _ml_session_get_window (s, windowid);
461
462         lbl = new_ml_text_label (pool, windowid);
463         ml_multicol_layout_pack (win_tbl, lbl);
464         lbl = new_ml_text_label (pool, psprintf (pool, "%p", window));
465         ml_multicol_layout_pack (win_tbl, lbl);
466       }
467
468     ml_form_layout_pack (tbl, "windows", win_tbl);
469   }
470
471   /* Actions. */
472   {
473     vector actions;
474     ml_multicol_layout act_tbl;
475     int i;
476
477     actions = _ml_session_get_actions (s, pool);
478
479     act_tbl = new_ml_multicol_layout (pool, 3);
480     ml_widget_set_property (act_tbl, "class", "ml_stats_table");
481
482     lbl = new_ml_text_label (pool, "actionid");
483     ml_multicol_layout_set_header (act_tbl, 1);
484     ml_multicol_layout_pack (act_tbl, lbl);
485
486     lbl = new_ml_text_label (pool, "function");
487     ml_multicol_layout_set_header (act_tbl, 1);
488     ml_multicol_layout_pack (act_tbl, lbl);
489
490     lbl = new_ml_text_label (pool, "data");
491     ml_multicol_layout_set_header (act_tbl, 1);
492     ml_multicol_layout_pack (act_tbl, lbl);
493
494     for (i = 0; i < vector_size (actions); ++i)
495       {
496         const char *actionid;
497         void *fn, *data;
498
499         vector_get (actions, i, actionid);
500         _ml_session_get_action (s, actionid, &fn, &data);
501
502         lbl = new_ml_text_label (pool, actionid);
503         ml_multicol_layout_pack (act_tbl, lbl);
504         lbl = new_ml_text_label (pool, resolve_addr (pool,
505                                                        (unsigned long) fn));
506         ml_multicol_layout_pack (act_tbl, lbl);
507         lbl = new_ml_text_label (pool, psprintf (pool, "%p", data));
508         ml_multicol_layout_pack (act_tbl, lbl);
509       }
510
511     ml_form_layout_pack (tbl, "actions", act_tbl);
512   }
513
514   ml_form_layout_pack (tbl, "userid",
515                        new_ml_text_label (pool,
516                          pitoa (pool, ml_session_userid (s))));
517
518   ml_window_pack (win, tbl);
519
520   /* Set window title. */
521   ml_window_set_title (win, psprintf (pool, "Session %s",
522                                       disguise_sessionid (pool, sessionid)));
523 }
524
525 static void
526 list_threads (ml_session session, struct data *data)
527 {
528   pool pool = data->pool;
529   vector threads;
530   int i;
531   ml_multicol_layout tbl;
532   ml_text_label lbl;
533
534   /* Get the threads. */
535   threads = pseudothread_get_threads (pool);
536
537   tbl = new_ml_multicol_layout (pool, 4);
538   ml_widget_set_property (tbl, "class", "ml_stats_table");
539
540   /* Table headers. */
541   lbl = new_ml_text_label (pool, "id");
542   ml_multicol_layout_set_header (tbl, 1);
543   ml_multicol_layout_pack (tbl, lbl);
544   lbl = new_ml_text_label (pool, "program counter");
545   ml_multicol_layout_set_header (tbl, 1);
546   ml_multicol_layout_pack (tbl, lbl);
547   lbl = new_ml_text_label (pool, "size");
548   ml_multicol_layout_set_header (tbl, 1);
549   ml_multicol_layout_pack (tbl, lbl);
550   lbl = new_ml_text_label (pool, "name");
551   ml_multicol_layout_set_header (tbl, 1);
552   ml_multicol_layout_pack (tbl, lbl);
553
554   for (i = 0; i < vector_size (threads); ++i)
555     {
556       pseudothread pth;
557       ml_button b;
558       struct pool *tp;
559       struct pool_stats pool_stats;
560       unsigned long pc;
561       const char *pc_symbol;
562       const char *name;
563
564       vector_get_ptr (threads, i, pth);
565
566       lbl = new_ml_text_label (pool, pitoa (pool, pth_get_thread_num (pth)));
567       ml_multicol_layout_pack (tbl, lbl);
568
569       pc = pth_get_PC (pth);
570       pc_symbol = resolve_addr (pool, pc);
571       lbl = new_ml_text_label (pool, pc_symbol);
572       ml_multicol_layout_pack (tbl, lbl);
573
574       tp = pth_get_pool (pth);
575       pool_get_stats (tp, &pool_stats, sizeof (pool_stats));
576       lbl = new_ml_text_label (pool, pitoa (pool, pool_stats.struct_size));
577       ml_multicol_layout_pack (tbl, lbl);
578
579       name = pstrdup (pool, pth_get_name (pth));
580       b = new_ml_button (pool, name);
581       ml_widget_set_property (b, "button.style", "link");
582       ml_multicol_layout_pack (tbl, b);
583     }
584
585   pack (data, tbl);
586 }
587
588 static const char *
589 pr_time (pool pool, reactor_time_t time)
590 {
591   int secs = (reactor_time - time) / 1000LL;
592
593   if (secs < 60)
594     return psprintf (pool, "%ds", secs);
595   else if (secs < 3600)
596     return psprintf (pool, "%dm %ds", secs / 60, secs % 60);
597   else
598     return psprintf (pool, "%dh %dm %ds", secs / 3600, (secs / 60) % 60,
599                      secs % 60);
600 }
601
602 /*----- Address to symbol resolution code. -----*/
603
604 /* This code is highly Linux-dependent.
605  *
606  * The strategy taken is as follows:
607  *
608  * (1) Look at /proc/$$/maps (if it exists) to try and work out which
609  *     executable or shared library the symbol exists in.
610  * (2) Look for a corresponding symbol table file in /usr/share/rws/symtabs/
611  * (3) If the symbol table file is found, then we can resolve the symbol
612  *     immediately with that file.
613  * (4) If no symbol table file is found, use the dladdr(3) function from
614  *     glibc.
615  * (5) If there is no dladdr(3) function, just print the address.
616  *
617  * All of this must be done without blocking.
618  */
619
620 #define RES_DEBUG 0
621
622 static const char *resolve_addr_in_maps (pool, unsigned long);
623 static const char *resolve_addr_in_symtab (pool, unsigned long, const char *,
624                                            unsigned long, unsigned long);
625 static const char *resolve_addr_with_dladdr (pool, unsigned long);
626
627 static const char *
628 resolve_addr (pool session_pool, unsigned long addr)
629 {
630   pool pool;
631   const char *s;
632
633 #if RES_DEBUG
634   fprintf (stderr, "trying to resolve address %lx\n", addr);
635 #endif
636
637   /* Give the address resolution code its own scratch pool, but copy
638    * the result into the more permanent storage of the session pool.
639    */
640   pool = new_subpool (session_pool);
641   s = pstrdup (session_pool, resolve_addr_in_maps (pool, addr));
642   delete_pool (pool);
643
644 #if RES_DEBUG
645   fprintf (stderr, "resolved address %lx as %s\n", addr, s);
646 #endif
647
648   return s;
649 }
650
651 static const char *
652 resolve_addr_in_maps (pool pool, unsigned long addr)
653 {
654   FILE *fp;
655   char *maps_filename;
656 #define RES_BUF_SIZE 2048
657   char *buffer;
658   char *filename, *t;
659   unsigned long low, high, offset;
660
661   if (addr == 0) return "NULL";
662
663   maps_filename = psprintf (pool, "/proc/%d/maps", (int) getpid ());
664
665   fp = fopen (maps_filename, "r");
666   if (fp == 0) return resolve_addr_with_dladdr (pool, addr);
667
668   buffer = pmalloc (pool, RES_BUF_SIZE);
669
670   /* Read the maps file until we find the appropriate address range. */
671   while (fgets (buffer, RES_BUF_SIZE, fp) != 0)
672     {
673       if (buffer[strlen(buffer)-1] == '\n')
674         buffer[strlen(buffer)-1] = '\0';
675
676       if (sscanf (buffer, "%lx-%lx r-xp %lx", &low, &high, &offset) != 3)
677         continue;
678
679 #if RES_DEBUG
680       fprintf (stderr, "\t%lx-%lx offset %lx\n", low, high, offset);
681 #endif
682       if (!(low <= addr && addr < high))
683         continue;
684
685       /* Found the address, so we can close the file. */
686       fclose (fp);
687
688       /* Find the filename (just the basename). */
689       filename = strrchr (buffer, '/');
690       if (!filename) return resolve_addr_with_dladdr (pool, addr);
691       filename++;
692
693       t = strchr (filename, '.');
694       if (t) *t = '\0';
695
696 #if RES_DEBUG
697       fprintf (stderr, "\tbase filename = %s\n", filename);
698 #endif
699
700       return resolve_addr_in_symtab (pool, addr, filename,
701                                      low, offset);
702     }
703
704   /* Address not found. Go to the back-up approach. */
705   fclose (fp);
706
707   return resolve_addr_with_dladdr (pool, addr);
708 #undef RES_BUF_SIZE
709 }
710
711 static const char *
712 resolve_addr_in_symtab (pool pool, unsigned long addr,
713                         const char *filename, unsigned long low,
714                         unsigned long offset)
715 {
716   FILE *fp;
717 #define RES_BUF_SIZE 2048
718   char *symtabfile, *buffer, *symname, *lastsymname = "(none)";
719   unsigned long lastsymaddr = 0, symaddr, diff;
720
721   /* Look for a matching symtab file. */
722   symtabfile = psprintf (pool, SYMTABSDIR "/%s.syms", filename);
723
724   fp = fopen (symtabfile, "r");
725   if (!fp) return resolve_addr_with_dladdr (pool, addr);
726
727   buffer = pmalloc (pool, RES_BUF_SIZE);
728
729   /* Read the symbols from the symtab file until one matches. */
730   while (fgets (buffer, RES_BUF_SIZE, fp) != 0)
731     {
732       if (buffer[strlen(buffer)-1] == '\n')
733         buffer[strlen(buffer)-1] = '\0';
734
735       symname = strchr (buffer, ' ');
736       if (!symname) continue;
737       *symname = '\0';
738       symname++;
739
740       if (sscanf (buffer, "%lx", &symaddr) != 1) continue;
741
742 #if RES_DEBUG
743       fprintf (stderr, "\t%s: %lx %s\n", symtabfile, symaddr, symname);
744 #endif
745
746       /* Work out the in-memory address of this symbol. */
747       symaddr += low - offset;
748
749       /* If we've gone past the address, then the symbol must be the
750        * previous symbol.
751        */
752       if (symaddr > addr)
753         goto found_it;
754
755       lastsymaddr = symaddr;
756       lastsymname = pstrdup (pool, symname);
757     }
758
759  found_it:                      /* It's the previous symbol. */
760   fclose (fp);
761
762   diff = addr - lastsymaddr;
763   if (diff != 0)
764     return psprintf (pool, "%s+0x%lx (%s)", lastsymname, diff, filename);
765   else
766     return psprintf (pool, "%s (%s)", lastsymname, filename);
767
768 #undef RES_BUF_SIZE
769 }
770
771 static const char *
772 resolve_addr_with_dladdr (pool pool, unsigned long addr)
773 {
774 #if defined(HAVE_DLFCN_H) && defined(HAVE_DLADDR)
775   int r;
776   Dl_info info;
777
778   r = dladdr ((void *) addr, &info);
779   if (r != 0 && info.dli_saddr != 0)
780     {
781       unsigned long real_addr = (unsigned long) info.dli_saddr;
782       unsigned long diff = addr - real_addr;
783
784       if (diff != 0)
785         return psprintf (pool, "%s+0x%lx (%s)",
786                          info.dli_sname, diff, info.dli_fname);
787       else
788         return psprintf (pool, "%s (%s)", info.dli_sname, info.dli_fname);
789     }
790   else
791     return psprintf (pool, "%p", (void *) addr);
792 #else
793   return psprintf (pool, "%p", (void *) addr);
794 #endif
795 }
796
797 /*----- End of symbol resolution code. -----*/