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