source: memlog.c @ 5a9481e

Revision 5a9481e, 6.2 KB checked in by Hal Finkel <hfinkel@…>, 9 years ago (diff)

obtain lock to close files, etc.; misc. rearranging

  • Property mode set to 100644
Line 
1#ifndef _GNU_SOURCE
2#define _GNU_SOURCE
3#endif
4
5#include <stdlib.h>
6#include <stdio.h>
7#include <limits.h>
8#include <string.h>
9
10#include <malloc.h>
11#include <execinfo.h>
12#include <sys/syscall.h>
13#include <sys/time.h>
14#include <sys/resource.h>
15#include <sys/types.h>
16#include <sys/stat.h>
17#include <sys/utsname.h>
18#include <fcntl.h>
19#include <unistd.h>
20
21#include <pthread.h>
22
23#include <dlfcn.h>
24
25// NOTE: When static linking, this depends on linker wrapping.
26// Add to your LDFLAGS:
27//   -Wl,--wrap,malloc,--wrap,free,--wrap,realloc,--wrap,calloc,--wrap,memalign /path/to/memlog_s.o -lpthread -ldl
28
29FILE *log_file = NULL;
30static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
31
32// The malloc hook might use functions that call malloc, and we need to make
33// sure this does not cause an infinite loop.
34static __thread int in_malloc = 0;
35static char self_path[PATH_MAX+1] = { '\0' };
36
37__attribute__((__constructor__))
38static void record_init() {
39  struct utsname u;
40  uname(&u);
41
42  char log_name[PATH_MAX+1];
43  snprintf(log_name, PATH_MAX+1, "%s.%d.memlog", u.nodename, getpid());
44  log_file = fopen(log_name, "w");
45  if (!log_file)
46    fprintf(stderr, "fopen failed for '%s': %m\n", log_name);
47
48  const char *link_name = "/proc/self/exe";
49  readlink(link_name, self_path, PATH_MAX);
50}
51
52__attribute__((__destructor__))
53static void record_cleanup() {
54  if (!log_file)
55    return;
56
57  // These functions might call free, but we're shutting down, so don't try to
58  // unwind the stack from here...
59  in_malloc = 1;
60
61  // Avoid any racing by obtaining the lock.
62  if (pthread_mutex_lock(&log_mutex))
63    return;
64
65  (void) fflush(log_file);
66  (void) fclose(log_file);
67}
68
69// dladdr is, relatively, quit slow. For this to work on a large application,
70// we need to cache the lookup results.
71static int dladdr_cached(void *addr, Dl_info *info) {
72  return dladdr(addr, info);
73}
74
75static void print_context(const void *caller, int show_backtrace) {
76  struct rusage usage;
77  if (getrusage(RUSAGE_SELF, &usage)) {
78    fprintf(stderr, "getrusage failed: %m\n");
79    return;
80  }
81
82  fprintf(log_file, "\t%ld.%06ld %ld %ld", usage.ru_utime.tv_sec,
83          usage.ru_utime.tv_usec, usage.ru_maxrss, syscall(SYS_gettid));
84
85  if (!show_backtrace)
86    return;
87
88  void *pcs[1024];
89  int num_pcs = backtrace(pcs, 1024);
90
91  int found_caller = 0;
92  for (int pci = 0; pci < num_pcs; ++pci) {
93    intptr_t pc = (intptr_t) pcs[pci];
94
95    if (!pc)
96      break;
97
98    if (!found_caller) {
99      if (pc != (intptr_t) caller)
100        continue;
101
102      found_caller = 1;
103    }
104
105    intptr_t off, relpc;
106    const char *proc_name;
107    const char *file_name;
108    Dl_info dlinfo;
109    if (dladdr_cached((void *) pc, &dlinfo) && dlinfo.dli_fname &&
110        *dlinfo.dli_fname) {
111      intptr_t saddr = (intptr_t) dlinfo.dli_saddr;
112      if (saddr) {
113#if defined(__powerpc64__) && !defined(__powerpc64le__)
114        // On PPC64 ELFv1, the symbol address points to the function descriptor, not
115        // the actual starting address.
116        saddr = *(intptr_t*) saddr;
117#endif
118
119        off = pc - saddr;
120        relpc = pc - ((intptr_t) dlinfo.dli_fbase);
121      } else {
122        off = 0;
123        relpc = 0;
124      }
125
126      proc_name = dlinfo.dli_sname;
127      if (!proc_name)
128        proc_name = "?";
129
130      file_name = dlinfo.dli_fname;
131    } else {
132      // We don't know these...
133      off = 0;
134      relpc = 0;
135      proc_name = "?";
136
137      // If we can't determine the file, assume it is the base executable
138      // (which does the right thing for statically-linked binaries).
139      file_name = self_path;
140    }
141
142    fprintf(log_file, "\t%s (%s+0x%x) [0x%lx (0x%lx)]", file_name, proc_name, (int) off,
143            (long) pc, (long) relpc);
144  }
145}
146
147static void record_malloc(size_t size, void *ptr, const void *caller) {
148  if (!log_file)
149    return;
150
151  if (pthread_mutex_lock(&log_mutex))
152    return;
153
154  fprintf(log_file, "M: %zd %p", size, ptr);
155  print_context(caller, 1);
156  fprintf(log_file, "\n");
157
158done:
159  pthread_mutex_unlock(&log_mutex);
160}
161
162static void record_free(void *ptr, const void *caller) {
163  if (!log_file)
164    return;
165
166  if (pthread_mutex_lock(&log_mutex))
167    return;
168
169  fprintf(log_file, "F: %p", ptr);
170  print_context(caller, 0);
171  fprintf(log_file, "\n");
172
173done:
174  pthread_mutex_unlock(&log_mutex);
175}
176
177// glibc exports its underlying malloc implementation under the name
178// __libc_malloc so that hooks like this can use it.
179extern void *__libc_malloc(size_t size);
180extern void *__libc_realloc(void *ptr, size_t size);
181extern void *__libc_calloc(size_t nmemb, size_t size);
182extern void *__libc_memalign(size_t boundary, size_t size);
183extern void __libc_free(void *ptr);
184
185#ifdef __PIC__
186#define FUNC(x) x
187#else
188#define FUNC(x) __wrap_ ## x
189#endif
190
191void *FUNC(malloc)(size_t size) {
192  const void *caller =
193    __builtin_extract_return_addr(__builtin_return_address(0));
194
195  if (in_malloc)
196    return __libc_malloc(size);
197
198  in_malloc = 1;
199
200  void *ptr = __libc_malloc(size);
201
202  record_malloc(size, ptr, caller);
203
204  in_malloc = 0;
205  return ptr;
206}
207
208void *FUNC(realloc)(void *ptr, size_t size) {
209  const void *caller =
210    __builtin_extract_return_addr(__builtin_return_address(0));
211
212  if (in_malloc)
213    return __libc_realloc(ptr, size);
214
215  in_malloc = 1;
216
217  void *nptr = __libc_realloc(ptr, size);
218
219  if (ptr)
220    record_free(ptr, caller);
221  record_malloc(size, nptr, caller);
222
223  in_malloc = 0;
224
225  return nptr;
226}
227
228void *FUNC(calloc)(size_t nmemb, size_t size) {
229  const void *caller =
230    __builtin_extract_return_addr(__builtin_return_address(0));
231
232  if (in_malloc)
233    return __libc_calloc(nmemb, size);
234
235  in_malloc = 1;
236
237  void *ptr = __libc_calloc(nmemb, size);
238
239  record_malloc(nmemb*size, ptr, caller);
240
241  in_malloc = 0;
242
243  return ptr;
244}
245
246void *FUNC(memalign)(size_t boundary, size_t size) {
247  const void *caller =
248    __builtin_extract_return_addr(__builtin_return_address(0));
249
250  if (in_malloc)
251    return __libc_memalign(boundary, size);
252
253  in_malloc = 1;
254
255  void *ptr = __libc_memalign(boundary, size);
256
257  record_malloc(size, ptr, caller);
258
259  in_malloc = 0;
260
261  return ptr;
262}
263
264void FUNC(free)(void *ptr) {
265  const void *caller =
266    __builtin_extract_return_addr(__builtin_return_address(0));
267
268  if (in_malloc || !ptr)
269    return __libc_free(ptr);
270
271  in_malloc = 1;
272
273  record_free(ptr, caller);
274
275  __libc_free(ptr);
276
277  in_malloc = 0;
278}
279
Note: See TracBrowser for help on using the repository browser.