1 //===-- mem_map_fuchsia.cpp -------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "mem_map_fuchsia.h" 10 11 #include "atomic_helpers.h" 12 #include "common.h" 13 #include "string_utils.h" 14 15 #if SCUDO_FUCHSIA 16 17 #include <zircon/process.h> 18 #include <zircon/status.h> 19 #include <zircon/syscalls.h> 20 21 namespace scudo { 22 23 static void NORETURN dieOnError(zx_status_t Status, const char *FnName, 24 uptr Size) { 25 char Error[128]; 26 formatString(Error, sizeof(Error), 27 "SCUDO ERROR: %s failed with size %zuKB (%s)", FnName, 28 Size >> 10, _zx_status_get_string(Status)); 29 outputRaw(Error); 30 die(); 31 } 32 33 static void setVmoName(zx_handle_t Vmo, const char *Name) { 34 size_t Len = strlen(Name); 35 DCHECK_LT(Len, ZX_MAX_NAME_LEN); 36 zx_status_t Status = _zx_object_set_property(Vmo, ZX_PROP_NAME, Name, Len); 37 CHECK_EQ(Status, ZX_OK); 38 } 39 40 // Returns the (cached) base address of the root VMAR. 41 static uptr getRootVmarBase() { 42 static atomic_uptr CachedResult = {0}; 43 44 uptr Result = atomic_load(&CachedResult, memory_order_acquire); 45 if (UNLIKELY(!Result)) { 46 zx_info_vmar_t VmarInfo; 47 zx_status_t Status = 48 _zx_object_get_info(_zx_vmar_root_self(), ZX_INFO_VMAR, &VmarInfo, 49 sizeof(VmarInfo), nullptr, nullptr); 50 CHECK_EQ(Status, ZX_OK); 51 CHECK_NE(VmarInfo.base, 0); 52 53 atomic_store(&CachedResult, VmarInfo.base, memory_order_release); 54 Result = VmarInfo.base; 55 } 56 57 return Result; 58 } 59 60 // Lazily creates and then always returns the same zero-sized VMO. 61 static zx_handle_t getPlaceholderVmo() { 62 static atomic_u32 StoredVmo = {ZX_HANDLE_INVALID}; 63 64 zx_handle_t Vmo = atomic_load(&StoredVmo, memory_order_acquire); 65 if (UNLIKELY(Vmo == ZX_HANDLE_INVALID)) { 66 // Create a zero-sized placeholder VMO. 67 zx_status_t Status = _zx_vmo_create(0, 0, &Vmo); 68 if (UNLIKELY(Status != ZX_OK)) 69 dieOnError(Status, "zx_vmo_create", 0); 70 71 setVmoName(Vmo, "scudo:reserved"); 72 73 // Atomically store its handle. If some other thread wins the race, use its 74 // handle and discard ours. 75 zx_handle_t OldValue = atomic_compare_exchange_strong( 76 &StoredVmo, ZX_HANDLE_INVALID, Vmo, memory_order_acq_rel); 77 if (UNLIKELY(OldValue != ZX_HANDLE_INVALID)) { 78 Status = _zx_handle_close(Vmo); 79 CHECK_EQ(Status, ZX_OK); 80 81 Vmo = OldValue; 82 } 83 } 84 85 return Vmo; 86 } 87 88 MemMapFuchsia::MemMapFuchsia(uptr Base, uptr Capacity) 89 : MapAddr(Base), WindowBase(Base), WindowSize(Capacity) { 90 // Create the VMO. 91 zx_status_t Status = _zx_vmo_create(Capacity, 0, &Vmo); 92 if (UNLIKELY(Status != ZX_OK)) 93 dieOnError(Status, "zx_vmo_create", Capacity); 94 } 95 96 bool MemMapFuchsia::mapImpl(UNUSED uptr Addr, uptr Size, const char *Name, 97 uptr Flags) { 98 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM); 99 const bool PreCommit = !!(Flags & MAP_PRECOMMIT); 100 const bool NoAccess = !!(Flags & MAP_NOACCESS); 101 102 // Create the VMO. 103 zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo); 104 if (UNLIKELY(Status != ZX_OK)) { 105 if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) 106 dieOnError(Status, "zx_vmo_create", Size); 107 return false; 108 } 109 110 if (Name != nullptr) 111 setVmoName(Vmo, Name); 112 113 // Map it. 114 zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS; 115 if (!NoAccess) 116 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; 117 Status = 118 _zx_vmar_map(_zx_vmar_root_self(), MapFlags, 0, Vmo, 0, Size, &MapAddr); 119 if (UNLIKELY(Status != ZX_OK)) { 120 if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) 121 dieOnError(Status, "zx_vmar_map", Size); 122 123 Status = _zx_handle_close(Vmo); 124 CHECK_EQ(Status, ZX_OK); 125 126 MapAddr = 0; 127 Vmo = ZX_HANDLE_INVALID; 128 return false; 129 } 130 131 if (PreCommit) { 132 Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr, 133 Size, nullptr, 0); 134 CHECK_EQ(Status, ZX_OK); 135 } 136 137 WindowBase = MapAddr; 138 WindowSize = Size; 139 return true; 140 } 141 142 void MemMapFuchsia::unmapImpl(uptr Addr, uptr Size) { 143 zx_status_t Status; 144 145 if (Size == WindowSize) { 146 // NOTE: Closing first and then unmapping seems slightly faster than doing 147 // the same operations in the opposite order. 148 Status = _zx_handle_close(Vmo); 149 CHECK_EQ(Status, ZX_OK); 150 Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size); 151 CHECK_EQ(Status, ZX_OK); 152 153 MapAddr = WindowBase = WindowSize = 0; 154 Vmo = ZX_HANDLE_INVALID; 155 } else { 156 // Unmap the subrange. 157 Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size); 158 CHECK_EQ(Status, ZX_OK); 159 160 // Decommit the pages that we just unmapped. 161 Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, Addr - MapAddr, Size, 162 nullptr, 0); 163 CHECK_EQ(Status, ZX_OK); 164 165 if (Addr == WindowBase) 166 WindowBase += Size; 167 WindowSize -= Size; 168 } 169 } 170 171 bool MemMapFuchsia::remapImpl(uptr Addr, uptr Size, const char *Name, 172 uptr Flags) { 173 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM); 174 const bool PreCommit = !!(Flags & MAP_PRECOMMIT); 175 const bool NoAccess = !!(Flags & MAP_NOACCESS); 176 177 // NOTE: This will rename the *whole* VMO, not only the requested portion of 178 // it. But we cannot do better than this given the MemMap API. In practice, 179 // the upper layers of Scudo always pass the same Name for a given MemMap. 180 if (Name != nullptr) 181 setVmoName(Vmo, Name); 182 183 uptr MappedAddr; 184 zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS | ZX_VM_SPECIFIC_OVERWRITE; 185 if (!NoAccess) 186 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; 187 zx_status_t Status = 188 _zx_vmar_map(_zx_vmar_root_self(), MapFlags, Addr - getRootVmarBase(), 189 Vmo, Addr - MapAddr, Size, &MappedAddr); 190 if (UNLIKELY(Status != ZX_OK)) { 191 if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) 192 dieOnError(Status, "zx_vmar_map", Size); 193 return false; 194 } 195 DCHECK_EQ(Addr, MappedAddr); 196 197 if (PreCommit) { 198 Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr, 199 Size, nullptr, 0); 200 CHECK_EQ(Status, ZX_OK); 201 } 202 203 return true; 204 } 205 206 void MemMapFuchsia::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) { 207 zx_status_t Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, From - MapAddr, 208 Size, nullptr, 0); 209 CHECK_EQ(Status, ZX_OK); 210 } 211 212 void MemMapFuchsia::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) { 213 const bool NoAccess = !!(Flags & MAP_NOACCESS); 214 215 zx_vm_option_t MapFlags = 0; 216 if (!NoAccess) 217 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; 218 zx_status_t Status = 219 _zx_vmar_protect(_zx_vmar_root_self(), MapFlags, Addr, Size); 220 CHECK_EQ(Status, ZX_OK); 221 } 222 223 bool ReservedMemoryFuchsia::createImpl(UNUSED uptr Addr, uptr Size, 224 UNUSED const char *Name, uptr Flags) { 225 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM); 226 227 // Reserve memory by mapping the placeholder VMO without any permission. 228 zx_status_t Status = _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_ALLOW_FAULTS, 0, 229 getPlaceholderVmo(), 0, Size, &Base); 230 if (UNLIKELY(Status != ZX_OK)) { 231 if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) 232 dieOnError(Status, "zx_vmar_map", Size); 233 return false; 234 } 235 236 Capacity = Size; 237 return true; 238 } 239 240 void ReservedMemoryFuchsia::releaseImpl() { 241 zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), Base, Capacity); 242 CHECK_EQ(Status, ZX_OK); 243 } 244 245 ReservedMemoryFuchsia::MemMapT ReservedMemoryFuchsia::dispatchImpl(uptr Addr, 246 uptr Size) { 247 return ReservedMemoryFuchsia::MemMapT(Addr, Size); 248 } 249 250 } // namespace scudo 251 252 #endif // SCUDO_FUCHSIA 253