]> Dogcows Code - chaz/p5-Linux-Proc-Maps/blob - lib/Linux/Proc/Maps.pm
require at least perl 5.8.0
[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 =cut
210
211 1;
This page took 0.051444 seconds and 4 git commands to generate.