llvm-project/lldb/tools/debugserver/source/MacOSX/MachVMMemory.cpp
1//===-- MachVMMemory.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// Created by Greg Clayton on 6/26/07.
10//
11//===----------------------------------------------------------------------===//
12
13#include "MachVMMemory.h"
14#include "DNBLog.h"
15#include "MachVMRegion.h"
16#include <cassert>
17#include <dlfcn.h>
18#include <mach/mach_vm.h>
19#include <mach/shared_region.h>
20#include <sys/sysctl.h>
21
22#if defined(WITH_FBS) || defined(WITH_BKS)
23extern "C" {
24#import <System/sys/kern_memorystatus.h>
25}
26#endif
27
28static const vm_size_t kInvalidPageSize = ~0;
29
30MachVMMemory::MachVMMemory() : m_page_size(kInvalidPageSize), m_err(0) {}
31
32MachVMMemory::~MachVMMemory() = default;
33
34nub_size_t MachVMMemory::PageSize(task_t task) {
35 if (m_page_size == kInvalidPageSize) {
36#if defined(TASK_VM_INFO) && TASK_VM_INFO >= 22
37 if (task != TASK_NULL) {
38 kern_return_t kr;
39 mach_msg_type_number_t info_count = TASK_VM_INFO_COUNT;
40 task_vm_info_data_t vm_info;
41 kr = task_info(task, TASK_VM_INFO, (task_info_t)&vm_info, &info_count);
42 if (kr == KERN_SUCCESS) {
43 DNBLogThreadedIf(
44 LOG_TASK,
45 "MachVMMemory::PageSize task_info returned page size of 0x%x",
46 (int)vm_info.page_size);
47 m_page_size = vm_info.page_size;
48 return m_page_size;
49 } else {
50 DNBLogThreadedIf(LOG_TASK, "MachVMMemory::PageSize task_info call "
51 "failed to get page size, TASK_VM_INFO %d, "
52 "TASK_VM_INFO_COUNT %d, kern return %d",
53 TASK_VM_INFO, TASK_VM_INFO_COUNT, kr);
54 }
55 }
56#endif
57 m_err = ::host_page_size(::mach_host_self(), &m_page_size);
58 if (m_err.Fail())
59 m_page_size = 0;
60 }
61 return m_page_size;
62}
63
64nub_size_t MachVMMemory::MaxBytesLeftInPage(task_t task, nub_addr_t addr,
65 nub_size_t count) {
66 const nub_size_t page_size = PageSize(task);
67 if (page_size > 0) {
68 nub_size_t page_offset = (addr % page_size);
69 nub_size_t bytes_left_in_page = page_size - page_offset;
70 if (count > bytes_left_in_page)
71 count = bytes_left_in_page;
72 }
73 return count;
74}
75
76#define MAX_STACK_ALLOC_DISPOSITIONS \
77 (16 * 1024 / sizeof(int)) // 16K of allocations
78
79std::vector<nub_addr_t> get_dirty_pages(task_t task, mach_vm_address_t addr,
80 mach_vm_size_t size) {
81 std::vector<nub_addr_t> dirty_pages;
82
83 int pages_to_query = size / vm_page_size;
84 // Don't try to fetch too many pages' dispositions in a single call or we
85 // could blow our stack out.
86 mach_vm_size_t dispositions_size =
87 std::min(pages_to_query, (int)MAX_STACK_ALLOC_DISPOSITIONS);
88 int dispositions[dispositions_size];
89
90 mach_vm_size_t chunk_count =
91 ((pages_to_query + MAX_STACK_ALLOC_DISPOSITIONS - 1) /
92 MAX_STACK_ALLOC_DISPOSITIONS);
93
94 for (mach_vm_size_t cur_disposition_chunk = 0;
95 cur_disposition_chunk < chunk_count; cur_disposition_chunk++) {
96 mach_vm_size_t dispositions_already_queried =
97 cur_disposition_chunk * MAX_STACK_ALLOC_DISPOSITIONS;
98
99 mach_vm_size_t chunk_pages_to_query = std::min(
100 pages_to_query - dispositions_already_queried, dispositions_size);
101 mach_vm_address_t chunk_page_aligned_start_addr =
102 addr + (dispositions_already_queried * vm_page_size);
103
104 kern_return_t kr = mach_vm_page_range_query(
105 task, chunk_page_aligned_start_addr,
106 chunk_pages_to_query * vm_page_size, (mach_vm_address_t)dispositions,
107 &chunk_pages_to_query);
108 if (kr != KERN_SUCCESS)
109 return dirty_pages;
110 for (mach_vm_size_t i = 0; i < chunk_pages_to_query; i++) {
111 uint64_t dirty_addr = chunk_page_aligned_start_addr + (i * vm_page_size);
112 if (dispositions[i] & VM_PAGE_QUERY_PAGE_DIRTY)
113 dirty_pages.push_back(dirty_addr);
114 }
115 }
116 return dirty_pages;
117}
118
119nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address,
120 DNBRegionInfo *region_info) {
121 MachVMRegion vmRegion(task);
122
123 if (vmRegion.GetRegionForAddress(address)) {
124 region_info->addr = vmRegion.StartAddress();
125 region_info->size = vmRegion.GetByteSize();
126 region_info->permissions = vmRegion.GetDNBPermissions();
127 region_info->flags = vmRegion.GetFlags();
128 region_info->dirty_pages =
129 get_dirty_pages(task, vmRegion.StartAddress(), vmRegion.GetByteSize());
130 region_info->vm_types = vmRegion.GetMemoryTypes();
131 } else {
132 region_info->addr = address;
133 region_info->size = 0;
134 if (vmRegion.GetError().Success()) {
135 // vmRegion.GetRegionForAddress() return false, indicating that "address"
136 // wasn't in a valid region, but the "vmRegion" info was successfully
137 // read from the task which means the info describes the next valid
138 // region from which we can infer the size of this invalid region
139 mach_vm_address_t start_addr = vmRegion.StartAddress();
140 if (address < start_addr)
141 region_info->size = start_addr - address;
142 }
143 // If we can't get any info about the size from the next region it means
144 // we asked about an address that was past all mappings, so the size
145 // of this region will take up all remaining address space.
146 if (region_info->size == 0)
147 region_info->size = INVALID_NUB_ADDRESS - region_info->addr;
148
149 // Not readable, writeable or executable
150 region_info->permissions = 0;
151 }
152 return true;
153}
154
155// API availability:
156// mach_vm_update_pointers_with_remote_tags() - 26.0
157// VM_OFFSET_LIST_MAX macro - 26.1
158#ifndef VM_OFFSET_LIST_MAX
159#define VM_OFFSET_LIST_MAX 512
160#endif
161using mach_vm_offset_list_t = mach_vm_offset_t *;
162using mach_vm_update_pointers_with_remote_tags_t = kern_return_t(
163 mach_port_name_t target, mach_vm_offset_list_t in_pointer_list,
164 mach_msg_type_number_t in_pointer_listCnt,
165 mach_vm_offset_list_t out_pointer_list,
166 mach_msg_type_number_t *out_pointer_listCnt);
167
168nub_bool_t MachVMMemory::GetMemoryTags(task_t task, nub_addr_t address,
169 nub_size_t size,
170 std::vector<uint8_t> &tags) {
171 static auto mach_vm_update_pointers_with_remote_tags =
172 (mach_vm_update_pointers_with_remote_tags_t *)dlsym(
173 RTLD_DEFAULT, "mach_vm_update_pointers_with_remote_tags");
174 assert(mach_vm_update_pointers_with_remote_tags);
175
176 // Max batch size supported by mach_vm_update_pointers_with_remote_tags.
177 constexpr uint32_t max_ptr_count = VM_OFFSET_LIST_MAX;
178 constexpr uint32_t tag_shift = 56;
179 constexpr nub_addr_t tag_mask =
180 ((nub_addr_t)0x0f << tag_shift); // Lower half of top byte.
181 constexpr uint32_t tag_granule = 16;
182
183 mach_msg_type_number_t ptr_count =
184 (size / tag_granule) + ((size % tag_granule > 0) ? 1 : 0);
185 ptr_count = std::min(ptr_count, max_ptr_count);
186
187 auto ptr_arr = std::make_unique<mach_vm_offset_t[]>(ptr_count);
188 for (size_t i = 0; i < ptr_count; i++)
189 ptr_arr[i] = (address + i * tag_granule);
190
191 mach_msg_type_number_t ptr_count_out = ptr_count;
192 m_err = mach_vm_update_pointers_with_remote_tags(
193 task, ptr_arr.get(), ptr_count, ptr_arr.get(), &ptr_count_out);
194
195 const bool failed = (m_err.Fail() || (ptr_count != ptr_count_out));
196 if (failed || DNBLogCheckLogBit(LOG_MEMORY))
197 m_err.LogThreaded("::mach_vm_update_pointers_with_remote_tags ( task = "
198 "0x%4.4x, ptr_count = %d ) => %i ( ptr_count_out = %d)",
199 task, ptr_count, m_err.Status(), ptr_count_out);
200 if (failed)
201 return false;
202
203 tags.reserve(ptr_count);
204 for (size_t i = 0; i < ptr_count; i++) {
205 nub_addr_t tag = (ptr_arr[i] & tag_mask) >> tag_shift;
206 tags.push_back(tag);
207 }
208
209 return true;
210}
211
212static uint64_t GetPhysicalMemory() {
213 // This doesn't change often at all. No need to poll each time.
214 static uint64_t physical_memory = 0;
215 static bool calculated = false;
216 if (calculated)
217 return physical_memory;
218
219 size_t len = sizeof(physical_memory);
220 sysctlbyname("hw.memsize", &physical_memory, &len, NULL, 0);
221
222 calculated = true;
223 return physical_memory;
224}
225
226nub_bool_t MachVMMemory::GetMemoryProfile(
227 DNBProfileDataScanType scanType, task_t task, struct task_basic_info ti,
228 cpu_type_t cputype, nub_process_t pid, vm_statistics64_data_t &vminfo,
229 uint64_t &physical_memory, uint64_t &anonymous,
230 uint64_t &phys_footprint, uint64_t &memory_cap)
231{
232 if (scanType & eProfileHostMemory)
233 physical_memory = GetPhysicalMemory();
234
235 if (scanType & eProfileMemory) {
236 static mach_port_t localHost = mach_host_self();
237 mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
238 host_statistics64(localHost, HOST_VM_INFO64, (host_info64_t)&vminfo,
239 &count);
240
241 kern_return_t kr;
242 mach_msg_type_number_t info_count;
243 task_vm_info_data_t vm_info;
244
245 info_count = TASK_VM_INFO_COUNT;
246 kr = task_info(task, TASK_VM_INFO_PURGEABLE, (task_info_t)&vm_info, &info_count);
247 if (kr == KERN_SUCCESS) {
248 if (scanType & eProfileMemoryAnonymous) {
249 anonymous = vm_info.internal + vm_info.compressed - vm_info.purgeable_volatile_pmap;
250 }
251
252 phys_footprint = vm_info.phys_footprint;
253 }
254 }
255
256#if defined(WITH_FBS) || defined(WITH_BKS)
257 if (scanType & eProfileMemoryCap) {
258 memorystatus_memlimit_properties_t memlimit_properties;
259 memset(&memlimit_properties, 0, sizeof(memlimit_properties));
260 if (memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &memlimit_properties, sizeof(memlimit_properties)) == 0) {
261 memory_cap = memlimit_properties.memlimit_active;
262 }
263 }
264#endif
265
266 return true;
267}
268
269nub_size_t MachVMMemory::Read(task_t task, nub_addr_t address, void *data,
270 nub_size_t data_count) {
271 if (data == NULL || data_count == 0)
272 return 0;
273
274 nub_size_t total_bytes_read = 0;
275 nub_addr_t curr_addr = address;
276 uint8_t *curr_data = (uint8_t *)data;
277 while (total_bytes_read < data_count) {
278 mach_vm_size_t curr_size =
279 MaxBytesLeftInPage(task, curr_addr, data_count - total_bytes_read);
280 mach_msg_type_number_t curr_bytes_read = 0;
281 vm_offset_t vm_memory = 0;
282 m_err = ::mach_vm_read(task, curr_addr, curr_size, &vm_memory,
283 &curr_bytes_read);
284
285 if (DNBLogCheckLogBit(LOG_MEMORY))
286 m_err.LogThreaded("::mach_vm_read ( task = 0x%4.4x, addr = 0x%8.8llx, "
287 "size = %llu, data => %8.8p, dataCnt => %i )",
288 task, (uint64_t)curr_addr, (uint64_t)curr_size,
289 vm_memory, curr_bytes_read);
290
291 if (m_err.Success()) {
292 if (curr_bytes_read != curr_size) {
293 if (DNBLogCheckLogBit(LOG_MEMORY))
294 m_err.LogThreaded(
295 "::mach_vm_read ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, "
296 "data => %8.8p, dataCnt=>%i ) only read %u of %llu bytes",
297 task, (uint64_t)curr_addr, (uint64_t)curr_size, vm_memory,
298 curr_bytes_read, curr_bytes_read, (uint64_t)curr_size);
299 }
300 ::memcpy(curr_data, (void *)vm_memory, curr_bytes_read);
301 ::vm_deallocate(mach_task_self(), vm_memory, curr_bytes_read);
302 total_bytes_read += curr_bytes_read;
303 curr_addr += curr_bytes_read;
304 curr_data += curr_bytes_read;
305 } else {
306 break;
307 }
308 }
309 return total_bytes_read;
310}
311
312nub_size_t MachVMMemory::Write(task_t task, nub_addr_t address,
313 const void *data, nub_size_t data_count) {
314 MachVMRegion vmRegion(task);
315
316 nub_size_t total_bytes_written = 0;
317 nub_addr_t curr_addr = address;
318 const uint8_t *curr_data = (const uint8_t *)data;
319
320 while (total_bytes_written < data_count) {
321 if (vmRegion.GetRegionForAddress(curr_addr)) {
322 mach_vm_size_t curr_data_count = data_count - total_bytes_written;
323 mach_vm_size_t region_bytes_left = vmRegion.BytesRemaining(curr_addr);
324 if (region_bytes_left == 0) {
325 break;
326 }
327 if (curr_data_count > region_bytes_left)
328 curr_data_count = region_bytes_left;
329
330 if (vmRegion.SetProtections(curr_addr, curr_data_count,
331 VM_PROT_READ | VM_PROT_WRITE)) {
332 nub_size_t bytes_written =
333 WriteRegion(task, curr_addr, curr_data, curr_data_count);
334 if (bytes_written <= 0) {
335 // Status should have already be posted by WriteRegion...
336 break;
337 } else {
338 total_bytes_written += bytes_written;
339 curr_addr += bytes_written;
340 curr_data += bytes_written;
341 }
342 } else {
343 DNBLogThreadedIf(
344 LOG_MEMORY_PROTECTIONS, "Failed to set read/write protections on "
345 "region for address: [0x%8.8llx-0x%8.8llx)",
346 (uint64_t)curr_addr, (uint64_t)(curr_addr + curr_data_count));
347 break;
348 }
349 } else {
350 DNBLogThreadedIf(LOG_MEMORY_PROTECTIONS,
351 "Failed to get region for address: 0x%8.8llx",
352 (uint64_t)address);
353 break;
354 }
355 }
356
357 return total_bytes_written;
358}
359
360nub_size_t MachVMMemory::WriteRegion(task_t task, const nub_addr_t address,
361 const void *data,
362 const nub_size_t data_count) {
363 if (data == NULL || data_count == 0)
364 return 0;
365
366 nub_size_t total_bytes_written = 0;
367 nub_addr_t curr_addr = address;
368 const uint8_t *curr_data = (const uint8_t *)data;
369 while (total_bytes_written < data_count) {
370 mach_msg_type_number_t curr_data_count =
371 static_cast<mach_msg_type_number_t>(MaxBytesLeftInPage(
372 task, curr_addr, data_count - total_bytes_written));
373 m_err =
374 ::mach_vm_write(task, curr_addr, (pointer_t)curr_data, curr_data_count);
375 if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail())
376 m_err.LogThreaded("::mach_vm_write ( task = 0x%4.4x, addr = 0x%8.8llx, "
377 "data = %8.8p, dataCnt = %u )",
378 task, (uint64_t)curr_addr, curr_data, curr_data_count);
379
380#if !defined(__i386__) && !defined(__x86_64__)
381 vm_machine_attribute_val_t mattr_value = MATTR_VAL_CACHE_FLUSH;
382
383 m_err = ::vm_machine_attribute(task, curr_addr, curr_data_count,
384 MATTR_CACHE, &mattr_value);
385 if (DNBLogCheckLogBit(LOG_MEMORY) || m_err.Fail())
386 m_err.LogThreaded("::vm_machine_attribute ( task = 0x%4.4x, addr = "
387 "0x%8.8llx, size = %u, attr = MATTR_CACHE, mattr_value "
388 "=> MATTR_VAL_CACHE_FLUSH )",
389 task, (uint64_t)curr_addr, curr_data_count);
390#endif
391
392 if (m_err.Success()) {
393 total_bytes_written += curr_data_count;
394 curr_addr += curr_data_count;
395 curr_data += curr_data_count;
396 } else {
397 break;
398 }
399 }
400 return total_bytes_written;
401}