]> Dogcows Code - chaz/p5-Linux-Proc-Maps/blob - lib/Linux/Proc/Maps.pm
document the possibility of integer overloading
[chaz/p5-Linux-Proc-Maps] / lib / Linux / Proc / Maps.pm
1 package Linux::Proc::Maps;
2 # ABSTRACT: Read and write /proc/[pid]/maps files
3 # KEYWORDS: linux proc procfs
4
5 use 5.008;
6 use warnings;
7 use strict;
8
9 our $VERSION = '999.999'; # VERSION
10
11 use Carp qw(croak);
12 use Exporter qw(import);
13 use namespace::clean -except => [qw(import)];
14
15 our @EXPORT_OK = qw(read_maps write_maps parse_maps_single_line format_maps_single_line);
16
17 =head1 SYNOPSIS
18
19 use Linux::Proc::Maps qw(read_maps);
20
21 # by pid:
22 my $vm_regions = read_maps(pid => $$);
23
24 # by pid with explicit procfs mount:
25 my $vm_regions = read_maps(mnt => '/proc', pid => 123);
26
27 # by file:
28 my $vm_regions = read_maps(file => '/proc/456/maps');
29
30 =head1 DESCRIPTION
31
32 This module reads and writes F</proc/[pid]/maps> files that contain listed mapped memory regions.
33
34 =func read_maps
35
36 Read and parse a maps file, returning an arrayref of regions (each represented as a hashref). See
37 L</parse_maps_single_line> to see the format of the hashrefs.
38
39 my $regions = read_maps(%args);
40
41 Arguments:
42
43 =for :list
44 * C<file> - Path to maps file
45 * C<pid> - Process ID (one of C<file> or C<pid> is required)
46 * C<mnt> - Absolute path where L<proc(5)> is mounted (optional, default: C</proc>)
47
48 =cut
49
50 sub read_maps {
51 my %args = @_ == 1 ? (pid => $_[0]) : @_;
52
53 my $file = $args{file};
54
55 if (!$file and my $pid = $args{pid}) {
56 if ($pid =~ /^\d+$/) {
57 require File::Spec::Functions;
58 my $procfs = $args{mnt} || $ENV{PERL_LINUX_PROC_MAPS_MOUNT} ||
59 File::Spec::Functions::catdir(File::Spec::Functions::rootdir(), 'proc');
60 $file = File::Spec::Functions::catfile($procfs, $pid, 'maps');
61 }
62 else {
63 $file = $args{pid};
64 }
65 }
66
67 $file or croak 'Filename or PID required';
68 open(my $fh, '<:encoding(UTF-8)', $file) or croak "Open failed ($file): $!";
69
70 my @regions;
71
72 while (my $line = <$fh>) {
73 chomp $line;
74
75 my $region = parse_maps_single_line($line);
76 next if !$region;
77
78 push @regions, $region;
79 }
80
81 return \@regions;
82 }
83
84 =func write_maps
85
86 Returns a string with the contents of a maps file from the memory regions passed.
87
88 my $file_content = write_maps(\@regions, %args);
89
90 This is the opposite of L</read_maps>.
91
92 Arguments:
93
94 =for :list
95 * C<fh> - Write maps to this open file handle (optional)
96 * C<file> - Open this filepath and write maps to that file (optional)
97
98 =cut
99
100 sub write_maps {
101 my $regions = shift or croak 'Regions required';
102 my %args = @_;
103
104 ref $regions eq 'ARRAY' or croak 'Regions must be an arrayref';
105
106 my $out = '';
107
108 for my $region (@$regions) {
109 $out .= format_maps_single_line($region);
110 }
111
112 # maybe print out the memory regions to a filehandle
113 my $fh = $args{fh};
114 if (!$fh and my $file = $args{file}) {
115 open($fh, '>:encoding(UTF-8)', $file) or croak "Open failed ($file): $!";
116 }
117 print $fh $out if $fh;
118
119 return $out;
120 }
121
122 =func parse_maps_single_line
123
124 Parse and return a single line from a maps file into a region represented as a hashref.
125
126 my $region = parse_maps_single_line($line);
127
128 For example,
129
130 # address perms offset dev inode pathname
131 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
132
133 becomes:
134
135 {
136 address_start => 134512640,
137 address_end => 134569984,
138 read => 1,
139 write => '',
140 execute => 1,
141 shared => '',
142 offset => 0,
143 device => '03:0c'
144 inode => '64593',
145 pathname => '/usr/sbin/gpm',
146 }
147
148 =cut
149
150 sub parse_maps_single_line {
151 my $line = shift or croak 'Line from a maps file required';
152
153 chomp $line;
154
155 my ($addr1, $addr2, $read, $write, $exec, $shared, $offset, $device, $inode, $pathname) = $line =~ m{
156 ^
157 ([[:xdigit:]]+)-([[:xdigit:]]+)
158 \s+ ([r-])([w-])([x-])([sp])
159 \s+ ([[:xdigit:]]+)
160 \s+ ([[:xdigit:]]+:[[:xdigit:]]+)
161 \s+ (\d+)
162 (?: \s+ (.*))?
163 }x;
164
165 return if !$addr1;
166
167 no warnings 'portable'; # for hex() on 64-bit perls
168
169 return {
170 address_start => hex($addr1),
171 address_end => hex($addr2),
172 read => 'r' eq $read,
173 write => 'w' eq $write,
174 execute => 'x' eq $exec,
175 shared => 's' eq $shared,
176 offset => hex($offset),
177 device => $device,
178 inode => $inode,
179 pathname => $pathname || '',
180 };
181 }
182
183 =func format_maps_single_line
184
185 Return a single line for a maps file from a region represented as a hashref.
186
187 my $line = format_maps_single_line(\%region);
188
189 This is the opposite of L</parse_maps_single_line>.
190
191 =cut
192
193 sub format_maps_single_line {
194 my $region = shift or croak 'Region required';
195
196 my @args = @{$region}{qw(address_start address_end read write execute shared offset device inode)};
197 $args[2] = $args[2] ? 'r' : '-';
198 $args[3] = $args[3] ? 'w' : '-';
199 $args[4] = $args[4] ? 'x' : '-';
200 $args[5] = $args[5] ? 's' : 'p';
201
202 return sprintf("%-72s %s\n", sprintf("%x-%x %s%s%s%s %08x %s %d", @args), $region->{pathname});
203 }
204
205 =head1 SEE ALSO
206
207 L<proc(5)> describes the file format.
208
209 =head1 CAVEATS
210
211 Integer overloading may occur if you try to parse memory regions from address spaces larger than
212 your current architecture (or perl) supports. This is currently not fatal, though you will get
213 warnings from perl that you probably shouldn't ignore.
214
215 =cut
216
217 1;
This page took 0.048188 seconds and 4 git commands to generate.