Skip to content

Commit 2c3f61e

Browse files
committed
pmap: move parsing to maps_format_parser.rs
1 parent 005c7ea commit 2c3f61e

File tree

2 files changed

+166
-140
lines changed

2 files changed

+166
-140
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
// Represents a parsed single line from /proc/<PID>/maps.
7+
#[derive(Debug, PartialEq)]
8+
pub struct MapLine {
9+
pub address: String,
10+
pub size_in_kb: u64,
11+
pub perms: String,
12+
pub mapping: String,
13+
}
14+
15+
// Parses a single line from /proc/<PID>/maps. It assumes the format of `line` is correct (see
16+
// https://www.kernel.org/doc/html/latest/filesystems/proc.html for details).
17+
pub fn parse_map_line(line: &str) -> MapLine {
18+
let (memory_range, rest) = line.split_once(' ').expect("line should contain ' '");
19+
let (address, size_in_kb) = parse_address(memory_range);
20+
21+
let (perms, rest) = rest.split_once(' ').expect("line should contain 2nd ' '");
22+
let perms = parse_perms(perms);
23+
24+
let mapping: String = rest.splitn(4, ' ').skip(3).collect();
25+
let mapping = mapping.trim_ascii_start();
26+
let mapping = parse_mapping(mapping);
27+
28+
MapLine {
29+
address,
30+
size_in_kb,
31+
perms,
32+
mapping,
33+
}
34+
}
35+
36+
// Returns the start address and the size of the provided memory range. The start address is always
37+
// 16-digits and padded with 0, if necessary. The size is in KB.
38+
fn parse_address(memory_range: &str) -> (String, u64) {
39+
let (start, end) = memory_range
40+
.split_once('-')
41+
.expect("memory range should contain '-'");
42+
43+
let low = u64::from_str_radix(start, 16).expect("should be a hex value");
44+
let high = u64::from_str_radix(end, 16).expect("should be a hex value");
45+
let size_in_kb = (high - low) / 1024;
46+
47+
(format!("{start:0>16}"), size_in_kb)
48+
}
49+
50+
// Turns a 4-char perms string from /proc/<PID>/maps into a 5-char perms string. The first three
51+
// chars are left untouched.
52+
fn parse_perms(perms: &str) -> String {
53+
let perms = perms.replace("p", "-");
54+
55+
// the fifth char seems to be always '-' in the original pmap
56+
format!("{perms}-")
57+
}
58+
59+
fn parse_mapping(mapping: &str) -> String {
60+
if mapping == "[stack]" {
61+
return " [ stack ]".into();
62+
}
63+
64+
if mapping.is_empty() || mapping.starts_with('[') || mapping.starts_with("anon") {
65+
return " [ anon ]".into();
66+
}
67+
68+
match mapping.rsplit_once('/') {
69+
Some((_, name)) => name.into(),
70+
None => mapping.into(),
71+
}
72+
}
73+
74+
#[cfg(test)]
75+
mod test {
76+
use super::*;
77+
78+
fn create_map_line(address: &str, size_in_kb: u64, perms: &str, mapping: &str) -> MapLine {
79+
MapLine {
80+
address: address.to_string(),
81+
size_in_kb,
82+
perms: perms.to_string(),
83+
mapping: mapping.to_string(),
84+
}
85+
}
86+
87+
#[test]
88+
fn test_parse_map_line() {
89+
let data = [
90+
(
91+
create_map_line("000062442eb9e000", 16, "r----", "konsole"),
92+
"62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole"
93+
),
94+
(
95+
create_map_line("000071af50000000", 132, "rw---", " [ anon ]"),
96+
"71af50000000-71af50021000 rw-p 00000000 00:00 0 "
97+
),
98+
(
99+
create_map_line("00007ffc3f8df000", 132, "rw---", " [ stack ]"),
100+
"7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]"
101+
),
102+
(
103+
create_map_line("000071af8c9e6000", 16, "rw-s-", " [ anon ]"),
104+
"71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem"
105+
),
106+
(
107+
create_map_line("000071af6cf0c000", 3560, "rw-s-", "memfd:wayland-shm (deleted)"),
108+
"71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)"
109+
),
110+
(
111+
create_map_line("ffffffffff600000", 4, "--x--", " [ anon ]"),
112+
"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]"
113+
),
114+
(
115+
create_map_line("00005e8187da8000", 24, "r----", "hello world"),
116+
"5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world"
117+
),
118+
];
119+
120+
for (expected_map_line, line) in data {
121+
assert_eq!(expected_map_line, parse_map_line(line));
122+
}
123+
}
124+
125+
#[test]
126+
fn test_parse_address() {
127+
let (start, size) = parse_address("ffffffffff600000-ffffffffff601000");
128+
assert_eq!(start, "ffffffffff600000");
129+
assert_eq!(size, 4);
130+
131+
let (start, size) = parse_address("7ffc4f0c2000-7ffc4f0e3000");
132+
assert_eq!(start, "00007ffc4f0c2000");
133+
assert_eq!(size, 132);
134+
}
135+
136+
#[test]
137+
fn test_parse_perms() {
138+
assert_eq!("-----", parse_perms("---p"));
139+
assert_eq!("---s-", parse_perms("---s"));
140+
assert_eq!("rwx--", parse_perms("rwxp"));
141+
}
142+
143+
#[test]
144+
fn test_parse_mapping() {
145+
assert_eq!(" [ anon ]", parse_mapping(""));
146+
assert_eq!(" [ anon ]", parse_mapping("[vvar]"));
147+
assert_eq!(" [ anon ]", parse_mapping("[vdso]"));
148+
assert_eq!(" [ anon ]", parse_mapping("anon_inode:i915.gem"));
149+
assert_eq!(" [ stack ]", parse_mapping("[stack]"));
150+
assert_eq!(
151+
"ld-linux-x86-64.so.2",
152+
parse_mapping("/usr/lib/ld-linux-x86-64.so.2")
153+
);
154+
}
155+
}

src/uu/pmap/src/pmap.rs

Lines changed: 11 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
// file that was distributed with this source code.
55

66
use clap::{crate_version, Arg, ArgAction, Command};
7+
use maps_format_parser::parse_map_line;
78
use std::env;
89
use std::fs;
910
use std::io::Error;
1011
use uucore::error::{set_exit_code, UResult};
1112
use uucore::{format_usage, help_about, help_usage};
1213

14+
mod maps_format_parser;
15+
1316
const ABOUT: &str = help_about!("pmap.md");
1417
const USAGE: &str = help_usage!("pmap.md");
1518

@@ -41,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4144
}
4245

4346
fn parse_cmdline(pid: &str) -> Result<String, Error> {
44-
let path = format!("/proc/{}/cmdline", pid);
47+
let path = format!("/proc/{pid}/cmdline");
4548
let contents = fs::read(path)?;
4649
// Command line arguments are separated by null bytes.
4750
// Replace them with spaces for display.
@@ -55,78 +58,22 @@ fn parse_cmdline(pid: &str) -> Result<String, Error> {
5558
}
5659

5760
fn parse_maps(pid: &str) -> Result<u64, Error> {
58-
let path = format!("/proc/{}/maps", pid);
61+
let path = format!("/proc/{pid}/maps");
5962
let contents = fs::read_to_string(path)?;
6063
let mut total = 0;
6164

6265
for line in contents.lines() {
63-
let (generated_line, size) = parse_map_line(line);
64-
println!("{generated_line}");
65-
total += size;
66+
let map_line = parse_map_line(line);
67+
println!(
68+
"{} {:>6}K {} {}",
69+
map_line.address, map_line.size_in_kb, map_line.perms, map_line.mapping
70+
);
71+
total += map_line.size_in_kb;
6672
}
6773

6874
Ok(total)
6975
}
7076

71-
// Parses a single line from /proc/<PID>/maps.
72-
fn parse_map_line(line: &str) -> (String, u64) {
73-
let (memory_range, rest) = line.split_once(' ').expect("line should contain ' '");
74-
let (start_address, size_in_kb) = parse_memory_range(memory_range);
75-
76-
let (perms, rest) = rest.split_once(' ').expect("line should contain 2nd ' '");
77-
let perms = parse_perms(perms);
78-
79-
let filename: String = rest.splitn(4, ' ').skip(3).collect();
80-
let filename = filename.trim_ascii_start();
81-
let filename = parse_filename(filename);
82-
83-
(
84-
format!("{start_address} {size_in_kb:>6}K {perms} {filename}"),
85-
size_in_kb,
86-
)
87-
}
88-
89-
// Returns the start address and the size of the provided memory range. The start address is always
90-
// 16-digits and padded with 0, if necessary. The size is in KB.
91-
//
92-
// This function assumes the provided `memory_range` comes from /proc/<PID>/maps and thus its
93-
// format is correct.
94-
fn parse_memory_range(memory_range: &str) -> (String, u64) {
95-
let (start, end) = memory_range
96-
.split_once('-')
97-
.expect("memory range should contain '-'");
98-
99-
let low = u64::from_str_radix(start, 16).expect("should be a hex value");
100-
let high = u64::from_str_radix(end, 16).expect("should be a hex value");
101-
let size_in_kb = (high - low) / 1024;
102-
103-
(format!("{start:0>16}"), size_in_kb)
104-
}
105-
106-
// Turns a 4-char perms string from /proc/<PID>/maps into a 5-char perms string. The first three
107-
// chars are left untouched.
108-
fn parse_perms(perms: &str) -> String {
109-
let perms = perms.replace("p", "-");
110-
111-
// the fifth char seems to be always '-' in the original pmap
112-
format!("{perms}-")
113-
}
114-
115-
fn parse_filename(filename: &str) -> String {
116-
if filename == "[stack]" {
117-
return " [ stack ]".into();
118-
}
119-
120-
if filename.is_empty() || filename.starts_with('[') || filename.starts_with("anon") {
121-
return " [ anon ]".into();
122-
}
123-
124-
match filename.rsplit_once('/') {
125-
Some((_, name)) => name.into(),
126-
None => filename.into(),
127-
}
128-
}
129-
13077
pub fn uu_app() -> Command {
13178
Command::new(env!("CARGO_PKG_NAME"))
13279
.version(crate_version!())
@@ -208,79 +155,3 @@ pub fn uu_app() -> Command {
208155
.help("limit results to the given range"),
209156
)
210157
}
211-
212-
#[cfg(test)]
213-
mod test {
214-
use super::*;
215-
216-
#[test]
217-
fn test_parse_map_line() {
218-
let data = [
219-
(
220-
("000062442eb9e000 16K r---- konsole", 16),
221-
"62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole"
222-
),
223-
(
224-
("000071af50000000 132K rw--- [ anon ]", 132),
225-
"71af50000000-71af50021000 rw-p 00000000 00:00 0 "
226-
),
227-
(
228-
("00007ffc3f8df000 132K rw--- [ stack ]", 132),
229-
"7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]"
230-
),
231-
(
232-
("000071af8c9e6000 16K rw-s- [ anon ]", 16),
233-
"71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem"
234-
),
235-
(
236-
("000071af6cf0c000 3560K rw-s- memfd:wayland-shm (deleted)", 3560),
237-
"71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)"
238-
),
239-
(
240-
("ffffffffff600000 4K --x-- [ anon ]", 4),
241-
"ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]"
242-
),
243-
(
244-
("00005e8187da8000 24K r---- hello world", 24),
245-
"5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world"
246-
),
247-
];
248-
249-
for ((expected_line, expected_size), line) in data {
250-
let (generated_line, size) = parse_map_line(line);
251-
assert_eq!(expected_line, generated_line);
252-
assert_eq!(expected_size, size);
253-
}
254-
}
255-
256-
#[test]
257-
fn test_parse_memory_range() {
258-
let (start, size) = parse_memory_range("ffffffffff600000-ffffffffff601000");
259-
assert_eq!(start, "ffffffffff600000");
260-
assert_eq!(size, 4);
261-
262-
let (start, size) = parse_memory_range("7ffc4f0c2000-7ffc4f0e3000");
263-
assert_eq!(start, "00007ffc4f0c2000");
264-
assert_eq!(size, 132);
265-
}
266-
267-
#[test]
268-
fn test_parse_perms() {
269-
assert_eq!("-----", parse_perms("---p"));
270-
assert_eq!("---s-", parse_perms("---s"));
271-
assert_eq!("rwx--", parse_perms("rwxp"));
272-
}
273-
274-
#[test]
275-
fn test_parse_filename() {
276-
assert_eq!(" [ anon ]", parse_filename(""));
277-
assert_eq!(" [ anon ]", parse_filename("[vvar]"));
278-
assert_eq!(" [ anon ]", parse_filename("[vdso]"));
279-
assert_eq!(" [ anon ]", parse_filename("anon_inode:i915.gem"));
280-
assert_eq!(" [ stack ]", parse_filename("[stack]"));
281-
assert_eq!(
282-
"ld-linux-x86-64.so.2",
283-
parse_filename("/usr/lib/ld-linux-x86-64.so.2")
284-
);
285-
}
286-
}

0 commit comments

Comments
 (0)