1 /*
2 * Copyright (c) 2021 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 PANDA_LIBPANDABASE_OS_MEM_H_
17 #define PANDA_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 "mem/mem.h"
24
25 #include <cstddef>
26 #ifdef PANDA_TARGET_UNIX
27 #include "unix/unix_mem.h"
28 #elif PANDA_TARGET_WINDOWS
29 #include "windows/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 readable and executable
45 * @param mem Pointer to memory region (should be aligned to page size)
46 * @param size Size of memory region
47 * @return Error object if any errors occur
48 */
49 std::optional<Error> MakeMemReadExec(void *mem, size_t size);
50
51 /**
52 * \brief Make memory region \param mem with size \param size readable and writable
53 * @param mem Pointer to memory region (should be aligned to page size)
54 * @param size Size of memory region
55 * @return Error object if any errors occur
56 */
57 std::optional<Error> MakeMemReadWrite(void *mem, size_t size);
58
59 /**
60 * \brief Make memory region \param mem with size \param size readable
61 * @param mem Pointer to memory region (should be aligned to page size)
62 * @param size Size of memory region
63 * @return Error object if any errors occur
64 */
65 std::optional<Error> MakeMemReadOnly(void *mem, size_t size);
66
67 /**
68 * \brief Align addr \param addr to page size to pass it to MakeMem functions
69 * @param addr Address to align
70 * @return Aligned address
71 */
72 uintptr_t AlignDownToPageSize(uintptr_t addr);
73
74 /**
75 * @param alignment_in_bytes - alignment in bytes
76 * @param size - min required size in bytes
77 * @return
78 */
79 void *AlignedAlloc(size_t alignment_in_bytes, size_t size);
80
81 template <class T>
82 class MapRange {
83 public:
MapRange(T * ptr,size_t size)84 MapRange(T *ptr, size_t size) : sp_(reinterpret_cast<std::byte *>(ptr), size) {}
85
GetSubRange(size_t offset,size_t size)86 MapRange GetSubRange(size_t offset, size_t size)
87 {
88 return MapRange(sp_.SubSpan(offset, size));
89 }
90
MakeReadExec()91 Expected<const std::byte *, Error> MakeReadExec()
92 {
93 auto res = MakeMemReadExec(sp_.Data(), sp_.Size());
94 if (res) {
95 return Unexpected(res.value());
96 }
97
98 return sp_.Data();
99 }
100
MakeReadOnly()101 Expected<const std::byte *, Error> MakeReadOnly()
102 {
103 auto res = MakeMemReadOnly(sp_.Data(), sp_.Size());
104 if (res) {
105 return Unexpected(res.value());
106 }
107
108 return sp_.Data();
109 }
110
MakeReadWrite()111 Expected<std::byte *, Error> MakeReadWrite()
112 {
113 auto res = MakeMemReadWrite(sp_.Data(), sp_.Size());
114 if (res) {
115 return Unexpected(res.value());
116 }
117
118 return sp_.Data();
119 }
120
Align()121 MapRange<T> Align() const
122 {
123 auto unaligned = reinterpret_cast<uintptr_t>(sp_.Data());
124 auto aligned = AlignDownToPageSize(unaligned);
125 Span<std::byte> sp(reinterpret_cast<std::byte *>(aligned), sp_.Size() + unaligned - aligned);
126 return MapRange<T>(sp);
127 }
128
GetSize()129 size_t GetSize() const
130 {
131 return sp_.Size();
132 }
133
GetData()134 std::byte *GetData()
135 {
136 return sp_.Data();
137 }
138
139 virtual ~MapRange() = default;
140
141 DEFAULT_COPY_SEMANTIC(MapRange);
142 NO_MOVE_SEMANTIC(MapRange);
143
144 private:
MapRange(const Span<std::byte> & sp)145 explicit MapRange(const Span<std::byte> &sp) : sp_(sp) {}
146
147 Span<std::byte> sp_;
148 };
149
150 enum class MapPtrType { CONST, NON_CONST };
151
152 template <class T, MapPtrType type>
153 class MapPtr {
154 public:
155 using Deleter = void (*)(T *, size_t) noexcept;
156
MapPtr(T * ptr,size_t size,Deleter deleter)157 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)158 MapPtr(T *ptr, size_t size, size_t page_offset, Deleter deleter)
159 : ptr_(ptr), size_(size), page_offset_(page_offset), deleter_(deleter)
160 {
161 }
162
MapPtr(MapPtr && other)163 MapPtr(MapPtr &&other) noexcept
164 {
165 ptr_ = other.ptr_;
166 page_offset_ = other.page_offset_;
167 size_ = other.size_;
168 deleter_ = other.deleter_;
169 other.ptr_ = nullptr;
170 other.deleter_ = nullptr;
171 }
172
173 MapPtr &operator=(MapPtr &&other) noexcept
174 {
175 ptr_ = other.ptr_;
176 page_offset_ = other.page_offset_;
177 size_ = other.size_;
178 deleter_ = other.deleter_;
179 other.ptr_ = nullptr;
180 other.deleter_ = nullptr;
181 return *this;
182 }
183
Get()184 std::conditional_t<type == MapPtrType::CONST, const T *, T *> Get() const
185 {
186 return ptr_;
187 }
188
GetSize()189 size_t GetSize() const
190 {
191 return size_;
192 }
193
GetMapRange()194 MapRange<T> GetMapRange() const
195 {
196 return MapRange(ptr_, size_);
197 }
198
GetMapRange()199 MapRange<T> GetMapRange()
200 {
201 return MapRange(ptr_, size_);
202 }
203
ToConst()204 MapPtr<T, MapPtrType::CONST> ToConst()
205 {
206 MapPtr<T, MapPtrType::CONST> res(ptr_, size_, page_offset_, deleter_);
207 ptr_ = nullptr;
208 return res;
209 }
210
211 /*
212 * memory layout for mmap
213 *
214 * addr(is )
215 * ^
216 * page_offset_ | size_
217 * |--------|-----------|
218 * P0 P1 | P2 | P3 P4
219 * | | | | | | | 4 pages
220 * +-----------+--------S--+--------E--+-----------+
221 * ^
222 * |
223 * ptr_
224 * |--------------------| mmap memory
225 * size
226 *
227 * S: file start; E: file end
228 * Available space: [ptr_...(ptr_ + size_ - 1)]
229 * addr should be page aligned for file map but it is not guaranteed for anonymous map
230 * For anonymous map, page_offset_ = 0
231 */
~MapPtr()232 ~MapPtr()
233 {
234 if (ptr_ == nullptr) {
235 return;
236 }
237 uintptr_t addr = reinterpret_cast<uintptr_t>(ptr_) - page_offset_;
238 // LINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
239 size_t size = size_ + page_offset_;
240 deleter_(reinterpret_cast<T *>(addr), size);
241 }
242
243 private:
244 T *ptr_;
245 size_t size_;
246 size_t page_offset_;
247 Deleter deleter_;
248
249 NO_COPY_SEMANTIC(MapPtr);
250 };
251
252 using ByteMapRange = MapRange<std::byte>;
253 using BytePtr = MapPtr<std::byte, MapPtrType::NON_CONST>;
254 using ConstBytePtr = MapPtr<std::byte, MapPtrType::CONST>;
255
256 /**
257 * Map the specified file into memory.
258 * The interface is similar to POSIX mmap.
259 * @param file - file to map
260 * @param prot - memory protection flags, a combination of MMAP_PROT_XXX values.
261 * @param flags - memory map flags, a combination of MMAP_FLAG_XXX values.
262 * @param file_offset - an offset in the file. If the offset is not multiple of page size
263 * the function handles this situation. The resulting BytePtr will point to desired data.
264 * @param hint - an desired address to map file to.
265 */
266 BytePtr MapFile(file::File file, uint32_t prot, uint32_t flags, size_t size, size_t file_offset = 0,
267 void *hint = nullptr);
268
269 /**
270 * \brief allocates executed memory of size \param size
271 * @param size Size of memory region
272 * @return
273 */
274 BytePtr MapExecuted(size_t size);
275
276 /**
277 * Anonymous mmap with READ | WRITE protection for pages
278 * Note: returned memory will be poisoned in ASAN targets,
279 * if you need other behavior - consider to change interface, or use manual unpoisoning.
280 * @param size - size in bytes, should be multiple of PAGE_SIZE
281 * @param force_poison - poison mmaped memory
282 * @return
283 */
284 void *MapRWAnonymousRaw(size_t size, bool force_poison = true);
285
286 /**
287 * Anonymous mmap with READ | WRITE protection for pages.
288 * Returned address will be aligned as \param aligment_in_bytes.
289 * Note: returned memory will be poisoned in ASAN targets,
290 * if you need other behavior - consider to change interface, or use manual unpoisoning.
291 * @param size - size in bytes, should be multiple of PAGE_SIZE
292 * @param aligment_in_bytes - alignment in bytes, should be multiple of PAGE_SIZE
293 * @param force_poison - poison mmaped memory
294 * @return
295 */
296 void *MapRWAnonymousWithAlignmentRaw(size_t size, size_t aligment_in_bytes, bool force_poison = true);
297
298 // ASAN mapped its structures at this magic address (shadow offset)
299 // Therefore, we can successfully allocate memory at fixed address started somewhere at lower addresses
300 // and it can overlap sanitizer address space and mmap with MAP_FIXED flag finished successfully.
301 // (one can look at the MAP_FIXED flag description of Linux mmap)
302 // However, all load/store from this memory is prohibited.
303 // We can get an error during mmap call only if we use MAP_FIXED_NOREPLACE argument,
304 // but it is supported only since Linux 4.17 (Ubuntu 18 has 4.15)
305 #ifdef PANDA_TARGET_ARM64
306 static constexpr uint64_t MMAP_FIXED_MAGIC_ADDR_FOR_ASAN = 1ULL << 36ULL;
307 #else
308 static constexpr uint64_t MMAP_FIXED_MAGIC_ADDR_FOR_ASAN = 0x7fff8000ULL;
309 #endif
310
311 /**
312 * Anonymous mmap with fixed address and READ | WRITE protection for pages
313 * Note: returned memory will be poisoned in ASAN targets,
314 * if you need other behavior - consider to change interface, or use manual unpoisoning.
315 * @param mem used address
316 * @param size size in bytes, should be multiple of PAGE_SIZE
317 * @param force_poison poison mmaped memory
318 * @return pointer to the mapped area
319 */
320 void *MapRWAnonymousFixedRaw(void *mem, size_t size, bool force_poison = true);
321
322 /**
323 * Unmap previously mapped memory.
324 * Note: memory will be unpoisoned before unmapping in ASAN targets.
325 * @param mem - pointer to the memory
326 * @param size - size of memory to unmap
327 * @return Error object if any error occur
328 */
329 std::optional<Error> UnmapRaw(void *mem, size_t size);
330
331 /**
332 * \brief Get page size for the system
333 * @return
334 */
335 uint32_t GetPageSize();
336
337 /**
338 * Release pages [pages_start, pages_end] to os.
339 * @param pages_start - address of pages beginning, should be multiple of PAGE_SIZE
340 * @param pages_end - address of pages ending, should be multiple of PAGE_SIZE
341 * @return
342 */
ReleasePages(uintptr_t pages_start,uintptr_t pages_end)343 inline void ReleasePages([[maybe_unused]] uintptr_t pages_start, [[maybe_unused]] uintptr_t pages_end)
344 {
345 ASSERT(pages_start % os::mem::GetPageSize() == 0);
346 ASSERT(pages_end % os::mem::GetPageSize() == 0);
347 ASSERT(pages_end >= pages_start);
348 #ifdef PANDA_TARGET_UNIX
349 madvise(ToVoidPtr(pages_start), pages_end - pages_start, MADV_DONTNEED);
350 #else
351 UNREACHABLE();
352 #endif
353 }
354
355 /**
356 * Tag anonymous memory with a debug name.
357 * @param mem - pointer to the memory
358 * @param size - size of memory to tag
359 * @param tag - pointer to the debug name (must be a literal or heap object)
360 * @return Error object if any error occur
361 */
362 std::optional<Error> TagAnonymousMemory(const void *mem, size_t size, const char *tag);
363
364 static constexpr size_t DEFAULT_NATIVE_BYTES_FROM_MALLINFO = 100000;
365
366 size_t GetNativeBytesFromMallinfo();
367
368 } // namespace panda::os::mem
369
370 #endif // PANDA_LIBPANDABASE_OS_MEM_H_
371