-
Notifications
You must be signed in to change notification settings - Fork 2
hirose31/inspect-perl-proc
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Long qw(:config posix_default no_ignore_case no_ignore_case_always permute);
use Pod::Usage;
use Carp;
my $PROG = substr($0, rindex($0, '/')+1);
my $Debug = 0;
sub dprint (@) {
return unless $Debug;
my @m = @_;
chomp @m;
print STDERR @m,"\n";
}
MAIN: {
my $target_pid;
my $mode;
GetOptions(
'pid|p=i' => \$target_pid,
'mode|m=s' => \$mode,
'debug|d+' => \$Debug,
'help|h|?' => sub { pod2usage(-verbose=>1) }) or pod2usage();
defined $target_pid or pod2usage;
$ENV{LM_DEBUG} = 1 if $Debug;
if ($PROG eq 'dump-perl-inc') {
$mode = 'dump-inc';
} elsif ($PROG eq 'dump-perl-incpath') {
$mode = 'dump-incpath';
} elsif ($PROG eq 'dump-perl-memusage') {
$mode = 'dump-memusage';
} elsif ($PROG eq 'dump-perl-env') {
$mode = 'dump-env';
} elsif ($PROG eq 'dump-perl-stacktrace') {
$mode = 'dump-stacktrace';
} elsif ($PROG eq 'hook-perl-require') {
$mode = 'hook-require';
}
defined $mode or pod2usage('Missing --mode');
my $perl_code;
my $output_file;
if ($mode eq 'dump-inc') {
$perl_code = <<'EOPC';
open my $fh, '>', qq{/tmp/dump_inc.$$.}.time() or die $!;
print {$fh} qq<{\n>;
for my $mod (keys %INC) {
printf {$fh} qq{'%s' => '%s',\n}, $mod, $INC{$mod};
}
print {$fh} qq<};\n>;
close $fh;
EOPC
$output_file = "/tmp/dump_inc.${target_pid}.XXXXX";
} elsif ($mode eq 'dump-incpath') {
$perl_code = <<'EOPC';
open my $fh, '>', qq{/tmp/dump_incpath.$$.}.time() or die $!;
print {$fh} qq<(\n>;
print {$fh} qq{'$_',\n} for @INC;
print {$fh} qq<);\n>;
close $fh;
EOPC
$output_file = "/tmp/dump_incpath.${target_pid}.XXXXX";
} elsif ($mode eq 'dump-memusage') {
$perl_code = <<'EOPC';
if (open my $fh, '>', qq{/tmp/dump_memusage.$$.}.time()) {
eval {
require B::Size2::Terse;
unless (B::Size2::Terse->VERSION >= 2.07) {
die qq{requires B::Size2::Terse >= 2.07};
}
use Devel::Symdump;
my @packages = Devel::Symdump->rnew('main')->packages;
print {$fh} qq<{\n>;
for my $package ('main', sort @packages) {
local $@;
eval {
my($subs, $opcount, $opsize) = B::Size2::Terse::package_size($package);
printf {$fh} qq{'%s' => '%s',\n}, $package, $opsize;
};
if ($@) {
my $e = $@; chomp $e;
printf {$fh} qq{'%s' => '0', # ERROR %s\n}, $package, $e;
}
}
print {$fh} qq<};\n>;
};
print {$fh} qq{$@} if $@;
close $fh;
};
EOPC
$output_file = "/tmp/dump_memusage.${target_pid}.XXXXX";
} elsif ($mode eq 'dump-env') {
$perl_code = <<'EOPC';
open my $fh, '>', qq{/tmp/dump_env.$$.}.time() or die $!;
print {$fh} qq<{\n>;
for my $k (sort keys %ENV) {
printf {$fh} qq{'%s' => '%s',\n}, $k, $ENV{$k};
}
print {$fh} qq<};\n>;
close $fh;
EOPC
$output_file = "/tmp/dump_env.${target_pid}.XXXXX";
} elsif ($mode eq 'dump-stacktrace') {
$perl_code = <<'EOPC';
open my $fh, '>', qq{/tmp/dump_stacktrace.$$.}.time() or die $!;
require Carp;
print {$fh} Carp::longmess(q{Dump stacktrace});
close $fh;
EOPC
$output_file = "/tmp/dump_stacktrace.${target_pid}.XXXXX";
} elsif ($mode eq 'hook-require') {
# moudle call stack break off, so skip eval, base.pm, parent.pm
$perl_code = <<'EOPC';
unshift @INC, sub {
my ($coderef, $filename) = @_;
my $i = 0;
my ($pkg, $fn, $ln, $sub);
my $sfn = qq{-};
my @t = localtime(); $t[5] += 1900; $t[4]++;
my $time = sprintf(qq{%04d/%02d/%02d %02d:%02d:%02d}, @t[5,4,3,2,1,0]);
while (1) {
($pkg, $fn, $ln, $sub) = caller($i);
last if !defined $fn;
++$i;
next if substr($fn, -9) eq qq{parent.pm};
next if substr($fn, -7) eq qq{base.pm};
next if substr($fn, 0, 5) eq qq{(eval};
last;
}
for my $path (sort {length($b) <=> length($a)} @INC) {
if (substr($fn, 0, length($path)) eq $path) {
$sfn = substr($fn, length($path)+1);
last;
}
}
open my $fh, '>>', qq{/tmp/hook_require.$$} or die $!;
print {$fh} qq{$time\t$filename\t$sfn\t$fn\t$ln\n};
close $fh;
return undef;
};
EOPC
$output_file = "/tmp/hook_require.${target_pid}";
} elsif ($mode eq 'debug') {
$perl_code = <<'EOPC';
use Carp;
Carp::cluck('Hello world');
EOPC
$output_file = "";
} else {
pod2usage("Invalid mode: $mode");
}
$perl_code =~ s/\n/ /g;
dprint("perl_code: $perl_code");
my $gdb = Devel::GDB::Tiny->new(
pid => $target_pid,
);
my $res;
# http://www.perlmonks.org/?node_id
# With Perl 5.16.3, process will exit after detach gdb,
# so we set breakpoint at main loop (Perl_runops_standard).
my $breakpoint;
$res = $gdb->send(qq{bt});
for my $frame (split /[\r\n]/, $res) {
if ($frame =~ /\s+Perl_runops_standard\s+.+at\s+(.+:[0-9]+)/) {
$breakpoint = $1;
dprint("b $breakpoint");
$gdb->send("b $breakpoint");
last;
}
}
unless ($breakpoint) {
$gdb->finish;
print "Cannot find a suitable breakpoint\n";
exit 1;
}
$gdb->send(qq{c});
$gdb->send(qq{delete breakpoints});
$res = $gdb->send(qq{call Perl_eval_pv("$perl_code",0)\n});
if ($res =~ /Too few arguments in function call/) {
# thread enabled perl?
$res = $gdb->get(qq{call Perl_eval_pv(Perl_get_context(), "$perl_code",0)\n});
}
$gdb->finish;
if ($res !~ /\s0x\w+/) {
print "Failed to inspect. Is your perl built with debug symbol?\n";
exit 1;
}
print "DONE. Please check ${output_file}\n";
exit 0;
}
# ========================================================================
package
Devel::GDB::Tiny;
use strict;
use warnings;
use IPC::Open2;
sub dprint (@) {
return unless $Debug;
my @m = @_;
chomp @m;
print STDERR @m,"\n";
}
sub new {
my($class, %args) = @_;
my $self = bless {
%args,
_rh => undef,
_rw => undef,
_initialized => 0,
}, $class;
return $self;
}
sub init {
my $self = shift;
dprint("init");
my $gdb_cmd = 'gdb -silent -nw ';
if (defined $self->{pid}) {
$gdb_cmd .= "-p $self->{pid}";
} else {
die "fixme to accept executable-file core-file";
}
$gdb_cmd .= ' 2>&1';
open2 $self->{_rh}, $self->{_wh}, $gdb_cmd or die $!;
$self->{_initialized} = 1;
$self->send('');
$self->send('set pagination off');
$self->send('set unwindonsignal on');
}
sub finish {
my $self = shift;
dprint("finish");
return unless $self->{_initialized};
$self->send('detach');
$self->send('quit');
if (defined $self->{pid}) {
kill 'CONT', $self->{pid};
}
close $self->{_rh}; $self->{_rh} = undef;
close $self->{_wh}; $self->{_wh} = undef;
$self->{_initialized} = 0;
}
sub send {
my($self, $cmd) = @_;
$self->init unless $self->{_initialized};
dprint("C $cmd");
if ($cmd) {
chomp $cmd; $cmd .= "\n";
my $len = syswrite $self->{_wh}, $cmd;
if ($len < length($cmd)) {
die "failed to exec: $cmd\n";
}
}
my $res = '';
while (1) {
my $buf = '';
my $len = sysread $self->{_rh}, $buf, 1024;
last if $len <= 0;
$res .= $buf;
last if $res =~ /\(gdb\)\s+$/;
}
dprint("R $res");
return $res;
}
# $1 = 0xce5ad0 "AUTOJUMP_AUTOCOMPLETE_CMDS=vim cp em"
# -> return "AUTOJUMP_AUTOCOMPLETE_CMDS=vim cp em"
sub get {
my($self, $cmd) = @_;
my $res = $self->send($cmd);
return '' if ($res !~ /.* =\s+(.+)/s);
my $v = $1;
if ($res =~ /0x\w+\s+\"(.+)\"/) {
return $1;
}
return $v;
}
sub DESTROY {
my $self = shift;
$self->finish;
}
__END__
=head1 NAME
B<inspect-perl-proc> - inspect running perl process
=head1 SYNOPSIS
B<inspect-perl-proc>
[B<-m> I<MODE>]
[B<-p> I<PID>]
[B<-d> | B<--debug>]
B<dump-perl-inc>
[B<-p> I<PID>]
[B<-d> | B<--debug>]
B<dump-perl-incpath>
[B<-p> I<PID>]
[B<-d> | B<--debug>]
B<dump-perl-memusage>
[B<-p> I<PID>]
[B<-d> | B<--debug>]
B<dump-perl-env>
[B<-p> I<PID>]
[B<-d> | B<--debug>]
B<dump-perl-stacktrace>
[B<-p> I<PID>]
[B<-d> | B<--debug>]
B<hook-perl-require>
[B<-p> I<PID>]
[B<-d> | B<--debug>]
B<inspect-perl-proc> B<-h> | B<--help> | B<-?>
$ inspect-perl-proc -m 'dump-inc' -p 1974
OR
$ dump-perl-inc -p 1974
$ inspect-perl-proc -m 'dump-incpath' -p 1974
OR
$ dump-perl-incpath -p 1974
$ inspect-perl-proc -m 'dump-memusage' -p 1974
OR
$ dump-perl-memusage -p 1974
$ inspect-perl-proc -m 'dump-env' -p 1974
OR
$ dump-perl-env -p 1974
$ inspect-perl-proc -m 'dump-stacktrace' -p 1974
OR
$ dump-perl-stacktrace -p 1974
$ inspect-perl-proc -m 'hook-require' -p 1974
OR
$ hook-perl-require -p 1974
After inspecting, you can get a result as shown below.
my $result = do '/tmp/dump_memusage.1974.1368772330';
warn Dumper($result);
=head1 DESCRIPTION
This script is for inspecting running perl process.
inspect-perl-proc has several modes.
"dump-inc" is to dump %INC (loaded modules).
"inspect-perl-proc --mode 'dump-inc'" is same as "dump-perl-inc".
"dump-incpath" is to dump @INC (load paths).
"inspect-perl-proc --mode 'dump-incpath'" is same as "dump-perl-incpath".
"dump-memusage" is to dump memory size by package.
"inspect-perl-proc --mode 'dump-memusage'" is same as "dump-perl-memusage".
"dump-env" is to dump %ENV.
"inspect-perl-proc --mode 'dump-env'" is same as "dump-perl-env".
"dump-stacktrace" is to dump stacktrace (backtrace).
"inspect-perl-proc --mode 'dump-stacktrace'" is same as "dump-perl-stacktrace".
"hook-perl-require" is to insert hooks into the import facility and dump name of the file to be included.
L<perl-requiretree> output tree format from L<hook-perl-require> dump file.
"inspect-perl-proc --mode 'hook-require'" is same as "hook-perl-require".
=head1 OPTIONS
=over 4
=item B<-m> I<MODE>, B<--mode> I<MODE>
Specify mode. I<MODE> is "dump-inc" or or "dump-incpath" "dump-memusage" or "dump-env" or "dump-stacktrace" or "hook-perl-require".
=item B<-p> I<PID>, B<--pid> I<PID>
Specify PID which process you want to examine.
=item B<-d>, B<--debug>
increase debug level.
-d -d more verbosely.
=back
=head1 KNOWN ISSUE
Inspecting on CentOS 5.8's system perl(5.8.8) causes segmentation fault. (perlbrewed perl-5.8.8 on CentOS 5.8 is OK)
=head1 AUTHOR
HIROSE, Masaaki E<lt>hirose31 _at_ gmail.comE<gt>
=head1 LICENSE
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
# for Emacsen
# Local Variables:
# mode: cperl
# cperl-indent-level: 4
# cperl-close-paren-offset: -4
# cperl-indent-parens-as-block: t
# indent-tabs-mode: nil
# coding: utf-8
# End:
# vi: set ts=4 sw=4 sts=0 et ft=perl fenc=utf-8 ff=unix :
About
get %INC and dump into file
Resources
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published