|
9 | 9 | //! and initialized data from files, most notably ELF binaries. |
10 | 10 | //! - Anonymous (via [`VMAreaKind::Anon`]): Used for demand-zeroed memory like |
11 | 11 | //! the process stack, heap, and BSS sections. |
| 12 | +use core::cmp; |
| 13 | + |
12 | 14 | use crate::{ |
13 | 15 | fs::Inode, |
14 | 16 | memory::{PAGE_MASK, PAGE_SIZE, address::VA, region::VirtMemoryRegion}, |
@@ -408,6 +410,42 @@ impl VMArea { |
408 | 410 | pub fn is_file_backed(&self) -> bool { |
409 | 411 | matches!(self.kind, VMAreaKind::File(_)) |
410 | 412 | } |
| 413 | + |
| 414 | + /// Shrink this VMA's region to `new_region`, recalculating file offsets, |
| 415 | + /// for file mappings. |
| 416 | + #[must_use] |
| 417 | + pub(crate) fn shrink_to(&self, new_region: VirtMemoryRegion) -> Self { |
| 418 | + debug_assert!(self.region.contains(new_region)); |
| 419 | + |
| 420 | + let mut new_vma = self.clone_with_new_region(new_region); |
| 421 | + |
| 422 | + match self.kind { |
| 423 | + VMAreaKind::File(ref vmfile_mapping) => { |
| 424 | + let start_offset = |
| 425 | + new_region.start_address().value() - self.region.start_address().value(); |
| 426 | + |
| 427 | + let new_sz = cmp::min( |
| 428 | + vmfile_mapping.len.saturating_sub(start_offset as u64), |
| 429 | + new_region.size() as u64, |
| 430 | + ); |
| 431 | + |
| 432 | + if new_sz == 0 { |
| 433 | + // convert this VMA into an anonymous VMA, since we've |
| 434 | + // shrunk past the file mapping. |
| 435 | + new_vma.kind = VMAreaKind::Anon; |
| 436 | + } else { |
| 437 | + new_vma.kind = VMAreaKind::File(VMFileMapping { |
| 438 | + file: vmfile_mapping.file.clone(), |
| 439 | + offset: vmfile_mapping.offset + start_offset as u64, |
| 440 | + len: new_sz, |
| 441 | + }); |
| 442 | + } |
| 443 | + |
| 444 | + new_vma |
| 445 | + } |
| 446 | + VMAreaKind::Anon => new_vma, |
| 447 | + } |
| 448 | + } |
411 | 449 | } |
412 | 450 |
|
413 | 451 | #[cfg(test)] |
@@ -569,4 +607,149 @@ pub mod tests { |
569 | 607 | let result = vma.resolve_fault(fault_addr); |
570 | 608 | assert!(result.is_none(), "Anonymous VMA fault should return None"); |
571 | 609 | } |
| 610 | + |
| 611 | + #[test] |
| 612 | + fn shrink_anonymous_vma() { |
| 613 | + // Easy case: Standard shrinking of an anonymous region |
| 614 | + let vma = VMArea::new( |
| 615 | + VirtMemoryRegion::new(VA::from_value(0x5000), 0x4000), |
| 616 | + VMAreaKind::Anon, |
| 617 | + VMAPermissions::rw(), |
| 618 | + ); |
| 619 | + |
| 620 | + // Shrink to the middle. |
| 621 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x6000), 0x1000); |
| 622 | + let result = vma.shrink_to(new_region); |
| 623 | + |
| 624 | + assert_eq!(result.region, new_region); |
| 625 | + assert!(matches!(result.kind, VMAreaKind::Anon)); |
| 626 | + } |
| 627 | + |
| 628 | + #[test] |
| 629 | + fn shrink_file_vma_from_front() { |
| 630 | + // Scenario: [ File (0x4000) ] |
| 631 | + // Cut: xx[ File (0x1000) ] |
| 632 | + // Expect: Offset increases, Length decreases |
| 633 | + |
| 634 | + let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x4000); |
| 635 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x4000), 0x1000); |
| 636 | + |
| 637 | + let result = vma.shrink_to(new_region); |
| 638 | + |
| 639 | + assert_eq!(result.region, new_region); |
| 640 | + match result.kind { |
| 641 | + VMAreaKind::File(vmfile_mapping) => { |
| 642 | + assert_eq!(vmfile_mapping.len, 0x1000); |
| 643 | + assert_eq!(vmfile_mapping.offset, 0x3000); |
| 644 | + } |
| 645 | + _ => panic!("Expected File VMA"), |
| 646 | + } |
| 647 | + } |
| 648 | + |
| 649 | + #[test] |
| 650 | + fn shrink_file_vma_from_end() { |
| 651 | + // Scenario: [ File (0x4000) ] |
| 652 | + // Cut: [ File (0x2000) ]xx |
| 653 | + |
| 654 | + let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x4000); |
| 655 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x1000), 0x2000); |
| 656 | + |
| 657 | + let result = vma.shrink_to(new_region); |
| 658 | + |
| 659 | + assert_eq!(result.region, new_region); |
| 660 | + |
| 661 | + match result.kind { |
| 662 | + VMAreaKind::File(vmfile_mapping) => { |
| 663 | + // Offset shouldn't change |
| 664 | + assert_eq!(vmfile_mapping.offset, 0x0); |
| 665 | + assert_eq!(vmfile_mapping.len, 0x2000); |
| 666 | + } |
| 667 | + _ => panic!("Expected File VMA"), |
| 668 | + } |
| 669 | + } |
| 670 | + |
| 671 | + #[test] |
| 672 | + fn shrink_file_vma_both_sides() { |
| 673 | + // Scenario: [ File (0x4000) ] |
| 674 | + // Cut: xx[ File (0x1000) ]xx |
| 675 | + let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x4000); |
| 676 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x2000), 0x1000); |
| 677 | + |
| 678 | + let result = vma.shrink_to(new_region); |
| 679 | + |
| 680 | + match result.kind { |
| 681 | + VMAreaKind::File(vmfile_mapping) => { |
| 682 | + assert_eq!(vmfile_mapping.offset, 0x1000); |
| 683 | + assert_eq!(vmfile_mapping.len, 0x1000); |
| 684 | + } |
| 685 | + _ => panic!("Expected File VMA"), |
| 686 | + } |
| 687 | + } |
| 688 | + |
| 689 | + #[test] |
| 690 | + fn shrink_mixed_vma_past_file_boundary_becomes_anon() { |
| 691 | + // Scenario: [ File (0x2000) | Anon (0x2000) ] |
| 692 | + // Cut front by 0x3000. xx[ Anon (0x1000) ] |
| 693 | + |
| 694 | + let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000); |
| 695 | + |
| 696 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x4000), 0x1000); |
| 697 | + |
| 698 | + let result = vma.shrink_to(new_region); |
| 699 | + |
| 700 | + assert_eq!(result.region, new_region); |
| 701 | + assert!( |
| 702 | + matches!(result.kind, VMAreaKind::Anon), |
| 703 | + "Should have converted to Anon because we skipped the file part" |
| 704 | + ); |
| 705 | + } |
| 706 | + |
| 707 | + #[test] |
| 708 | + fn shrink_mixed_vma_keep_file_part() { |
| 709 | + // Scenario: [ File (0x2000) | Anon (0x2000) ] |
| 710 | + // cut: xx[ File(0x1000)| Anon (0x2000) ] |
| 711 | + let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000); |
| 712 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x2000), 0x3000); |
| 713 | + |
| 714 | + let result = vma.shrink_to(new_region); |
| 715 | + |
| 716 | + match result.kind { |
| 717 | + VMAreaKind::File(mapping) => { |
| 718 | + assert_eq!(mapping.offset, 0x1000); |
| 719 | + assert_eq!(mapping.len, 0x1000); |
| 720 | + } |
| 721 | + _ => panic!("Expected File VMA"), |
| 722 | + } |
| 723 | + } |
| 724 | + |
| 725 | + #[test] |
| 726 | + fn shrink_mixed_vma_from_end_remove_anon_part() { |
| 727 | + // Scenario: [ File (0x2000) | Anon (0x2000) ] |
| 728 | + // Cut: [ File (0x2000) ]xx |
| 729 | + let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000); |
| 730 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x1000), 0x2000); |
| 731 | + |
| 732 | + let result = vma.shrink_to(new_region); |
| 733 | + |
| 734 | + match result.kind { |
| 735 | + VMAreaKind::File(mapping) => { |
| 736 | + assert_eq!(mapping.offset, 0x0); |
| 737 | + assert_eq!(mapping.len, 0x2000); |
| 738 | + } |
| 739 | + _ => panic!("Expected File VMA"), |
| 740 | + } |
| 741 | + } |
| 742 | + |
| 743 | + #[test] |
| 744 | + fn shrink_mixed_vma_exact_boundary() { |
| 745 | + // Scenario: [ File (0x2000) | Anon (0x2000) ] |
| 746 | + // Cut: xxx[ Anon (0x2000) ] |
| 747 | + |
| 748 | + let vma = create_test_vma(0x1000, 0x4000, 0x0, 0x2000); |
| 749 | + let new_region = VirtMemoryRegion::new(VA::from_value(0x3000), 0x1000); |
| 750 | + |
| 751 | + let result = vma.shrink_to(new_region); |
| 752 | + |
| 753 | + assert!(matches!(result.kind, VMAreaKind::Anon)); |
| 754 | + } |
572 | 755 | } |
0 commit comments