File Coverage

blib/lib/Sys/Trace/Impl/Strace.pm
Criterion Covered Total %
statement 55 76 72.3
branch 20 34 58.8
condition 1 6 16.6
subroutine 9 13 69.2
pod 0 6 0.0
total 85 135 62.9


line stmt bran cond sub pod time code
1             package Sys::Trace::Impl::Strace;
2 2     2   6182 use strict;
  2         5  
  2         78  
3              
4 2     2   12 use Cwd ();
  2         4  
  2         30  
5 2     2   10 use File::Spec ();
  2         3  
  2         30  
6 2     2   2499 use File::Temp ();
  2         67427  
  2         55  
7 2     2   4028 use POSIX ();
  2         25119  
  2         73  
8 2     2   3204 use Text::Balanced qw(extract_quotelike extract_bracketed);
  2         298766  
  2         2571  
9              
10             =head1 NAME
11              
12             Sys::Trace::Impl::Strace - Sys::Trace implementation for strace(1)
13              
14             =head1 DESCRIPTION
15              
16             This should not normally be used directly, instead use L which will
17             pick a suitable interface for your platform.
18              
19             =cut
20              
21             sub usable {
22 0     0 0 0 system q{strace 2>/dev/null};
23 0   0     0 return POSIX::WIFEXITED($?) && POSIX::WEXITSTATUS($?) == 1;
24             }
25              
26             sub new {
27 1     1 0 784 my($class, %args) = @_;
28 1         5 my $self = bless {}, $class;
29              
30 1         4 my @run = qw(strace -ttt -T);
31              
32 1 50       6 if($args{follow_forks}) {
33 0         0 push @run, "-f";
34             }
35              
36             # TODO: Support saving this elsewhere for offline processing?
37 1         11 $self->{temp} = File::Temp->new;
38 1         912 push @run, "-o", $self->{temp};
39              
40 1 50       8 if($args{exec}) {
    50          
41 0         0 push @run, ref $args{exec}
42 0 0       0 ? @{$args{exec}}
43             : (qw(sh -c), $args{exec});
44             } elsif($args{pid}) {
45 0         0 push @run, "-p", $args{pid};
46             }
47              
48 1         2 $self->{run} = \@run;
49              
50 1         4 return $self;
51             }
52              
53             sub call {
54 0     0 0 0 my($self, @calls) = @_;
55             # We need chdir to track the working directory, so add iff filtering.
56 0         0 push @calls, "chdir";
57              
58 0         0 splice @{$self->{run}}, 1, 0, map { ("-e", $_) } @calls;
  0         0  
  0         0  
59             }
60              
61             sub run {
62 0     0 0 0 my($self) = @_;
63 0         0 $self->{cwd} = Cwd::getcwd;
64 0 0       0 exec @{$self->{run}} or die "Unable to exec: $!";
  0         0  
65             }
66              
67             sub pid {
68 0     0 0 0 my($self, $pid) = @_;
69 0 0       0 $self->{pid} = $pid if defined $pid;
70 0         0 $self->{pid};
71             }
72              
73             {
74              
75             # System calls that take a name argument and the position
76             # XXX: need to handle multiple args
77             my %name_syscalls = (
78             open => 0,
79             stat => 0,
80             lstat => 0,
81             stat64 => 0,
82             lstat64 => 0,
83             chdir => 0,
84             link => 0,
85             unlink => 0,
86             rmdir => 0,
87             mkdir => 0,
88             rename => 0,
89             access => 0,
90             execve => 0,
91             );
92              
93             my $line_re = qr{^
94             ([0-9]+)\s+ # PID
95             ([0-9.]+)\s+ # Clock time
96             (\w+)\((.*)\) # syscall(...args...)
97             \s+=\s+
98             (?:(-?[0-9]+|0x[0-9a-f]+) # Return value
99             (?:\s+(\w+)\s+\(([A-Za-z0-9 ]+)\))? # Error code
100             \s+<([0-9.]+)> # Time in syscall
101             |\?) # Unknown return (e.g. exit)
102             $}x;
103             my @line_names = qw(pid walltime call args return errno strerror systime);
104              
105             sub parse {
106 1     1 0 7 my($self, $fh) = @_;
107              
108 1 50       7 if(!$fh) {
109 0 0       0 open $fh, "<", $self->{temp} or die $!;
110             }
111              
112 1         2 my @calls;
113 1         9 while(<$fh>) {
114 110         124 my %call;
115 110         10886 @call{@line_names} = ($_ =~ $line_re);
116 110         396 $call{args} = _parse_args($call{args});
117              
118 110 100       418 next unless defined $call{call};
119              
120 108 100       262 if(exists $name_syscalls{$call{call}}) {
121 34         89 $call{name} = $call{args}->[$name_syscalls{$call{call}}];
122              
123 34 50       110 if($call{name} !~ m{^/}) {
124             # Resolve realtive paths
125 0         0 $call{name} = File::Spec->rel2abs($call{name}, $self->{cwd});
126             }
127              
128             # Need to keep track of cwd for the relative path resolving
129 34 50 33     100 if($call{call} eq 'chdir' && $call{return} == 0) {
130 0         0 $self->{cwd} = $call{name};
131             }
132             }
133              
134 108         757 push @calls, \%call;
135             }
136              
137 1         9 return \@calls;
138             }
139              
140             }
141              
142             sub _parse_args {
143 110     110   156 my($args) = @_;
144              
145 110         112 my @args;
146 110         210 while($args) {
147 321 100       3336 if($args =~ /^"/) { # String
    100          
    100          
    100          
148 38         103 (my $string, $args) = extract_quotelike($args);
149 38         3190 ($string) = $string =~ /"(.*)"/;
150              
151 38 100       120 $string .= "..." if $args =~ s/\.\.\.//;
152 38         108 push @args, $string;
153              
154             } elsif($args =~ /^([[{])/) { # Start of structure
155 21         58 (my $string, $args) = extract_bracketed($args, $1);
156 21         5320 push @args, $string;
157              
158             } elsif($args =~ s{(0x[a-f0-9]+|-?[0-9]+)(?:\s+(/\* .*? \*/))?}{}) {
159             # Number (plus optional comment)
160 124         301 push @args, $1;
161             } elsif($args =~ s/^([^,]+)//) {
162             # Constant or similar
163 98         268 push @args, $1;
164             }
165              
166 321         1097 $args =~ s/^,\s*//;
167             }
168              
169 110         301 return \@args;
170             }
171              
172             1;