source: memlog.cpp @ 4c51931

Revision 4c51931, 11.7 KB checked in by Hal Finkel <hfinkel@…>, 9 years ago (diff)

move cache from bring a local static to avoid deadlocking cxa_atexit

  • Property mode set to 100644
Line 
1#ifndef _GNU_SOURCE
2#define _GNU_SOURCE
3#endif
4
5#include <cstdlib>
6#include <cstdio>
7#include <cstring>
8#include <cstdint>
9
10// NOTE: This source makes very minimal use of C++11 features. It can still be
11// compiled by g++ 4.4.7 with -std=gnu++0x.
12#include <unordered_map>
13#include <utility>
14
15#include <limits.h>
16#include <errno.h>
17#include <malloc.h>
18#include <execinfo.h>
19#include <sys/mman.h>
20#include <sys/syscall.h>
21#include <sys/time.h>
22#include <sys/resource.h>
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <sys/utsname.h>
26#include <fcntl.h>
27#include <unistd.h>
28
29#include <pthread.h>
30#include <dlfcn.h>
31
32#ifdef __bgq__
33#include <spi/include/kernel/location.h>
34#include <spi/include/kernel/memory.h>
35#endif
36
37using namespace std;
38
39// NOTE: When static linking, this depends on linker wrapping.
40// Add to your LDFLAGS:
41//   -Wl,--wrap,malloc,--wrap,free,--wrap,realloc,--wrap,calloc,--wrap,memalign /path/to/memlog_s.o -lpthread -ldl
42
43static FILE *log_file = 0;
44static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
45
46// The malloc hook might use functions that call malloc, and we need to make
47// sure this does not cause an infinite loop.
48static __thread int in_malloc = 0;
49static char self_path[PATH_MAX+1] = { '\0' };
50
51#ifdef __bgq__
52static int on_bgq = 0;
53#endif
54
55static void *initial_brk = 0;
56
57static unordered_map<void *, Dl_info> *dladdr_cache = 0;
58
59__attribute__((__constructor__))
60static void record_init() {
61  struct utsname u;
62  uname(&u);
63
64  int id = (int) getpid();
65#ifdef __bgq__
66  // If we're really running on a BG/Q compute node, use the job rank instead
67  // of the pid because the node name might not really be globally unique.
68  if (!strcmp(u.sysname, "CNK") && !strcmp(u.machine, "BGQ")) {
69    id = (int) Kernel_GetRank();
70    on_bgq = 1;
71  }
72#endif
73
74  // If we're running under a common batch system, add the job id to the output
75  // file names (add it as a prefix so that sorting the files will sort by job
76  // first).
77  char *job_id = 0;
78  const char *job_id_vars[] =
79    { "COBALT_JOBID", "PBS_JOBID", "SLURM_JOB_ID", "JOB_ID" };
80  for (int i = 0; i < sizeof(job_id_vars)/sizeof(job_id_vars[0]); ++i) {
81    job_id = getenv(job_id_vars[i]);
82    if (job_id)
83      break;
84  }
85
86  char log_name[PATH_MAX+1];
87  if (job_id)
88    snprintf(log_name, PATH_MAX+1, "%s.%s.%d.memlog", job_id, u.nodename, id);
89  else
90    snprintf(log_name, PATH_MAX+1, "%s.%d.memlog", u.nodename, id);
91  log_file = fopen(log_name, "w");
92  if (!log_file)
93    fprintf(stderr, "fopen failed for '%s': %m\n", log_name);
94
95  const char *link_name = "/proc/self/exe";
96  readlink(link_name, self_path, PATH_MAX);
97
98  initial_brk = sbrk(0);
99}
100
101__attribute__((__destructor__))
102static void record_cleanup() {
103  if (!log_file)
104    return;
105
106  // These functions might call free, but we're shutting down, so don't try to
107  // unwind the stack from here...
108  in_malloc = 1;
109
110  // Avoid any racing by obtaining the lock.
111  if (pthread_mutex_lock(&log_mutex))
112    return;
113
114  (void) fflush(log_file);
115  (void) fclose(log_file);
116
117  if (dladdr_cache)
118    delete dladdr_cache;
119}
120
121// dladdr is, relatively, quit slow. For this to work on a large application,
122// we need to cache the lookup results.
123static int dladdr_cached(void * addr, Dl_info *info) {
124  if (!dladdr_cache)
125    dladdr_cache = new unordered_map<void *, Dl_info>;
126
127  auto I = dladdr_cache->find(addr);
128  if (I == dladdr_cache->end()) {
129    int r;
130    if (!(r = dladdr(addr, info)))
131      memset(info, 0, sizeof(Dl_info));
132
133    dladdr_cache->insert(make_pair(addr, *info));
134    return r;
135  }
136
137  memcpy(info, &I->second, sizeof(Dl_info));
138  return 1;
139}
140
141static void print_context(const void *caller, int show_backtrace) {
142  struct rusage usage;
143  if (getrusage(RUSAGE_SELF, &usage)) {
144    fprintf(stderr, "getrusage failed: %m\n");
145    return;
146  }
147
148  fprintf(log_file, "\t%ld.%06ld %ld %ld", usage.ru_utime.tv_sec,
149          usage.ru_utime.tv_usec, usage.ru_maxrss, syscall(SYS_gettid));
150
151  // Some other memory stats (like with maxrss, report these in KB).
152  size_t arena_size = ((size_t) sbrk(0)) - (size_t) initial_brk;
153
154  uint64_t mmap_size = 0;
155#ifdef __bgq__
156  if (on_bgq)
157    (void) Kernel_GetMemorySize(KERNEL_MEMSIZE_MMAP, &mmap_size);
158#endif
159
160  fprintf(log_file, " %ld %ld", arena_size >> 10, mmap_size >> 10);
161
162  if (!show_backtrace)
163    return;
164
165  void *pcs[1024];
166  int num_pcs = backtrace(pcs, 1024);
167
168  int found_caller = 0;
169  for (int pci = 0; pci < num_pcs; ++pci) {
170    intptr_t pc = (intptr_t) pcs[pci];
171
172    if (!pc)
173      break;
174
175    if (!found_caller) {
176      if (pc != (intptr_t) caller)
177        continue;
178
179      found_caller = 1;
180    }
181
182    intptr_t off, relpc;
183    const char *proc_name;
184    const char *file_name;
185    Dl_info dlinfo;
186    if (dladdr_cached((void *) pc, &dlinfo) && dlinfo.dli_fname &&
187        *dlinfo.dli_fname) {
188      intptr_t saddr = (intptr_t) dlinfo.dli_saddr;
189      if (saddr) {
190#if defined(__powerpc64__) && !defined(__powerpc64le__)
191        // On PPC64 ELFv1, the symbol address points to the function descriptor, not
192        // the actual starting address.
193        saddr = *(intptr_t*) saddr;
194#endif
195
196        off = pc - saddr;
197        relpc = pc - ((intptr_t) dlinfo.dli_fbase);
198      } else {
199        off = 0;
200        relpc = 0;
201      }
202
203      proc_name = dlinfo.dli_sname;
204      if (!proc_name)
205        proc_name = "?";
206
207      file_name = dlinfo.dli_fname;
208    } else {
209      // We don't know these...
210      off = 0;
211      relpc = 0;
212      proc_name = "?";
213
214      // If we can't determine the file, assume it is the base executable
215      // (which does the right thing for statically-linked binaries).
216      file_name = self_path;
217    }
218
219    fprintf(log_file, "\t%s (%s+0x%x) [0x%lx (0x%lx)]", file_name, proc_name, (int) off,
220            (long) pc, (long) relpc);
221  }
222}
223
224static void record_malloc(size_t size, void *ptr, const void *caller) {
225  if (!log_file)
226    return;
227
228  if (pthread_mutex_lock(&log_mutex))
229    return;
230
231  fprintf(log_file, "M: %zd %p", size, ptr);
232  print_context(caller, 1);
233  fprintf(log_file, "\n");
234
235done:
236  pthread_mutex_unlock(&log_mutex);
237}
238
239static void record_free(void *ptr, const void *caller) {
240  if (!log_file)
241    return;
242
243  if (pthread_mutex_lock(&log_mutex))
244    return;
245
246  fprintf(log_file, "F: %p", ptr);
247  print_context(caller, 0);
248  fprintf(log_file, "\n");
249
250done:
251  pthread_mutex_unlock(&log_mutex);
252}
253
254#ifdef __PIC__
255static int (*__real_posix_memalign)(void **memptr, size_t alignment,
256                                    size_t size) = 0;
257
258static void *(*__real_mmap)(void *addr, size_t length, int prot, int flags,
259                            int fd, off_t offset) = 0;
260static void *(*__real_mmap64)(void *addr, size_t length, int prot, int flags,
261                              int fd, off64_t offset) = 0;
262static int (*__real_munmap)(void *addr, size_t length) = 0;
263#else
264extern "C" {
265extern int __real_posix_memalign(void **memptr, size_t alignment, size_t size);
266
267extern void *__real_mmap(void *addr, size_t length, int prot, int flags,
268                         int fd, off_t offset);
269extern void *__real_mmap64(void *addr, size_t length, int prot, int flags,
270                           int fd, off64_t offset);
271extern int __real_munmap(void *addr, size_t length);
272}
273#endif
274
275// glibc exports its underlying malloc implementation under the name
276// __libc_malloc so that hooks like this can use it.
277extern "C" {
278extern void *__libc_malloc(size_t size);
279extern void *__libc_valloc(size_t size);
280extern void *__libc_realloc(void *ptr, size_t size);
281extern void *__libc_calloc(size_t nmemb, size_t size);
282extern void *__libc_memalign(size_t boundary, size_t size);
283extern void __libc_free(void *ptr);
284
285#ifdef __PIC__
286#define FUNC(x) x
287#else
288#define FUNC(x) __wrap_ ## x
289#endif
290
291void *FUNC(malloc)(size_t size) {
292  const void *caller =
293    __builtin_extract_return_addr(__builtin_return_address(0));
294
295  if (in_malloc)
296    return __libc_malloc(size);
297
298  in_malloc = 1;
299
300  void *ptr = __libc_malloc(size);
301  if (ptr)
302    record_malloc(size, ptr, caller);
303
304  in_malloc = 0;
305  return ptr;
306}
307
308void *FUNC(valloc)(size_t size) {
309  const void *caller =
310    __builtin_extract_return_addr(__builtin_return_address(0));
311
312  if (in_malloc)
313    return __libc_valloc(size);
314
315  in_malloc = 1;
316
317  void *ptr = __libc_valloc(size);
318  if (ptr)
319    record_malloc(size, ptr, caller);
320
321  in_malloc = 0;
322  return ptr;
323}
324
325void *FUNC(realloc)(void *ptr, size_t size) {
326  const void *caller =
327    __builtin_extract_return_addr(__builtin_return_address(0));
328
329  if (in_malloc)
330    return __libc_realloc(ptr, size);
331
332  in_malloc = 1;
333
334  void *nptr = __libc_realloc(ptr, size);
335
336  if (ptr)
337    record_free(ptr, caller);
338  if (nptr)
339    record_malloc(size, nptr, caller);
340
341  in_malloc = 0;
342
343  return nptr;
344}
345
346void *FUNC(calloc)(size_t nmemb, size_t size) {
347  const void *caller =
348    __builtin_extract_return_addr(__builtin_return_address(0));
349
350  if (in_malloc)
351    return __libc_calloc(nmemb, size);
352
353  in_malloc = 1;
354
355  void *ptr = __libc_calloc(nmemb, size);
356
357  if (ptr)
358    record_malloc(nmemb*size, ptr, caller);
359
360  in_malloc = 0;
361
362  return ptr;
363}
364
365void *FUNC(memalign)(size_t boundary, size_t size) {
366  const void *caller =
367    __builtin_extract_return_addr(__builtin_return_address(0));
368
369  if (in_malloc)
370    return __libc_memalign(boundary, size);
371
372  in_malloc = 1;
373
374  void *ptr = __libc_memalign(boundary, size);
375
376  if (ptr)
377    record_malloc(size, ptr, caller);
378
379  in_malloc = 0;
380
381  return ptr;
382}
383
384void FUNC(free)(void *ptr) {
385  const void *caller =
386    __builtin_extract_return_addr(__builtin_return_address(0));
387
388  if (in_malloc || !ptr)
389    return __libc_free(ptr);
390
391  in_malloc = 1;
392
393  record_free(ptr, caller);
394
395  __libc_free(ptr);
396
397  in_malloc = 0;
398}
399
400int FUNC(posix_memalign)(void **memptr, size_t alignment, size_t size) {
401  const void *caller =
402    __builtin_extract_return_addr(__builtin_return_address(0));
403
404#ifdef __PIC__
405  if (!__real_posix_memalign)
406    if (!(*(void **) (&__real_posix_memalign) =
407        dlsym(RTLD_NEXT, "posix_memalign"))) {
408      return ELIBACC;
409    }
410#endif
411
412  if (in_malloc)
413    return __real_posix_memalign(memptr, alignment, size);
414
415  in_malloc = 1;
416
417  int r = __real_posix_memalign(memptr, alignment, size);
418
419  if (!r)
420    record_malloc(size, *memptr, caller);
421
422  in_malloc = 0;
423
424  return r;
425}
426
427void *FUNC(mmap)(void *addr, size_t length, int prot, int flags,
428                 int fd, off_t offset) {
429  const void *caller =
430    __builtin_extract_return_addr(__builtin_return_address(0));
431
432#ifdef __PIC__
433  if (!__real_mmap)
434    if (!(*(void **) (&__real_mmap) = dlsym(RTLD_NEXT, "mmap"))) {
435      errno = ELIBACC;
436      return MAP_FAILED;
437    }
438#endif
439
440  if (in_malloc)
441    return __real_mmap(addr, length, prot, flags, fd, offset);
442
443  in_malloc = 1;
444
445  void *ptr = __real_mmap(addr, length, prot, flags, fd, offset);
446
447  if (ptr != MAP_FAILED)
448    record_malloc(length, ptr, caller);
449
450  in_malloc = 0;
451
452  return ptr;
453}
454
455void *FUNC(mmap64)(void *addr, size_t length, int prot, int flags,
456                   int fd, off64_t offset) {
457  const void *caller =
458    __builtin_extract_return_addr(__builtin_return_address(0));
459
460#ifdef __PIC__
461  if (!__real_mmap64)
462    if (!(*(void **) (&__real_mmap64) = dlsym(RTLD_NEXT, "mmap64"))) {
463      errno = ELIBACC;
464      return MAP_FAILED;
465    }
466#endif
467
468  if (in_malloc)
469    return __real_mmap64(addr, length, prot, flags, fd, offset);
470
471  in_malloc = 1;
472
473  void *ptr = __real_mmap64(addr, length, prot, flags, fd, offset);
474
475  if (ptr != MAP_FAILED)
476    record_malloc(length, ptr, caller);
477
478  in_malloc = 0;
479
480  return ptr;
481}
482
483int FUNC(munmap)(void *addr, size_t length) {
484  const void *caller =
485    __builtin_extract_return_addr(__builtin_return_address(0));
486
487#ifdef __PIC__
488  if (!__real_munmap)
489    if (!(*(void **) (&__real_munmap) = dlsym(RTLD_NEXT, "munmap"))) {
490      errno = ELIBACC;
491      return -1;
492    }
493#endif
494
495  if (in_malloc)
496    return __real_munmap(addr, length);
497
498  in_malloc = 1;
499
500  record_free(addr, caller);
501
502  int r = __real_munmap(addr, length);
503
504  in_malloc = 0;
505
506  return r;
507}
508
509} // extern "C"
510
Note: See TracBrowser for help on using the repository browser.