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 | 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 | |
32 | static 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. |
40 | static 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. |
60 | static 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. |
88 | static 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 | |
94 | MemMapFuchsia::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 | |
102 | bool 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 | |
148 | void 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 | |
177 | bool 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 | |
212 | void 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 | |
218 | void 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 | |
229 | bool 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 | |
246 | void ReservedMemoryFuchsia::releaseImpl() { |
247 | zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), Base, Capacity); |
248 | CHECK_EQ(Status, ZX_OK); |
249 | } |
250 | |
251 | ReservedMemoryFuchsia::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 | |