source: memlog.cpp @ ed8b809

Revision ed8b809, 11.5 KB checked in by Hal Finkel <hfinkel@…>, 9 years ago (diff)

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