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.
30 #include <sys/ptrace.h>
33 #include <glib/gi18n.h>
39 prepare_block (Block
*block
, gpointer data
)
41 GPtrArray
*arr
= data
;
43 g_assert (!(block
->flags
& BLOCK_IS_ROOT
));
44 block
->flags
&= ~BLOCK_MARKED
;
46 g_ptr_array_add (arr
, block
);
50 compare_blocks (const void *a
, const void *b
)
52 Block
* const *b1
= a
;
53 Block
* const *b2
= b
;
55 return ((*b1
)->addr
< (*b2
)->addr
) ? -1 :
56 ((*b1
)->addr
== (*b2
)->addr
? 0 : 1);
60 read_proc_stat (int pid
, char *status
, guint
*start_stack
, guint
*end_stack
)
68 fname
= g_strdup_printf ("/proc/%d/stat", pid
);
69 in
= fopen (fname
, "r");
71 g_warning ("Can't open %s\n", fname
);
76 /* The fields in proc/stat are:
83 * tsk->tty ? kdev_t_to_nr(tsk->tty->device) : 0
102 * tsk->mm ? tsk->mm->rss : 0
103 * tsk->rlim ? tsk->rlim[RLIMIT_RSS].rlim_cur : 0
104 * tsk->mm ? tsk->mm->start_code : 0
105 * tsk->mm ? tsk->mm->end_code : 0
106 * tsk->mm ? tsk->mm->start_stack : 0
117 if (fscanf(in,"%*d %*s %c %*d %*d %*d %*d %*d %*lu %*lu \
118 %*lu %*lu %*lu %*lu %*lu %*ld %*ld %*ld %*ld %*ld \
119 %*ld %*lu %*lu %*ld %*lu %*lu %*lu %lu %lu %*lu \
120 %*lu %*lu %*lu %*lu %*lu %*lu %*lu",
122 if (fscanf(in
,"%*d %*s %c %*d %*d %*d %*d %*d %*u %*u \
123 %*u %*u %*u %*u %*u %*d %*d %*d %*d %*d \
124 %*d %*u %*u %*d %*u %*u %*u %lu %lu %*u \
125 %*u %*u %*u %*u %*u %*u %*u",
129 g_warning ("Error parsing /proc/%d/stat\n", pid
);
136 *start_stack
= tstart_stack
;
138 *end_stack
= tend_stack
;
146 process_status (int pid
)
148 char c
= 'T'; /* Avoid infinite loop in caller */
150 read_proc_stat (pid
, &c
, NULL
, NULL
);
155 /* FIXME: merge with read_process_maps */
157 read_stack_maps (MPProcess
*process
)
159 GList
*result
= NULL
;
164 guint start
, end
, major
, minor
, inode
;
166 snprintf (buffer
, 1023, "/proc/%d/maps", process
->pid
);
168 in
= fopen (buffer
, "r");
172 while (fgets(buffer
, 1023, in
)) {
173 int count
= sscanf (buffer
, "%x-%x %15s %*x %x:%x %u %255s",
174 &start
, &end
, perms
, &major
, &minor
, &inode
, file
);
176 if (strcmp (perms
, "rwxp") == 0) {
179 map
= g_new (Map
, 1);
181 map
->size
= end
- start
;
185 result
= g_list_prepend (result
, map
);
196 add_stack_root (MPProcess
*process
, GSList
*block_list
,
200 guint start_stack
, end_stack
;
204 if (!read_proc_stat (process
->pid
, NULL
, &start_stack
, &end_stack
))
208 Map
*map
= tmp_list
->data
;
210 if (end_stack
>= map
->start
&&
211 end_stack
< map
->start
+ map
->size
) {
214 block
= g_new (Block
, 1);
216 block
->flags
= BLOCK_IS_ROOT
;
217 block
->addr
= (void *)end_stack
;
218 if (start_stack
> map
->start
&&
219 start_stack
< map
->start
+ map
->size
)
220 block
->size
= start_stack
- end_stack
;
222 block
->size
= map
->start
+ map
->size
- end_stack
;
225 return g_slist_prepend (block_list
, block
);
228 tmp_list
= tmp_list
->next
;
235 add_stack_roots (MPProcess
*process
, GSList
*block_list
)
237 GList
*clones
, *tmp_list
;
240 clones
= process_get_clones (process
);
241 stack_maps
= read_stack_maps (process
);
245 block_list
= add_stack_root (tmp_list
->data
, block_list
, stack_maps
);
246 tmp_list
= tmp_list
->next
;
248 g_list_free (clones
);
250 g_list_foreach (stack_maps
, (GFunc
)g_free
, NULL
);
251 g_list_free (stack_maps
);
257 process_data_root (void *addr
, guint size
, gpointer user_data
)
259 GSList
**block_list
= user_data
;
262 block
= g_new (Block
, 1);
264 block
->flags
= BLOCK_IS_ROOT
;
269 *block_list
= g_slist_prepend (*block_list
, block
);
273 add_data_roots (MPProcess
*process
, GSList
*block_list
)
275 process_sections (process
, process_data_root
, &block_list
);
281 find_block (GPtrArray
*block_arr
, void *addr
)
284 guint first
, middle
, last
;
286 if (block_arr
->len
== 0)
290 last
= block_arr
->len
- 1;
293 data
= (Block
**)block_arr
->pdata
;
295 if (addr
< data
[first
]->addr
) {
298 else if (addr
< data
[last
]->addr
) {
299 /* Invariant: data[first].addr <= val < data[last].addr */
301 while (first
< last
- 1) {
302 middle
= (first
+ last
) / 2;
303 if (addr
< data
[middle
]->addr
)
308 if (addr
< data
[first
]->addr
+ data
[first
]->size
)
313 if (addr
< data
[last
]->addr
+ data
[last
]->size
)
321 scan_block_contents (GPtrArray
*block_arr
,
323 Block
*block
, void **mem
)
327 for (i
=0; i
< block
->size
/ sizeof(void *); i
++) {
330 new_block
= find_block (block_arr
, mem
[i
]);
332 g_assert (!new_block
|| !(new_block
->flags
& BLOCK_IS_ROOT
));
334 if (new_block
&& !(new_block
->flags
& BLOCK_MARKED
)) {
335 block_list
= g_slist_prepend (block_list
, new_block
);
336 new_block
->flags
|= BLOCK_MARKED
;
344 scan_block (pid_t pid
, int memfd
, GSList
*block_list
,
345 GPtrArray
*block_arr
, Block
*block
)
350 size_t length
= (block
->size
+ 3) / 4;
352 addr
= g_new (void *, length
);
355 for (i
= 0; i
< length
; i
++) {
356 mem
[i
] = (void *)ptrace (PTRACE_PEEKDATA
, pid
,
357 block
->addr
+i
*sizeof(void *),
361 g_warning ("Cannot read word %d/%d in block %p: %s\n",
362 i
, length
, block
->addr
, g_strerror (errno
));
368 block_list
= scan_block_contents (block_arr
, block_list
,
376 leaks_find (MPProcess
*process
)
379 GPtrArray
*block_arr
;
380 GSList
*block_list
= NULL
;
381 GSList
*result
= NULL
;
384 GList
*clones
, *tmp_list
;
386 clones
= process_get_clones (process
);
388 ptrace (PTRACE_ATTACH
, process
->pid
, 0, 0);
392 MPProcess
*clone
= tmp_list
->data
;
394 kill (clone
->pid
, SIGSTOP
);
396 /* Wait for the processes we are tracing to actually stop */
398 /* waitpid(clone->pid, &status, WUNTRACED); */
399 while (process_status (clone
->pid
) != 'T') {
403 tmp_list
= tmp_list
->next
;
406 fname
= g_strdup_printf ("/proc/%d/mem", process
->pid
);
407 memfd
= open (fname
, O_RDONLY
);
409 g_warning ("Can't open %s\n", fname
);
415 /* Mark all blocks as untouched, add them to list of blocks
418 block_arr
= g_ptr_array_new ();
419 process_block_foreach (process
, prepare_block
, block_arr
);
421 qsort (block_arr
->pdata
, block_arr
->len
, sizeof (Block
*),
424 /* Locate all the roots
427 block_list
= add_stack_roots (process
, block_list
);
428 block_list
= add_data_roots (process
, block_list
);
430 /* While there are blocks to check, scan each block,
431 * and add each new-found block to the global list.
436 Block
*block
= block_list
->data
;
438 tmp_list
= block_list
->next
;
439 g_slist_free_1 (block_list
);
440 block_list
= tmp_list
;
442 block_list
= scan_block (process
->pid
, memfd
, block_list
, block_arr
, block
);
445 if (block
->flags
& BLOCK_IS_ROOT
)
452 /* Look for leaked blocks
455 for (i
=0; i
<block_arr
->len
; i
++) {
456 Block
*block
= block_arr
->pdata
[i
];
457 if (!(block
->flags
& BLOCK_MARKED
)) {
458 result
= g_slist_prepend (result
, block
);
466 MPProcess
*clone
= tmp_list
->data
;
468 kill (clone
->pid
, SIGCONT
);
470 tmp_list
= tmp_list
->next
;
475 g_list_free (clones
);
477 g_ptr_array_free (block_arr
, FALSE
);
480 if (ptrace (PTRACE_DETACH
, process
->pid
, 0, 0) == -1)
481 g_warning ("Detach failed %s (%d)\n", g_strerror (errno
), errno
);
487 leaks_print (MPProcess
*process
, GSList
*blocks
, gchar
*outfile
)
489 GSList
*tmp_list
= blocks
;
492 out
= fopen (outfile
, "w");
495 show_error (NULL
, ERROR_MODAL
,
496 _("Cannot open output file: %s\n"),
503 Block
*block
= tmp_list
->data
;
505 fprintf (out
, "Leaked %p (%u bytes)\n", block
->addr
, block
->size
);
506 process_dump_stack (process
, out
, block
->stack
);
508 tmp_list
= tmp_list
->next
;