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