File Coverage

/.cpan/build/Devel-Plumber-0.3.4-xEj0QW/lib/Devel/Plumber.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Devel::Plumber;
2             #
3             # Copyright (C) 2011 by Opera Software Australia Pty Ltd
4             #
5             # This library is free software; you can redistribute it and/or modify
6             # it under the same terms as Perl itself.
7             #
8              
9 2     2   11 use strict;
  2         4  
  2         75  
10 2     2   11 use warnings;
  2         3  
  2         66  
11 2     2   10 no warnings 'portable'; # Support for 64-bit ints required
  2         4  
  2         84  
12 2     2   11 use vars qw($VERSION);
  2         154  
  2         168  
13             $VERSION = '0.3.4';
14 2     2   8690 use threads;
  0            
  0            
15             use IO::File;
16             use Devel::GDB;
17             use Tree::Interval;
18              
19             # states of a block
20             my $FREE = 0;
21             my $LEAKED = 1;
22             my $MAYBE = 2;
23             my $REACHED = 3;
24             my @state_names = qw(free LEAKED MAYBE_LEAKED reached);
25              
26             =head1 NAME
27              
28             Devel::Plumber - memory leak finder for C programs
29              
30             =head1 SYNOPSIS
31              
32             use Devel::Plumber;
33            
34             my $mario = new Devel::Plumber(binfile => 'myprogram',
35             pid => 12345);
36            
37             $mario = new Devel::Plumber(binfile => 'myprogram',
38             corefile => 'core.12345');
39            
40             $mario->find_leaks();
41             $mario->report_leaks();
42              
43             =head1 DESCRIPTION
44              
45             Devel::Plumber is a memory leak finder for C programs, implemented in
46             Perl. It uses GDB to walk internal glibc heap structures, so it can
47             work on either a live process or a core file.
48              
49             Devel::Plumber treats the C heap of the program under test as a
50             collection of non-overlapping blocks, and classifies them into
51             one of four states.
52              
53             =over
54              
55             =item Free
56              
57             The block is not allocated.
58              
59             =item Leaked
60              
61             The block is allocated but there are no pointers to any address in it,
62             so the program cannot reach it.
63              
64             =item Maybe Leaked
65              
66             The block is allocated and there are pointers to addresses within it,
67             but no pointers to the start of it. The program might be able to reach
68             it in some unobvious way via those pointers (e.g. using pointer
69             arithmetic), or the pointers may be dangling pointers to earlier
70             generations of blocks. Devel::Plumber cannot tell the difference
71             between these possibilities.
72              
73             =item Reached
74              
75             The block is allocated and there are pointers to the start of the block.
76              
77             =back
78              
79             Devel::Plumber proceeds in two main phases. In the first phase, the
80             glibc internal heap structures are walked to discover all the blocks.
81             Unallocated blocks are set to Free state at this time and allocated
82             blocks are initially set to Leaked state. In the second phase,
83             reachable blocks are marked. All the I<.data> and I<.bss> sections in
84             the program (and all loaded shared libraries) are scanned for pointers.
85             If a pointer points to the start of a block, the block is set to Reached
86             state; if it points into a Leaked block, the block is set to Maybe
87             Leaked state. In either case, the block's contents are also scanned for
88             pointers. After the second phase is complete, any blocks still in
89             Leaked state are definitely leaked.
90              
91             =head1 METHODS
92              
93             =over
94              
95             =item Inew(%parameters)>
96              
97             Create a new Devel::Plumber object. Devel::Plumber uses an entirely
98             object-oriented interface. The object can be created empty and set up
99             later by calling the I method, or the parameters to I may
100             be passed directly. See the description of I.
101              
102             =cut
103              
104             sub new
105             {
106             my ($class, %params) = @_;
107             my $self =
108             {
109             verbose => 0,
110             progress => 0,
111             blocks => Tree::Interval->new(),
112             sections => Tree::Interval->new(),
113             nprogress => 0,
114             gdb => undef,
115             expect => undef,
116             };
117             bless $self, $class;
118             $self->setup(%params) if scalar(%params);
119             return $self;
120             }
121              
122             =item I
123              
124             Shut down the Devel::Plumber object and it's captive GDB. It's
125             usually not necessary to call this, dropping the last reference
126             has the same effect.
127              
128             =cut
129              
130             sub close
131             {
132             my ($self) = @_;
133              
134             if ($self->{gdb})
135             {
136             $self->{gdb}->end;
137             $self->{expect}->slave->close;
138             $self->{expect}->expect(undef);
139             $self->{expect} = undef;
140             $self->{gdb} = undef;
141             }
142             }
143              
144             sub DESTROY
145             {
146             my ($self) = @_;
147             $self->close();
148             }
149              
150             =item I
151              
152             Initialise the Devel::Plumber object, and start a captive GDB session.
153             Errors are handled using I. The available parameters are
154              
155             =over
156              
157             =item I
158              
159             The filename of the program's executable image, e.g.
160             I. Used with GDB's I command. Required.
161              
162             =item I
163              
164             The filename of a core file dumped by the program. Used with GDB's
165             I command. One of I or I is required.
166              
167             =item I
168              
169             The process id of the running program. Used with GDB's I
170             command. One of I or I is required.
171              
172             =item I
173              
174             Non-zero values cause a progress indicator to be emitted to stderr.
175             Optional.
176              
177             =item I
178              
179             Non-zero values cause debugging messages to be emitted to stderr.
180             Optional.
181              
182             =back
183              
184             =cut
185              
186             sub setup
187             {
188             my ($self, %params) = @_;
189             my $binfile = delete $params{binfile};
190             my $corefile = delete $params{corefile};
191             my $pid = delete $params{pid};
192             my $progress = delete $params{progress} || 0;
193             my $verbose = delete $params{verbose} || 0;
194              
195             die "Unexpected parameters: " . join(" ", keys %params)
196             if scalar(%params);
197              
198             $self->{progress} = $progress;
199             $self->{verbose} = $verbose;
200             $self->_setup_gdb($binfile, $corefile, $pid);
201             $self->_setup_platform();
202             $self->_setup_sections();
203              
204             return 1;
205             }
206              
207             sub _setup_gdb
208             {
209             my ($self, $binfile, $corefile, $pid) = @_;
210              
211             return 1 if defined $self->{gdb};
212             die "Required parameter binfile missing"
213             unless defined $binfile;
214             die "Either a corefile or a pid parameter is required"
215             unless (defined $corefile || defined $pid);
216              
217             my $gdb = new Devel::GDB( '-create-expect' => 1,
218             '-params' => [ '-q' ] );
219             die "Faield to create Devel::GDB object"
220             unless $gdb;
221              
222             $gdb->send_cmd("file $binfile");
223             $gdb->send_cmd("core-file $corefile")
224             if defined $corefile;
225             $gdb->send_cmd("attach $pid")
226             if defined $pid;
227              
228             $self->{gdb} = $gdb;
229             $self->{expect} = $gdb->get_expect_obj();
230             }
231              
232             sub _setup_platform
233             {
234             my ($self) = @_;
235              
236             my $word_size = $self->_expr('sizeof (void *)');
237             my $plat;
238              
239             if ($word_size == 4)
240             {
241             $plat =
242             {
243             word_size => 4,
244             word_xletter => 'w',
245             word_mask => 0x3,
246             word_fmt => '0x%08x',
247             word_unpack => 'CCCC',
248             word_pack => 'L',
249             chunk_head_size => 2*$word_size,
250             };
251             }
252             elsif ($word_size == 8)
253             {
254             $plat =
255             {
256             word_size => 8,
257             word_xletter => 'g',
258             word_mask => 0x7,
259             word_fmt => '0x%016x',
260             word_unpack => 'CCCCCCCC',
261             word_pack => 'Q',
262             chunk_head_size => 2*$word_size,
263             };
264             }
265             else
266             {
267             die "Unknown word size: $word_size";
268             }
269              
270             $self->{platform} = $plat;
271             }
272              
273             sub _vmsg
274             {
275             my ($self, $fmt, @args) = @_;
276              
277             return unless $self->{verbose};
278             print STDERR "plumber: " . sprintf($fmt, @args) . "\n";
279             STDERR->flush;
280             }
281              
282             sub _progress_begin
283             {
284             my ($self) = @_;
285             return unless $self->{progress};
286             print STDERR "\n";
287             STDERR->flush;
288             }
289              
290             sub _progress_tick
291             {
292             my ($self) = @_;
293              
294             return unless $self->{progress};
295             $self->{nprogress}++;
296             if ($self->{nprogress} % 20 == 0)
297             {
298             print STDERR '.';
299             STDERR->flush;
300             }
301             }
302              
303             sub _progress_end
304             {
305             my ($self) = @_;
306             return unless $self->{progress};
307             print STDERR "\n";
308             STDERR->flush;
309             }
310              
311             sub _expr
312             {
313             my ($self, $expr) = @_;
314             my $line = $self->{gdb}->get("output (void *)($expr)") || return;
315             $line =~ s/\n//g;
316             $line =~ s/^.*\)\s*//;
317             $line =~ s/\s//g;
318             my $r = 0 + oct($line);
319             print STDERR "==> _expr($expr) = $r\n" if ($self->{verbose} > 1);
320             return $r;
321             }
322              
323             sub _symbol
324             {
325             my ($self, $expr) = @_;
326             my $line = $self->{gdb}->get("info sym ($expr)") || return;
327              
328             # _nss_nis_getgrgid_r + 1 in section .text
329             # _nss_nis_getgrgid_r in section .text
330             my ($sym, $off) = ($line =~ m/^\s*(\S*)\s*\+\s*(\d+)\s+in\s+section\s+(\S+)/);
331             return ($sym, 0+$off)
332             if (defined $off);
333             ($sym) = ($line =~ m/^\s*(\S*)\s+in\s+section\s+(\S+)/);
334             return ($sym, 0);
335             }
336              
337             sub _words
338             {
339             my ($self, $addr, $count) = @_;
340             my $plat = $self->{platform};
341             my $s = $self->{gdb}->get("x/" . $count . "x" .
342             $plat->{word_xletter} .
343             " " . $addr);
344             $s =~ s/0x[0-9a-f]+://g;
345             $s =~ s/^\s+//;
346             my @a = split(/\s+/, $s);
347             map { $_ = oct($_); } @a;
348             return @a;
349             }
350              
351             sub _setup_sections
352             {
353             my ($self) = @_;
354              
355             # 0xb801c270 - 0xb802cea8 is .text in /usr/lib/libsasl2.so.2
356             # 0x0804d3c0 - 0x080e28ec is .text
357             # `/home/gnb/software/plumber/cyrus/imapd', file type elf32-i386.
358             my $binary;
359             my $s = $self->{gdb}->get("info files");
360             foreach (split(/\n/, $s))
361             {
362             chomp;
363              
364             my ($t) = m/`([^']+)',\s+file\s+type\s+/;
365             if (defined $t)
366             {
367             $binary = $t;
368             next;
369             }
370              
371             my ($start, $end, $name, $image) =
372             m/\s*(0x[0-9a-f]+)\s*-\s*(0x[0-9a-f]+)\s+is\s+(\S+)(?:\s+in\s+(\S+))?/;
373             if (defined $name)
374             {
375             next if ($name =~ m/^load\d+$/);
376             $image ||= $binary;
377             $start = oct($start);
378             $end = oct($end);
379             $self->_vmsg("start=0x%x end=0x%x name=%s image=%s",
380             $start, $end, $name, $image);
381             eval
382             {
383             # sometimes gdb reports overlapping sections
384             # but it doesn't seem to be for important ones
385             # so just ignore it
386             $self->{sections}->insert($start, $end-1, {
387             addr => $start,
388             size => $end - $start,
389             name => $name,
390             image => $image,
391             });
392             };
393             next;
394             }
395             }
396             }
397              
398             sub _chunk_size
399             {
400             my ($self, $chunk) = @_;
401             # The 0x7 here masks out the extra bits of info
402             # stored in the chunk size, and is fixed for all
403             # word sizes.
404             return $self->_expr("((struct malloc_chunk *)$chunk)->size") & ~0x7;
405             }
406              
407             sub _add_chunk
408             {
409             my ($self, $addr, $size, $state) = @_;
410             my $plat = $self->{platform};
411             $addr += $plat->{chunk_head_size};
412             $size -= $plat->{chunk_head_size};
413             my $end = $addr + $size - 1;
414              
415             $self->_progress_tick();
416              
417             my $old = $self->{blocks}->find($addr);
418             die "Duplicate block at $addr"
419             if (defined $old &&
420             ($old->{addr} != $addr ||
421             $old->{size} != $size ||
422             $old->{state} != $FREE));
423             return if ($old);
424             $self->{blocks}->insert($addr, $end,
425             {
426             marked => 0,
427             addr => $addr,
428             size => $size,
429             state => $state,
430             });
431             $self->_vmsg("block 0x%x %d %s",
432             $addr, $size, $state_names[$state]);
433             }
434              
435             sub _make_root
436             {
437             my ($addr, $size) = @_;
438             return
439             {
440             marked => 0,
441             addr => $addr,
442             size => $size,
443             state => $REACHED,
444             };
445             }
446              
447             sub _make_arena
448             {
449             my ($self, $addr) = @_;
450              
451             my $top = $self->_expr("((struct malloc_state *)$addr)->top");
452             my $max_addr = $top + $self->_chunk_size($top);
453             my $min_addr = $max_addr - $self->_expr("((struct malloc_state *)$addr)->system_mem");
454              
455             # printf "top=0x%x\n", $top;
456             # printf "min_addr=0x%x\n", $min_addr;
457             # printf "max_addr=0x%x\n", $max_addr;
458             # exit 0;
459              
460             my $arena =
461             {
462             addr => $addr,
463             top => $top,
464             max_addr => $max_addr,
465             min_addr => $min_addr,
466             };
467             return $arena;
468             }
469              
470             sub _walk_freelist
471             {
472             my ($self, $arena, $chunk, $desc) = @_;
473             my $n = 0;
474              
475             while ($chunk >= $arena->{min_addr} && $chunk < $arena->{max_addr})
476             {
477             $self->_vmsg("free 0x%x %d", $chunk, $self->_chunk_size($chunk));
478             $self->_add_chunk($chunk, $self->_chunk_size($chunk), $FREE)
479             if ($chunk != $arena->{top});
480             $chunk = $self->_expr("((struct malloc_chunk *)$chunk)->fd");
481             $n++;
482             }
483             $self->_vmsg("Found $n free blocks on freelist: $desc") if ($n);
484             }
485              
486             sub _walk_chunks
487             {
488             my ($self, $arena) = @_;
489             my $chunk;
490             my $size;
491              
492             for ($chunk = $arena->{min_addr} ;
493             $chunk < $arena->{max_addr} ;
494             $chunk += $size)
495             {
496             $size = $self->_chunk_size($chunk);
497             $self->_add_chunk($chunk, $size, $LEAKED)
498             if ($chunk != $arena->{top});
499             }
500             }
501              
502             sub _walk_arena
503             {
504             my ($self, $arena) = @_;
505              
506             $self->_vmsg("Arena $arena->{addr}");
507              
508             $self->_vmsg("Walking freelists");
509             for (my $i = 0 ; $i < 10 ; $i++)
510             {
511             my $chunk = $self->_expr("((struct malloc_state *)$arena->{addr})->fastbinsY[$i]");
512             # my $chunk = $self->_expr("($arena->{addr})->fastbins[$i]");
513             $self->_walk_freelist($arena, $chunk, "fastbin $i");
514             }
515              
516             for (my $i = 0 ; $i < 254 ; $i+=2)
517             {
518             my $chunk = $self->_expr("((struct malloc_state *)$arena->{addr})->bins[$i]");
519             $self->_walk_freelist($arena, $chunk, "bin " . $i/2);
520             }
521              
522             $self->_walk_chunks($arena);
523             }
524              
525             sub _mark_blocks
526             {
527             my ($self, $rootaddr, $rootsize) = @_;
528             my $plat = $self->{platform};
529              
530             # We do a breadth-first traversal of blocks.
531             #
532             # Initialise the pending list a fake block representing
533             # the root section. It won't be entered into the global
534             # data structure so it can't be accidentally found later.
535             my @pending = ( _make_root($rootaddr, $rootsize) );
536              
537             while (my $block = shift @pending)
538             {
539             $self->_vmsg(" block: 0x%x @ 0x%x", $block->{size}, $block->{addr});
540              
541             # avoid loops
542             next if $block->{marked};
543             $block->{marked} = 1;
544              
545             # Hmm, this is a dangling pointer, we should
546             # probably complain about it.
547             next if $block->{state} == $FREE;
548              
549             # try to reach other blocks pointed to by
550             # the contents of this block
551             my @words = $self->_words($block->{addr},
552             int($block->{size} / $plat->{word_size}));
553             foreach my $word (@words)
554             {
555             $self->_progress_tick();
556              
557             my $ref = $self->{blocks}->find($word);
558             if (defined $ref)
559             {
560             $self->_vmsg(" ref=0x%x", $ref->{addr});
561              
562             # mark the block reached
563             my $state = ($word == $ref->{addr}) ? $block->{state} : $MAYBE;
564             $ref->{state} = $state
565             if $state > $ref->{state};
566              
567             # push on the stack
568             push (@pending, $ref)
569             }
570             }
571             }
572             }
573              
574             =item I
575              
576             Perform the leak finding algorithm. Errors are handled using I.
577             This can be quite slow, use the I optional parameter to
578             I to give a progress indicator.
579              
580             =cut
581              
582             sub find_leaks
583             {
584             my ($self) = @_;
585              
586             $self->_progress_begin();
587              
588             $self->_walk_arena($self->_make_arena('&main_arena'));
589              
590             my %is_root = ( '.bss' => 1, '.data' => 1 );
591              
592             my @root_sections = grep { $is_root{$_->{name}} } $self->{sections}->values();
593             foreach my $sec (@root_sections)
594             {
595             $self->_vmsg("Marking blocks for section %s in %s",
596             $sec->{name}, $sec->{image});
597             $self->_mark_blocks($sec->{addr}, $sec->{size});
598             }
599              
600             $self->_progress_end();
601             }
602              
603             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
604              
605             my @asciify;
606              
607             sub _setup_asciify
608             {
609             map { $asciify[$_] = " ." } (0..255);
610             $asciify[0x0a] = "\\n";
611             $asciify[0x0d] = "\\r";
612             map { $asciify[$_] = sprintf(" %c", $_) } (0x20..0x7e);
613             }
614             _setup_asciify();
615              
616             sub _asciify_word
617             {
618             my ($self, $word) = @_;
619             my $plat = $self->{platform};
620             my @bytes = unpack($plat->{word_unpack}, pack($plat->{word_pack}, $word));
621             return join(' ', map { $asciify[$_]; } @bytes);
622             }
623              
624             sub _describe_word
625             {
626             my ($self, $word) = @_;
627             my $plat = $self->{platform};
628              
629             my $block = $self->{blocks}->find($word);
630             if ($block)
631             {
632             return sprintf("ptr to %s block of %d bytes",
633             $state_names[$block->{state}],
634             $block->{size})
635             if ($word == $block->{addr});
636             return sprintf("ptr %d bytes into %s block of %d bytes at $plat->{word_fmt}",
637             ($word - $block->{addr}),
638             $state_names[$block->{state}],
639             $block->{size},
640             $block->{addr});
641             }
642              
643             my $sec = $self->{sections}->find($word);
644             if ($sec)
645             {
646             my ($sym, $off) = $self->_symbol($word);
647             return sprintf("%s in section %s in %s",
648             $sym,
649             $sec->{name},
650             $sec->{image})
651             if (defined $sym && $off == 0);
652             return sprintf("%s+%d in section %s in %s",
653             $sym, $off,
654             $sec->{name},
655             $sec->{image})
656             if (defined $sym && $off == 0);
657             return sprintf("offset 0x%x into section %s in %s",
658             ($word - $sec->{addr}),
659             $sec->{name},
660             $sec->{image});
661             }
662              
663             return undef;
664             }
665              
666             sub _hexdump
667             {
668             my ($self, $addr, $size, $prefix) = @_;
669             my $plat = $self->{platform};
670             my $off = 0;
671             my @words = $self->_words($addr, int($size / $plat->{word_size}));
672             foreach my $word (@words)
673             {
674             my $asciified = $self->_asciify_word($word);
675             my $desc = $self->_describe_word($word);
676             $desc = ($desc ? "\t// $desc" : "");
677             printf "%s0x%04x: $plat->{word_fmt} %s%s\n",
678             $prefix, $off, $word, $asciified, $desc;
679             $off += $plat->{word_size};
680             }
681             }
682              
683             =item I
684              
685             Emits a detailed human-readable leak report to stdout. The report
686             comprises two sections, LEAKS and SUMMARY.
687              
688             The LEAKS section shows each leaked or maybe-leaked block, including
689             it's address, size, and contents. Block contents are shown as hex
690             words, ASCII octets, and an annotation indicating whether the word is a
691             pointer to symbol in the I<.data> I<.bss> or I<.text> sections, or to a
692             block. These annotations are often useful in working out what code
693             allocated the block.
694              
695             The SUMMARY section summarises the total number of blocks and bytes in
696             each of the states: Free, Leaked, Maybe Leaked, and Reached.
697              
698             =cut
699              
700             sub report_leaks
701             {
702             my ($self) = @_;
703             my @count = ( 0, 0, 0, 0 );
704             my @size = ( 0, 0, 0, 0 );
705             my $plat = $self->{platform};
706              
707             printf "==== LEAKS ====\n";
708             foreach my $block ($self->{blocks}->values())
709             {
710             if ($block->{state} == $LEAKED || $block->{state} == $MAYBE)
711             {
712             printf "%s %d bytes at $plat->{word_fmt}\n",
713             $state_names[$block->{state}],
714             $block->{size},
715             $block->{addr};
716             $self->_hexdump($block->{addr}, $block->{size}, " ");
717             }
718             $count[$block->{state}] ++;
719             $size[$block->{state}] += $block->{size};
720             }
721             printf "==== SUMMARY ====\n";
722             foreach my $state ($FREE..$REACHED)
723             {
724             printf "%d bytes in %d blocks %s\n",
725             $size[$state],
726             $count[$state],
727             $state_names[$state];
728             }
729             }
730              
731             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
732              
733             =item I
734              
735             Emits a text report showing all the heap blocks, to stdout. Useful for
736             testing Devel::Plumber.
737              
738             =cut
739              
740             sub dump_blocks
741             {
742             my ($self) = @_;
743              
744             foreach my $block ($self->{blocks}->values())
745             {
746             printf "0x%016x 0x%x %s\n",
747             $block->{addr},
748             $block->{size},
749             $state_names[$block->{state}];
750             }
751             }
752              
753             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
754              
755             =item I
756              
757             Get all the leaked blocks found by I.
758             Returns a reference to an array of hashes containing
759              
760             =over
761              
762             =item I
763              
764             Address of the block.
765              
766             =item I
767              
768             Size of the block in bytes.
769              
770             =item I
771              
772             An integer representing the state of the block, one of
773              
774             =over
775              
776             =item *
777             1 = Leaked.
778              
779             =item *
780             2 = Maybe Leaked.
781              
782             =back
783              
784             =back
785              
786             =cut
787              
788             sub get_leaks
789             {
790             my ($self) = @_;
791              
792             my @leaks;
793             foreach my $block ($self->{blocks}->values())
794             {
795             if ($block->{state} == $LEAKED || $block->{state} == $MAYBE)
796             {
797             push(@leaks, {
798             addr => $block->{addr},
799             size => $block->{size},
800             state => $block->{state},
801             });
802             }
803             }
804              
805             return \@leaks;
806             }
807              
808             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
809             =back
810              
811             =head1 PLATFORMS
812              
813             X86 and x86-64 Linux with glibc. Devel::Plumber potentially could be
814             ported to any platform that supports GDB, but only if the C library's
815             heap structures could be discovered.
816              
817             =head1 CAVEATS
818              
819             For GDB to be able to access internal glibc data structures, it needs
820             debugging symbols. Most Linux distributions ship a stripped glibc but
821             also provide a separate package containing just the debugging information,
822             in a directory where GDB knows how to find it. That package is usually
823             not installed by default; for Devel::Plumber to work you need to install
824             that package. For example, on Ubuntu
825              
826             ubuntu% sudo apt-get install libc6-dbg
827              
828             Note that in this case you do not need to restart the program, the debug
829             package contains no information that is used at runtime.
830              
831             =head1 AUTHOR
832              
833             Greg Banks
834              
835             =head1 COPYRIGHT
836              
837             Copyright (C) 2011 by Opera Software Australia Pty Ltd
838              
839             This library is free software; you can redistribute it and/or modify
840             it under the same terms as Perl itself.
841              
842             =head1 SEE ALSO
843              
844             B(1).
845              
846             =cut
847             # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
848             1;