source: memlog.cpp @ 09f3093

Revision 09f3093, 8.6 KB checked in by Hal Finkel <hfinkel@…>, 9 years ago (diff)

Add open-source license

  • Property mode set to 100644
Line 
1// *****************************************************************************
2//                   Copyright (C) 2015, UChicago Argonne, LLC
3//                              All Rights Reserved
4//                            memlog (ANL-SF-15-081)
5//                    Hal Finkel, Argonne National Laboratory
6//
7//                              OPEN SOURCE LICENSE
8//
9// Under the terms of Contract No. DE-AC02-06CH11357 with UChicago Argonne, LLC,
10// the U.S. Government retains certain rights in this software.
11//
12// Redistribution and use in source and binary forms, with or without
13// modification, are permitted provided that the following conditions are met:
14//
15// 1. Redistributions of source code must retain the above copyright notice, this
16//    list of conditions and the following disclaimer.
17//
18// 2. Redistributions in binary form must reproduce the above copyright notice,
19//    this list of conditions and the following disclaimer in the documentation
20//    and/or other materials provided with the distribution.
21//
22// 3. Neither the names of UChicago Argonne, LLC or the Department of Energy nor
23//    the names of its contributors may be used to endorse or promote products
24//    derived from this software without specific prior written permission.
25// 
26// *****************************************************************************
27//                                  DISCLAIMER
28//
29// THE SOFTWARE IS SUPPLIED “AS IS” WITHOUT WARRANTY OF ANY KIND.
30//
31// NEITHER THE UNTED STATES GOVERNMENT, NOR THE UNITED STATES DEPARTMENT OF
32// ENERGY, NOR UCHICAGO ARGONNE, LLC, NOR ANY OF THEIR EMPLOYEES, MAKES ANY
33// WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY LEGAL LIABILITY OR RESPONSIBILITY
34// FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF ANY INFORMATION, DATA,
35// APPARATUS, PRODUCT, OR PROCESS DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT
36// INFRINGE PRIVATELY OWNED RIGHTS.
37//
38// *****************************************************************************
39
40#ifndef _GNU_SOURCE
41#define _GNU_SOURCE
42#endif
43
44#include <cstdlib>
45#include <cstdio>
46#include <cstring>
47
48// NOTE: This source makes very minimal use of C++11 features. It can still be
49// compiled by g++ 4.4.7 with -std=gnu++0x.
50#include <unordered_map>
51#include <utility>
52
53#include <limits.h>
54#include <malloc.h>
55#include <execinfo.h>
56#include <sys/syscall.h>
57#include <sys/time.h>
58#include <sys/resource.h>
59#include <sys/types.h>
60#include <sys/stat.h>
61#include <sys/utsname.h>
62#include <fcntl.h>
63#include <unistd.h>
64
65#include <pthread.h>
66#include <dlfcn.h>
67
68using namespace std;
69
70// NOTE: When static linking, this depends on linker wrapping.
71// Add to your LDFLAGS:
72//   -Wl,--wrap,malloc,--wrap,free,--wrap,realloc,--wrap,calloc,--wrap,memalign /path/to/memlog_s.o -lpthread -ldl
73
74FILE *log_file = NULL;
75static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
76
77// The malloc hook might use functions that call malloc, and we need to make
78// sure this does not cause an infinite loop.
79static __thread int in_malloc = 0;
80static char self_path[PATH_MAX+1] = { '\0' };
81
82__attribute__((__constructor__))
83static void record_init() {
84  struct utsname u;
85  uname(&u);
86
87  char log_name[PATH_MAX+1];
88  snprintf(log_name, PATH_MAX+1, "%s.%d.memlog", u.nodename, getpid());
89  log_file = fopen(log_name, "w");
90  if (!log_file)
91    fprintf(stderr, "fopen failed for '%s': %m\n", log_name);
92
93  const char *link_name = "/proc/self/exe";
94  readlink(link_name, self_path, PATH_MAX);
95}
96
97__attribute__((__destructor__))
98static void record_cleanup() {
99  if (!log_file)
100    return;
101
102  // These functions might call free, but we're shutting down, so don't try to
103  // unwind the stack from here...
104  in_malloc = 1;
105
106  // Avoid any racing by obtaining the lock.
107  if (pthread_mutex_lock(&log_mutex))
108    return;
109
110  (void) fflush(log_file);
111  (void) fclose(log_file);
112}
113
114// dladdr is, relatively, quit slow. For this to work on a large application,
115// we need to cache the lookup results.
116static int dladdr_cached(void * addr, Dl_info *info) {
117  static unordered_map<void *, Dl_info> dladdr_cache;
118
119  auto I = dladdr_cache.find(addr);
120  if (I == dladdr_cache.end()) {
121    int r;
122    if (!(r = dladdr(addr, info)))
123      memset(info, 0, sizeof(Dl_info));
124
125    dladdr_cache.insert(make_pair(addr, *info));
126    return r;
127  }
128
129  memcpy(info, &I->second, sizeof(Dl_info));
130  return 1;
131}
132
133static void print_context(const void *caller, int show_backtrace) {
134  struct rusage usage;
135  if (getrusage(RUSAGE_SELF, &usage)) {
136    fprintf(stderr, "getrusage failed: %m\n");
137    return;
138  }
139
140  fprintf(log_file, "\t%ld.%06ld %ld %ld", usage.ru_utime.tv_sec,
141          usage.ru_utime.tv_usec, usage.ru_maxrss, syscall(SYS_gettid));
142
143  if (!show_backtrace)
144    return;
145
146  void *pcs[1024];
147  int num_pcs = backtrace(pcs, 1024);
148
149  int found_caller = 0;
150  for (int pci = 0; pci < num_pcs; ++pci) {
151    intptr_t pc = (intptr_t) pcs[pci];
152
153    if (!pc)
154      break;
155
156    if (!found_caller) {
157      if (pc != (intptr_t) caller)
158        continue;
159
160      found_caller = 1;
161    }
162
163    intptr_t off, relpc;
164    const char *proc_name;
165    const char *file_name;
166    Dl_info dlinfo;
167    if (dladdr_cached((void *) pc, &dlinfo) && dlinfo.dli_fname &&
168        *dlinfo.dli_fname) {
169      intptr_t saddr = (intptr_t) dlinfo.dli_saddr;
170      if (saddr) {
171#if defined(__powerpc64__) && !defined(__powerpc64le__)
172        // On PPC64 ELFv1, the symbol address points to the function descriptor, not
173        // the actual starting address.
174        saddr = *(intptr_t*) saddr;
175#endif
176
177        off = pc - saddr;
178        relpc = pc - ((intptr_t) dlinfo.dli_fbase);
179      } else {
180        off = 0;
181        relpc = 0;
182      }
183
184      proc_name = dlinfo.dli_sname;
185      if (!proc_name)
186        proc_name = "?";
187
188      file_name = dlinfo.dli_fname;
189    } else {
190      // We don't know these...
191      off = 0;
192      relpc = 0;
193      proc_name = "?";
194
195      // If we can't determine the file, assume it is the base executable
196      // (which does the right thing for statically-linked binaries).
197      file_name = self_path;
198    }
199
200    fprintf(log_file, "\t%s (%s+0x%x) [0x%lx (0x%lx)]", file_name, proc_name, (int) off,
201            (long) pc, (long) relpc);
202  }
203}
204
205static void record_malloc(size_t size, void *ptr, const void *caller) {
206  if (!log_file)
207    return;
208
209  if (pthread_mutex_lock(&log_mutex))
210    return;
211
212  fprintf(log_file, "M: %zd %p", size, ptr);
213  print_context(caller, 1);
214  fprintf(log_file, "\n");
215
216done:
217  pthread_mutex_unlock(&log_mutex);
218}
219
220static void record_free(void *ptr, const void *caller) {
221  if (!log_file)
222    return;
223
224  if (pthread_mutex_lock(&log_mutex))
225    return;
226
227  fprintf(log_file, "F: %p", ptr);
228  print_context(caller, 0);
229  fprintf(log_file, "\n");
230
231done:
232  pthread_mutex_unlock(&log_mutex);
233}
234
235// glibc exports its underlying malloc implementation under the name
236// __libc_malloc so that hooks like this can use it.
237extern "C" {
238extern void *__libc_malloc(size_t size);
239extern void *__libc_realloc(void *ptr, size_t size);
240extern void *__libc_calloc(size_t nmemb, size_t size);
241extern void *__libc_memalign(size_t boundary, size_t size);
242extern void __libc_free(void *ptr);
243
244#ifdef __PIC__
245#define FUNC(x) x
246#else
247#define FUNC(x) __wrap_ ## x
248#endif
249
250void *FUNC(malloc)(size_t size) {
251  const void *caller =
252    __builtin_extract_return_addr(__builtin_return_address(0));
253
254  if (in_malloc)
255    return __libc_malloc(size);
256
257  in_malloc = 1;
258
259  void *ptr = __libc_malloc(size);
260
261  record_malloc(size, ptr, caller);
262
263  in_malloc = 0;
264  return ptr;
265}
266
267void *FUNC(realloc)(void *ptr, size_t size) {
268  const void *caller =
269    __builtin_extract_return_addr(__builtin_return_address(0));
270
271  if (in_malloc)
272    return __libc_realloc(ptr, size);
273
274  in_malloc = 1;
275
276  void *nptr = __libc_realloc(ptr, size);
277
278  if (ptr)
279    record_free(ptr, caller);
280  record_malloc(size, nptr, caller);
281
282  in_malloc = 0;
283
284  return nptr;
285}
286
287void *FUNC(calloc)(size_t nmemb, size_t size) {
288  const void *caller =
289    __builtin_extract_return_addr(__builtin_return_address(0));
290
291  if (in_malloc)
292    return __libc_calloc(nmemb, size);
293
294  in_malloc = 1;
295
296  void *ptr = __libc_calloc(nmemb, size);
297
298  record_malloc(nmemb*size, ptr, caller);
299
300  in_malloc = 0;
301
302  return ptr;
303}
304
305void *FUNC(memalign)(size_t boundary, size_t size) {
306  const void *caller =
307    __builtin_extract_return_addr(__builtin_return_address(0));
308
309  if (in_malloc)
310    return __libc_memalign(boundary, size);
311
312  in_malloc = 1;
313
314  void *ptr = __libc_memalign(boundary, size);
315
316  record_malloc(size, ptr, caller);
317
318  in_malloc = 0;
319
320  return ptr;
321}
322
323void FUNC(free)(void *ptr) {
324  const void *caller =
325    __builtin_extract_return_addr(__builtin_return_address(0));
326
327  if (in_malloc || !ptr)
328    return __libc_free(ptr);
329
330  in_malloc = 1;
331
332  record_free(ptr, caller);
333
334  __libc_free(ptr);
335
336  in_malloc = 0;
337}
338
339} // extern "C"
340
Note: See TracBrowser for help on using the repository browser.