Merge pull request #227 from cakebaker/pmap_introduce_maps_format_parser

pmap: move parsing to `maps_format_parser.rs`
This commit is contained in:
Sylvestre Ledru
2024-10-06 21:01:33 +02:00
committed by GitHub
2 changed files with 166 additions and 140 deletions
+155
View File
@@ -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/<PID>/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/<PID>/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/<PID>/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")
);
}
}
+11 -140
View File
@@ -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<String, Error> {
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<String, Error> {
}
fn parse_maps(pid: &str) -> Result<u64, Error> {
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/<PID>/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/<PID>/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/<PID>/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")
);
}
}