source: memlog.cpp @ 6444e03

Revision 6444e03, 10.8 KB checked in by Hal Finkel <hfinkel@…>, 9 years ago (diff)

working on wrapping mmap, etc.

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