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.
29 #include <sys/socket.h>
31 #include <glib-object.h>
32 #include <glib/gi18n.h>
34 #include "memintercept.h"
37 /* If USE_SOCKET_DIRECTORY is defined, then the temporary sockets will
38 * be created as /tmp/memprof.UID/server.PID. Otherwise, they will
39 * be created as /tmp/memprof.XXXXXX. Despite calling mktemp(), the
40 * latter should be completely safe, because unix domain socket creation
41 * will fail with EADDRINUSE if the file already exists.
44 #undef USE_SOCKET_DIRECTORY
46 #define SOCKET_TEMPLATE "memprof.XXXXXX"
48 static void mp_server_class_init (MPServerClass
*class);
49 static void mp_server_init (MPServer
*server
);
50 static void mp_server_finalize (GObject
*object
);
52 static char * find_lib_location (MPServer
*server
);
53 static void create_control_socket (MPServer
*server
);
54 static gboolean
control_func (GIOChannel
*source
,
55 GIOCondition condition
,
59 GObject parent_instance
;
65 /* It would be faster to implement this as a flat array with a
66 * binary search, but this is easier as a first pass
68 GHashTable
*pid_table
;
71 MPProfileType profile_type
;
72 guint interval
; /* In usecs */
75 struct _MPServerClass
{
76 GObjectClass parent_class
;
78 void (*process_created
) (MPServer
*server
, MPProcess
*process
);
81 /* Global list of socket paths so we can cleanup in an atexit() function
83 static GSList
*socket_paths
;
84 static int terminate_pipe
[2];
90 static guint server_signals
[LAST_SIGNAL
] = { 0 };
93 fatal (const char *format
, ...)
97 va_start (va
, format
);
98 vfprintf (stderr
, format
, va
);
104 mp_server_get_type (void)
106 static GType server_type
= 0;
109 static const GTypeInfo server_info
= {
110 sizeof (MPServerClass
),
111 (GBaseInitFunc
) NULL
,
112 (GBaseFinalizeFunc
) NULL
,
113 (GClassInitFunc
) mp_server_class_init
,
114 NULL
, /* class_finalize */
115 NULL
, /* class_data */
118 (GInstanceInitFunc
) mp_server_init
121 server_type
= g_type_register_static (G_TYPE_OBJECT
,
130 mp_server_class_init (MPServerClass
*class)
132 static gboolean initialized
= FALSE
;
134 GObjectClass
*o_class
= G_OBJECT_CLASS (class);
136 o_class
->finalize
= mp_server_finalize
;
137 class->process_created
= NULL
;
140 server_signals
[PROCESS_CREATED
] =
141 g_signal_new ("process_created",
144 G_STRUCT_OFFSET (MPServerClass
, process_created
),
146 g_cclosure_marshal_VOID__OBJECT
,
147 G_TYPE_NONE
, 1, MP_TYPE_PROCESS
);
154 mp_server_init (MPServer
*server
)
158 server
->profile_type
= MP_PROFILE_MEMORY
;
159 server
->interval
= 10000;
161 server
->pid_table
= NULL
;
162 create_control_socket (server
);
164 channel
= g_io_channel_unix_new (server
->socket_fd
);
165 g_io_channel_set_encoding (channel
, NULL
, NULL
);
167 server
->control_watch
= g_io_add_watch (channel
, G_IO_IN
| G_IO_PRI
| G_IO_ERR
| G_IO_HUP
| G_IO_NVAL
,
168 control_func
, server
);
169 g_io_channel_unref (channel
);
171 g_object_ref (G_OBJECT (server
));
175 mp_server_finalize (GObject
*object
)
177 MPServer
*server
= MP_SERVER (object
);
179 if (server
->control_watch
)
180 g_source_remove (server
->control_watch
);
181 if (server
->pid_table
)
182 g_hash_table_destroy (server
->pid_table
);
183 close (server
->socket_fd
);
185 socket_paths
= g_slist_remove (socket_paths
, server
->socket_path
);
186 g_free (server
->socket_path
);
192 MPServer
*server
= g_object_new (MP_TYPE_SERVER
, NULL
);
198 mp_server_set_profile_type (MPServer
*server
,
202 g_return_if_fail (MP_IS_SERVER (server
));
203 server
->profile_type
= type
;
207 mp_server_set_interval (MPServer
*server
,
210 g_return_if_fail (MP_IS_SERVER (server
));
212 server
->interval
= usecs
;
216 mp_server_instrument (MPServer
*server
, const char *program
, char **args
)
219 char *lib_location
= find_lib_location (server
);
223 show_error (NULL
, ERROR_FATAL
, "Cannot fork: %s\n",
226 if (pid
== 0) { /* Child */
229 envstr
= g_strdup_printf ("%s=%s", "_MEMPROF_SOCKET", server
->socket_path
);
232 envstr
= g_strdup_printf ("%s=%s", "LD_PRELOAD", lib_location
);
235 if (server
->profile_type
!= MP_PROFILE_MEMORY
) {
236 envstr
= g_strdup_printf ("%s=%d", "_MEMPROF_INTERVAL", server
->interval
);
239 envstr
= g_strdup_printf ("%s=%s", "_MEMPROF_SPEED_TYPE",
240 server
->profile_type
== MP_PROFILE_CYCLES
?
245 execvp (program
, args
);
247 g_warning ("Cannot run program: %s", g_strerror (errno
));
251 g_free (lib_location
);
258 find_lib_location (MPServer
*server
)
260 const char **dirname
;
261 const char *basename
;
263 static const char *directories
[] = {
272 if (server
->profile_type
== MP_PROFILE_MEMORY
)
273 basename
= "libmemintercept.so";
275 basename
= "libspeedintercept.so";
278 for (dirname
= directories
; *dirname
; dirname
++) {
279 char *path
= g_build_filename (*dirname
, basename
, NULL
);
280 if (!access (path
, R_OK
)) {
288 show_error (NULL
, ERROR_FATAL
,
289 _("Cannot find %s"), basename
);
291 /* Make lib_location absolute */
293 if (lib_location
[0] != '/') {
294 char *wd
= g_get_current_dir();
295 char *newloc
= g_strconcat (wd
, "/", lib_location
, NULL
);
296 g_free (lib_location
);
297 lib_location
= newloc
;
305 cleanup_socket (void)
307 g_slist_foreach (socket_paths
, (GFunc
)unlink
, NULL
);
309 #ifdef USE_SOCKET_DIRECTORY
310 /* We try to remove the directory ; if there are other copies of memprof running, we expect
314 char *tmpdir
= tmpdir
= g_strdup_printf ("%s/memprof.%d", g_get_tmp_dir(), getuid());
315 if (rmdir (tmpdir
) != 0) {
316 if (errno
!= ENOTEMPTY
)
317 g_warning ("Unlinking %s failed with error: %s\n", tmpdir
, g_strerror (errno
));
326 term_handler (int signum
)
328 static int terminated
= 0;
333 exit(1); /* Impatient user, risk reentrancy problems */
337 ret
= write (terminate_pipe
[1], &c
, 1);
341 terminate_io_handler (GIOChannel
*channel
,
342 GIOCondition condition
,
348 ret
= read (terminate_pipe
[0], &c
, 1);
350 fprintf (stderr
, "memprof: Caught signal %d (%s), cleaning up\n", c
, g_strsignal(c
));
358 static gboolean added_cleanup
;
361 if (!added_cleanup
) {
362 g_atexit (cleanup_socket
);
364 signal (SIGINT
, term_handler
);
365 signal (SIGTERM
, term_handler
);
366 if (pipe (terminate_pipe
) < 0)
367 fatal ("bind: %s\n", g_strerror (errno
));
369 channel
= g_io_channel_unix_new (terminate_pipe
[0]);
370 g_io_channel_set_encoding (channel
, NULL
, NULL
);
372 g_io_add_watch (channel
, G_IO_IN
| G_IO_PRI
, terminate_io_handler
, NULL
);
373 g_io_channel_unref (channel
);
375 added_cleanup
= TRUE
;
380 create_control_socket (MPServer
*server
)
382 struct sockaddr_un addr
;
385 mode_t old_mask
= umask (077);
387 #ifdef USE_SOCKET_DIRECTORY
388 char *tmpdir
= g_strdup_printf ("%s/memprof.%d", g_get_tmp_dir(), getuid());
392 memset (&addr
, 0, sizeof(addr
));
394 addr
.sun_family
= AF_UNIX
;
396 server
->socket_fd
= socket (PF_UNIX
, SOCK_STREAM
, 0);
397 if (server
->socket_fd
< 0)
398 g_error ("socket: %s\n", g_strerror (errno
));
401 if (retry_count
-- == 0)
402 fatal ("Too many retries while creating control socket\n");
404 if (server
->socket_path
)
405 g_free (server
->socket_path
);
407 #ifdef USE_SOCKET_DIRECTORY
408 if (stat (tmpdir
, &st_buf
) == 0) {
409 if (!S_ISDIR(st_buf
.st_mode
) || st_buf
.st_uid
!= getuid())
410 fatal ("memprof: %s not owned by user or is not a directory\n", tmpdir
);
412 if (chmod (tmpdir
, 0700) != 0) {
413 if (errno
== ENOENT
) {
414 g_warning ("%s vanished, retrying\n", tmpdir
);
417 fatal ("memprof: cannot set permissions on %s: %s\n", tmpdir
, g_strerror (errno
));
420 } else if (errno
== ENOENT
) {
421 if (mkdir (tmpdir
, 0700) != 0) {
425 fatal ("memprof: Cannot create %s, %d", tmpdir
, g_strerror (errno
));
428 fatal ("memprof: error calling stat() on %s: %s\n", tmpdir
, g_strerror (errno
));
430 server
->socket_path
= g_strdup_printf ("%s/server.%d", tmpdir
, getpid());
431 if (g_file_exists (server
->socket_path
)) {
432 g_warning ("Stale memprof socket %s, removing\n", server
->socket_path
);
433 unlink (server
->socket_path
);
436 #else /* !USE_SOCKET_DIRECTORY */
437 server
->socket_path
= g_build_filename (g_get_tmp_dir(), SOCKET_TEMPLATE
, NULL
);
438 if (strlen(mktemp (server
->socket_path
)) == 0)
439 fatal ("mktemp: %s\n", g_strerror (errno
));
440 #endif /* USE_SOCKET_DIRECTORY */
442 strncpy (addr
.sun_path
, server
->socket_path
, sizeof (addr
.sun_path
));
443 addrlen
= sizeof(addr
.sun_family
) + strlen (addr
.sun_path
);
444 if (addrlen
> sizeof (addr
))
445 addrlen
= sizeof(addr
);
447 if (bind (server
->socket_fd
, (struct sockaddr
*)&addr
, addrlen
) < 0) {
448 if (errno
== EADDRINUSE
)
451 fatal ("bind: %s\n", g_strerror (errno
));
456 socket_paths
= g_slist_prepend (socket_paths
, server
->socket_path
);
458 if (listen (server
->socket_fd
, 8) < 0)
459 fatal ("listen: %s\n", g_strerror (errno
));
463 #ifdef USE_SOCKET_DIRECTORY
469 mp_server_process_created (MPServer
*server
, MPProcess
*process
)
471 g_signal_emit_by_name (server
, "process_created", process
);
474 /* Input func to receive new process connections */
476 control_func (GIOChannel
*source
,
477 GIOCondition condition
,
483 MPServer
*server
= data
;
485 MPProcess
*process
= NULL
;
486 MPProcess
*parent_process
;
490 newfd
= accept (server
->socket_fd
, NULL
, 0);
492 g_warning ("accept: %s\n", g_strerror (errno
));
496 count
= read (newfd
, &info
, sizeof(info
));
497 if (count
< sizeof(info
)) {
498 g_warning ("short read from new process\n");
502 switch (info
.operation
) {
504 parent_process
= mp_server_find_process (server
, info
.fork
.pid
);
505 if (parent_process
&& !parent_process
->follow_fork
) {
506 goto out
; /* Return negative response */
511 process
= mp_server_find_process (server
, info
.fork
.new_pid
);
513 if (process
->follow_exec
) {
514 MPProcess
*old_process
= process
;
516 process
= process_new (server
);
517 process
->pid
= old_process
->pid
;
518 process
->parent
= old_process
->parent
;
520 mp_server_add_process (server
, process
); /* Overwrites old process */
521 mp_server_process_created (server
, process
);
524 process_exec_reset (process
);
528 parent_process
= mp_server_find_process (server
, info
.fork
.pid
);
529 if (!parent_process
) {
530 /* This is a child of a forked child we aren't tracing, ignore it */
534 process
= process_duplicate (parent_process
);
535 process
->pid
= info
.fork
.new_pid
;
536 mp_server_add_process (server
, process
);
537 mp_server_process_created (server
, process
);
540 process_set_status (process
, MP_PROCESS_RUNNING
);
544 parent_process
= mp_server_find_process (server
, info
.fork
.pid
);
546 g_error ("Clone of nonexistant parent process\n");
548 process
= process_new (server
);
549 process_set_status (process
, MP_PROCESS_RUNNING
);
551 process
->clone_of
= parent_process
;
552 process
->pid
= info
.fork
.new_pid
;
554 mp_server_add_process (server
, process
);
556 while (parent_process
->clone_of
)
557 parent_process
= parent_process
->clone_of
;
567 g_assert_not_reached ();
571 process
->input_channel
= g_io_channel_unix_new (newfd
);
572 g_io_channel_set_encoding (process
->input_channel
, NULL
, NULL
);
574 process_start_input (process
);
580 int ret
= write (newfd
, &response
, 1);
581 if (!response
|| ret
< 0)
588 /************************************************************
589 * Code to keep track of current processes
590 ************************************************************/
593 mp_server_find_process (MPServer
*server
, pid_t pid
)
595 if (server
->pid_table
) {
596 return g_hash_table_lookup (server
->pid_table
, GUINT_TO_POINTER (pid
));
603 mp_server_add_process (MPServer
*server
, MPProcess
*process
)
605 if (!server
->pid_table
)
606 server
->pid_table
= g_hash_table_new (g_direct_hash
, NULL
);
608 g_hash_table_insert (server
->pid_table
, GUINT_TO_POINTER (process
->pid
), process
);
612 mp_server_remove_process (MPServer
*server
, MPProcess
*process
)
616 g_return_if_fail (server
->pid_table
!= NULL
);
618 /* In the case of --follow-exec, the process in the table could have been overwritten
620 in_table
= g_hash_table_lookup (server
->pid_table
, GUINT_TO_POINTER (process
->pid
));
621 if (in_table
== process
)
622 g_hash_table_remove (server
->pid_table
, GUINT_TO_POINTER (process
->pid
));
631 add_clone (gpointer key
, MPProcess
*process
, CloneInfo
*ci
)
633 MPProcess
*parent
= process
;
634 while (parent
->clone_of
)
635 parent
= parent
->clone_of
;
637 if (parent
== ci
->parent
)
638 ci
->result
= g_list_prepend (ci
->result
, process
);
642 mp_server_get_process_clones (MPServer
*server
, MPProcess
*process
)
649 g_hash_table_foreach (server
->pid_table
, (GHFunc
)add_clone
, &ci
);