HPCToolkit
fnbounds_dynamic.c
Go to the documentation of this file.
1 // -*-Mode: C++;-*- // technically C99
2 
3 // * BeginRiceCopyright *****************************************************
4 //
5 // $HeadURL$
6 // $Id$
7 //
8 // --------------------------------------------------------------------------
9 // Part of HPCToolkit (hpctoolkit.org)
10 //
11 // Information about sources of support for research and development of
12 // HPCToolkit is at 'hpctoolkit.org' and in 'README.Acknowledgments'.
13 // --------------------------------------------------------------------------
14 //
15 // Copyright ((c)) 2002-2019, Rice University
16 // All rights reserved.
17 //
18 // Redistribution and use in source and binary forms, with or without
19 // modification, are permitted provided that the following conditions are
20 // met:
21 //
22 // * Redistributions of source code must retain the above copyright
23 // notice, this list of conditions and the following disclaimer.
24 //
25 // * Redistributions in binary form must reproduce the above copyright
26 // notice, this list of conditions and the following disclaimer in the
27 // documentation and/or other materials provided with the distribution.
28 //
29 // * Neither the name of Rice University (RICE) nor the names of its
30 // contributors may be used to endorse or promote products derived from
31 // this software without specific prior written permission.
32 //
33 // This software is provided by RICE and contributors "as is" and any
34 // express or implied warranties, including, but not limited to, the
35 // implied warranties of merchantability and fitness for a particular
36 // purpose are disclaimed. In no event shall RICE or contributors be
37 // liable for any direct, indirect, incidental, special, exemplary, or
38 // consequential damages (including, but not limited to, procurement of
39 // substitute goods or services; loss of use, data, or profits; or
40 // business interruption) however caused and on any theory of liability,
41 // whether in contract, strict liability, or tort (including negligence
42 // or otherwise) arising in any way out of the use of this software, even
43 // if advised of the possibility of such damage.
44 //
45 // ******************************************************* EndRiceCopyright *
46 
47 //=====================================================================
48 // File: fnbounds_dynamic.c
49 //
50 // provide information about function bounds for functions in
51 // dynamically linked load modules. use an extra "server" process
52 // to handle computing the symbols to insulate the client process
53 // from the complexity of this task, including use of the system
54 // command to fork new processes. having the server process
55 // enables use to avoid dealing with forking off new processes
56 // with system when there might be multiple threads active with
57 // sampling enabled.
58 //
59 // Modification history:
60 // 2008 April 28 - created John Mellor-Crummey
61 //
62 //=====================================================================
63 
64 
65 //*********************************************************************
66 // system includes
67 //*********************************************************************
68 
69 #include <stdio.h> // fopen, fclose, etc
70 #include <dlfcn.h> // for dlopen/dlclose
71 #include <string.h> // for strcmp, strerror
72 #include <stdlib.h> // for getenv
73 #include <errno.h> // for errno
74 #include <stdint.h>
75 #include <stdbool.h>
76 #include <sys/param.h> // for PATH_MAX
77 #include <sys/types.h>
78 #include <unistd.h> // getpid
79 
80 
81 #include <include/hpctoolkit-config.h>
82 
83 //*********************************************************************
84 // external libraries
85 //*********************************************************************
86 
87 #include <monitor.h>
88 
89 //*********************************************************************
90 // local includes
91 //*********************************************************************
92 
93 #include "fnbounds_interface.h"
94 #include "fnbounds_file_header.h"
95 #include "client.h"
96 #include "dylib.h"
97 
98 #include <hpcrun/main.h>
99 #include <hpcrun_dlfns.h>
100 #include <hpcrun_stats.h>
101 #include <disabled.h>
102 #include <loadmap.h>
103 #include <env.h>
104 #include <epoch.h>
105 #include <sample_event.h>
106 #include <thread_data.h>
107 
109 #include <messages/messages.h>
110 
111 #include <lib/prof-lean/spinlock.h>
112 
113 #include "data_tree.h"
114 
115 //*********************************************************************
116 // local types
117 //*********************************************************************
118 
119 #define PERFORM_RELOCATION(addr, offset) \
120  ((void *) (((unsigned long) addr) + ((long) offset)))
121 
122 #define MAPPING_END(addr, length) \
123  ((void *) (((unsigned long) addr) + ((unsigned long) length)))
124 
125 
126 //*********************************************************************
127 // local variables
128 //*********************************************************************
129 
130 // locking functions to ensure that dynamic bounds data structures
131 // are consistent.
132 
134 
135 #define FNBOUNDS_LOCK do { \
136  spinlock_lock(&fnbounds_lock); \
137  TD_GET(fnbounds_lock) = 1; \
138 } while (0)
139 
140 #define FNBOUNDS_UNLOCK do { \
141  spinlock_unlock(&fnbounds_lock); \
142  TD_GET(fnbounds_lock) = 0; \
143 } while (0)
144 
145 
146 //*********************************************************************
147 // forward declarations
148 //*********************************************************************
149 
150 static load_module_t *
151 fnbounds_get_loadModule(void *ip);
152 
153 static dso_info_t *
154 fnbounds_compute(const char *filename, void *start, void *end);
155 
156 static void
158 
159 static bool
161 
162 //*********************************************************************
163 // interface operations
164 //*********************************************************************
165 
166 
167 //---------------------------------------------------------------------
168 // function fnbounds_init:
169 //
170 // for dynamically-linked executables, start an fnbounds server
171 // process to that will compute function bounds information upon
172 // demand for dynamically-linked load modules.
173 //
174 // return code = 0 upon success, otherwise fork failed
175 //
176 // NOTE: don't make this routine idempotent: it may be needed to
177 // start a new server if the process forks
178 //---------------------------------------------------------------------
179 
180 int
182 {
183  if (hpcrun_get_disabled()) return 0;
184 
188 
189  return 0;
190 }
191 
192 bool
193 fnbounds_enclosing_addr(void* ip, void** start, void** end, load_module_t** lm)
194 {
196 
197  bool ret = false; // failure unless otherwise reset to 0 below
198 
200  dso_info_t* dso = (lm_) ? lm_->dso_info : NULL;
201 
202  if (dso && dso->nsymbols > 0) {
203  void* ip_norm = ip;
204  if (dso->is_relocatable) {
205  ip_norm = (void*) (((unsigned long) ip_norm) - dso->start_to_ref_dist);
206  }
207 
208  // no dso table means no enclosing addr
209 
210  if (dso->table) {
211  // N.B.: works on normalized IPs
212  int rv = fnbounds_table_lookup(dso->table, dso->nsymbols, ip_norm,
213  (void**) start, (void**) end);
214 
215  ret = (rv == 0);
216  // Convert 'start' and 'end' into unnormalized IPs since they are
217  // currently normalized.
218  if (rv == 0 && dso->is_relocatable) {
219  *start = PERFORM_RELOCATION(*start, dso->start_to_ref_dist);
220  *end = PERFORM_RELOCATION(*end , dso->start_to_ref_dist);
221  }
222  }
223  }
224 
225  if (lm) {
226  *lm = lm_;
227  }
228 
230 
231  return ret;
232 }
233 
234 
235 //---------------------------------------------------------------------
236 // Function: fnbounds_map_open_dsos
237 // Purpose:
238 // identify any new dsos that have been mapped.
239 // analyze them and add their information to the open list.
240 //---------------------------------------------------------------------
241 
242 void
244 {
246 }
247 
248 
249 //---------------------------------------------------------------------
250 // Function: insert_var_table
251 // Purpose:
252 // create a tree of static variables to be used later by datacentric
253 // or other modules
254 //---------------------------------------------------------------------
255 static void
256 insert_var_table(void **var_table, unsigned long num)
257 {
258  if(!var_table) return;
259 
260  int i;
261  for (i = 0; i < num; i+=2) {
262  size_t num_bytes = (size_t)var_table[i+1];
263  if (num_bytes < DATACENTRIC_MIN_BYTES)
264  continue;
265 
266  // create splay node
267  struct datatree_info_s *data_info = hpcrun_malloc(sizeof(struct datatree_info_s));
268 
269  data_info->memblock = var_table[i];
270  data_info->bytes = num_bytes;
271  data_info->rmemblock = data_info->memblock + num_bytes;
272 
273  data_info->left = data_info->right = NULL;
274 
275  data_info->magic = DATA_STATIC_MAGIC;
276  data_info->context = NULL;
277 
278  datatree_splay_insert(data_info);
279  }
280 }
281 
282 
286 static void
288 {
289  struct fnbounds_file_header fh;
290  char *filename = dso->name;
291 
292  fh.is_relocatable = 0;
293  fh.mmap_size = 0;
294  fh.num_entries = 0;
295  fh.reference_offset = 0;
296 
297  void **var_table = (void **) hpcrun_syserv_query_var(filename, &fh);
298 
299  if (var_table != NULL)
300  insert_var_table(var_table, fh.num_entries);
301 
302  TMSG(DATACENTRIC, "%s has %ld entries", filename, fh.num_entries);
303 }
304 
305 
306 //
307 // Find start and end of executable from /proc/self/maps
308 //
309 static void
310 fnbounds_find_exec_bounds_proc_maps(char* exename, void**start, void** end)
311 {
312  *start = NULL; *end = NULL;
313  FILE* loadmap = fopen("/proc/self/maps", "r");
314  if (! loadmap) {
315  EMSG("Could not open /proc/self/maps");
316  return;
317  }
318  char linebuf[1024 + 1];
319  char tmpname[PATH_MAX];
320  char* addr = NULL;
321  for(;;) {
322  char* l = fgets(linebuf, sizeof(linebuf), loadmap);
323  if (feof(loadmap)) break;
324  char* save = NULL;
325  const char delim[] = " \n";
326  addr = strtok_r(l, delim, &save);
327  char* perms = strtok_r(NULL, delim, &save);
328  // skip 3 tokens
329  for (int i=0; i < 3; i++) { (void) strtok_r(NULL, delim, &save);}
330  char* name = strtok_r(NULL, delim, &save);
331  realpath(name, tmpname);
332  if ((strncmp(perms, "r-x", 3) == 0) && (strcmp(tmpname, exename) == 0)) break;
333  }
334  fclose(loadmap);
335  char* save = NULL;
336  const char dash[] = "-";
337  char* start_str = strtok_r(addr, dash, &save);
338  char* end_str = strtok_r(NULL, dash, &save);
339  *start = (void*) (uintptr_t) strtol(start_str, NULL, 16);
340  *end = (void*) (uintptr_t) strtol(end_str, NULL, 16);
341 }
342 
343 dso_info_t*
345 {
346  char filename[PATH_MAX];
347  struct fnbounds_file_header fh;
348  void* start = NULL;
349  void* end = NULL;
350 
351  TMSG(MAP_EXEC, "Entry");
352  realpath("/proc/self/exe", filename);
353  void** nm_table = (void**) hpcrun_syserv_query(filename, &fh);
354  if (! nm_table) {
355  EMSG("No nm_table for executable %s", filename);
356  return hpcrun_dso_make(filename, NULL, NULL, NULL, NULL, 0);
357  }
358  if (fh.num_entries < 1) {
359  EMSG("fnbounds returns no symbols for file %s, (all intervals poisoned)", filename);
360  return hpcrun_dso_make(filename, NULL, NULL, NULL, NULL, 0);
361  }
362  TMSG(MAP_EXEC, "Relocatable exec");
363  if (fh.is_relocatable) {
364  if (nm_table[0] >= start && nm_table[0] <= end) {
365  // segment loaded at its preferred address
366  fh.is_relocatable = 0;
367  }
368  // Use loadmap to find start, end for a relocatable executable
369  fnbounds_find_exec_bounds_proc_maps(filename, &start, &end);
370  TMSG(MAP_EXEC, "Bounds for relocatable exec = %p, %p", start, end);
371  }
372  else {
373  TMSG(MAP_EXEC, "NON relocatable exec");
374  char executable_name[PATH_MAX];
375  void* mstart;
376  void* mend;
377  if (dylib_find_module_containing_addr(nm_table[0],
378  executable_name, &mstart, &mend)) {
379  start = (void*) mstart;
380  end = (void*) mend;
381  }
382  else {
383  start = nm_table[0];
384  end = nm_table[fh.num_entries - 1];
385  }
386  }
387 
388  dso_info_t *dso = hpcrun_dso_make(filename, nm_table, &fh, start, end, fh.mmap_size);
389 
391  TMSG(DATACENTRIC, "fnbounds_dso_exec %s", filename);
392  // ----------------------------------------------------------
393  // add into var data tree
394  // ----------------------------------------------------------
396  }
397 
398  return dso;
399 }
400 
401 bool
402 fnbounds_ensure_mapped_dso(const char *module_name, void *start, void *end)
403 {
404  bool isOk = true;
405 
407 
408  load_module_t *lm = hpcrun_loadmap_findByAddr(start, end);
409  if (!lm) {
410  dso_info_t *dso = fnbounds_compute(module_name, start, end);
411  if (dso) {
412  hpcrun_loadmap_map(dso);
413  }
414  else {
415  EMSG("!! INTERNAL ERROR, not possible to map dso for %s (%p, %p)",
416  module_name, start, end);
417  isOk = false;
418  }
419  }
420 
422 
423  return isOk;
424 }
425 
426 
427 //---------------------------------------------------------------------
428 // Function: fnbounds_unmap_closed_dsos
429 // Purpose:
430 // identify any dsos that are no longer mapped.
431 // move them from the open to the closed list.
432 //---------------------------------------------------------------------
433 
434 void
436 {
438 
439  TMSG(LOADMAP, "Unmapping closed dsos");
441  while (current) {
442  if (current->dso_info) {
443  if (!dylib_addr_is_mapped(current->dso_info->start_addr)) {
444  TMSG(LOADMAP, "Unmapping %s", current->name);
445  hpcrun_loadmap_unmap(current);
446  }
447  }
448  current = current->next;
449  }
450 
452 }
453 
454 
455 //---------------------------------------------------------------------
456 // function fnbounds_fini:
457 //
458 // for dynamically-linked executables, shut down the fnbounds
459 // server process
460 //---------------------------------------------------------------------
461 
462 void
464 {
465  if (hpcrun_get_disabled()) return;
466 
468 }
469 
470 
471 void
473 {
475 }
476 
477 
480 {
481  char exename[PATH_MAX];
482  realpath("/proc/self/exe", exename);
483  TMSG(INTERVALS_PRINT, "name of loadmap = %s", exename);
484  load_module_t* exe_lm = hpcrun_loadmap_findByName(exename);
485  TMSG(INTERVALS_PRINT, "load module found = %p", exe_lm);
486  if (!exe_lm) return (fnbounds_table_t) {.table = (void**) NULL, .len = 0};
487  TMSG(INTERVALS_PRINT, "dso info for load module = %p", exe_lm->dso_info);
488  if (! exe_lm->dso_info) return (fnbounds_table_t) {.table = (void**) NULL, .len = 0};
489  return (fnbounds_table_t)
490  { .table = exe_lm->dso_info->table, .len = exe_lm->dso_info->nsymbols};
491 }
492 
493 
494 //*********************************************************************
495 // private operations
496 //
497 // Note: the private operations should all assume that fnbounds_lock
498 // is already locked (mostly).
499 //*********************************************************************
500 
501 
502 static bool
504 {
505  char *events = getenv(HPCRUN_EVENT_LIST);
506  return strcasestr(events, EVNAME_DATACENTRIC) != NULL;
507 }
508 
509 
510 static dso_info_t*
511 fnbounds_compute(const char* incoming_filename, void* start, void* end)
512 {
513  struct fnbounds_file_header fh;
514  char filename[PATH_MAX];
515  void** nm_table;
516  long map_size;
517 
518  if (incoming_filename == NULL) {
519  return (NULL);
520  }
521 
522  // linux-vdso.so and linux-gate.so are virtual files and don't exist
523  // in the file system.
524  if (strncmp(incoming_filename, "linux-vdso.so", 13) == 0
525  || strncmp(incoming_filename, "linux-gate.so", 13) == 0) {
526  return hpcrun_dso_make(incoming_filename, NULL, NULL, start, end, 0);
527  }
528 
529  realpath(incoming_filename, filename);
530 
531  nm_table = (void**) hpcrun_syserv_query(filename, &fh);
532  if (nm_table == NULL) {
533  return hpcrun_dso_make(filename, NULL, NULL, start, end, 0);
534  }
535  map_size = fh.mmap_size;
536 
537  if (fh.num_entries < 1) {
538  EMSG("fnbounds returns no symbols for file %s, (all intervals poisoned)", filename);
539  return hpcrun_dso_make(filename, NULL, NULL, start, end, 0);
540  }
541 
542  //
543  // Note: we no longer care if binary is stripped.
544  //
545  if (fh.is_relocatable) {
546  if (nm_table[0] >= start && nm_table[0] <= end) {
547  // segment loaded at its preferred address
548  fh.is_relocatable = 0;
549  }
550  }
551  else {
552  char executable_name[PATH_MAX];
553  void *mstart;
554  void *mend;
555  if (dylib_find_module_containing_addr(nm_table[0],
556  executable_name, &mstart, &mend)) {
557  start = (void*) mstart;
558  end = (void*) mend;
559  }
560  else {
561  start = nm_table[0];
562  end = nm_table[fh.num_entries - 1];
563  }
564  }
565 
566  dso_info_t *dso = hpcrun_dso_make(filename, nm_table, &fh, start, end, map_size);
567 
569  TMSG(DATACENTRIC, "fnbounds_compute %s", filename);
570  // ----------------------------------------------------------
571  // add into var data tree
572  // ----------------------------------------------------------
574  }
575  return dso;
576 }
577 
578 
579 // fnbounds_get_loadModule(): Given the (unnormalized) IP 'ip',
580 // attempt to return the enclosing load module. Note that the
581 // function may fail.
582 static load_module_t *
584 {
586  dso_info_t* dso = (lm) ? lm->dso_info : NULL;
587 
588  // We can't call dl_iterate_phdr() in general because catching a
589  // sample at just the wrong point inside dlopen() will segfault or
590  // deadlock.
591  //
592  // However, the risk is small, and if we're willing to take the
593  // risk, then analyzing the new DSO here allows us to sample inside
594  // an init constructor.
595  if (!dso && ENABLED(DLOPEN_RISKY) && hpcrun_dlopen_pending() > 0) {
596  char module_name[PATH_MAX];
597  void *mstart, *mend;
598 
599  if (dylib_find_module_containing_addr(ip, module_name, &mstart, &mend)) {
600  dso = fnbounds_compute(module_name, mstart, mend);
601 
602  if (dso) {
603  lm = hpcrun_loadmap_map(dso);
604 
605  // ----------------------------------------------------------
606  // add into var data tree
607  // ----------------------------------------------------------
608  //fnbounds_run_var_analysis(module_name, dso);
609  }
610  }
611  }
612 
613  return lm;
614 }
615 
616 
617 static void
619 {
620  // dylib_map_executable() ==>
621  // fnbounds_ensure_mapped_dso("/proc/self/exe", NULL, NULL) ==>
622  //{
623  // FNBOUNDS_LOCK;
624  // dso = fnbound_compute(exename, ...);
625  // hpcrun_loadmap_map(dso);
626  // FNBOUNDS_UNLOCK;
627  //}
631 }
char * name
Definition: loadmap.h:74
char * executable_name
static void fnbounds_run_var_analysis(dso_info_t *dso)
void * memblock
Definition: data_tree.h:70
#define FNBOUNDS_LOCK
struct datatree_info_s * right
Definition: data_tree.h:74
long hpcrun_dlopen_pending(void)
Definition: hpcrun_dlfns.c:105
const char * tmpname()
Definition: FileUtil.cpp:418
int fnbounds_init()
void hpcrun_loadmap_unmap(load_module_t *lm)
Definition: loadmap.c:433
load_module_t * hpcrun_loadmap_findByName(const char *name)
Definition: loadmap.c:287
static spinlock_t fnbounds_lock
const char * HPCRUN_EVENT_LIST
Definition: env.c:55
int fnbounds_table_lookup(void **table, int length, void *ip, void **start, void **end)
dso_info_t * hpcrun_dso_make(const char *name, void **table, struct fnbounds_file_header *fh, void *startaddr, void *endaddr, unsigned long map_size)
Definition: loadmap.c:130
void hpcrun_syserv_fini(void)
void fnbounds_map_open_dsos()
void * hpcrun_syserv_query(const char *fname, struct fnbounds_file_header *fh)
size_t bytes
Definition: data_tree.h:69
void fnbounds_release_lock(void)
char * name
Definition: loadmap.h:128
#define DATACENTRIC_MIN_BYTES
Definition: data_tree.h:59
void datatree_splay_insert(struct datatree_info_s *node)
Definition: data_tree.c:129
struct datatree_info_s * left
Definition: data_tree.h:73
int dylib_addr_is_mapped(void *addr)
Definition: dylib.c:144
void fnbounds_unmap_closed_dsos()
fnbounds_table_t fnbounds_fetch_executable_table(void)
dso_info_t * dso_info
Definition: loadmap.h:129
#define EVNAME_DATACENTRIC
Definition: env.h:92
bool fnbounds_ensure_mapped_dso(const char *module_name, void *start, void *end)
void * hpcrun_syserv_query_var(const char *fname, struct fnbounds_file_header *fh)
#define EMSG
Definition: messages.h:70
uintptr_t start_to_ref_dist
Definition: loadmap.h:77
dso_info_t * fnbounds_dso_exec(void)
load_module_t * hpcrun_loadmap_map(dso_info_t *dso)
Definition: loadmap.c:389
static bool fnbounds_is_datacentric_enabled()
struct load_module_t * next
Definition: loadmap.h:130
void dylib_map_open_dsos()
Definition: dylib.c:125
int is_relocatable
Definition: loadmap.h:81
static void insert_var_table(void **var_table, unsigned long num)
int hpcrun_syserv_init(void)
void * start_addr
Definition: loadmap.h:75
load_module_t * lm_head
Definition: loadmap.h:150
#define FNBOUNDS_UNLOCK
static void DATACENTRIC()
Definition: cct_bundle.c:71
void * hpcrun_malloc(size_t size)
Definition: mem.c:275
cct_node_t * context
Definition: data_tree.h:68
void fnbounds_fini()
#define TMSG(f,...)
Definition: messages.h:93
int dylib_find_module_containing_addr(void *addr, char *module_name, void **start, void **end)
Definition: dylib.c:155
unsigned long reference_offset
#define DATA_STATIC_MAGIC
Definition: data_tree.h:57
#define NULL
Definition: ElfHelper.cpp:85
void ** table
Definition: loadmap.h:78
static dso_info_t * fnbounds_compute(const char *filename, void *start, void *end)
unsigned long nsymbols
Definition: loadmap.h:80
bool hpcrun_get_disabled(void)
Definition: disabled.c:80
cct_addr_t * addr
Definition: cct.c:130
load_module_t * hpcrun_loadmap_findByAddr(void *begin, void *end)
Definition: loadmap.c:261
static void fnbounds_map_executable()
#define PERFORM_RELOCATION(addr, offset)
void * rmemblock
Definition: data_tree.h:71
static void fnbounds_find_exec_bounds_proc_maps(char *exename, void **start, void **end)
#define SPINLOCK_UNLOCKED
Definition: spinlock.h:84
bool fnbounds_enclosing_addr(void *ip, void **start, void **end, load_module_t **lm)
static load_module_t * fnbounds_get_loadModule(void *ip)
#define ENABLED(f)
Definition: debug-flag.h:76
hpcrun_loadmap_t * hpcrun_getLoadmap()
Definition: loadmap.c:501