diff --git a/core/mmu.cc b/core/mmu.cc
index 70fcb132821f8d4bc599c97572c302a8823fbfae..f57968b4b1665bf8f8314df37bc1105806a04749 100644
--- a/core/mmu.cc
+++ b/core/mmu.cc
@@ -310,32 +310,33 @@ template<typename PageOp> class map_level<PageOp, -1>
 {
 private:
     friend class map_level<PageOp, 0>;
-    map_level(uintptr_t vcur, size_t size, PageOp page_mapper, size_t slop) {}
-    void operator()(hw_ptep parent, uintptr_t base_virt, uintptr_t vstart) {
+    map_level(uintptr_t vma_start, uintptr_t vcur, size_t size, PageOp page_mapper, size_t slop) {}
+    void operator()(hw_ptep parent, uintptr_t base_virt) {
         assert(0);
     }
 };
 
 template<typename PageOp>
-        void map_range(uintptr_t vstart, size_t size, PageOp& page_mapper, size_t slop = page_size)
+        void map_range(uintptr_t vma_start, uintptr_t vstart, size_t size, PageOp& page_mapper, size_t slop = page_size)
 {
-    map_level<PageOp, 4> pt_mapper(vstart, size, page_mapper, slop);
-    pt_mapper(hw_ptep::force(&page_table_root), 0, vstart);
+    map_level<PageOp, 4> pt_mapper(vma_start, vstart, size, page_mapper, slop);
+    pt_mapper(hw_ptep::force(&page_table_root));
 }
 
 template<typename PageOp, int ParentLevel> class map_level {
 private:
+    uintptr_t vma_start;
     uintptr_t vcur;
     uintptr_t vend;
     size_t slop;
     PageOp& page_mapper;
     static constexpr int level = ParentLevel - 1;
 
-    friend void map_range<PageOp>(uintptr_t, size_t, PageOp&, size_t);
+    friend void map_range<PageOp>(uintptr_t, uintptr_t, size_t, PageOp&, size_t);
     friend class map_level<PageOp, ParentLevel + 1>;
 
-    map_level(uintptr_t vcur, size_t size, PageOp& page_mapper, size_t slop) :
-        vcur(vcur), vend(vcur + size - 1), slop(slop), page_mapper(page_mapper) {}
+    map_level(uintptr_t vma_start, uintptr_t vcur, size_t size, PageOp& page_mapper, size_t slop) :
+        vma_start(vma_start), vcur(vcur), vend(vcur + size - 1), slop(slop), page_mapper(page_mapper) {}
     bool skip_pte(hw_ptep ptep) {
         return page_mapper.skip_empty() && ptep.read().empty();
     }
@@ -343,12 +344,12 @@ private:
         return page_mapper.descend() && !ptep.read().empty() && !ptep.read().large();
     }
     void map_range(uintptr_t vcur, size_t size, PageOp& page_mapper, size_t slop,
-            hw_ptep ptep, uintptr_t base_virt, uintptr_t vstart)
+            hw_ptep ptep, uintptr_t base_virt)
     {
-        map_level<PageOp, level> pt_mapper(vcur, size, page_mapper, slop);
-        pt_mapper(ptep, base_virt, vstart);
+        map_level<PageOp, level> pt_mapper(vma_start, vcur, size, page_mapper, slop);
+        pt_mapper(ptep, base_virt);
     }
-    void operator()(hw_ptep parent, uintptr_t base_virt = 0, uintptr_t vstart = 0) {
+    void operator()(hw_ptep parent, uintptr_t base_virt = 0) {
         if (!parent.read().present()) {
             if (!page_mapper.allocate_intermediate()) {
                 return;
@@ -365,7 +366,7 @@ private:
                 split_large_page(parent, ParentLevel);
             } else {
                 // If page_mapper does not want to split, let it handle subpage by itself
-                page_mapper.sub_page(parent, ParentLevel, base_virt - vstart);
+                page_mapper.sub_page(parent, ParentLevel, base_virt - vma_start);
                 return;
             }
         }
@@ -381,11 +382,11 @@ private:
             uintptr_t vstart1 = vcur, vend1 = vend;
             clamp(vstart1, vend1, base_virt, base_virt + step - 1, slop);
             if (unsigned(level) < nr_page_sizes && vstart1 == base_virt && vend1 == base_virt + step - 1) {
-                uintptr_t offset = base_virt - vstart;
+                uintptr_t offset = base_virt - vma_start;
                 if (level) {
                     if (!skip_pte(ptep)) {
                         if (descend(ptep) || !page_mapper.huge_page(ptep, offset)) {
-                            map_range(vstart1, vend1 - vstart1 + 1, page_mapper, slop, ptep, base_virt, vstart);
+                            map_range(vstart1, vend1 - vstart1 + 1, page_mapper, slop, ptep, base_virt);
                             page_mapper.intermediate_page_post(ptep, offset);
                         }
                     }
@@ -395,7 +396,7 @@ private:
                     }
                 }
             } else {
-                map_range(vstart1, vend1 - vstart1 + 1, page_mapper, slop, ptep, base_virt, vstart);
+                map_range(vstart1, vend1 - vstart1 + 1, page_mapper, slop, ptep, base_virt);
             }
             base_virt += step;
             ++idx;
@@ -592,12 +593,12 @@ public:
     }
 };
 
-template<typename T> ulong operate_range(T mapper, void *start, size_t size)
+template<typename T> ulong operate_range(T mapper, void *vma_start, void *start, size_t size)
 {
     start = align_down(start, page_size);
     size = std::max(align_up(size, page_size), page_size);
     uintptr_t virt = reinterpret_cast<uintptr_t>(start);
-    map_range(virt, size, mapper);
+    map_range(reinterpret_cast<uintptr_t>(vma_start), virt, size, mapper);
 
     // TODO: consider if instead of requesting a full TLB flush, we should
     // instead try to make more judicious use of INVLPG - e.g., in
@@ -610,9 +611,9 @@ template<typename T> ulong operate_range(T mapper, void *start, size_t size)
     return mapper.account_results();
 }
 
-template<typename T> ulong operate_range(T mapper, const vma &vma)
+template<typename T> ulong operate_range(T mapper, void *start, size_t size)
 {
-    return operate_range(mapper, (void*)vma.start(), vma.size());
+    return operate_range(mapper, start, start, size);
 }
 
 phys virt_to_phys_pt(void* virt)
@@ -620,7 +621,7 @@ phys virt_to_phys_pt(void* virt)
     auto v = reinterpret_cast<uintptr_t>(virt);
     auto vbase = align_down(v, page_size);
     virt_to_phys_map v2p_mapper(v);
-    map_range(vbase, page_size, v2p_mapper);
+    map_range(vbase, vbase, page_size, v2p_mapper);
     return v2p_mapper.addr();
 }
 
@@ -654,9 +655,9 @@ static error protect(void *addr, size_t size, unsigned int perm)
         i->split(start);
         if (contains(start, end, *i)) {
             i->protect(perm);
+            i->operate_range(protection(perm));
         }
     }
-    operate_range(protection(perm), addr, size);
     return no_error();
 }
 
@@ -700,7 +701,7 @@ ulong evacuate(uintptr_t start, uintptr_t end)
         i->split(start);
         if (contains(start, end, *i)) {
             auto& dead = *i--;
-            auto size = operate_range(unpopulate<account_opt::yes>(), dead);
+            auto size = dead.operate_range(unpopulate<account_opt::yes>());
             ret += size;
             if (dead.has_flags(mmap_jvm_heap)) {
                 memory::stats::on_jvm_heap_free(size);
@@ -826,10 +827,10 @@ void* map_anon(void* addr, size_t size, unsigned flags, unsigned perm)
     if (flags & mmap_populate) {
         if (flags & mmap_uninitialized) {
             fill_anon_page_noinit zfill;
-            operate_range(populate<>(&zfill, perm), v, size);
+            vma->operate_range(populate<>(&zfill, perm), v, size);
         } else {
             fill_anon_page zfill;
-            operate_range(populate<>(&zfill, perm), v, size);
+            vma->operate_range(populate<>(&zfill, perm), v, size);
         }
     }
     return v;
@@ -847,7 +848,7 @@ void* map_file(void* addr, size_t size, unsigned flags, unsigned perm,
     void *v;
     WITH_LOCK(vma_list_mutex) {
         v = (void*) allocate(vma, start, asize, search);
-        operate_range(populate<>(&fill, perm), v, asize);
+        vma->operate_range(populate<>(&fill, perm), v, asize);
     }
     fill.finalize(f.get());
     return v;
@@ -1008,6 +1009,17 @@ bool vma::has_flags(unsigned flag)
     return _flags & flag;
 }
 
+template<typename T> ulong vma::operate_range(T mapper, void *addr, size_t size)
+{
+    return mmu::operate_range(mapper, reinterpret_cast<void*>(start()), addr, size);
+}
+
+template<typename T> ulong vma::operate_range(T mapper)
+{
+    void *addr = reinterpret_cast<void*>(start());
+    return mmu::operate_range(mapper, addr, addr, size());
+}
+
 anon_vma::anon_vma(addr_range range, unsigned perm, unsigned flags)
     : vma(range, perm, flags)
 {
@@ -1119,7 +1131,7 @@ static vma *mark_jvm_heap(void* addr)
         vma& vma = *v;
 
         if (!vma.has_flags(mmap_jvm_heap)) {
-            auto mem = operate_range(count_maps(), vma);
+            auto mem = vma.operate_range(count_maps());
             memory::stats::on_jvm_heap_alloc(mem);
         }
 
@@ -1217,7 +1229,7 @@ void linear_map(void* _virt, phys addr, size_t size, size_t slop)
     slop = std::min(slop, page_size_level(nr_page_sizes - 1));
     assert((virt & (slop - 1)) == (addr & (slop - 1)));
     linear_page_mapper phys_map(addr, size);
-    map_range(virt, size, phys_map, slop);
+    map_range(virt, virt, size, phys_map, slop);
 }
 
 void free_initial_memory_range(uintptr_t addr, size_t size)
diff --git a/include/osv/mmu.hh b/include/osv/mmu.hh
index 708540dfc43c24649218fb90f96c984fb1c52249..530b8f628a932baaa721ca2119606f5b67b1bf8b 100644
--- a/include/osv/mmu.hh
+++ b/include/osv/mmu.hh
@@ -95,6 +95,8 @@ public:
     virtual int validate_perm(unsigned perm) { return 0; }
     void update_flags(unsigned flag);
     bool has_flags(unsigned flag);
+    template<typename T> ulong operate_range(T mapper, void *start, size_t size);
+    template<typename T> ulong operate_range(T mapper);
     class addr_compare;
 protected:
     addr_range _range;