procfs: implement /proc/<PID>/maps

Implement the `maps` file which shows a process's VMA entries. Example
output:

```
[root@moss-machine /]# cat /proc/1/maps
500000000000-500000117000 r-xp 0000000000                    /bin/bash
50000012b000-500000130000 r--p 000011b000                    /bin/bash
500000130000-50000013e000 rw-p 0000120000                    /bin/bash
700000000000-70000002b000 r-xp 0000000000                    /lib/ld-linux-aarch64.so.1
70000003e000-700000040000 r--p 000002e000                    /lib/ld-linux-aarch64.so.1
700000040000-700000042000 rw-p 0000030000                    /lib/ld-linux-aarch64.so.1
7fffff510000-7fffff585000 r-xp 0000000000                    /usr/lib/libncursesw.so.6
7fffff585000-7fffff59b000 ---p 0000075000                    /usr/lib/libncursesw.so.6
7fffff59b000-7fffff5a0000 r--p 000007b000                    /usr/lib/libncursesw.so.6
7fffff5a0000-7fffff5a1000 rw-p 0000080000                    /usr/lib/libncursesw.so.6
7fffff5b0000-7fffff760000 r-xp 0000000000                    /usr/lib/libc.so.6
7fffff760000-7fffff76d000 ---p 00001b0000                    /usr/lib/libc.so.6
7fffff76d000-7fffff770000 r--p 00001bd000                    /usr/lib/libc.so.6
7fffff770000-7fffff772000 rw-p 00001c0000                    /usr/lib/libc.so.6
7fffff772000-7fffff779000 rw-p 0000000000
7fffff780000-7fffff7d9000 r-xp 0000000000                    /usr/lib/libreadline.so.8
7fffff7d9000-7fffff7ed000 ---p 0000059000                    /usr/lib/libreadline.so.8
7fffff7ed000-7fffff7f0000 r--p 000005d000                    /usr/lib/libreadline.so.8
7fffff7f0000-7fffff7f6000 rw-p 0000060000                    /usr/lib/libreadline.so.8
7fffff7f6000-7fffff7fb000 rw-p 0000000000
7fffff800000-800000063000 rw-p 0000000000                    [stack]
```
This commit is contained in:
Matthew Leach
2026-01-26 22:01:13 +00:00
parent d386b8ba36
commit 87fe041ba0
8 changed files with 119 additions and 16 deletions

View File

@@ -7,7 +7,7 @@ use crate::{
region::VirtMemoryRegion,
},
};
use alloc::{collections::BTreeMap, vec::Vec};
use alloc::{collections::BTreeMap, string::String, vec::Vec};
const MMAP_BASE: usize = 0x4000_0000_0000;
@@ -85,6 +85,7 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
mut len: usize,
perms: VMAPermissions,
kind: VMAreaKind,
name: String,
) -> Result<VA> {
if len == 0 {
return Err(KernelError::InvalidValue);
@@ -133,7 +134,9 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
// At this point, `start_addr` points to a valid, free region.
// We can now create and insert the new VMA, handling merges.
let new_vma = VMArea::new(region, kind, perms);
let mut new_vma = VMArea::new(region, kind, perms);
new_vma.set_name(name);
self.insert_and_merge(new_vma);
@@ -502,6 +505,10 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
pub fn vma_count(&self) -> usize {
self.vmas.len()
}
pub fn iter_vmas(&self) -> impl Iterator<Item = &VMArea> {
self.vmas.values()
}
}
#[cfg(test)]

View File

@@ -194,6 +194,7 @@ fn test_mmap_any_empty() {
size,
VMAPermissions::rw(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();
@@ -218,6 +219,7 @@ fn test_mmap_any_with_existing() {
size,
VMAPermissions::ro(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();
@@ -231,6 +233,7 @@ fn test_mmap_any_with_existing() {
size,
VMAPermissions::ro(), // different permissions to prevent merge.
VMAreaKind::Anon,
String::new(),
)
.unwrap();
assert_eq!(bottom_addr.value(), existing_addr - size);
@@ -254,6 +257,7 @@ fn test_mmap_hint_free() {
size,
VMAPermissions::rw(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();
@@ -282,6 +286,7 @@ fn test_mmap_hint_taken() {
size,
VMAPermissions::rw(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();
@@ -309,6 +314,7 @@ fn test_mmap_fixed_clobber_complete_overlap() {
3 * PAGE_SIZE,
VMAPermissions::rw(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();
@@ -345,6 +351,7 @@ fn test_mmap_fixed_clobber_partial_end() {
new_size,
VMAPermissions::rw(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();
@@ -378,6 +385,7 @@ fn test_mmap_fixed_clobber_partial_end_spill() {
new_size,
VMAPermissions::rw(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();
@@ -413,6 +421,7 @@ fn test_mmap_fixed_no_clobber_fails() {
new_size,
VMAPermissions::rw(),
VMAreaKind::Anon,
String::new(),
)
.is_err()
);
@@ -438,6 +447,7 @@ fn test_mmap_fixed_clobber_punch_hole() {
new_size,
VMAPermissions::ro(),
VMAreaKind::Anon,
String::new(),
)
.unwrap();

View File

@@ -4,6 +4,7 @@ use crate::{
UserAddressSpace,
error::{KernelError, Result},
};
use alloc::string::ToString;
use memory_map::{AddressRequest, MemoryMap};
use vmarea::{AccessKind, FaultValidation, VMAPermissions, VMArea, VMAreaKind};
@@ -137,6 +138,7 @@ impl<AS: UserAddressSpace> ProcessVM<AS> {
growth_size,
BRK_PERMISSIONS,
VMAreaKind::Anon,
"[heap]".to_string(),
)?;
self.brk = new_brk_region;
@@ -174,6 +176,7 @@ mod tests {
region: VirtMemoryRegion::new(VA::from_value(0x1000), PAGE_SIZE),
kind: VMAreaKind::Anon, // Simplification for test
permissions: VMAPermissions::rx(),
name: String::new(),
};
ProcessVM::from_vma(text_vma).unwrap()
@@ -329,6 +332,7 @@ mod tests {
region: VirtMemoryRegion::new(obstacle_addr, PAGE_SIZE),
kind: VMAreaKind::Anon,
permissions: VMAPermissions::ro(),
name: String::new(),
};
vm.mm.insert_and_merge(obstacle_vma);
assert_eq!(vm.mm.vma_count(), 2);

View File

@@ -12,9 +12,10 @@
use core::cmp;
use crate::{
fs::Inode,
fs::{Inode, InodeId},
memory::{PAGE_MASK, PAGE_SIZE, address::VA, region::VirtMemoryRegion},
};
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use object::{
Endian,
@@ -173,6 +174,7 @@ impl VMAreaKind {
#[derive(Clone, PartialEq)]
pub struct VMArea {
pub region: VirtMemoryRegion,
pub(super) name: String,
pub(super) kind: VMAreaKind,
pub(super) permissions: VMAPermissions,
}
@@ -189,9 +191,14 @@ impl VMArea {
region,
kind,
permissions,
name: String::new(),
}
}
pub fn set_name<S: AsRef<str>>(&mut self, s: S) {
self.name = s.as_ref().to_string();
}
/// Creates a file-backed `VMArea` directly from an ELF program header.
///
/// This is a convenience function used by the ELF loader. It parses the
@@ -246,6 +253,7 @@ impl VMArea {
len: hdr.p_filesz(endian) + mappable_region.offset() as u64,
}),
permissions,
name: String::new(),
}
}
@@ -455,6 +463,24 @@ impl VMArea {
pub fn region(&self) -> VirtMemoryRegion {
self.region
}
pub fn file_offset(&self) -> Option<u64> {
match self.kind {
VMAreaKind::File(ref vmfile_mapping) => Some(vmfile_mapping.offset()),
VMAreaKind::Anon => None,
}
}
pub fn inode_id(&self) -> Option<InodeId> {
match self.kind {
VMAreaKind::File(ref vmfile_mapping) => Some(vmfile_mapping.file().id()),
VMAreaKind::Anon => None,
}
}
pub fn name(&self) -> &str {
&self.name
}
}
#[cfg(test)]

View File

@@ -126,12 +126,18 @@ impl Inode for ProcTaskInode {
FileType::Directory,
7,
));
entries.push(Dirent::new(
"maps".to_string(),
InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "maps"])),
FileType::File,
8,
));
if self.desc.tid().value() == self.desc.tgid().value() && !self.is_task_dir {
entries.push(Dirent::new(
"task".to_string(),
InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "task"])),
FileType::Directory,
8,
9,
));
}

View File

@@ -17,6 +17,7 @@ pub enum TaskFileType {
Root,
State,
Stat,
Maps,
}
impl TryFrom<&str> for TaskFileType {
@@ -30,6 +31,7 @@ impl TryFrom<&str> for TaskFileType {
"stat" => Ok(TaskFileType::Stat),
"cwd" => Ok(TaskFileType::Cwd),
"root" => Ok(TaskFileType::Root),
"maps" => Ok(TaskFileType::Maps),
_ => Err(()),
}
}
@@ -52,6 +54,7 @@ impl ProcTaskFileInode {
TaskFileType::Status
| TaskFileType::Comm
| TaskFileType::State
| TaskFileType::Maps
| TaskFileType::Stat => FileType::File,
TaskFileType::Cwd | TaskFileType::Root => FileType::Symlink,
},
@@ -174,6 +177,26 @@ Threads:\t{tasks}\n",
}
TaskFileType::Cwd => task.cwd.lock_save_irq().clone().1.as_str().to_string(),
TaskFileType::Root => task.root.lock_save_irq().1.as_str().to_string(),
TaskFileType::Maps => {
let mut output = String::new();
let mut vm = task.vm.lock_save_irq();
for vma in vm.mm_mut().iter_vmas() {
output.push_str(&format!(
"{:x}-{:x} {}{}{}{} {:010x} {}\n",
vma.region().start_address().value(),
vma.region().end_address().value(),
if vma.permissions().read { "r" } else { "-" },
if vma.permissions().write { "w" } else { "-" },
if vma.permissions().execute { "x" } else { "-" },
"p", // Don't suport shared mappings... yet!
vma.file_offset().unwrap_or_default(),
vma.name()
));
}
output
}
}
} else {
"State:\tGone\n".to_string()

View File

@@ -1,6 +1,7 @@
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::{process::fd_table::Fd, sched::current::current_task};
use alloc::string::{String, ToString};
use libkernel::{
error::{KernelError, Result},
memory::{
@@ -85,8 +86,8 @@ pub async fn sys_mmap(
let requested_len = len as usize;
let kind = if (flags & (MAP_ANON | MAP_ANONYMOUS)) != 0 {
VMAreaKind::Anon
let (kind, name) = if (flags & (MAP_ANON | MAP_ANONYMOUS)) != 0 {
(VMAreaKind::Anon, String::new())
} else {
// File-backed mapping: require a valid fd and use the provided offset.
let fd = current_task()
@@ -96,8 +97,12 @@ pub async fn sys_mmap(
.ok_or(KernelError::BadFd)?;
let inode = fd.inode().ok_or(KernelError::BadFd)?;
let name = fd
.path()
.map(|x| x.as_str().to_string())
.unwrap_or_default();
VMAreaKind::new_file(inode, offset, len)
(VMAreaKind::new_file(inode, offset, len), name)
};
let address_request = if addr.is_null() {
@@ -125,6 +130,7 @@ pub async fn sys_mmap(
requested_len,
permissions,
kind,
name,
)?;
Ok(new_mapping_addr.value())

View File

@@ -59,13 +59,14 @@ fn process_prog_headers<E: Endian>(
vmas: &mut Vec<VMArea>,
bias: Option<usize>,
elf_file: Arc<dyn Inode>,
path: &Path,
endian: E,
) -> Option<VA> {
let mut hdr_addr = None;
for hdr in hdrs {
if hdr.p_type(endian) == PT_LOAD {
let vma = VMArea::from_pheader(elf_file.clone(), *hdr, endian, bias);
let mut vma = VMArea::from_pheader(elf_file.clone(), *hdr, endian, bias);
// Find PHDR: Assumption segment with p_offset == 0 contains
// headers.
@@ -73,6 +74,8 @@ fn process_prog_headers<E: Endian>(
hdr_addr = Some(vma.region().start_address());
}
vma.set_name(path.as_str());
vmas.push(vma);
}
}
@@ -80,7 +83,12 @@ fn process_prog_headers<E: Endian>(
hdr_addr
}
async fn exec_elf(inode: Arc<dyn Inode>, argv: Vec<String>, envp: Vec<String>) -> Result<()> {
async fn exec_elf(
inode: Arc<dyn Inode>,
path: &Path,
argv: Vec<String>,
envp: Vec<String>,
) -> Result<()> {
// Read ELF header
let mut buf = [0u8; core::mem::size_of::<elf::FileHeader64<LittleEndian>>()];
inode.read_at(0, &mut buf).await?;
@@ -137,7 +145,8 @@ async fn exec_elf(inode: Arc<dyn Inode>, argv: Vec<String>, envp: Vec<String>) -
let mut vmas = Vec::new();
// Process the binary program headers.
if let Some(hdr_addr) = process_prog_headers(hdrs, &mut vmas, main_bias, inode.clone(), endian)
if let Some(hdr_addr) =
process_prog_headers(hdrs, &mut vmas, main_bias, inode.clone(), path, endian)
{
auxv.push(AT_PHDR);
auxv.push(hdr_addr.add_bytes(elf.e_phoff(endian) as _).value() as _);
@@ -160,11 +169,15 @@ async fn exec_elf(inode: Arc<dyn Inode>, argv: Vec<String>, envp: Vec<String>) -
main_entry
};
vmas.push(VMArea::new(
let mut stack_vma = VMArea::new(
VirtMemoryRegion::new(VA::from_value(STACK_START), STACK_SZ),
VMAreaKind::Anon,
VMAPermissions::rw(),
));
);
stack_vma.set_name("[stack]");
vmas.push(stack_vma);
let mut mem_map = MemoryMap::from_vmas(vmas)?;
let stack_ptr = setup_user_stack(&mut mem_map, &argv, &envp, auxv)?;
@@ -231,12 +244,13 @@ async fn exec_script(
new_argv.push(path.as_str().to_string());
new_argv.extend(argv.into_iter().skip(1)); // Skip original argv[0]
// Resolve interpreter inode
let interp_path = Path::new(interp_path);
let task = current_task_shared();
let interp_inode = VFS
.resolve_path(Path::new(interp_path), VFS.root_inode(), &task)
.resolve_path(interp_path, VFS.root_inode(), &task)
.await?;
// Execute interpreter
exec_elf(interp_inode, new_argv, envp).await?;
exec_elf(interp_inode, interp_path, new_argv, envp).await?;
Ok(())
}
@@ -249,7 +263,7 @@ pub async fn kernel_exec(
let mut buf = [0u8; 4];
inode.read_at(0, &mut buf).await?;
if buf == [0x7F, b'E', b'L', b'F'] {
exec_elf(inode, argv, envp).await
exec_elf(inode, path, argv, envp).await
} else if buf.starts_with(b"#!") {
exec_script(path, inode, argv, envp).await
} else {
@@ -394,7 +408,14 @@ async fn process_interp(interp_path: String, vmas: &mut Vec<VMArea>) -> Result<V
.map_err(|_| ExecError::InvalidPHdrFormat)?;
// Build VMAs for interpreter
process_prog_headers(interp_hdrs, vmas, Some(LINKER_BIAS), interp_inode, iendian);
process_prog_headers(
interp_hdrs,
vmas,
Some(LINKER_BIAS),
interp_inode,
path,
iendian,
);
let interp_entry = VA::from_value(LINKER_BIAS + interp_elf.e_entry(iendian) as usize);