|  | #!/usr/bin/perl | 
|  |  | 
|  | # This script processes strace -f output.  It displays a graph of invoked | 
|  | # subprocesses, and is useful for finding out what complex commands do. | 
|  |  | 
|  | # You will probably want to invoke strace with -q as well, and with | 
|  | # -s 100 to get complete filenames. | 
|  |  | 
|  | # The script can also handle the output with strace -t, -tt, or -ttt. | 
|  | # It will add elapsed time for each process in that case. | 
|  |  | 
|  | # Copyright (c) 1998 by Richard Braakman <dark@xs4all.nl>. | 
|  | # Copyright (c) 1998-2017 The strace developers. | 
|  |  | 
|  | # Redistribution and use in source and binary forms, with or without | 
|  | # modification, are permitted provided that the following conditions | 
|  | # are met: | 
|  | # 1. Redistributions of source code must retain the above copyright | 
|  | #    notice, this list of conditions and the following disclaimer. | 
|  | # 2. Redistributions in binary form must reproduce the above copyright | 
|  | #    notice, this list of conditions and the following disclaimer in the | 
|  | #    documentation and/or other materials provided with the distribution. | 
|  | # 3. The name of the author may not be used to endorse or promote products | 
|  | #    derived from this software without specific prior written permission. | 
|  | # | 
|  | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | 
|  | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | 
|  | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | 
|  | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | 
|  | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | 
|  | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 
|  | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | use strict; | 
|  | use warnings; | 
|  |  | 
|  | my %unfinished; | 
|  | my $floatform; | 
|  |  | 
|  | # Scales for strace slowdown.  Make configurable! | 
|  | my $scale_factor = 3.5; | 
|  | my %running_fqname; | 
|  |  | 
|  | while (<>) { | 
|  | my ($pid, $call, $args, $result, $time, $time_spent); | 
|  | chop; | 
|  | $floatform = 0; | 
|  |  | 
|  | s/^(\d+)\s+//; | 
|  | $pid = $1; | 
|  |  | 
|  | if (s/^(\d\d):(\d\d):(\d\d)(?:\.(\d\d\d\d\d\d))? //) { | 
|  | $time = $1 * 3600 + $2 * 60 + $3; | 
|  | if (defined $4) { | 
|  | $time = $time + $4 / 1000000; | 
|  | $floatform = 1; | 
|  | } | 
|  | } elsif (s/^(\d+)\.(\d\d\d\d\d\d) //) { | 
|  | $time = $1 + ($2 / 1000000); | 
|  | $floatform = 1; | 
|  | } | 
|  |  | 
|  | if (s/ <unfinished ...>$//) { | 
|  | $unfinished{$pid} = $_; | 
|  | next; | 
|  | } | 
|  |  | 
|  | if (s/^<... \S+ resumed> //) { | 
|  | unless (exists $unfinished{$pid}) { | 
|  | print STDERR "$0: $ARGV: cannot find start of resumed call on line $."; | 
|  | next; | 
|  | } | 
|  | $_ = $unfinished{$pid} . $_; | 
|  | delete $unfinished{$pid}; | 
|  | } | 
|  |  | 
|  | if (/^--- SIG(\S+) (.*) ---$/) { | 
|  | # $pid received signal $1 | 
|  | # currently we don't do anything with this | 
|  | next; | 
|  | } | 
|  |  | 
|  | if (/^\+\+\+ killed by SIG(\S+) \+\+\+$/) { | 
|  | # $pid received signal $1 | 
|  | handle_killed($pid, $time); | 
|  | next; | 
|  | } | 
|  |  | 
|  | if (/^\+\+\+ exited with (\d+) \+\+\+$/) { | 
|  | # $pid exited $1 | 
|  | # currently we don't do anything with this | 
|  | next; | 
|  | } | 
|  |  | 
|  | ($call, $args, $result) = /(\S+)\((.*)\)\s+= (.*)$/; | 
|  | if ($result =~ /^(.*) <([0-9.]*)>$/) { | 
|  | ($result, $time_spent) = ($1, $2); | 
|  | } | 
|  | unless (defined $result) { | 
|  | print STDERR "$0: $ARGV: $.: cannot parse line.\n"; | 
|  | next; | 
|  | } | 
|  |  | 
|  | handle_trace($pid, $call, $args, $result, $time); | 
|  | } | 
|  |  | 
|  | display_trace(); | 
|  |  | 
|  | exit 0; | 
|  |  | 
|  | sub parse_str { | 
|  | my ($in) = @_; | 
|  | my $result = ""; | 
|  |  | 
|  | while (1) { | 
|  | if ($in =~ s/^\\(.)//) { | 
|  | $result .= $1; | 
|  | } elsif ($in =~ s/^\"//) { | 
|  | if ($in =~ s/^\.\.\.//) { | 
|  | return ("$result...", $in); | 
|  | } | 
|  | return ($result, $in); | 
|  | } elsif ($in =~ s/([^\\\"]*)//) { | 
|  | $result .= $1; | 
|  | } else { | 
|  | return (undef, $in); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | sub parse_one { | 
|  | my ($in) = @_; | 
|  |  | 
|  | if ($in =~ s/^\"//) { | 
|  | my $tmp; | 
|  | ($tmp, $in) = parse_str($in); | 
|  | if (not defined $tmp) { | 
|  | print STDERR "$0: $ARGV: $.: cannot parse string.\n"; | 
|  | return (undef, $in); | 
|  | } | 
|  | return ($tmp, $in); | 
|  | } elsif ($in =~ s/^0x([[:xdigit:]]+)//) { | 
|  | return (hex $1, $in); | 
|  | } elsif ($in =~ s/^(\d+)//) { | 
|  | return (int $1, $in); | 
|  | } else { | 
|  | print STDERR "$0: $ARGV: $.: unrecognized element.\n"; | 
|  | return (undef, $in); | 
|  | } | 
|  | } | 
|  |  | 
|  | sub parseargs { | 
|  | my ($in) = @_; | 
|  | my @args = (); | 
|  | my $tmp; | 
|  |  | 
|  | while (length $in) { | 
|  | if ($in =~ s/^\[//) { | 
|  | my @subarr = (); | 
|  | if ($in =~ s,^/\* (\d+) vars \*/\],,) { | 
|  | push @args, $1; | 
|  | } else { | 
|  | while ($in !~ s/^\]//) { | 
|  | ($tmp, $in) = parse_one($in); | 
|  | defined $tmp or return undef; | 
|  | push @subarr, $tmp; | 
|  | unless ($in =~ /^\]/ or $in =~ s/^, //) { | 
|  | print STDERR "$0: $ARGV: $.: missing comma in array.\n"; | 
|  | return undef; | 
|  | } | 
|  | if ($in =~ s/^\.\.\.//) { | 
|  | push @subarr, "..."; | 
|  | } | 
|  | } | 
|  | push @args, \@subarr; | 
|  | } | 
|  | } elsif ($in =~ s/^\{//) { | 
|  | my %subhash = (); | 
|  | while ($in !~ s/^\}//) { | 
|  | my $key; | 
|  | unless ($in =~ s/^(\w+)=//) { | 
|  | print STDERR "$0: $ARGV: $.: struct field expected.\n"; | 
|  | return undef; | 
|  | } | 
|  | $key = $1; | 
|  | ($tmp, $in) = parse_one($in); | 
|  | defined $tmp or return undef; | 
|  | $subhash{$key} = $tmp; | 
|  | unless ($in =~ s/, //) { | 
|  | print STDERR "$0: $ARGV: $.: missing comma in struct.\n"; | 
|  | return undef; | 
|  | } | 
|  | } | 
|  | push @args, \%subhash; | 
|  | } else { | 
|  | ($tmp, $in) = parse_one($in); | 
|  | defined $tmp or return undef; | 
|  | push @args, $tmp; | 
|  | } | 
|  | unless (length($in) == 0 or $in =~ s/^, //) { | 
|  | print STDERR "$0: $ARGV: $.: missing comma.\n"; | 
|  | return undef; | 
|  | } | 
|  | } | 
|  | return @args; | 
|  | } | 
|  |  | 
|  |  | 
|  | my $depth = ""; | 
|  |  | 
|  | # process info, indexed by pid. | 
|  | # fields: | 
|  | #    parent         pid number | 
|  | #    seq            clones, forks and execs for this pid, in sequence  (array) | 
|  |  | 
|  | #  filename and argv (from latest exec) | 
|  | #  basename (derived from filename) | 
|  | # argv[0] is modified to add the basename if it differs from the 0th argument. | 
|  |  | 
|  | my %pr; | 
|  |  | 
|  | sub handle_trace { | 
|  | my ($pid, $call, $args, $result, $time) = @_; | 
|  | my $pid_fqname = $pid . "-" . $time; | 
|  |  | 
|  | if (defined $time and not defined $running_fqname{$pid}) { | 
|  | $pr{$pid_fqname}{start} = $time; | 
|  | $running_fqname{$pid} = $pid_fqname; | 
|  | } | 
|  |  | 
|  | $pid_fqname = $running_fqname{$pid}; | 
|  |  | 
|  | if ($call eq 'execve') { | 
|  | return if $result ne '0'; | 
|  |  | 
|  | my ($filename, $argv) = parseargs($args); | 
|  | my ($basename) = $filename =~ m/([^\/]*)$/; | 
|  | if ($basename ne $$argv[0]) { | 
|  | $$argv[0] = "$basename($$argv[0])"; | 
|  | } | 
|  | my $seq = $pr{$pid_fqname}{seq}; | 
|  | $seq = [] if not defined $seq; | 
|  |  | 
|  | push @$seq, ['EXEC', $filename, $argv]; | 
|  |  | 
|  | $pr{$pid_fqname}{seq} = $seq; | 
|  | } elsif ($call eq 'fork' || $call eq 'clone' || $call eq 'vfork') { | 
|  | return if $result == 0; | 
|  |  | 
|  | my $seq = $pr{$pid_fqname}{seq}; | 
|  | my $result_fqname= $result . "-" . $time; | 
|  | $seq = [] if not defined $seq; | 
|  | push @$seq, ['FORK', $result_fqname]; | 
|  | $pr{$pid_fqname}{seq} = $seq; | 
|  | $pr{$result_fqname}{start} = $time; | 
|  | $pr{$result_fqname}{parent} = $pid_fqname; | 
|  | $pr{$result_fqname}{seq} = []; | 
|  | $running_fqname{$result} = $result_fqname; | 
|  | } elsif ($call eq '_exit' || $call eq 'exit_group') { | 
|  | $pr{$running_fqname{$pid}}{end} = $time if defined $time and not defined $pr{$running_fqname{$pid}}{end}; | 
|  | delete $running_fqname{$pid}; | 
|  | } | 
|  | } | 
|  |  | 
|  | sub handle_killed { | 
|  | my ($pid, $time) = @_; | 
|  | $pr{$pid}{end} = $time if defined $time and not defined $pr{$pid}{end}; | 
|  | } | 
|  |  | 
|  | sub straight_seq { | 
|  | my ($pid) = @_; | 
|  | my $seq = $pr{$pid}{seq}; | 
|  |  | 
|  | for my $elem (@$seq) { | 
|  | if ($$elem[0] eq 'EXEC') { | 
|  | my $argv = $$elem[2]; | 
|  | print "$$elem[0] $$elem[1] @$argv\n"; | 
|  | } elsif ($$elem[0] eq 'FORK') { | 
|  | print "$$elem[0] $$elem[1]\n"; | 
|  | } else { | 
|  | print "$$elem[0]\n"; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | sub first_exec { | 
|  | my ($pid) = @_; | 
|  | my $seq = $pr{$pid}{seq}; | 
|  |  | 
|  | for my $elem (@$seq) { | 
|  | if ($$elem[0] eq 'EXEC') { | 
|  | return $elem; | 
|  | } | 
|  | } | 
|  | return undef; | 
|  | } | 
|  |  | 
|  | sub display_pid_trace { | 
|  | my ($pid, $lead) = @_; | 
|  | my $i = 0; | 
|  | my @seq = @{$pr{$pid}{seq}}; | 
|  | my $elapsed; | 
|  |  | 
|  | if (not defined first_exec($pid)) { | 
|  | unshift @seq, ['EXEC', '', ['(anon)'] ]; | 
|  | } | 
|  |  | 
|  | if (defined $pr{$pid}{start} and defined $pr{$pid}{end}) { | 
|  | $elapsed = $pr{$pid}{end} - $pr{$pid}{start}; | 
|  | $elapsed /= $scale_factor; | 
|  | if ($floatform) { | 
|  | $elapsed = sprintf("%0.02f", $elapsed); | 
|  | } else { | 
|  | $elapsed = int $elapsed; | 
|  | } | 
|  | } | 
|  |  | 
|  | for my $elem (@seq) { | 
|  | $i++; | 
|  | if ($$elem[0] eq 'EXEC') { | 
|  | my $argv = $$elem[2]; | 
|  | if (defined $elapsed) { | 
|  | print "$lead [$elapsed] $pid @$argv\n"; | 
|  | undef $elapsed; | 
|  | } else { | 
|  | print "$lead $pid @$argv\n"; | 
|  | } | 
|  | } elsif ($$elem[0] eq 'FORK') { | 
|  | if ($i == 1) { | 
|  | if ($lead =~ /-$/) { | 
|  | display_pid_trace($$elem[1], "$lead--+--"); | 
|  | } else { | 
|  | display_pid_trace($$elem[1], "$lead  +--"); | 
|  | } | 
|  | } elsif ($i == @seq) { | 
|  | display_pid_trace($$elem[1], "$lead  `--"); | 
|  | } else { | 
|  | display_pid_trace($$elem[1], "$lead  +--"); | 
|  | } | 
|  | } | 
|  | if ($i == 1) { | 
|  | $lead =~ s/\`--/   /g; | 
|  | $lead =~ s/-/ /g; | 
|  | $lead =~ s/\+/|/g; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | sub display_trace { | 
|  | my ($startpid) = @_; | 
|  |  | 
|  | $startpid = (keys %pr)[0]; | 
|  | while ($pr{$startpid}{parent}) { | 
|  | $startpid = $pr{$startpid}{parent}; | 
|  | } | 
|  |  | 
|  | display_pid_trace($startpid, ""); | 
|  | } |