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
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 its
73 // 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 contain
90 // a suitable free spot.
91 return Status == ZX_ERR_NO_MEMORY || Status == ZX_ERR_NO_RESOURCES;
92}
93
94MemMapFuchsia::MemMapFuchsia(uptr Base, uptr Capacity)
95 : MapAddr(Base), WindowBase(Base), WindowSize(Capacity) {
96 // Create the VMO.
97 zx_status_t Status = _zx_vmo_create(Capacity, 0, &Vmo);
98 if (UNLIKELY(Status != ZX_OK))
99 dieOnError(Status, "zx_vmo_create", Capacity);
100}
101
102bool MemMapFuchsia::mapImpl(UNUSED uptr Addr, uptr Size, const char *Name,
103 uptr Flags) {
104 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
105 const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
106 const bool NoAccess = !!(Flags & MAP_NOACCESS);
107
108 // Create the VMO.
109 zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo);
110 if (UNLIKELY(Status != ZX_OK)) {
111 if (!IsNoMemError(Status) || !AllowNoMem)
112 dieOnError(Status, "zx_vmo_create", Size);
113 return false;
114 }
115
116 if (Name != nullptr)
117 setVmoName(Vmo, Name);
118
119 // Map it.
120 zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS;
121 if (!NoAccess)
122 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
123 Status =
124 _zx_vmar_map(_zx_vmar_root_self(), MapFlags, 0, Vmo, 0, Size, &MapAddr);
125 if (UNLIKELY(Status != ZX_OK)) {
126 if (!IsNoMemError(Status) || !AllowNoMem)
127 dieOnError(Status, "zx_vmar_map", Size);
128
129 Status = _zx_handle_close(Vmo);
130 CHECK_EQ(Status, ZX_OK);
131
132 MapAddr = 0;
133 Vmo = ZX_HANDLE_INVALID;
134 return false;
135 }
136
137 if (PreCommit) {
138 Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
139 Size, nullptr, 0);
140 CHECK_EQ(Status, ZX_OK);
141 }
142
143 WindowBase = MapAddr;
144 WindowSize = Size;
145 return true;
146}
147
148void MemMapFuchsia::unmapImpl(uptr Addr, uptr Size) {
149 zx_status_t Status;
150
151 if (Size == WindowSize) {
152 // NOTE: Closing first and then unmapping seems slightly faster than doing
153 // the same operations in the opposite order.
154 Status = _zx_handle_close(Vmo);
155 CHECK_EQ(Status, ZX_OK);
156 Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
157 CHECK_EQ(Status, ZX_OK);
158
159 MapAddr = WindowBase = WindowSize = 0;
160 Vmo = ZX_HANDLE_INVALID;
161 } else {
162 // Unmap the subrange.
163 Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
164 CHECK_EQ(Status, ZX_OK);
165
166 // Decommit the pages that we just unmapped.
167 Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, Addr - MapAddr, Size,
168 nullptr, 0);
169 CHECK_EQ(Status, ZX_OK);
170
171 if (Addr == WindowBase)
172 WindowBase += Size;
173 WindowSize -= Size;
174 }
175}
176
177bool MemMapFuchsia::remapImpl(uptr Addr, uptr Size, const char *Name,
178 uptr Flags) {
179 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
180 const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
181 const bool NoAccess = !!(Flags & MAP_NOACCESS);
182
183 // NOTE: This will rename the *whole* VMO, not only the requested portion of
184 // it. But we cannot do better than this given the MemMap API. In practice,
185 // the upper layers of Scudo always pass the same Name for a given MemMap.
186 if (Name != nullptr)
187 setVmoName(Vmo, Name);
188
189 uptr MappedAddr;
190 zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS | ZX_VM_SPECIFIC_OVERWRITE;
191 if (!NoAccess)
192 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
193 zx_status_t Status =
194 _zx_vmar_map(_zx_vmar_root_self(), MapFlags, Addr - getRootVmarBase(),
195 Vmo, Addr - MapAddr, Size, &MappedAddr);
196 if (UNLIKELY(Status != ZX_OK)) {
197 if (!IsNoMemError(Status) || !AllowNoMem)
198 dieOnError(Status, "zx_vmar_map", Size);
199 return false;
200 }
201 DCHECK_EQ(Addr, MappedAddr);
202
203 if (PreCommit) {
204 Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
205 Size, nullptr, 0);
206 CHECK_EQ(Status, ZX_OK);
207 }
208
209 return true;
210}
211
212void MemMapFuchsia::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) {
213 zx_status_t Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, From - MapAddr,
214 Size, nullptr, 0);
215 CHECK_EQ(Status, ZX_OK);
216}
217
218void MemMapFuchsia::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) {
219 const bool NoAccess = !!(Flags & MAP_NOACCESS);
220
221 zx_vm_option_t MapFlags = 0;
222 if (!NoAccess)
223 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
224 zx_status_t Status =
225 _zx_vmar_protect(_zx_vmar_root_self(), MapFlags, Addr, Size);
226 CHECK_EQ(Status, ZX_OK);
227}
228
229bool ReservedMemoryFuchsia::createImpl(UNUSED uptr Addr, uptr Size,
230 UNUSED const char *Name, uptr Flags) {
231 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
232
233 // Reserve memory by mapping the placeholder VMO without any permission.
234 zx_status_t Status = _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_ALLOW_FAULTS, 0,
235 getPlaceholderVmo(), 0, Size, &Base);
236 if (UNLIKELY(Status != ZX_OK)) {
237 if (!IsNoMemError(Status) || !AllowNoMem)
238 dieOnError(Status, "zx_vmar_map", Size);
239 return false;
240 }
241
242 Capacity = Size;
243 return true;
244}
245
246void ReservedMemoryFuchsia::releaseImpl() {
247 zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), Base, Capacity);
248 CHECK_EQ(Status, ZX_OK);
249}
250
251ReservedMemoryFuchsia::MemMapT ReservedMemoryFuchsia::dispatchImpl(uptr Addr,
252 uptr Size) {
253 return ReservedMemoryFuchsia::MemMapT(Addr, Size);
254}
255
256} // namespace scudo
257
258#endif // SCUDO_FUCHSIA
259

source code of compiler-rt/lib/scudo/standalone/mem_map_fuchsia.cpp