1 //===-- mem_map_fuchsia.cpp -------------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "mem_map_fuchsia.h"
11 #include "atomic_helpers.h"
13 #include "string_utils.h"
17 #include <zircon/process.h>
18 #include <zircon/status.h>
19 #include <zircon/syscalls.h>
23 static void NORETURN
dieOnError(zx_status_t Status
, const char *FnName
,
26 Error
.append("SCUDO ERROR: %s failed with size %zuKB (%s)", FnName
,
27 Size
>> 10, _zx_status_get_string(Status
));
28 outputRaw(Error
.data());
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
);
39 // Returns the (cached) base address of the root VMAR.
40 static uptr
getRootVmarBase() {
41 static atomic_uptr CachedResult
= {0};
43 uptr Result
= atomic_load(&CachedResult
, memory_order_acquire
);
44 if (UNLIKELY(!Result
)) {
45 zx_info_vmar_t VmarInfo
;
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);
52 atomic_store(&CachedResult
, VmarInfo
.base
, memory_order_release
);
53 Result
= VmarInfo
.base
;
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
};
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);
70 setVmoName(Vmo
, "scudo:reserved");
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
);
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
;
94 // Note: this constructor is only called by ReservedMemoryFuchsia::dispatch.
95 MemMapFuchsia::MemMapFuchsia(uptr Base
, uptr Capacity
)
96 : MapAddr(Base
), WindowBase(Base
), WindowSize(Capacity
) {
98 zx_status_t Status
= _zx_vmo_create(Capacity
, 0, &Vmo
);
99 if (UNLIKELY(Status
!= ZX_OK
))
100 dieOnError(Status
, "zx_vmo_create", Capacity
);
102 setVmoName(Vmo
, "scudo:dispatched");
105 bool MemMapFuchsia::mapImpl(UNUSED uptr Addr
, uptr Size
, const char *Name
,
107 const bool AllowNoMem
= !!(Flags
& MAP_ALLOWNOMEM
);
108 const bool PreCommit
= !!(Flags
& MAP_PRECOMMIT
);
109 const bool NoAccess
= !!(Flags
& MAP_NOACCESS
);
112 zx_status_t Status
= _zx_vmo_create(Size
, 0, &Vmo
);
113 if (UNLIKELY(Status
!= ZX_OK
)) {
114 if (AllowNoMem
&& IsNoMemError(Status
))
116 dieOnError(Status
, "zx_vmo_create", Size
);
120 setVmoName(Vmo
, Name
);
123 zx_vm_option_t MapFlags
= ZX_VM_ALLOW_FAULTS
;
125 MapFlags
|= ZX_VM_PERM_READ
| ZX_VM_PERM_WRITE
;
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
);
134 Vmo
= ZX_HANDLE_INVALID
;
137 dieOnError(Status
, "zx_vmar_map", Size
);
141 Status
= _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT
, MapAddr
,
143 CHECK_EQ(Status
, ZX_OK
);
146 WindowBase
= MapAddr
;
151 void MemMapFuchsia::unmapImpl(uptr Addr
, uptr Size
) {
154 if (Size
== WindowSize
) {
155 // NOTE: Closing first and then unmapping seems slightly faster than doing
156 // 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
);
162 MapAddr
= WindowBase
= WindowSize
= 0;
163 Vmo
= ZX_HANDLE_INVALID
;
165 // Unmap the subrange.
166 Status
= _zx_vmar_unmap(_zx_vmar_root_self(), Addr
, Size
);
167 CHECK_EQ(Status
, ZX_OK
);
169 // Decommit the pages that we just unmapped.
170 Status
= _zx_vmo_op_range(Vmo
, ZX_VMO_OP_DECOMMIT
, Addr
- MapAddr
, Size
,
172 CHECK_EQ(Status
, ZX_OK
);
174 if (Addr
== WindowBase
)
180 bool MemMapFuchsia::remapImpl(uptr Addr
, uptr Size
, const char *Name
,
182 const bool AllowNoMem
= !!(Flags
& MAP_ALLOWNOMEM
);
183 const bool PreCommit
= !!(Flags
& MAP_PRECOMMIT
);
184 const bool NoAccess
= !!(Flags
& MAP_NOACCESS
);
186 // NOTE: This will rename the *whole* VMO, not only the requested portion of
187 // 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.
190 setVmoName(Vmo
, Name
);
193 zx_vm_option_t MapFlags
= ZX_VM_ALLOW_FAULTS
| ZX_VM_SPECIFIC_OVERWRITE
;
195 MapFlags
|= ZX_VM_PERM_READ
| ZX_VM_PERM_WRITE
;
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
))
202 dieOnError(Status
, "zx_vmar_map", Size
);
204 DCHECK_EQ(Addr
, MappedAddr
);
207 Status
= _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT
, MapAddr
,
209 CHECK_EQ(Status
, ZX_OK
);
215 void MemMapFuchsia::releaseAndZeroPagesToOSImpl(uptr From
, uptr Size
) {
216 zx_status_t Status
= _zx_vmo_op_range(Vmo
, ZX_VMO_OP_DECOMMIT
, From
- MapAddr
,
218 CHECK_EQ(Status
, ZX_OK
);
221 void MemMapFuchsia::setMemoryPermissionImpl(uptr Addr
, uptr Size
, uptr Flags
) {
222 const bool NoAccess
= !!(Flags
& MAP_NOACCESS
);
224 zx_vm_option_t MapFlags
= 0;
226 MapFlags
|= ZX_VM_PERM_READ
| ZX_VM_PERM_WRITE
;
228 _zx_vmar_protect(_zx_vmar_root_self(), MapFlags
, Addr
, Size
);
229 CHECK_EQ(Status
, ZX_OK
);
232 bool ReservedMemoryFuchsia::createImpl(UNUSED uptr Addr
, uptr Size
,
233 UNUSED
const char *Name
, uptr Flags
) {
234 const bool AllowNoMem
= !!(Flags
& MAP_ALLOWNOMEM
);
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
))
242 dieOnError(Status
, "zx_vmar_map", Size
);
249 void ReservedMemoryFuchsia::releaseImpl() {
250 zx_status_t Status
= _zx_vmar_unmap(_zx_vmar_root_self(), Base
, Capacity
);
251 CHECK_EQ(Status
, ZX_OK
);
254 ReservedMemoryFuchsia::MemMapT
ReservedMemoryFuchsia::dispatchImpl(uptr Addr
,
256 return ReservedMemoryFuchsia::MemMapT(Addr
, Size
);
261 #endif // SCUDO_FUCHSIA