source: memlog.c @ a736d81

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

use backtrace instead

  • 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 <execinfo.h>
11#include <sys/syscall.h>
12#include <sys/time.h>
13#include <sys/resource.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <sys/utsname.h>
17#include <fcntl.h>
18#include <unistd.h>
19
20#include <pthread.h>
21
22#define UNW_LOCAL_ONLY
23#include <libunwind.h>
24
25#include <dlfcn.h>
26
27FILE *log_file = NULL;
28static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
29
30// The malloc hook might use functions that call malloc, and we need to make
31// sure this does not cause an infinite loop.
32static __thread int in_malloc = 0;
33
34__attribute__((__constructor__))
35static void record_init() {
36  struct utsname u;
37  uname(&u);
38
39  char log_name[PATH_MAX];
40  snprintf(log_name, PATH_MAX, "%s.%d.memory.log_file", u.nodename, getpid());
41  log_file = fopen(log_name, "w");
42  if (!log_file)
43    fprintf(stderr, "fopen failed for '%s': %m\n", log_name);
44}
45
46__attribute__((__destructor__))
47static void record_cleanup() {
48  if (!log_file)
49    return;
50
51  // These functions might call free, but we're shutting down, so don't try to
52  // unwind the stack from here...
53  in_malloc = 1;
54
55  (void) fflush(log_file);
56  (void) fclose(log_file);
57}
58
59static void print_context(unw_context_t *context) {
60  struct rusage usage;
61  if (getrusage(RUSAGE_SELF, &usage)) {
62    fprintf(stderr, "getrusage failed: %m\n");
63    return;
64  }
65
66  fprintf(log_file, "\t%ld.%06ld %ld %ld", usage.ru_utime.tv_sec,
67          usage.ru_utime.tv_usec, usage.ru_maxrss, syscall(SYS_gettid));
68
69  int r;
70  unw_cursor_t cursor;
71  if ((r = unw_init_local(&cursor, context))) {
72    fprintf(stderr, "unw_init_local failed: %s [%d]\n",
73            unw_strerror(r), r);
74    return;
75  }
76
77  void *pcs[1024];
78  int num_pcs = backtrace(pcs, 1024);
79  for (int pci = 0; pci < num_pcs; ++pci) {
80    unw_word_t pc = (unw_word_t) pcs[pci];
81#if 0
82  while ((r = unw_step(&cursor)) > 0) {
83    unw_word_t pc;
84    if ((r = unw_get_reg(&cursor, UNW_REG_IP, &pc))) {
85      fprintf(stderr, "unw_get_reg UNW_REG_IP failed: %s [%d]\n",
86              unw_strerror(r), r);
87      return;
88    }
89#endif
90
91    if (!pc)
92      break;
93
94    unw_word_t off, relpc;
95    const char *proc_name;
96    const char *file_name;
97    Dl_info dlinfo;
98    if (dladdr((void *) pc, &dlinfo) && dlinfo.dli_fname &&
99        *dlinfo.dli_fname) {
100      unw_word_t saddr = (unw_word_t) dlinfo.dli_saddr;
101      if (saddr) {
102#if defined(__powerpc64__) && !defined(__powerpc64le__)
103        // On PPC64 ELFv1, the symbol address points to the function descriptor, not
104        // the actual starting address.
105        saddr = *(unw_word_t*) saddr;
106#endif
107
108        off = pc - saddr;
109        relpc = pc - ((unw_word_t) dlinfo.dli_fbase);
110      } else {
111        off = 0;
112        relpc = 0;
113      }
114
115      proc_name = dlinfo.dli_sname;
116      if (!proc_name)
117        proc_name = "?";
118
119      file_name = dlinfo.dli_fname;
120    } else {
121      off = pc;
122      relpc = pc;
123      proc_name = "?";
124      file_name = "?";
125    }
126
127    fprintf(log_file, "\t%s (%s+0x%x) [0x%lx (0x%lx)]", file_name, proc_name, (int) off,
128            (long) pc, (long) relpc);
129
130#if 0
131    unw_word_t off;
132    char proc_name[PATH_MAX];
133    if (unw_get_proc_name(&cursor, proc_name, PATH_MAX, &off)) {
134      off = 0;
135      strcpy(proc_name, "?");
136    }
137
138    unw_proc_info_t pip;
139    if ((r = unw_get_proc_info(&cursor, &pip))) {
140      // unw_get_proc_info is not supported on some platforms (ppc and ppc64,
141      // for example), so we need to try harder...
142      if (r == -UNW_EINVAL) {
143        unw_word_t pc;
144        if ((r = unw_get_reg(&cursor, UNW_REG_IP, &pc))) {
145          fprintf(stderr, "unw_get_reg UNW_REG_IP failed: %s [%d]\n",
146                  unw_strerror(r), r);
147          return;
148        }
149
150        if ((r = unw_get_proc_info_by_ip(unw_local_addr_space, pc, &pip, NULL))) {
151          if (r == -UNW_ENOINFO)
152            break; // the cursor is now invalid; must break here.
153
154          fprintf(stderr, "unw_get_proc_info_by_ip failed: %s [%d]\n",
155                  unw_strerror(r), r);
156          return;
157        }
158      } else {
159        if (r == -UNW_ENOINFO)
160          break; // the cursor is now invalid; must break here.
161
162        fprintf(stderr, "unw_get_proc_info failed: %s [%d]\n",
163                unw_strerror(r), r);
164        return;
165      }
166    }
167
168    const char *file_name;
169    Dl_info dlinfo;
170    if (dladdr((void *)(pip.start_ip + off), &dlinfo) && dlinfo.dli_fname &&
171        *dlinfo.dli_fname)
172      file_name = dlinfo.dli_fname;
173    else
174      file_name = "?";
175
176    fprintf(log_file, "\t%s (%s+0x%x) [%p]", file_name, proc_name, (int) off,
177            (void *) (pip.start_ip + off));
178#endif
179  }
180
181  if (r < 0 && r != -UNW_ENOINFO)
182    fprintf(stderr, "unw_step failed: %s [%d]\n",
183            unw_strerror(r), r);
184}
185
186static void record_malloc(size_t size, void *ptr, unw_context_t *uc) {
187  if (!log_file)
188    return;
189
190  if (pthread_mutex_lock(&log_mutex))
191    return;
192
193  fprintf(log_file, "M: %zd %p", size, ptr);
194  print_context(uc);
195  fprintf(log_file, "\n");
196
197done:
198  pthread_mutex_unlock(&log_mutex);
199}
200
201static void record_free(void *ptr, unw_context_t *uc) {
202  if (!log_file)
203    return;
204
205  if (pthread_mutex_lock(&log_mutex))
206    return;
207
208  fprintf(log_file, "F: %p", ptr);
209  print_context(uc);
210  fprintf(log_file, "\n");
211
212done:
213  pthread_mutex_unlock(&log_mutex);
214}
215
216// glibc exports its underlying malloc implementation under the name
217// __libc_malloc so that hooks like this can use it.
218extern void *__libc_malloc(size_t size);
219extern void *__libc_realloc(void *ptr, size_t size);
220extern void *__libc_calloc(size_t nmemb, size_t size);
221extern void *__libc_memalign(size_t boundary, size_t size);
222extern void __libc_free(void *ptr);
223
224void *malloc(size_t size) {
225  if (in_malloc)
226    return __libc_malloc(size);
227
228  in_malloc = 1;
229
230  void *ptr = __libc_malloc(size);
231
232  unw_context_t uc;
233  if (!unw_getcontext(&uc))
234    record_malloc(size, ptr, &uc);
235
236  in_malloc = 0;
237  return ptr;
238}
239
240void *realloc(void *ptr, size_t size) {
241  if (in_malloc)
242    return __libc_realloc(ptr, size);
243
244  in_malloc = 1;
245
246  void *nptr = __libc_realloc(ptr, size);
247
248  unw_context_t uc;
249  if (!unw_getcontext(&uc)) {
250    record_free(ptr, &uc);
251    record_malloc(size, nptr, &uc);
252  }
253
254  in_malloc = 0;
255
256  return nptr;
257}
258
259void *calloc(size_t nmemb, size_t size) {
260  if (in_malloc)
261    return __libc_calloc(nmemb, size);
262
263  in_malloc = 1;
264
265  void *ptr = __libc_calloc(nmemb, size);
266
267  unw_context_t uc;
268  if (!unw_getcontext(&uc))
269    record_malloc(nmemb*size, ptr, &uc);
270
271  in_malloc = 0;
272
273  return ptr;
274}
275
276void *memalign(size_t boundary, size_t size) {
277  if (in_malloc)
278    return __libc_memalign(boundary, size);
279
280  in_malloc = 1;
281
282  void *ptr = __libc_memalign(boundary, size);
283
284  unw_context_t uc;
285  if (!unw_getcontext(&uc))
286    record_malloc(size, ptr, &uc);
287
288  in_malloc = 0;
289
290  return ptr;
291}
292
293void free(void *ptr) {
294  if (in_malloc)
295    return __libc_free(ptr);
296
297  in_malloc = 1;
298
299  unw_context_t uc;
300  if (!unw_getcontext(&uc))
301    record_free(ptr, &uc);
302
303  __libc_free(ptr);
304
305  in_malloc = 0;
306}
307
Note: See TracBrowser for help on using the repository browser.