33// For the full copyright and license information, please view the LICENSE
44// file that was distributed with this source code.
55
6+ use std:: fmt;
67use std:: io:: { Error , ErrorKind } ;
78
8- // Represents a parsed single line from /proc/<PID>/maps.
9+ // Represents a parsed single line from /proc/<PID>/maps for the default and device formats. It
10+ // omits the inode information because it's not used by those formats.
911#[ derive( Debug , PartialEq ) ]
1012pub struct MapLine {
1113 pub address : String ,
1214 pub size_in_kb : u64 ,
13- pub perms : String ,
15+ pub perms : Perms ,
16+ pub offset : String ,
17+ pub device : String ,
1418 pub mapping : String ,
1519}
1620
21+ // Represents a set of permissions from the "perms" column of /proc/<PID>/maps.
22+ #[ derive( Clone , Copy , Debug , PartialEq ) ]
23+ pub struct Perms {
24+ pub readable : bool ,
25+ pub writable : bool ,
26+ pub executable : bool ,
27+ pub shared : bool ,
28+ }
29+
30+ impl From < & str > for Perms {
31+ fn from ( s : & str ) -> Self {
32+ let mut chars = s. chars ( ) ;
33+
34+ Self {
35+ readable : chars. next ( ) == Some ( 'r' ) ,
36+ writable : chars. next ( ) == Some ( 'w' ) ,
37+ executable : chars. next ( ) == Some ( 'x' ) ,
38+ shared : chars. next ( ) == Some ( 's' ) ,
39+ }
40+ }
41+ }
42+
43+ // Please note: While `Perms` has four boolean fields, it's string representation has five
44+ // characters because pmap's default and device formats use five characters for the perms,
45+ // with the last character always being '-'.
46+ impl fmt:: Display for Perms {
47+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
48+ write ! (
49+ f,
50+ "{}{}{}{}-" ,
51+ if self . readable { 'r' } else { '-' } ,
52+ if self . writable { 'w' } else { '-' } ,
53+ if self . executable { 'x' } else { '-' } ,
54+ if self . shared { 's' } else { '-' } ,
55+ )
56+ }
57+ }
58+
1759// Parses a single line from /proc/<PID>/maps. See
1860// https://www.kernel.org/doc/html/latest/filesystems/proc.html for details about the expected
1961// format.
@@ -30,16 +72,29 @@ pub fn parse_map_line(line: &str) -> Result<MapLine, Error> {
3072 let ( perms, rest) = rest
3173 . split_once ( ' ' )
3274 . ok_or_else ( || Error :: from ( ErrorKind :: InvalidData ) ) ?;
33- let perms = parse_perms ( perms) ;
75+ let perms = Perms :: from ( perms) ;
76+
77+ let ( offset, rest) = rest
78+ . split_once ( ' ' )
79+ . ok_or_else ( || Error :: from ( ErrorKind :: InvalidData ) ) ?;
80+ let offset = format ! ( "{offset:0>16}" ) ;
81+
82+ let ( device, rest) = rest
83+ . split_once ( ' ' )
84+ . ok_or_else ( || Error :: from ( ErrorKind :: InvalidData ) ) ?;
85+ let device = parse_device ( device) ?;
3486
35- let mapping: String = rest. splitn ( 4 , ' ' ) . skip ( 3 ) . collect ( ) ;
87+ // skip the "inode" column
88+ let mapping: String = rest. splitn ( 2 , ' ' ) . skip ( 1 ) . collect ( ) ;
3689 let mapping = mapping. trim_ascii_start ( ) ;
3790 let mapping = parse_mapping ( mapping) ;
3891
3992 Ok ( MapLine {
4093 address,
4194 size_in_kb,
4295 perms,
96+ offset,
97+ device,
4398 mapping,
4499 } )
45100}
@@ -58,13 +113,12 @@ fn parse_address(memory_range: &str) -> Result<(String, u64), Error> {
58113 Ok ( ( format ! ( "{start:0>16}" ) , size_in_kb) )
59114}
60115
61- // Turns a 4-char perms string from /proc/<PID>/maps into a 5-char perms string. The first three
62- // chars are left untouched.
63- fn parse_perms ( perms : & str ) -> String {
64- let perms = perms. replace ( "p" , "-" ) ;
65-
66- // the fifth char seems to be always '-' in the original pmap
67- format ! ( "{perms}-" )
116+ // Pads the device info from /proc/<PID>/maps with zeros and turns AB:CD into 0AB:000CD.
117+ fn parse_device ( device : & str ) -> Result < String , Error > {
118+ let ( major, minor) = device
119+ . split_once ( ':' )
120+ . ok_or_else ( || Error :: from ( ErrorKind :: InvalidData ) ) ?;
121+ Ok ( format ! ( "{major:0>3}:{minor:0>5}" ) )
68122}
69123
70124fn parse_mapping ( mapping : & str ) -> String {
@@ -86,44 +140,60 @@ fn parse_mapping(mapping: &str) -> String {
86140mod test {
87141 use super :: * ;
88142
89- fn create_map_line ( address : & str , size_in_kb : u64 , perms : & str , mapping : & str ) -> MapLine {
143+ fn create_map_line (
144+ address : & str ,
145+ size_in_kb : u64 ,
146+ perms : Perms ,
147+ offset : & str ,
148+ device : & str ,
149+ mapping : & str ,
150+ ) -> MapLine {
90151 MapLine {
91152 address : address. to_string ( ) ,
92153 size_in_kb,
93- perms : perms. to_string ( ) ,
154+ perms,
155+ offset : offset. to_string ( ) ,
156+ device : device. to_string ( ) ,
94157 mapping : mapping. to_string ( ) ,
95158 }
96159 }
97160
161+ #[ test]
162+ fn test_perms_to_string ( ) {
163+ assert_eq ! ( "-----" , Perms :: from( "---p" ) . to_string( ) ) ;
164+ assert_eq ! ( "---s-" , Perms :: from( "---s" ) . to_string( ) ) ;
165+ assert_eq ! ( "rwx--" , Perms :: from( "rwxp" ) . to_string( ) ) ;
166+ }
167+
98168 #[ test]
99169 fn test_parse_map_line ( ) {
100170 let data = [
101171 (
102- create_map_line ( "000062442eb9e000" , 16 , "r---- " , "konsole" ) ,
172+ create_map_line ( "000062442eb9e000" , 16 , Perms :: from ( "r--p" ) , "0000000000000000" , "008:00008 ", "konsole" ) ,
103173 "62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole"
104174 ) ,
105175 (
106- create_map_line ( "000071af50000000" , 132 , "rw--- " , " [ anon ]" ) ,
176+ create_map_line ( "000071af50000000" , 132 , Perms :: from ( "rw-p" ) , "0000000000000000" , "000:00000 ", " [ anon ]" ) ,
107177 "71af50000000-71af50021000 rw-p 00000000 00:00 0 "
108178 ) ,
109179 (
110- create_map_line ( "00007ffc3f8df000" , 132 , "rw--- " , " [ stack ]" ) ,
180+ create_map_line ( "00007ffc3f8df000" , 132 , Perms :: from ( "rw-p" ) , "0000000000000000" , "000:00000 ", " [ stack ]" ) ,
111181 "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]"
112182 ) ,
113183 (
114- create_map_line ( "000071af8c9e6000" , 16 , "rw-s- " , " [ anon ]" ) ,
184+ create_map_line ( "000071af8c9e6000" , 16 , Perms :: from ( "rw-s" ) , "0000000105830000" , "000:00010 ", " [ anon ]" ) ,
115185 "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem"
116186 ) ,
117187 (
118- create_map_line ( "000071af6cf0c000" , 3560 , "rw-s- " , "memfd:wayland-shm (deleted)" ) ,
188+ create_map_line ( "000071af6cf0c000" , 3560 , Perms :: from ( "rw-s" ) , "0000000000000000" , "000:00001 ", "memfd:wayland-shm (deleted)" ) ,
119189 "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)"
120190 ) ,
121191 (
122- create_map_line ( "ffffffffff600000" , 4 , "--x-- " , " [ anon ]" ) ,
192+ create_map_line ( "ffffffffff600000" , 4 , Perms :: from ( "--xp" ) , "0000000000000000" , "000:00000 ", " [ anon ]" ) ,
123193 "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]"
124194 ) ,
125195 (
126- create_map_line ( "00005e8187da8000" , 24 , "r---- " , "hello world" ) ,
196+ create_map_line ( "00005e8187da8000" , 24 , Perms :: from ( "r--p" ) , "0000000000000000" , "008:00008 ", "hello world" ) ,
127197 "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world"
128198 ) ,
129199 ] ;
@@ -161,10 +231,14 @@ mod test {
161231 }
162232
163233 #[ test]
164- fn test_parse_perms ( ) {
165- assert_eq ! ( "-----" , parse_perms( "---p" ) ) ;
166- assert_eq ! ( "---s-" , parse_perms( "---s" ) ) ;
167- assert_eq ! ( "rwx--" , parse_perms( "rwxp" ) ) ;
234+ fn test_parse_device ( ) {
235+ assert_eq ! ( "012:00034" , parse_device( "12:34" ) . unwrap( ) ) ;
236+ assert_eq ! ( "000:00000" , parse_device( "00:00" ) . unwrap( ) ) ;
237+ }
238+
239+ #[ test]
240+ fn test_parse_device_without_colon ( ) {
241+ assert ! ( parse_device( "1234" ) . is_err( ) ) ;
168242 }
169243
170244 #[ test]
0 commit comments