1 /**
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #ifndef LIBPANDABASE_OS_MEM_H
17 #define LIBPANDABASE_OS_MEM_H
18
19 #include "file.h"
20 #include "macros.h"
21 #include "utils/expected.h"
22 #include "utils/span.h"
23 #include "libpandabase/mem/mem.h"
24
25 #include <cstddef>
26 #ifdef PANDA_TARGET_UNIX
27 #include "platforms/unix/libpandabase/unix_mem.h"
28 #elif PANDA_TARGET_WINDOWS
29 #include "platforms/windows/libpandabase/windows_mem.h"
30 #else
31 #error "Unsupported target: please provide mmap API"
32 #endif
33
34 #include <functional>
35 #include <memory>
36 #include <optional>
37 #include <type_traits>
38
39 namespace panda::os::mem {
40
41 void MmapDeleter(std::byte *ptr, size_t size) noexcept;
42
43 /**
44 * \brief Make memory region \param mem with size \param size with protection flags \param prot
45 * @param mem Pointer to memory region (should be aligned to page size)
46 * @param size Size of memory region
47 * @param prot Memory protection flags, a combination of MMAP_PROT_XXX values
48 * @return Error object if any errors occur
49 */
50 std::optional<Error> MakeMemWithProtFlag(void *mem, size_t size, int prot);
51
52 /**
53 * \brief Make memory region \param mem with size \param size readable and executable
54 * @param mem Pointer to memory region (should be aligned to page size)
55 * @param size Size of memory region
56 * @return Error object if any errors occur
57 */
58 std::optional<Error> MakeMemReadExec(void *mem, size_t size);
59
60 /**
61 * \brief Make memory region \param mem with size \param size readable and writable
62 * @param mem Pointer to memory region (should be aligned to page size)
63 * @param size Size of memory region
64 * @return Error object if any errors occur
65 */
66 std::optional<Error> MakeMemReadWrite(void *mem, size_t size);
67
68 /**
69 * \brief Make memory region \param mem with size \param size readable
70 * @param mem Pointer to memory region (should be aligned to page size)
71 * @param size Size of memory region
72 * @return Error object if any errors occur
73 */
74 std::optional<Error> MakeMemReadOnly(void *mem, size_t size);
75
76 /**
77 * \brief Make memory region \param mem with size \param size protected
78 * @param mem Pointer to memory region (should be aligned to page size)
79 * @param size Size of memory region
80 * @return Error object if any errors occur
81 */
82 std::optional<Error> MakeMemProtected(void *mem, size_t size);
83
84 /**
85 * \brief Align addr \param addr to page size to pass it to MakeMem functions
86 * @param addr Address to align
87 * @return Aligned address
88 */
89 uintptr_t AlignDownToPageSize(uintptr_t addr);
90
91 /**
92 * \brief Allocated aligned memory with alignment \param alignment_in_bytes and
93 * with size \param size. Use AlignedFree to free this memory.
94 * @param alignment_in_bytes - alignment in bytes
95 * @param size - min required size in bytes
96 * @return
97 */
98 void *AlignedAlloc(size_t alignment_in_bytes, size_t size);
99
100 /**
101 * \brief Free memory, allocated by AlignedAlloc.
102 * @param mem - Pointer to memory, allocated by AlignedAlloc
103 */
104 void AlignedFree(void *mem);
105
106 template <class T>
107 class MapRange {
108 public:
MapRange(T * ptr,size_t size)109 MapRange(T *ptr, size_t size) : sp_(reinterpret_cast<std::byte *>(ptr), size) {}
110
GetSubRange(size_t offset,size_t size)111 MapRange GetSubRange(size_t offset, size_t size)
112 {
113 return MapRange(sp_.SubSpan(offset, size));
114 }
115
MakeReadExec()116 Expected<const std::byte *, Error> MakeReadExec()
117 {
118 auto res = MakeMemReadExec(sp_.Data(), sp_.Size());
119 if (res) {
120 return Unexpected(res.value());
121 }
122
123 return sp_.Data();
124 }
125
MakeReadOnly()126 Expected<const std::byte *, Error> MakeReadOnly()
127 {
128 auto res = MakeMemReadOnly(sp_.Data(), sp_.Size());
129 if (res) {
130 return Unexpected(res.value());
131 }
132
133 return sp_.Data();
134 }
135
MakeReadWrite()136 Expected<std::byte *, Error> MakeReadWrite()
137 {
138 auto res = MakeMemReadWrite(sp_.Data(), sp_.Size());
139 if (res) {
140 return Unexpected(res.value());
141 }
142
143 return sp_.Data();
144 }
145
Align()146 MapRange<T> Align() const
147 {
148 auto unaligned = reinterpret_cast<uintptr_t>(sp_.Data());
149 auto aligned = AlignDownToPageSize(unaligned);
150 Span<std::byte> sp(reinterpret_cast<std::byte *>(aligned), sp_.Size() + unaligned - aligned);
151 return MapRange<T>(sp);
152 }
153
GetSize()154 size_t GetSize() const
155 {
156 return sp_.Size();
157 }
158
GetData()159 std::byte *GetData()
160 {
161 return sp_.Data();
162 }
163
164 virtual ~MapRange() = default;
165
166 DEFAULT_COPY_SEMANTIC(MapRange);
167 NO_MOVE_SEMANTIC(MapRange);
168
169 private:
MapRange(const Span<std::byte> & sp)170 explicit MapRange(const Span<std::byte> &sp) : sp_(sp) {}
171
172 Span<std::byte> sp_;
173 };
174
175 enum class MapPtrType { CONST, NON_CONST };
176
177 template <class T, MapPtrType type>
178 class MapPtr {
179 public:
180 using Deleter = void (*)(T *, size_t) noexcept;
181
MapPtr(T * ptr,size_t size,Deleter deleter)182 MapPtr(T *ptr, size_t size, Deleter deleter) : ptr_(ptr), size_(size), page_offset_(0), deleter_(deleter) {}
MapPtr(T * ptr,size_t size,size_t page_offset,Deleter deleter)183 MapPtr(T *ptr, size_t size, size_t page_offset, Deleter deleter)
184 : ptr_(ptr), size_(size), page_offset_(page_offset), deleter_(deleter)
185 {
186 }
187
MapPtr(MapPtr && other)188 MapPtr(MapPtr &&other) noexcept
189 {
190 ptr_ = other.ptr_;
191 page_offset_ = other.page_offset_;
192 size_ = other.size_;
193 deleter_ = other.deleter_;
194 other.ptr_ = nullptr;
195 other.deleter_ = nullptr;
196 }
197
198 MapPtr &operator=(MapPtr &&other) noexcept
199 {
200 ptr_ = other.ptr_;
201 page_offset_ = other.page_offset_;
202 size_ = other.size_;
203 deleter_ = other.deleter_;
204 other.ptr_ = nullptr;
205 other.deleter_ = nullptr;
206 return *this;
207 }
208
Get()209 std::conditional_t<type == MapPtrType::CONST, const T *, T *> Get() const
210 {
211 return ptr_;
212 }
213
GetSize()214 size_t GetSize() const
215 {
216 return size_;
217 }
218
GetMapRange()219 MapRange<T> GetMapRange() const
220 {
221 return MapRange(ptr_, size_);
222 }
223
GetMapRange()224 MapRange<T> GetMapRange()
225 {
226 return MapRange(ptr_, size_);
227 }
228
GetPtrOffset()229 static constexpr uint32_t GetPtrOffset()
230 {
231 return MEMBER_OFFSET(MapPtr, ptr_);
232 }
233
ToConst()234 MapPtr<T, MapPtrType::CONST> ToConst()
235 {
236 MapPtr<T, MapPtrType::CONST> res(ptr_, size_, page_offset_, deleter_);
237 ptr_ = nullptr;
238 return res;
239 }
240
241 /*
242 * memory layout for mmap
243 *
244 * addr(is )
245 * ^
246 * page_offset_ | size_
247 * |--------|-----------|
248 * P0 P1 | P2 | P3 P4
249 * | | | | | | | 4 pages
250 * +-----------+--------S--+--------E--+-----------+
251 * ^
252 * |
253 * ptr_
254 * |--------------------| mmap memory
255 * size
256 *
257 * S: file start; E: file end
258 * Available space: [ptr_...(ptr_ + size_ - 1)]
259 * addr sould be page aligned for file map but it is not guaranteed for anonymous map
260 * For anonymous map, page_offset_ = 0
261 */
~MapPtr()262 ~MapPtr()
263 {
264 if (ptr_ == nullptr) {
265 return;
266 }
267 uintptr_t addr = reinterpret_cast<uintptr_t>(ptr_) - page_offset_;
268 // LINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
269 size_t size = size_ + page_offset_;
270 if (deleter_) {
271 deleter_(reinterpret_cast<T *>(addr), size);
272 }
273 }
274
275 private:
276 T *ptr_;
277 size_t size_;
278 size_t page_offset_;
279 Deleter deleter_;
280
281 NO_COPY_SEMANTIC(MapPtr);
282 };
283
284 using ByteMapRange = MapRange<std::byte>;
285 using BytePtr = MapPtr<std::byte, MapPtrType::NON_CONST>;
286 using ConstBytePtr = MapPtr<std::byte, MapPtrType::CONST>;
287 static_assert(ConstBytePtr::GetPtrOffset() == 0);
288
289 /**
290 * Map the specified file into memory.
291 * The interface is similat to POSIX mmap.
292 *
293 * @param file - file to map
294 * @param prot - memory protection flags, a combination of MMAP_PROT_XXX values.
295 * @param flags - memory map flags, a combination of MMAP_FLAG_XXX values.
296 * @param file_offset - an offset in the file. If the offset is not multiple of page size
297 * the function handles this situatio. The resulting BytePtr will points to desired data.
298 * @param hint - an desired address to map file to.
299 */
300 BytePtr MapFile(file::File file, uint32_t prot, uint32_t flags, size_t size, size_t file_offset = 0,
301 void *hint = nullptr);
302
303 /**
304 * \brief allocates executed memory of size \param size
305 * @param size
306 * @return
307 */
308 BytePtr MapExecuted(size_t size);
309
310 /**
311 * Anonymous mmap with READ | WRITE protection for pages
312 * Note: returned memory will be poisoned in ASAN targets,
313 * if you need other behavior - consider to change interface, or use manual unpoisoning.
314 * @param size - size in bytes, should be multiple of PAGE_SIZE
315 * @param force_poison - poison mmaped memory
316 * @return
317 */
318 void *MapRWAnonymousRaw(size_t size, bool force_poison = true);
319
320 /**
321 * Anonymous mmap with READ | WRITE protection for pages.
322 * Returned address will be aligned as \param aligment_in_bytes.
323 * Note: returned memory will be poisoned in ASAN targets,
324 * if you need other behavior - consider to change interface, or use manual unpoisoning.
325 * @param size - size in bytes, should be multiple of PAGE_SIZE
326 * @param aligment_in_bytes - alignment in bytes, should be multiple of PAGE_SIZE
327 * @param force_poison - poison mmaped memory
328 * @return
329 */
330 void *MapRWAnonymousWithAlignmentRaw(size_t size, size_t aligment_in_bytes, bool force_poison = true);
331
332 // ASAN mapped its structures at this magic address (shadow offset):
333 // see https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm
334 // Therefore, we can successfully allocate memory at fixed address started somewhere at lower addresses
335 // and it can overlap sanitizer address space and mmap with MAP_FIXED flag finished successfully.
336 // (one can look at the MAP_FIXED flag description of Linux mmap)
337 // However, all load/store from this memory is prohibited.
338 // We can get an error during mmap call only if we use MAP_FIXED_NOREPLACE argument,
339 // but it is supported only since Linux 4.17 (Ubuntu 18 has 4.15)
340 // TODO(aemelenko): Do smth with this constant.
341 #ifdef PANDA_TARGET_ARM64
342 static constexpr uint64_t MMAP_FIXED_MAGIC_ADDR_FOR_ASAN = 1ULL << 36ULL;
343 #else
344 static constexpr uint64_t MMAP_FIXED_MAGIC_ADDR_FOR_ASAN = 0x7fff8000ULL;
345 #endif
346
347 /**
348 * Anonymous mmap with fixed address and READ | WRITE protection for pages
349 * Note: returned memory will be poisoned in ASAN targets,
350 * if you need other behavior - consider to change interface, or use manual unpoisoning.
351 * @param mem used address
352 * @param size size in bytes, should be multiple of PAGE_SIZE
353 * @param force_poison poison mmaped memory
354 * @return pointer to the mapped area
355 */
356 void *MapRWAnonymousFixedRaw(void *mem, size_t size, bool force_poison = true);
357
358 /**
359 * Unmap previously mapped memory.
360 * Note: memory will be unpoisoned before unmapping in ASAN targets.
361 * @param mem - pointer to the memory
362 * @param size - size of memory to unmap
363 * @return Error object if any error occur
364 */
365 std::optional<Error> UnmapRaw(void *mem, size_t size);
366
367 /**
368 * Unmap part of previously mapped memory.
369 * Note: memory will be unpoisoned before unmapping in ASAN targets.
370 * @param mem - pointer to the memory
371 * @param size - size of memory piece which we want to unmap
372 * @return Error object if any error occur
373 */
374 std::optional<Error> PartiallyUnmapRaw(void *mem, size_t size);
375
376 /**
377 * \brief returns page size for the system
378 * @return
379 */
380 uint32_t GetPageSize();
381
382 /**
383 * Release pages [pages_start, pages_end] to os.
384 * @param pages_start - address of pages beginning, should be multiple of PAGE_SIZE
385 * @param pages_end - address of pages ending, should be multiple of PAGE_SIZE
386 * @return
387 */
ReleasePages(uintptr_t pages_start,uintptr_t pages_end)388 inline int ReleasePages([[maybe_unused]] uintptr_t pages_start, [[maybe_unused]] uintptr_t pages_end)
389 {
390 ASSERT(pages_start % os::mem::GetPageSize() == 0);
391 ASSERT(pages_end % os::mem::GetPageSize() == 0);
392 ASSERT(pages_end >= pages_start);
393 #ifdef PANDA_TARGET_UNIX
394 return madvise(ToVoidPtr(pages_start), pages_end - pages_start, MADV_DONTNEED);
395 #else
396 // On Windows systems we can do nothing
397 return 0;
398 #endif
399 }
400
401 /**
402 * Tag anonymous memory with a debug name.
403 * @param mem - pointer to the memory
404 * @param size - size of memory to tag
405 * @param tag - pointer to the debug name (must be a literal or heap object)
406 * @return Error object if any error occur
407 */
408 std::optional<Error> TagAnonymousMemory(const void *mem, size_t size, const char *tag);
409
410 static constexpr size_t DEFAULT_NATIVE_BYTES_FROM_MALLINFO = 100000;
411
412 size_t GetNativeBytesFromMallinfo();
413
414 } // namespace panda::os::mem
415
416 #endif // LIBPANDABASE_OS_MEM_H
417