From 2c3f61e782e32db1ac95a18b13b10644236dbf90 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 18 Sep 2024 16:08:25 +0200 Subject: [PATCH] pmap: move parsing to maps_format_parser.rs --- src/uu/pmap/src/maps_format_parser.rs | 155 ++++++++++++++++++++++++++ src/uu/pmap/src/pmap.rs | 151 ++----------------------- 2 files changed, 166 insertions(+), 140 deletions(-) create mode 100644 src/uu/pmap/src/maps_format_parser.rs diff --git a/src/uu/pmap/src/maps_format_parser.rs b/src/uu/pmap/src/maps_format_parser.rs new file mode 100644 index 0000000..815cf4e --- /dev/null +++ b/src/uu/pmap/src/maps_format_parser.rs @@ -0,0 +1,155 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +// Represents a parsed single line from /proc//maps. +#[derive(Debug, PartialEq)] +pub struct MapLine { + pub address: String, + pub size_in_kb: u64, + pub perms: String, + pub mapping: String, +} + +// Parses a single line from /proc//maps. It assumes the format of `line` is correct (see +// https://www.kernel.org/doc/html/latest/filesystems/proc.html for details). +pub fn parse_map_line(line: &str) -> MapLine { + let (memory_range, rest) = line.split_once(' ').expect("line should contain ' '"); + let (address, size_in_kb) = parse_address(memory_range); + + let (perms, rest) = rest.split_once(' ').expect("line should contain 2nd ' '"); + let perms = parse_perms(perms); + + let mapping: String = rest.splitn(4, ' ').skip(3).collect(); + let mapping = mapping.trim_ascii_start(); + let mapping = parse_mapping(mapping); + + MapLine { + address, + size_in_kb, + perms, + mapping, + } +} + +// Returns the start address and the size of the provided memory range. The start address is always +// 16-digits and padded with 0, if necessary. The size is in KB. +fn parse_address(memory_range: &str) -> (String, u64) { + let (start, end) = memory_range + .split_once('-') + .expect("memory range should contain '-'"); + + let low = u64::from_str_radix(start, 16).expect("should be a hex value"); + let high = u64::from_str_radix(end, 16).expect("should be a hex value"); + let size_in_kb = (high - low) / 1024; + + (format!("{start:0>16}"), size_in_kb) +} + +// Turns a 4-char perms string from /proc//maps into a 5-char perms string. The first three +// chars are left untouched. +fn parse_perms(perms: &str) -> String { + let perms = perms.replace("p", "-"); + + // the fifth char seems to be always '-' in the original pmap + format!("{perms}-") +} + +fn parse_mapping(mapping: &str) -> String { + if mapping == "[stack]" { + return " [ stack ]".into(); + } + + if mapping.is_empty() || mapping.starts_with('[') || mapping.starts_with("anon") { + return " [ anon ]".into(); + } + + match mapping.rsplit_once('/') { + Some((_, name)) => name.into(), + None => mapping.into(), + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn create_map_line(address: &str, size_in_kb: u64, perms: &str, mapping: &str) -> MapLine { + MapLine { + address: address.to_string(), + size_in_kb, + perms: perms.to_string(), + mapping: mapping.to_string(), + } + } + + #[test] + fn test_parse_map_line() { + let data = [ + ( + create_map_line("000062442eb9e000", 16, "r----", "konsole"), + "62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole" + ), + ( + create_map_line("000071af50000000", 132, "rw---", " [ anon ]"), + "71af50000000-71af50021000 rw-p 00000000 00:00 0 " + ), + ( + create_map_line("00007ffc3f8df000", 132, "rw---", " [ stack ]"), + "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]" + ), + ( + create_map_line("000071af8c9e6000", 16, "rw-s-", " [ anon ]"), + "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem" + ), + ( + create_map_line("000071af6cf0c000", 3560, "rw-s-", "memfd:wayland-shm (deleted)"), + "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)" + ), + ( + create_map_line("ffffffffff600000", 4, "--x--", " [ anon ]"), + "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]" + ), + ( + create_map_line("00005e8187da8000", 24, "r----", "hello world"), + "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world" + ), + ]; + + for (expected_map_line, line) in data { + assert_eq!(expected_map_line, parse_map_line(line)); + } + } + + #[test] + fn test_parse_address() { + let (start, size) = parse_address("ffffffffff600000-ffffffffff601000"); + assert_eq!(start, "ffffffffff600000"); + assert_eq!(size, 4); + + let (start, size) = parse_address("7ffc4f0c2000-7ffc4f0e3000"); + assert_eq!(start, "00007ffc4f0c2000"); + assert_eq!(size, 132); + } + + #[test] + fn test_parse_perms() { + assert_eq!("-----", parse_perms("---p")); + assert_eq!("---s-", parse_perms("---s")); + assert_eq!("rwx--", parse_perms("rwxp")); + } + + #[test] + fn test_parse_mapping() { + assert_eq!(" [ anon ]", parse_mapping("")); + assert_eq!(" [ anon ]", parse_mapping("[vvar]")); + assert_eq!(" [ anon ]", parse_mapping("[vdso]")); + assert_eq!(" [ anon ]", parse_mapping("anon_inode:i915.gem")); + assert_eq!(" [ stack ]", parse_mapping("[stack]")); + assert_eq!( + "ld-linux-x86-64.so.2", + parse_mapping("/usr/lib/ld-linux-x86-64.so.2") + ); + } +} diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs index 434d3b2..3c2d526 100644 --- a/src/uu/pmap/src/pmap.rs +++ b/src/uu/pmap/src/pmap.rs @@ -4,12 +4,15 @@ // file that was distributed with this source code. use clap::{crate_version, Arg, ArgAction, Command}; +use maps_format_parser::parse_map_line; use std::env; use std::fs; use std::io::Error; use uucore::error::{set_exit_code, UResult}; use uucore::{format_usage, help_about, help_usage}; +mod maps_format_parser; + const ABOUT: &str = help_about!("pmap.md"); const USAGE: &str = help_usage!("pmap.md"); @@ -41,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } fn parse_cmdline(pid: &str) -> Result { - let path = format!("/proc/{}/cmdline", pid); + let path = format!("/proc/{pid}/cmdline"); let contents = fs::read(path)?; // Command line arguments are separated by null bytes. // Replace them with spaces for display. @@ -55,78 +58,22 @@ fn parse_cmdline(pid: &str) -> Result { } fn parse_maps(pid: &str) -> Result { - let path = format!("/proc/{}/maps", pid); + let path = format!("/proc/{pid}/maps"); let contents = fs::read_to_string(path)?; let mut total = 0; for line in contents.lines() { - let (generated_line, size) = parse_map_line(line); - println!("{generated_line}"); - total += size; + let map_line = parse_map_line(line); + println!( + "{} {:>6}K {} {}", + map_line.address, map_line.size_in_kb, map_line.perms, map_line.mapping + ); + total += map_line.size_in_kb; } Ok(total) } -// Parses a single line from /proc//maps. -fn parse_map_line(line: &str) -> (String, u64) { - let (memory_range, rest) = line.split_once(' ').expect("line should contain ' '"); - let (start_address, size_in_kb) = parse_memory_range(memory_range); - - let (perms, rest) = rest.split_once(' ').expect("line should contain 2nd ' '"); - let perms = parse_perms(perms); - - let filename: String = rest.splitn(4, ' ').skip(3).collect(); - let filename = filename.trim_ascii_start(); - let filename = parse_filename(filename); - - ( - format!("{start_address} {size_in_kb:>6}K {perms} {filename}"), - size_in_kb, - ) -} - -// Returns the start address and the size of the provided memory range. The start address is always -// 16-digits and padded with 0, if necessary. The size is in KB. -// -// This function assumes the provided `memory_range` comes from /proc//maps and thus its -// format is correct. -fn parse_memory_range(memory_range: &str) -> (String, u64) { - let (start, end) = memory_range - .split_once('-') - .expect("memory range should contain '-'"); - - let low = u64::from_str_radix(start, 16).expect("should be a hex value"); - let high = u64::from_str_radix(end, 16).expect("should be a hex value"); - let size_in_kb = (high - low) / 1024; - - (format!("{start:0>16}"), size_in_kb) -} - -// Turns a 4-char perms string from /proc//maps into a 5-char perms string. The first three -// chars are left untouched. -fn parse_perms(perms: &str) -> String { - let perms = perms.replace("p", "-"); - - // the fifth char seems to be always '-' in the original pmap - format!("{perms}-") -} - -fn parse_filename(filename: &str) -> String { - if filename == "[stack]" { - return " [ stack ]".into(); - } - - if filename.is_empty() || filename.starts_with('[') || filename.starts_with("anon") { - return " [ anon ]".into(); - } - - match filename.rsplit_once('/') { - Some((_, name)) => name.into(), - None => filename.into(), - } -} - pub fn uu_app() -> Command { Command::new(env!("CARGO_PKG_NAME")) .version(crate_version!()) @@ -208,79 +155,3 @@ pub fn uu_app() -> Command { .help("limit results to the given range"), ) } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_parse_map_line() { - let data = [ - ( - ("000062442eb9e000 16K r---- konsole", 16), - "62442eb9e000-62442eba2000 r--p 00000000 08:08 10813151 /usr/bin/konsole" - ), - ( - ("000071af50000000 132K rw--- [ anon ]", 132), - "71af50000000-71af50021000 rw-p 00000000 00:00 0 " - ), - ( - ("00007ffc3f8df000 132K rw--- [ stack ]", 132), - "7ffc3f8df000-7ffc3f900000 rw-p 00000000 00:00 0 [stack]" - ), - ( - ("000071af8c9e6000 16K rw-s- [ anon ]", 16), - "71af8c9e6000-71af8c9ea000 rw-s 105830000 00:10 1075 anon_inode:i915.gem" - ), - ( - ("000071af6cf0c000 3560K rw-s- memfd:wayland-shm (deleted)", 3560), - "71af6cf0c000-71af6d286000 rw-s 00000000 00:01 256481 /memfd:wayland-shm (deleted)" - ), - ( - ("ffffffffff600000 4K --x-- [ anon ]", 4), - "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]" - ), - ( - ("00005e8187da8000 24K r---- hello world", 24), - "5e8187da8000-5e8187dae000 r--p 00000000 08:08 9524160 /usr/bin/hello world" - ), - ]; - - for ((expected_line, expected_size), line) in data { - let (generated_line, size) = parse_map_line(line); - assert_eq!(expected_line, generated_line); - assert_eq!(expected_size, size); - } - } - - #[test] - fn test_parse_memory_range() { - let (start, size) = parse_memory_range("ffffffffff600000-ffffffffff601000"); - assert_eq!(start, "ffffffffff600000"); - assert_eq!(size, 4); - - let (start, size) = parse_memory_range("7ffc4f0c2000-7ffc4f0e3000"); - assert_eq!(start, "00007ffc4f0c2000"); - assert_eq!(size, 132); - } - - #[test] - fn test_parse_perms() { - assert_eq!("-----", parse_perms("---p")); - assert_eq!("---s-", parse_perms("---s")); - assert_eq!("rwx--", parse_perms("rwxp")); - } - - #[test] - fn test_parse_filename() { - assert_eq!(" [ anon ]", parse_filename("")); - assert_eq!(" [ anon ]", parse_filename("[vvar]")); - assert_eq!(" [ anon ]", parse_filename("[vdso]")); - assert_eq!(" [ anon ]", parse_filename("anon_inode:i915.gem")); - assert_eq!(" [ stack ]", parse_filename("[stack]")); - assert_eq!( - "ld-linux-x86-64.so.2", - parse_filename("/usr/lib/ld-linux-x86-64.so.2") - ); - } -}