File Coverage

blib/lib/Sys/Trace/Impl/Truss.pm
Criterion Covered Total %
statement 61 84 72.6
branch 22 36 61.1
condition 1 6 16.6
subroutine 10 14 71.4
pod 0 6 0.0
total 94 146 64.3


line stmt bran cond sub pod time code
1             package Sys::Trace::Impl::Truss;
2 1     1   962 use strict;
  1         2  
  1         40  
3              
4 1     1   6 use Cwd ();
  1         2  
  1         14  
5 1     1   4 use File::Spec ();
  1         1  
  1         14  
6 1     1   1313 use File::Temp ();
  1         30374  
  1         24  
7 1     1   990 use POSIX ();
  1         7164  
  1         37  
8 1     1   1368 use Text::Balanced qw(extract_quotelike extract_bracketed);
  1         16433  
  1         101  
9 1     1   1078 use Time::HiRes qw(time);
  1         2115  
  1         7  
10              
11             =head1 NAME
12              
13             Sys::Trace::Impl::Truss - Sys::Trace implementation for truss(1)
14              
15             =head1 DESCRIPTION
16              
17             This should not normally be used directly, instead use L which will
18             pick a suitable interface for your platform.
19              
20             Truss is found on SVR4 systems. On Solaris in the C package. On AIX in
21             C (apparently, I don't have access to AIX, hopefully the
22             output is close enough).
23              
24             =cut
25              
26             sub usable {
27 0     0 0 0 system q{truss 2>/dev/null};
28 0   0     0 return POSIX::WIFEXITED($?) && POSIX::WEXITSTATUS($?) == 1;
29             }
30              
31             sub new {
32 1     1 0 858 my($class, %args) = @_;
33 1         4 my $self = bless {}, $class;
34              
35 1         4 my @run = qw(strace -d);
36              
37 1 50       5 if($args{follow_forks}) {
38 0         0 push @run, "-f";
39             }
40              
41             # TODO: Support saving this elsewhere for offline processing?
42 1         9 $self->{temp} = File::Temp->new;
43 1         821 push @run, "-o", $self->{temp};
44              
45 1 50       6 if($args{exec}) {
    50          
46 0         0 push @run, ref $args{exec}
47 0 0       0 ? @{$args{exec}}
48             : (qw(sh -c), $args{exec});
49             } elsif($args{pid}) {
50 0         0 push @run, "-p", $args{pid};
51             }
52              
53 1         3 $self->{run} = \@run;
54              
55 1         3 return $self;
56             }
57              
58             sub call {
59 0     0 0 0 my($self, @calls) = @_;
60             # We need chdir to track the working directory, so add iff filtering.
61 0         0 push @calls, "chdir";
62              
63 0         0 splice @{$self->{run}}, 1, 0, map { ("-t", $_) } @calls;
  0         0  
  0         0  
64             }
65              
66             sub run {
67 0     0 0 0 my($self) = @_;
68 0         0 $self->{cwd} = Cwd::getcwd;
69 0         0 $self->{basetime} = time;
70 0 0       0 exec @{$self->{run}} or die "Unable to exec: $!";
  0         0  
71             }
72              
73             sub pid {
74 0     0 0 0 my($self, $pid) = @_;
75 0 0       0 $self->{pid} = $pid if defined $pid;
76 0         0 $self->{pid};
77             }
78              
79             {
80              
81             # System calls that take a name argument and the position
82             # XXX: need to handle multiple args
83             my %name_syscalls = (
84             open => 0,
85             stat => 0,
86             lstat => 0,
87             stat64 => 0,
88             lstat64 => 0,
89             chdir => 0,
90             link => 0,
91             unlink => 0,
92             rmdir => 0,
93             mkdir => 0,
94             rename => 0,
95             access => 0,
96             execve => 0,
97             );
98              
99             my $line_re = qr{^
100             (?:([0-9]+):\s+)? # PID
101             ([0-9.]+)\s+ # Clock time
102             (\w+)\((.*)\) # syscall(...args...)
103             (?:
104             # Return value
105             \s+=\s+
106             (-?[0-9]+|0x[0-9A-Fa-f]+)
107             # Extra value (e.g. getpid())
108             (?:\s+\[\d+\])?
109              
110             | # argc = ?
111             \s+(\w+)\s+=\s+(\d+)
112              
113             | # Error
114             \s+Err\#(\d+) (\w+)
115             | # No return (e.g. exit)
116             )
117             $}x;
118             my @line_names = qw(pid time call args return extra_name extra_value errno);
119              
120             sub parse {
121 1     1 0 5 my($self, $fh) = @_;
122              
123 1 50       6 if(!$fh) {
124 0 0       0 open $fh, "<", $self->{temp} or die $!;
125             }
126              
127 1         2 my @calls;
128 1         6 while(<$fh>) {
129 68         70 my %call;
130              
131 68 100       143 if(/Base time stamp:\s+([0-9.]+)/) {
132 1         3 $self->{basetime} = $1;
133 1         6 next;
134             }
135              
136 67         927 @call{@line_names} = ($_ =~ $line_re);
137 67         204 $call{args} = _parse_args($call{args});
138              
139 67 100       156 next unless defined $call{call};
140              
141 64         198 $call{walltime} = $self->{basetime} + $call{time};
142              
143 64 100       172 if(exists $name_syscalls{$call{call}}) {
144 5         13 $call{name} = $call{args}->[$name_syscalls{$call{call}}];
145              
146 5 100       16 if($call{name} !~ m{^/}) {
147             # Resolve realtive paths
148 1         63 $call{name} = File::Spec->rel2abs($call{name}, $self->{cwd});
149             }
150              
151             # Need to keep track of cwd for the relative path resolving
152 5 50 33     15 if($call{call} eq 'chdir' && $call{return} == 0) {
153 0         0 $self->{cwd} = $call{name};
154             }
155             }
156              
157 64         394 push @calls, \%call;
158             }
159              
160 1         6 return \@calls;
161             }
162              
163             }
164              
165             sub _parse_args {
166 67     67   87 my($args) = @_;
167              
168 67         63 my @args;
169 67         128 while($args) {
170 225 100       2054 if($args =~ /^"/) { # String
    50          
    100          
    100          
171 21         47 (my $string, $args) = extract_quotelike($args);
172 21         1622 ($string) = $string =~ /"(.*)"/;
173              
174 21 100       56 $string .= "..." if $args =~ s/\.\.//;
175 21         33 push @args, $string;
176              
177             } elsif($args =~ /^([[{])/) { # Start of structure
178 0         0 (my $string, $args) = extract_bracketed($args, $1);
179 0         0 push @args, $string;
180              
181             } elsif($args =~ s{(0x[a-fA-F0-9]+|-?[0-9]+)(?:\s+(/\* .*? \*/))?}{}) {
182             # Number (plus optional comment)
183 137         299 push @args, $1;
184             } elsif($args =~ s/^([^,]+)//) {
185             # Constant or similar
186 46         96 push @args, $1;
187             }
188              
189 225         761 $args =~ s/^,\s*//;
190             }
191              
192 67         164 return \@args;
193             }
194              
195             1;