.gitignore: Ignore generated files
[memprof.git] / lib / intercept.c
blobca01137695940c53f8020b194bc9519ace66d1b7
1 /* -*- mode: C; c-file-style: "linux" -*- */
3 /* MemProf -- memory profiler and leak detector
4 * Copyright 1999, 2000, 2001, Red Hat, Inc.
5 * Copyright 2002, Kristian Rietveld
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 /*====*/
23 #define _GNU_SOURCE
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <dlfcn.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <string.h>
31 #include <assert.h>
33 #include "intercept.h"
34 #include "memintercept.h"
35 #include "memintercept-utils.h"
36 #include "stack-frame.h"
38 #include <sys/socket.h>
39 #include <sys/types.h>
40 #include <sys/un.h>
42 /* The code in this file is written to work for threaded operation without
43 * any reference to the thread library in operation. There are only
44 * two places where any interaction between threads is necessary -
45 * allocation of new slots in the pids[] array, and a parent thread
46 * waiting for a child thread to start.
50 * For the new NPTL implementation, all threads return the
51 * same 'getpid' - so instead we use a thread variable.
53 #define NPTL
55 typedef struct {
56 #ifdef NPTL
57 uint32_t ref_count;
58 #endif
59 pid_t pid;
60 int outfd;
61 pid_t clone_pid; /* See comments in clone_thunk */
62 } ThreadInfo;
64 static void new_process (ThreadInfo *thread,
65 pid_t old_pid,
66 MIOperation operation);
67 static void atexit_trap (void);
69 static int (*old_execve) (const char *filename,
70 char *const argv[],
71 char *const envp[]);
72 static int (*old_fork) (void);
73 static int (*old_vfork) (void);
74 static int (*old_clone) (int (*fn) (void *arg),
75 void *child_stack,
76 int flags,
77 void *arg,
78 void *xarg1, void *xarg2, void *xarg3, void *xarg4);
79 static void (*old__exit) (int status);
81 static int initialized = MI_FALSE;
82 static MIBool tracing = MI_TRUE;
84 #define MAX_THREADS 128
86 #ifndef NPTL
87 static ThreadInfo threads[MAX_THREADS];
88 #endif
89 static char *socket_path = NULL;
90 static char socket_buf[64];
91 static uint32_t seqno = 0;
93 #undef ENABLE_DEBUG
95 #ifdef ENABLE_DEBUG
96 #define MI_DEBUG(arg) mi_debug arg
97 #else /* !ENABLE_DEBUG */
98 #define MI_DEBUG(arg) (void)0
99 #endif /* ENABLE_DEBUG */
101 #ifdef NPTL
102 static __thread ThreadInfo global_per_thread = { 0, };
103 #endif
105 static ThreadInfo *
106 allocate_thread (void)
108 ThreadInfo *thread = NULL;
109 #ifdef NPTL
110 thread = &global_per_thread;
111 #else
112 int i;
114 for (i = 0; i < MAX_THREADS; i++) {
115 if (threads[i].ref_count == 0) {
116 unsigned int new_count = mi_atomic_increment (&threads[i].ref_count);
117 if (new_count == 1) {
118 thread = &threads[i];
119 break;
120 } else
121 mi_atomic_decrement (&threads[i].ref_count);
124 if (!thread) {
125 mi_debug ("Can't find free thread slot");
126 tracing = MI_FALSE;
127 _exit(1);
129 #endif
130 thread->pid = getpid();
131 thread->clone_pid = 0;
133 return thread;
136 static void
137 release_thread (ThreadInfo *thread)
139 thread->pid = 0;
140 #ifndef NPTL
141 mi_atomic_decrement (&thread->ref_count);
142 #endif
145 static ThreadInfo *
146 find_thread (void)
148 ThreadInfo *thread;
149 #ifdef NPTL
150 thread = &global_per_thread;
151 #else
152 int i;
153 pid_t pid = getpid();
155 for (i=0; i < MAX_THREADS; i++)
156 if (threads[i].pid == pid) {
157 thread = &threads[i];
158 break;
160 #endif
161 if (!thread) {
162 mi_debug ("Thread not found\n");
163 tracing = MI_FALSE;
164 _exit(1);
167 if (thread->clone_pid) {
168 /* See comments in clone_thunk() */
169 new_process (thread, thread->clone_pid, MI_CLONE);
170 thread->clone_pid = 0;
173 return thread;
176 static void
177 initialize ()
179 /* It's possible to get recursion here, since dlsym() can trigger
180 * memory allocation. To deal with this, we flag the initialization
181 * condition specially, then use the special knowledge that it's
182 * OK for malloc to fail during initialization (libc degrades
183 * gracefully), so we just return NULL from malloc(), realloc().
185 * This trick is borrowed from from libc's memusage.
187 initialized = -1;
188 old_execve = dlsym(RTLD_NEXT, "execve");
189 old_fork = dlsym(RTLD_NEXT, "__fork");
190 old_vfork = dlsym(RTLD_NEXT, "__vfork");
191 old_clone = dlsym(RTLD_NEXT, "__clone");
192 old__exit = dlsym(RTLD_NEXT, "_exit");
194 atexit (atexit_trap);
196 mi_init ();
197 initialized = 1;
200 static void
201 abort_unitialized (const char *call)
203 mi_printf (2, "MemProf: unexpected library call during initialization: %s\n", call);
204 abort();
207 static void
208 stop_tracing (int fd)
210 MI_DEBUG (("Stopping tracing\n"));
212 tracing = MI_FALSE;
213 close (fd);
214 putenv ("_MEMPROF_SOCKET=");
217 static void
218 new_process (ThreadInfo *thread,
219 pid_t old_pid,
220 MIOperation operation)
222 MIInfo info;
223 struct sockaddr_un addr;
224 int addrlen;
225 char response;
226 int outfd;
227 int count;
229 int old_errno = errno;
230 #ifdef ENABLE_DEBUG
231 static const char *const operation_names[] = { NULL, NULL, NULL, NULL, "NEW", "FORK", "CLONE", NULL };
232 #endif
234 MI_DEBUG (("New process, operation = %s, old_pid = %d\n",
235 operation_names[operation], old_pid));
237 memset (&addr, 0, sizeof(addr));
239 addr.sun_family = AF_UNIX;
240 strncpy (addr.sun_path, socket_path, sizeof (addr.sun_path));
241 addrlen = sizeof(addr.sun_family) + strlen (addr.sun_path);
242 if (addrlen > sizeof (addr))
243 addrlen = sizeof(addr);
245 outfd = socket (PF_UNIX, SOCK_STREAM, 0);
246 if (outfd < 0) {
247 mi_perror ("error creating socket");
248 tracing = MI_FALSE;
249 _exit(1);
252 if (connect (outfd, (struct sockaddr *)&addr, addrlen) < 0) {
253 mi_perror ("Error connecting to memprof");
254 tracing = MI_FALSE;
255 _exit (1);
257 if (fcntl (outfd, F_SETFD, FD_CLOEXEC) < 0) {
258 mi_perror ("error calling fcntl");
259 tracing = MI_FALSE;
260 _exit(1);
263 info.fork.operation = operation;
265 info.fork.pid = old_pid;
266 info.fork.new_pid = getpid();
267 info.fork.seqno = 0;
269 if (!thread)
270 thread = allocate_thread ();
272 thread->outfd = outfd;
274 MI_DEBUG (("new_process op %d\n", info.any.operation));
276 if (!mi_write (outfd, &info, sizeof (MIInfo))) {
277 stop_tracing (outfd);
278 count = 0;
279 } else {
280 while (1) {
281 count = read (outfd, &response, 1);
282 if (count >= 0 || errno != EINTR)
283 break;
287 if (count != 1 || !response) {
288 stop_tracing (outfd);
291 errno = old_errno;
294 static void
295 memprof_init (void)
297 int old_errno = errno;
299 socket_path = getenv ("_MEMPROF_SOCKET");
301 if (!socket_path) {
302 mi_printf (2, "libmemintercept: must be used with memprof\n");
303 exit (1);
306 if (strlen(socket_path) < sizeof(socket_buf)) {
307 strcpy(socket_buf, socket_path);
308 socket_path = socket_buf;
311 MI_DEBUG (("_MEMPROF_SOCKET = %s\n", socket_path));
313 if (socket_path[0] == '\0') /* tracing off */
314 tracing = MI_FALSE;
315 else
316 new_process (NULL, 0, MI_NEW);
318 errno = old_errno;
321 MIBool
322 mi_check_init (void)
324 if (initialized <= 0) {
325 if (initialized < 0)
326 return MI_FALSE;
328 initialize ();
331 if (!socket_path)
332 memprof_init ();
334 return MI_TRUE;
337 MIBool
338 mi_tracing (void)
340 return tracing;
343 void
344 mi_write_stack (int n_frames,
345 void **frames,
346 void *data)
348 MIInfo *info = data;
349 ThreadInfo *thread;
350 int old_errno = errno;
352 if (n_frames < 0 || n_frames > 20000)
354 MI_DEBUG (("mi_write_stack - elide bogus foo\n"));
355 return;
358 MI_DEBUG (("mi_write_stack op 0x%x stack size %d\n",
359 info->any.operation, n_frames));
361 info->alloc.stack_size = n_frames;
362 info->alloc.pid = getpid();
363 info->alloc.seqno = mi_atomic_increment (&seqno) - 1;
365 thread = find_thread ();
367 if (!mi_write (thread->outfd, info, sizeof (MIInfo)) ||
368 !mi_write (thread->outfd, frames, n_frames * sizeof(void *)))
369 stop_tracing (thread->outfd);
371 errno = old_errno;
375 __fork (void)
377 if (!mi_check_init ())
378 abort_unitialized ("__fork");
380 if (tracing) {
381 int pid;
382 int old_pid = getpid();
384 find_thread (); /* Make sure we're registered */
386 pid = (*old_fork) ();
388 if (!pid) /* New child process */
389 new_process (NULL, old_pid, MI_FORK);
391 return pid;
392 } else
393 return (*old_fork) ();
397 fork (void)
399 return __fork ();
403 __vfork (void)
405 if (!mi_check_init ())
406 abort_unitialized ("__vfork");
408 if (tracing) {
409 int pid;
410 int old_pid = getpid();
412 find_thread (); /* Make sure we're registered */
414 pid = (*old_vfork) ();
416 if (!pid) /* New child process */
417 new_process (NULL, old_pid, MI_FORK);
419 return pid;
420 } else
421 return (*old_vfork) ();
425 __execve (const char *filename,
426 char *const argv[],
427 char *const envp[])
429 if (!mi_check_init ())
430 abort_unitialized ("__execve");
432 if (tracing) {
433 /* Nothing */
434 } else {
435 int i;
437 for (i=0; envp[i]; i++)
438 if (strncmp (envp[i], "_MEMPROF_SOCKET=", 16) == 0)
439 envp[i][16] = '\0';
441 return (*old_execve) (filename, argv, envp);
444 typedef struct
446 int started;
447 int (*fn) (void *);
448 void *arg;
449 pid_t pid;
450 } CloneData;
452 static int
453 clone_thunk (void *arg)
455 ThreadInfo *thread;
456 CloneData *data = arg;
457 int (*fn)(void *) = data->fn;
458 void *sub_arg = data->arg;
460 /* At this point, we can't call new_process(), because errno
461 * still points to our parent's errno structure. (We assume
462 * getpid() is safe, but about anythhing else could be dangerous.)
463 * So, we simply allocate the structure for the thread and delay
464 * the initialization.
466 thread = allocate_thread ();
467 thread->clone_pid = data->pid;
468 data->started = 1;
470 return (*fn) (sub_arg);
473 int __clone (int (*fn) (void *arg),
474 void *child_stack,
475 int flags,
476 void *arg,
477 void *xarg1, void *xarg2, void *xarg3, void *xarg4)
479 volatile CloneData data;
480 int pid;
482 if (!mi_check_init ())
483 abort_unitialized ("__clone");
485 if (tracing) {
486 data.started = 0;
487 data.fn = fn;
488 data.arg = arg;
489 data.pid = getpid();
491 find_thread (); /* Make sure we're registered */
493 pid = (*old_clone) (clone_thunk, child_stack, flags, (void *)&data, xarg1, xarg2, xarg3, xarg4);
495 while (!data.started)
496 /* Wait */;
498 MI_DEBUG (("Clone: child=%d\n", pid));
500 return pid;
501 } else
502 return (*old_clone) (fn, child_stack, flags, arg, xarg1, xarg2, xarg3, xarg4);
505 int clone (int (*fn) (void *arg),
506 void *child_stack,
507 int flags,
508 void *arg,
509 void *xarg1, void *xarg2, void *xarg3, void *xarg4)
511 return __clone (fn, child_stack, flags, arg, xarg1, xarg2, xarg3, xarg4);
514 static void
515 exit_wait (void)
517 MIInfo info;
518 ThreadInfo *thread;
519 int count;
520 char response;
521 info.any.operation = MI_EXIT;
522 info.any.seqno = mi_atomic_increment (&seqno) - 1;
523 info.any.pid = getpid();
525 mi_stop ();
527 thread = find_thread ();
529 if (mi_write (thread->outfd, &info, sizeof (MIInfo)))
530 /* Wait for a response before really exiting
532 while (1) {
533 count = read (thread->outfd, &response, 1);
534 if (count >= 0 || errno != EINTR)
535 break;
538 close (thread->outfd);
539 thread->pid = 0;
540 release_thread (thread);
543 /* Because _exit() isn't interposable in recent versions of
544 * GNU libc, we can't depend on this, and instead use the less-good
545 * atexit_trap() below. But we leave this here just in case we
546 * are using an old version of libc where _exit() is interposable,
547 * so we can trap a wider range of exit conditions
549 void
550 _exit (int status)
552 if (initialized <= 0)
553 abort_unitialized ("_exit");
555 MI_DEBUG (("_Exiting\n"));
557 if (tracing) {
558 exit_wait ();
559 tracing = 0;
562 (*old__exit) (status);
564 /* Not reached as old__exit will not return but makes
565 * the compiler happy.
567 assert(0);
570 static void
571 atexit_trap (void)
573 if (tracing) {
574 exit_wait ();
575 tracing = 0;
579 static void construct () __attribute__ ((constructor));
581 static void construct ()
583 mi_check_init ();
584 mi_start ();