1 | //===-- MachTask.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 | // |
10 | // MachTask.cpp |
11 | // debugserver |
12 | // |
13 | // Created by Greg Clayton on 12/5/08. |
14 | // |
15 | //===----------------------------------------------------------------------===// |
16 | |
17 | #include "MachTask.h" |
18 | |
19 | // C Includes |
20 | |
21 | #include <mach-o/dyld_images.h> |
22 | #include <mach/mach_vm.h> |
23 | #import <sys/sysctl.h> |
24 | |
25 | #if defined(__APPLE__) |
26 | #include <pthread.h> |
27 | #include <sched.h> |
28 | #endif |
29 | |
30 | // C++ Includes |
31 | #include <iomanip> |
32 | #include <sstream> |
33 | |
34 | // Other libraries and framework includes |
35 | // Project includes |
36 | #include "CFUtils.h" |
37 | #include "DNB.h" |
38 | #include "DNBDataRef.h" |
39 | #include "DNBError.h" |
40 | #include "DNBLog.h" |
41 | #include "MachProcess.h" |
42 | |
43 | #ifdef WITH_SPRINGBOARD |
44 | |
45 | #include <CoreFoundation/CoreFoundation.h> |
46 | #include <SpringBoardServices/SBSWatchdogAssertion.h> |
47 | #include <SpringBoardServices/SpringBoardServer.h> |
48 | |
49 | #endif |
50 | |
51 | #ifdef WITH_BKS |
52 | extern "C" { |
53 | #import <BackBoardServices/BKSWatchdogAssertion.h> |
54 | #import <BackBoardServices/BackBoardServices.h> |
55 | #import <Foundation/Foundation.h> |
56 | } |
57 | #endif |
58 | |
59 | #include <AvailabilityMacros.h> |
60 | |
61 | #ifdef LLDB_ENERGY |
62 | #include <mach/mach_time.h> |
63 | #include <pmenergy.h> |
64 | #include <pmsample.h> |
65 | #endif |
66 | |
67 | extern "C" int |
68 | proc_get_cpumon_params(pid_t pid, int *percentage, |
69 | int *interval); // <libproc_internal.h> SPI |
70 | |
71 | //---------------------------------------------------------------------- |
72 | // MachTask constructor |
73 | //---------------------------------------------------------------------- |
74 | MachTask::MachTask(MachProcess *process) |
75 | : m_process(process), m_task(TASK_NULL), m_vm_memory(), |
76 | m_exception_thread(0), m_exception_port(MACH_PORT_NULL), |
77 | m_exec_will_be_suspended(false), m_do_double_resume(false) { |
78 | memset(&m_exc_port_info, 0, sizeof(m_exc_port_info)); |
79 | } |
80 | |
81 | //---------------------------------------------------------------------- |
82 | // Destructor |
83 | //---------------------------------------------------------------------- |
84 | MachTask::~MachTask() { Clear(); } |
85 | |
86 | //---------------------------------------------------------------------- |
87 | // MachTask::Suspend |
88 | //---------------------------------------------------------------------- |
89 | kern_return_t MachTask::Suspend() { |
90 | DNBError err; |
91 | task_t task = TaskPort(); |
92 | err = ::task_suspend(task); |
93 | if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) |
94 | err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )" , task); |
95 | return err.Status(); |
96 | } |
97 | |
98 | //---------------------------------------------------------------------- |
99 | // MachTask::Resume |
100 | //---------------------------------------------------------------------- |
101 | kern_return_t MachTask::Resume() { |
102 | struct task_basic_info task_info; |
103 | task_t task = TaskPort(); |
104 | if (task == TASK_NULL) |
105 | return KERN_INVALID_ARGUMENT; |
106 | |
107 | DNBError err; |
108 | err = BasicInfo(task, &task_info); |
109 | |
110 | if (err.Success()) { |
111 | if (m_do_double_resume && task_info.suspend_count == 2) { |
112 | err = ::task_resume(task); |
113 | if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) |
114 | err.LogThreaded("::task_resume double-resume after exec-start-stopped " |
115 | "( target_task = 0x%4.4x )" , task); |
116 | } |
117 | m_do_double_resume = false; |
118 | |
119 | // task_resume isn't counted like task_suspend calls are, are, so if the |
120 | // task is not suspended, don't try and resume it since it is already |
121 | // running |
122 | if (task_info.suspend_count > 0) { |
123 | err = ::task_resume(task); |
124 | if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) |
125 | err.LogThreaded("::task_resume ( target_task = 0x%4.4x )" , task); |
126 | } |
127 | } |
128 | return err.Status(); |
129 | } |
130 | |
131 | //---------------------------------------------------------------------- |
132 | // MachTask::ExceptionPort |
133 | //---------------------------------------------------------------------- |
134 | mach_port_t MachTask::ExceptionPort() const { return m_exception_port; } |
135 | |
136 | //---------------------------------------------------------------------- |
137 | // MachTask::ExceptionPortIsValid |
138 | //---------------------------------------------------------------------- |
139 | bool MachTask::ExceptionPortIsValid() const { |
140 | return MACH_PORT_VALID(m_exception_port); |
141 | } |
142 | |
143 | //---------------------------------------------------------------------- |
144 | // MachTask::Clear |
145 | //---------------------------------------------------------------------- |
146 | void MachTask::Clear() { |
147 | // Do any cleanup needed for this task |
148 | if (m_exception_thread) |
149 | ShutDownExcecptionThread(); |
150 | m_task = TASK_NULL; |
151 | m_exception_thread = 0; |
152 | m_exception_port = MACH_PORT_NULL; |
153 | m_exec_will_be_suspended = false; |
154 | m_do_double_resume = false; |
155 | } |
156 | |
157 | //---------------------------------------------------------------------- |
158 | // MachTask::SaveExceptionPortInfo |
159 | //---------------------------------------------------------------------- |
160 | kern_return_t MachTask::SaveExceptionPortInfo() { |
161 | return m_exc_port_info.Save(TaskPort()); |
162 | } |
163 | |
164 | //---------------------------------------------------------------------- |
165 | // MachTask::RestoreExceptionPortInfo |
166 | //---------------------------------------------------------------------- |
167 | kern_return_t MachTask::RestoreExceptionPortInfo() { |
168 | return m_exc_port_info.Restore(TaskPort()); |
169 | } |
170 | |
171 | //---------------------------------------------------------------------- |
172 | // MachTask::ReadMemory |
173 | //---------------------------------------------------------------------- |
174 | nub_size_t MachTask::ReadMemory(nub_addr_t addr, nub_size_t size, void *buf) { |
175 | nub_size_t n = 0; |
176 | task_t task = TaskPort(); |
177 | if (task != TASK_NULL) { |
178 | n = m_vm_memory.Read(task, addr, buf, size); |
179 | |
180 | DNBLogThreadedIf(LOG_MEMORY, "MachTask::ReadMemory ( addr = 0x%8.8llx, " |
181 | "size = %llu, buf = %p) => %llu bytes read" , |
182 | (uint64_t)addr, (uint64_t)size, buf, (uint64_t)n); |
183 | if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || |
184 | (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) { |
185 | DNBDataRef data((uint8_t *)buf, n, false); |
186 | data.Dump(0, static_cast<DNBDataRef::offset_t>(n), addr, |
187 | DNBDataRef::TypeUInt8, 16); |
188 | } |
189 | } |
190 | return n; |
191 | } |
192 | |
193 | //---------------------------------------------------------------------- |
194 | // MachTask::WriteMemory |
195 | //---------------------------------------------------------------------- |
196 | nub_size_t MachTask::WriteMemory(nub_addr_t addr, nub_size_t size, |
197 | const void *buf) { |
198 | nub_size_t n = 0; |
199 | task_t task = TaskPort(); |
200 | if (task != TASK_NULL) { |
201 | n = m_vm_memory.Write(task, addr, buf, size); |
202 | DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, " |
203 | "size = %llu, buf = %p) => %llu bytes written" , |
204 | (uint64_t)addr, (uint64_t)size, buf, (uint64_t)n); |
205 | if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || |
206 | (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8)) { |
207 | DNBDataRef data((const uint8_t *)buf, n, false); |
208 | data.Dump(0, static_cast<DNBDataRef::offset_t>(n), addr, |
209 | DNBDataRef::TypeUInt8, 16); |
210 | } |
211 | } |
212 | return n; |
213 | } |
214 | |
215 | //---------------------------------------------------------------------- |
216 | // MachTask::MemoryRegionInfo |
217 | //---------------------------------------------------------------------- |
218 | int MachTask::GetMemoryRegionInfo(nub_addr_t addr, DNBRegionInfo *region_info) { |
219 | task_t task = TaskPort(); |
220 | if (task == TASK_NULL) |
221 | return -1; |
222 | |
223 | int ret = m_vm_memory.GetMemoryRegionInfo(task, addr, region_info); |
224 | DNBLogThreadedIf(LOG_MEMORY, "MachTask::MemoryRegionInfo ( addr = 0x%8.8llx " |
225 | ") => %i (start = 0x%8.8llx, size = 0x%8.8llx, " |
226 | "permissions = %u)" , |
227 | (uint64_t)addr, ret, (uint64_t)region_info->addr, |
228 | (uint64_t)region_info->size, region_info->permissions); |
229 | return ret; |
230 | } |
231 | |
232 | #define TIME_VALUE_TO_TIMEVAL(a, r) \ |
233 | do { \ |
234 | (r)->tv_sec = (a)->seconds; \ |
235 | (r)->tv_usec = (a)->microseconds; \ |
236 | } while (0) |
237 | |
238 | // We should consider moving this into each MacThread. |
239 | static void get_threads_profile_data(DNBProfileDataScanType scanType, |
240 | task_t task, nub_process_t pid, |
241 | std::vector<uint64_t> &threads_id, |
242 | std::vector<std::string> &threads_name, |
243 | std::vector<uint64_t> &threads_used_usec) { |
244 | kern_return_t kr; |
245 | thread_act_array_t threads; |
246 | mach_msg_type_number_t tcnt; |
247 | |
248 | kr = task_threads(task, &threads, &tcnt); |
249 | if (kr != KERN_SUCCESS) |
250 | return; |
251 | |
252 | for (mach_msg_type_number_t i = 0; i < tcnt; i++) { |
253 | thread_identifier_info_data_t identifier_info; |
254 | mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; |
255 | kr = ::thread_info(threads[i], THREAD_IDENTIFIER_INFO, |
256 | (thread_info_t)&identifier_info, &count); |
257 | if (kr != KERN_SUCCESS) |
258 | continue; |
259 | |
260 | thread_basic_info_data_t basic_info; |
261 | count = THREAD_BASIC_INFO_COUNT; |
262 | kr = ::thread_info(threads[i], THREAD_BASIC_INFO, |
263 | (thread_info_t)&basic_info, &count); |
264 | if (kr != KERN_SUCCESS) |
265 | continue; |
266 | |
267 | if ((basic_info.flags & TH_FLAGS_IDLE) == 0) { |
268 | nub_thread_t tid = |
269 | MachThread::GetGloballyUniqueThreadIDForMachPortID(threads[i]); |
270 | threads_id.push_back(tid); |
271 | |
272 | if ((scanType & eProfileThreadName) && |
273 | (identifier_info.thread_handle != 0)) { |
274 | struct proc_threadinfo proc_threadinfo; |
275 | int len = ::proc_pidinfo(pid, PROC_PIDTHREADINFO, |
276 | identifier_info.thread_handle, |
277 | &proc_threadinfo, PROC_PIDTHREADINFO_SIZE); |
278 | if (len && proc_threadinfo.pth_name[0]) { |
279 | threads_name.push_back(proc_threadinfo.pth_name); |
280 | } else { |
281 | threads_name.push_back(x: "" ); |
282 | } |
283 | } else { |
284 | threads_name.push_back(x: "" ); |
285 | } |
286 | struct timeval tv; |
287 | struct timeval thread_tv; |
288 | TIME_VALUE_TO_TIMEVAL(&basic_info.user_time, &thread_tv); |
289 | TIME_VALUE_TO_TIMEVAL(&basic_info.system_time, &tv); |
290 | timeradd(&thread_tv, &tv, &thread_tv); |
291 | uint64_t used_usec = thread_tv.tv_sec * 1000000ULL + thread_tv.tv_usec; |
292 | threads_used_usec.push_back(x: used_usec); |
293 | } |
294 | |
295 | mach_port_deallocate(mach_task_self(), threads[i]); |
296 | } |
297 | mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)(uintptr_t)threads, |
298 | tcnt * sizeof(*threads)); |
299 | } |
300 | |
301 | #define RAW_HEXBASE std::setfill('0') << std::hex << std::right |
302 | #define DECIMAL std::dec << std::setfill(' ') |
303 | std::string MachTask::GetProfileData(DNBProfileDataScanType scanType) { |
304 | std::string result; |
305 | |
306 | static int32_t numCPU = -1; |
307 | struct host_cpu_load_info host_info; |
308 | if (scanType & eProfileHostCPU) { |
309 | int32_t mib[] = {CTL_HW, HW_AVAILCPU}; |
310 | size_t len = sizeof(numCPU); |
311 | if (numCPU == -1) { |
312 | if (sysctl(mib, sizeof(mib) / sizeof(int32_t), &numCPU, &len, NULL, 0) != |
313 | 0) |
314 | return result; |
315 | } |
316 | |
317 | mach_port_t localHost = mach_host_self(); |
318 | mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; |
319 | kern_return_t kr = host_statistics(localHost, HOST_CPU_LOAD_INFO, |
320 | (host_info_t)&host_info, &count); |
321 | if (kr != KERN_SUCCESS) |
322 | return result; |
323 | } |
324 | |
325 | task_t task = TaskPort(); |
326 | if (task == TASK_NULL) |
327 | return result; |
328 | |
329 | pid_t pid = m_process->ProcessID(); |
330 | |
331 | struct task_basic_info task_info; |
332 | DNBError err; |
333 | err = BasicInfo(task, &task_info); |
334 | |
335 | if (!err.Success()) |
336 | return result; |
337 | |
338 | uint64_t elapsed_usec = 0; |
339 | uint64_t task_used_usec = 0; |
340 | if (scanType & eProfileCPU) { |
341 | // Get current used time. |
342 | struct timeval current_used_time; |
343 | struct timeval tv; |
344 | TIME_VALUE_TO_TIMEVAL(&task_info.user_time, ¤t_used_time); |
345 | TIME_VALUE_TO_TIMEVAL(&task_info.system_time, &tv); |
346 | timeradd(¤t_used_time, &tv, ¤t_used_time); |
347 | task_used_usec = |
348 | current_used_time.tv_sec * 1000000ULL + current_used_time.tv_usec; |
349 | |
350 | struct timeval current_elapsed_time; |
351 | int res = gettimeofday(tv: ¤t_elapsed_time, NULL); |
352 | if (res == 0) { |
353 | elapsed_usec = current_elapsed_time.tv_sec * 1000000ULL + |
354 | current_elapsed_time.tv_usec; |
355 | } |
356 | } |
357 | |
358 | std::vector<uint64_t> threads_id; |
359 | std::vector<std::string> threads_name; |
360 | std::vector<uint64_t> threads_used_usec; |
361 | |
362 | if (scanType & eProfileThreadsCPU) { |
363 | get_threads_profile_data(scanType, task, pid, threads_id, threads_name, |
364 | threads_used_usec); |
365 | } |
366 | |
367 | vm_statistics64_data_t vminfo; |
368 | uint64_t physical_memory = 0; |
369 | uint64_t anonymous = 0; |
370 | uint64_t = 0; |
371 | uint64_t memory_cap = 0; |
372 | if (m_vm_memory.GetMemoryProfile(scanType, task, task_info, |
373 | m_process->GetCPUType(), pid, vminfo, |
374 | physical_memory, anonymous, |
375 | phys_footprint, memory_cap)) { |
376 | std::ostringstream profile_data_stream; |
377 | |
378 | if (scanType & eProfileHostCPU) { |
379 | profile_data_stream << "num_cpu:" << numCPU << ';'; |
380 | profile_data_stream << "host_user_ticks:" |
381 | << host_info.cpu_ticks[CPU_STATE_USER] << ';'; |
382 | profile_data_stream << "host_sys_ticks:" |
383 | << host_info.cpu_ticks[CPU_STATE_SYSTEM] << ';'; |
384 | profile_data_stream << "host_idle_ticks:" |
385 | << host_info.cpu_ticks[CPU_STATE_IDLE] << ';'; |
386 | } |
387 | |
388 | if (scanType & eProfileCPU) { |
389 | profile_data_stream << "elapsed_usec:" << elapsed_usec << ';'; |
390 | profile_data_stream << "task_used_usec:" << task_used_usec << ';'; |
391 | } |
392 | |
393 | if (scanType & eProfileThreadsCPU) { |
394 | const size_t num_threads = threads_id.size(); |
395 | for (size_t i = 0; i < num_threads; i++) { |
396 | profile_data_stream << "thread_used_id:" << std::hex << threads_id[i] |
397 | << std::dec << ';'; |
398 | profile_data_stream << "thread_used_usec:" << threads_used_usec[i] |
399 | << ';'; |
400 | |
401 | if (scanType & eProfileThreadName) { |
402 | profile_data_stream << "thread_used_name:" ; |
403 | const size_t len = threads_name[i].size(); |
404 | if (len) { |
405 | const char *thread_name = threads_name[i].c_str(); |
406 | // Make sure that thread name doesn't interfere with our delimiter. |
407 | profile_data_stream << RAW_HEXBASE << std::setw(2); |
408 | const uint8_t *ubuf8 = (const uint8_t *)(thread_name); |
409 | for (size_t j = 0; j < len; j++) { |
410 | profile_data_stream << (uint32_t)(ubuf8[j]); |
411 | } |
412 | // Reset back to DECIMAL. |
413 | profile_data_stream << DECIMAL; |
414 | } |
415 | profile_data_stream << ';'; |
416 | } |
417 | } |
418 | } |
419 | |
420 | if (scanType & eProfileHostMemory) |
421 | profile_data_stream << "total:" << physical_memory << ';'; |
422 | |
423 | if (scanType & eProfileMemory) { |
424 | static vm_size_t pagesize = vm_kernel_page_size; |
425 | |
426 | // This mimicks Activity Monitor. |
427 | uint64_t total_used_count = |
428 | (physical_memory / pagesize) - |
429 | (vminfo.free_count - vminfo.speculative_count) - |
430 | vminfo.external_page_count - vminfo.purgeable_count; |
431 | profile_data_stream << "used:" << total_used_count * pagesize << ';'; |
432 | |
433 | if (scanType & eProfileMemoryAnonymous) { |
434 | profile_data_stream << "anonymous:" << anonymous << ';'; |
435 | } |
436 | |
437 | profile_data_stream << "phys_footprint:" << phys_footprint << ';'; |
438 | } |
439 | |
440 | if (scanType & eProfileMemoryCap) { |
441 | profile_data_stream << "mem_cap:" << memory_cap << ';'; |
442 | } |
443 | |
444 | #ifdef LLDB_ENERGY |
445 | if (scanType & eProfileEnergy) { |
446 | struct rusage_info_v2 info; |
447 | int rc = proc_pid_rusage(pid, RUSAGE_INFO_V2, (rusage_info_t *)&info); |
448 | if (rc == 0) { |
449 | uint64_t now = mach_absolute_time(); |
450 | pm_task_energy_data_t pm_energy; |
451 | memset(&pm_energy, 0, sizeof(pm_energy)); |
452 | /* |
453 | * Disable most features of pm_sample_pid. It will gather |
454 | * network/GPU/WindowServer information; fill in the rest. |
455 | */ |
456 | pm_sample_task_and_pid(task, pid, &pm_energy, now, |
457 | PM_SAMPLE_ALL & ~PM_SAMPLE_NAME & |
458 | ~PM_SAMPLE_INTERVAL & ~PM_SAMPLE_CPU & |
459 | ~PM_SAMPLE_DISK); |
460 | pm_energy.sti.total_user = info.ri_user_time; |
461 | pm_energy.sti.total_system = info.ri_system_time; |
462 | pm_energy.sti.task_interrupt_wakeups = info.ri_interrupt_wkups; |
463 | pm_energy.sti.task_platform_idle_wakeups = info.ri_pkg_idle_wkups; |
464 | pm_energy.diskio_bytesread = info.ri_diskio_bytesread; |
465 | pm_energy.diskio_byteswritten = info.ri_diskio_byteswritten; |
466 | pm_energy.pageins = info.ri_pageins; |
467 | |
468 | uint64_t total_energy = |
469 | (uint64_t)(pm_energy_impact(&pm_energy) * NSEC_PER_SEC); |
470 | // uint64_t process_age = now - info.ri_proc_start_abstime; |
471 | // uint64_t avg_energy = 100.0 * (double)total_energy / |
472 | // (double)process_age; |
473 | |
474 | profile_data_stream << "energy:" << total_energy << ';'; |
475 | } |
476 | } |
477 | #endif |
478 | |
479 | if (scanType & eProfileEnergyCPUCap) { |
480 | int percentage = -1; |
481 | int interval = -1; |
482 | int result = proc_get_cpumon_params(pid, percentage: &percentage, interval: &interval); |
483 | if ((result == 0) && (percentage >= 0) && (interval >= 0)) { |
484 | profile_data_stream << "cpu_cap_p:" << percentage << ';'; |
485 | profile_data_stream << "cpu_cap_t:" << interval << ';'; |
486 | } |
487 | } |
488 | |
489 | profile_data_stream << "--end--;" ; |
490 | |
491 | result = profile_data_stream.str(); |
492 | } |
493 | |
494 | return result; |
495 | } |
496 | |
497 | //---------------------------------------------------------------------- |
498 | // MachTask::TaskPortForProcessID |
499 | //---------------------------------------------------------------------- |
500 | task_t MachTask::TaskPortForProcessID(DNBError &err, bool force) { |
501 | if (((m_task == TASK_NULL) || force) && m_process != NULL) |
502 | m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err); |
503 | return m_task; |
504 | } |
505 | |
506 | //---------------------------------------------------------------------- |
507 | // MachTask::TaskPortForProcessID |
508 | //---------------------------------------------------------------------- |
509 | task_t MachTask::TaskPortForProcessID(pid_t pid, DNBError &err, |
510 | uint32_t num_retries, |
511 | uint32_t usec_interval) { |
512 | if (pid != INVALID_NUB_PROCESS) { |
513 | DNBError err; |
514 | mach_port_t task_self = mach_task_self(); |
515 | task_t task = TASK_NULL; |
516 | for (uint32_t i = 0; i < num_retries; i++) { |
517 | DNBLog("[LaunchAttach] (%d) about to task_for_pid(%d)" , getpid(), pid); |
518 | err = ::task_for_pid(task_self, pid, &task); |
519 | |
520 | if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) { |
521 | char str[1024]; |
522 | ::snprintf(s: str, maxlen: sizeof(str), format: "::task_for_pid ( target_tport = 0x%4.4x, " |
523 | "pid = %d, &task ) => err = 0x%8.8x (%s)" , |
524 | task_self, pid, err.Status(), |
525 | err.AsString() ? err.AsString() : "success" ); |
526 | if (err.Fail()) { |
527 | err.SetErrorString(str); |
528 | DNBLogError( |
529 | "[LaunchAttach] MachTask::TaskPortForProcessID task_for_pid(%d) " |
530 | "failed: %s" , |
531 | pid, str); |
532 | } |
533 | err.LogThreaded(str); |
534 | } |
535 | |
536 | if (err.Success()) { |
537 | DNBLog("[LaunchAttach] (%d) successfully task_for_pid(%d)'ed" , getpid(), |
538 | pid); |
539 | return task; |
540 | } |
541 | |
542 | // Sleep a bit and try again |
543 | ::usleep(useconds: usec_interval); |
544 | } |
545 | } |
546 | return TASK_NULL; |
547 | } |
548 | |
549 | //---------------------------------------------------------------------- |
550 | // MachTask::BasicInfo |
551 | //---------------------------------------------------------------------- |
552 | kern_return_t MachTask::BasicInfo(struct task_basic_info *info) { |
553 | return BasicInfo(TaskPort(), info); |
554 | } |
555 | |
556 | //---------------------------------------------------------------------- |
557 | // MachTask::BasicInfo |
558 | //---------------------------------------------------------------------- |
559 | kern_return_t MachTask::BasicInfo(task_t task, struct task_basic_info *info) { |
560 | if (info == NULL) |
561 | return KERN_INVALID_ARGUMENT; |
562 | |
563 | DNBError err; |
564 | mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; |
565 | err = ::task_info(task, TASK_BASIC_INFO, (task_info_t)info, &count); |
566 | const bool log_process = DNBLogCheckLogBit(LOG_TASK); |
567 | if (log_process || err.Fail()) |
568 | err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = " |
569 | "TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => " |
570 | "%u )" , |
571 | task, info, count); |
572 | if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && |
573 | err.Success()) { |
574 | float user = (float)info->user_time.seconds + |
575 | (float)info->user_time.microseconds / 1000000.0f; |
576 | float system = (float)info->user_time.seconds + |
577 | (float)info->user_time.microseconds / 1000000.0f; |
578 | DNBLogThreaded("task_basic_info = { suspend_count = %i, virtual_size = " |
579 | "0x%8.8llx, resident_size = 0x%8.8llx, user_time = %f, " |
580 | "system_time = %f }" , |
581 | info->suspend_count, (uint64_t)info->virtual_size, |
582 | (uint64_t)info->resident_size, user, system); |
583 | } |
584 | return err.Status(); |
585 | } |
586 | |
587 | //---------------------------------------------------------------------- |
588 | // MachTask::IsValid |
589 | // |
590 | // Returns true if a task is a valid task port for a current process. |
591 | //---------------------------------------------------------------------- |
592 | bool MachTask::IsValid() const { return MachTask::IsValid(TaskPort()); } |
593 | |
594 | //---------------------------------------------------------------------- |
595 | // MachTask::IsValid |
596 | // |
597 | // Returns true if a task is a valid task port for a current process. |
598 | //---------------------------------------------------------------------- |
599 | bool MachTask::IsValid(task_t task) { |
600 | if (task != TASK_NULL) { |
601 | struct task_basic_info task_info; |
602 | return BasicInfo(task, &task_info) == KERN_SUCCESS; |
603 | } |
604 | return false; |
605 | } |
606 | |
607 | bool MachTask::StartExceptionThread( |
608 | const RNBContext::IgnoredExceptions &ignored_exceptions, |
609 | DNBError &err) { |
610 | DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )" , __FUNCTION__); |
611 | |
612 | task_t task = TaskPortForProcessID(err); |
613 | if (MachTask::IsValid(task)) { |
614 | // Got the mach port for the current process |
615 | mach_port_t task_self = mach_task_self(); |
616 | |
617 | // Allocate an exception port that we will use to track our child process |
618 | err = ::mach_port_allocate(task_self, MACH_PORT_RIGHT_RECEIVE, |
619 | &m_exception_port); |
620 | if (err.Fail()) |
621 | return false; |
622 | |
623 | // Add the ability to send messages on the new exception port |
624 | err = ::mach_port_insert_right(task_self, m_exception_port, |
625 | m_exception_port, MACH_MSG_TYPE_MAKE_SEND); |
626 | if (err.Fail()) |
627 | return false; |
628 | |
629 | // Save the original state of the exception ports for our child process |
630 | SaveExceptionPortInfo(); |
631 | |
632 | // We weren't able to save the info for our exception ports, we must stop... |
633 | if (m_exc_port_info.mask == 0) { |
634 | err.SetErrorString("failed to get exception port info" ); |
635 | return false; |
636 | } |
637 | |
638 | if (!ignored_exceptions.empty()) { |
639 | for (exception_mask_t mask : ignored_exceptions) |
640 | m_exc_port_info.mask = m_exc_port_info.mask & ~mask; |
641 | } |
642 | |
643 | // Set the ability to get all exceptions on this port |
644 | err = ::task_set_exception_ports( |
645 | task, m_exc_port_info.mask, m_exception_port, |
646 | EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); |
647 | if (DNBLogCheckLogBit(LOG_EXCEPTIONS) || err.Fail()) { |
648 | err.LogThreaded("::task_set_exception_ports ( task = 0x%4.4x, " |
649 | "exception_mask = 0x%8.8x, new_port = 0x%4.4x, behavior " |
650 | "= 0x%8.8x, new_flavor = 0x%8.8x )" , |
651 | task, m_exc_port_info.mask, m_exception_port, |
652 | (EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES), |
653 | THREAD_STATE_NONE); |
654 | } |
655 | |
656 | if (err.Fail()) |
657 | return false; |
658 | |
659 | // Create the exception thread |
660 | err = ::pthread_create(newthread: &m_exception_thread, NULL, start_routine: MachTask::ExceptionThread, |
661 | arg: this); |
662 | return err.Success(); |
663 | } else { |
664 | DNBLogError("MachTask::%s (): task invalid, exception thread start failed." , |
665 | __FUNCTION__); |
666 | } |
667 | return false; |
668 | } |
669 | |
670 | kern_return_t MachTask::ShutDownExcecptionThread() { |
671 | DNBError err; |
672 | |
673 | err = RestoreExceptionPortInfo(); |
674 | |
675 | // NULL our exception port and let our exception thread exit |
676 | mach_port_t exception_port = m_exception_port; |
677 | m_exception_port = 0; |
678 | |
679 | err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX); |
680 | if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) |
681 | err.LogThreaded("::pthread_cancel ( thread = %p )" , m_exception_thread); |
682 | |
683 | err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX); |
684 | if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) |
685 | err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)" , |
686 | m_exception_thread); |
687 | |
688 | // Deallocate our exception port that we used to track our child process |
689 | mach_port_t task_self = mach_task_self(); |
690 | err = ::mach_port_deallocate(task_self, exception_port); |
691 | if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) |
692 | err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )" , |
693 | task_self, exception_port); |
694 | |
695 | m_exec_will_be_suspended = false; |
696 | m_do_double_resume = false; |
697 | |
698 | return err.Status(); |
699 | } |
700 | |
701 | void *MachTask::ExceptionThread(void *arg) { |
702 | if (arg == NULL) |
703 | return NULL; |
704 | |
705 | MachTask *mach_task = (MachTask *)arg; |
706 | MachProcess *mach_proc = mach_task->Process(); |
707 | DNBLogThreadedIf(LOG_EXCEPTIONS, |
708 | "MachTask::%s ( arg = %p ) starting thread..." , __FUNCTION__, |
709 | arg); |
710 | |
711 | #if defined(__APPLE__) |
712 | pthread_setname_np("exception monitoring thread" ); |
713 | #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) |
714 | struct sched_param thread_param; |
715 | int thread_sched_policy; |
716 | if (pthread_getschedparam(pthread_self(), &thread_sched_policy, |
717 | &thread_param) == 0) { |
718 | thread_param.sched_priority = 47; |
719 | pthread_setschedparam(pthread_self(), thread_sched_policy, &thread_param); |
720 | } |
721 | #endif |
722 | #endif |
723 | |
724 | // We keep a count of the number of consecutive exceptions received so |
725 | // we know to grab all exceptions without a timeout. We do this to get a |
726 | // bunch of related exceptions on our exception port so we can process |
727 | // then together. When we have multiple threads, we can get an exception |
728 | // per thread and they will come in consecutively. The main loop in this |
729 | // thread can stop periodically if needed to service things related to this |
730 | // process. |
731 | // flag set in the options, so we will wait forever for an exception on |
732 | // our exception port. After we get one exception, we then will use the |
733 | // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current |
734 | // exceptions for our process. After we have received the last pending |
735 | // exception, we will get a timeout which enables us to then notify |
736 | // our main thread that we have an exception bundle available. We then wait |
737 | // for the main thread to tell this exception thread to start trying to get |
738 | // exceptions messages again and we start again with a mach_msg read with |
739 | // infinite timeout. |
740 | uint32_t num_exceptions_received = 0; |
741 | DNBError err; |
742 | task_t task = mach_task->TaskPort(); |
743 | mach_msg_timeout_t periodic_timeout = 0; |
744 | |
745 | #if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS) |
746 | mach_msg_timeout_t watchdog_elapsed = 0; |
747 | mach_msg_timeout_t watchdog_timeout = 60 * 1000; |
748 | pid_t pid = mach_proc->ProcessID(); |
749 | CFReleaser<SBSWatchdogAssertionRef> watchdog; |
750 | |
751 | if (mach_proc->ProcessUsingSpringBoard()) { |
752 | // Request a renewal for every 60 seconds if we attached using SpringBoard |
753 | watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60)); |
754 | DNBLogThreadedIf( |
755 | LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p" , |
756 | pid, watchdog.get()); |
757 | |
758 | if (watchdog.get()) { |
759 | ::SBSWatchdogAssertionRenew(watchdog.get()); |
760 | |
761 | CFTimeInterval watchdogRenewalInterval = |
762 | ::SBSWatchdogAssertionGetRenewalInterval(watchdog.get()); |
763 | DNBLogThreadedIf( |
764 | LOG_TASK, |
765 | "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds" , |
766 | watchdog.get(), watchdogRenewalInterval); |
767 | if (watchdogRenewalInterval > 0.0) { |
768 | watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000; |
769 | if (watchdog_timeout > 3000) |
770 | watchdog_timeout -= 1000; // Give us a second to renew our timeout |
771 | else if (watchdog_timeout > 1000) |
772 | watchdog_timeout -= |
773 | 250; // Give us a quarter of a second to renew our timeout |
774 | } |
775 | } |
776 | if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout) |
777 | periodic_timeout = watchdog_timeout; |
778 | } |
779 | #endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS) |
780 | |
781 | #ifdef WITH_BKS |
782 | CFReleaser<BKSWatchdogAssertionRef> watchdog; |
783 | if (mach_proc->ProcessUsingBackBoard()) { |
784 | pid_t pid = mach_proc->ProcessID(); |
785 | CFAllocatorRef alloc = kCFAllocatorDefault; |
786 | watchdog.reset(::BKSWatchdogAssertionCreateForPID(alloc, pid)); |
787 | } |
788 | #endif // #ifdef WITH_BKS |
789 | |
790 | while (mach_task->ExceptionPortIsValid()) { |
791 | ::pthread_testcancel(); |
792 | |
793 | MachException::Message exception_message; |
794 | |
795 | if (num_exceptions_received > 0) { |
796 | // No timeout, just receive as many exceptions as we can since we already |
797 | // have one and we want |
798 | // to get all currently available exceptions for this task |
799 | err = exception_message.Receive( |
800 | mach_task->ExceptionPort(), |
801 | MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 1); |
802 | } else if (periodic_timeout > 0) { |
803 | // We need to stop periodically in this loop, so try and get a mach |
804 | // message with a valid timeout (ms) |
805 | err = exception_message.Receive(mach_task->ExceptionPort(), |
806 | MACH_RCV_MSG | MACH_RCV_INTERRUPT | |
807 | MACH_RCV_TIMEOUT, |
808 | periodic_timeout); |
809 | } else { |
810 | // We don't need to parse all current exceptions or stop periodically, |
811 | // just wait for an exception forever. |
812 | err = exception_message.Receive(mach_task->ExceptionPort(), |
813 | MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0); |
814 | } |
815 | |
816 | if (err.Status() == MACH_RCV_INTERRUPTED) { |
817 | // If we have no task port we should exit this thread |
818 | if (!mach_task->ExceptionPortIsValid()) { |
819 | DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled..." ); |
820 | break; |
821 | } |
822 | |
823 | // Make sure our task is still valid |
824 | if (MachTask::IsValid(task)) { |
825 | // Task is still ok |
826 | DNBLogThreadedIf(LOG_EXCEPTIONS, |
827 | "interrupted, but task still valid, continuing..." ); |
828 | continue; |
829 | } else { |
830 | DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..." ); |
831 | mach_proc->SetState(eStateExited); |
832 | // Our task has died, exit the thread. |
833 | break; |
834 | } |
835 | } else if (err.Status() == MACH_RCV_TIMED_OUT) { |
836 | if (num_exceptions_received > 0) { |
837 | // We were receiving all current exceptions with a timeout of zero |
838 | // it is time to go back to our normal looping mode |
839 | num_exceptions_received = 0; |
840 | |
841 | // Notify our main thread we have a complete exception message |
842 | // bundle available and get the possibly updated task port back |
843 | // from the process in case we exec'ed and our task port changed |
844 | task = mach_proc->ExceptionMessageBundleComplete(); |
845 | |
846 | // in case we use a timeout value when getting exceptions... |
847 | // Make sure our task is still valid |
848 | if (MachTask::IsValid(task)) { |
849 | // Task is still ok |
850 | DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing..." ); |
851 | continue; |
852 | } else { |
853 | DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited..." ); |
854 | mach_proc->SetState(eStateExited); |
855 | // Our task has died, exit the thread. |
856 | break; |
857 | } |
858 | } |
859 | |
860 | #if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS) |
861 | if (watchdog.get()) { |
862 | watchdog_elapsed += periodic_timeout; |
863 | if (watchdog_elapsed >= watchdog_timeout) { |
864 | DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )" , |
865 | watchdog.get()); |
866 | ::SBSWatchdogAssertionRenew(watchdog.get()); |
867 | watchdog_elapsed = 0; |
868 | } |
869 | } |
870 | #endif |
871 | } else if (err.Status() != KERN_SUCCESS) { |
872 | DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something " |
873 | "about it??? nah, continuing for " |
874 | "now..." ); |
875 | // TODO: notify of error? |
876 | } else { |
877 | if (exception_message.CatchExceptionRaise(task)) { |
878 | if (exception_message.state.task_port != task) { |
879 | if (exception_message.state.IsValid()) { |
880 | // We exec'ed and our task port changed on us. |
881 | DNBLogThreadedIf(LOG_EXCEPTIONS, |
882 | "task port changed from 0x%4.4x to 0x%4.4x" , |
883 | task, exception_message.state.task_port); |
884 | task = exception_message.state.task_port; |
885 | mach_task->TaskPortChanged(exception_message.state.task_port); |
886 | } |
887 | } |
888 | ++num_exceptions_received; |
889 | mach_proc->ExceptionMessageReceived(exceptionMessage: exception_message); |
890 | } |
891 | } |
892 | } |
893 | |
894 | #if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS) |
895 | if (watchdog.get()) { |
896 | // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel |
897 | // when we |
898 | // all are up and running on systems that support it. The SBS framework has |
899 | // a #define |
900 | // that will forward SBSWatchdogAssertionRelease to |
901 | // SBSWatchdogAssertionCancel for now |
902 | // so it should still build either way. |
903 | DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)" , |
904 | watchdog.get()); |
905 | ::SBSWatchdogAssertionRelease(watchdog.get()); |
906 | } |
907 | #endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS) |
908 | |
909 | DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting..." , |
910 | __FUNCTION__, arg); |
911 | return NULL; |
912 | } |
913 | |
914 | // So the TASK_DYLD_INFO used to just return the address of the all image infos |
915 | // as a single member called "all_image_info". Then someone decided it would be |
916 | // a good idea to rename this first member to "all_image_info_addr" and add a |
917 | // size member called "all_image_info_size". This of course can not be detected |
918 | // using code or #defines. So to hack around this problem, we define our own |
919 | // version of the TASK_DYLD_INFO structure so we can guarantee what is inside |
920 | // it. |
921 | |
922 | struct hack_task_dyld_info { |
923 | mach_vm_address_t all_image_info_addr; |
924 | mach_vm_size_t all_image_info_size; |
925 | }; |
926 | |
927 | nub_addr_t MachTask::GetDYLDAllImageInfosAddress(DNBError &err) { |
928 | struct hack_task_dyld_info dyld_info; |
929 | mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; |
930 | // Make sure that COUNT isn't bigger than our hacked up struct |
931 | // hack_task_dyld_info. |
932 | // If it is, then make COUNT smaller to match. |
933 | if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t))) |
934 | count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)); |
935 | |
936 | task_t task = TaskPortForProcessID(err); |
937 | if (err.Success()) { |
938 | err = ::task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); |
939 | if (err.Success()) { |
940 | // We now have the address of the all image infos structure |
941 | return dyld_info.all_image_info_addr; |
942 | } |
943 | } |
944 | return INVALID_NUB_ADDRESS; |
945 | } |
946 | |
947 | //---------------------------------------------------------------------- |
948 | // MachTask::AllocateMemory |
949 | //---------------------------------------------------------------------- |
950 | nub_addr_t MachTask::AllocateMemory(size_t size, uint32_t permissions) { |
951 | mach_vm_address_t addr; |
952 | task_t task = TaskPort(); |
953 | if (task == TASK_NULL) |
954 | return INVALID_NUB_ADDRESS; |
955 | |
956 | DNBError err; |
957 | err = ::mach_vm_allocate(task, &addr, size, TRUE); |
958 | if (err.Status() == KERN_SUCCESS) { |
959 | // Set the protections: |
960 | vm_prot_t mach_prot = VM_PROT_NONE; |
961 | if (permissions & eMemoryPermissionsReadable) |
962 | mach_prot |= VM_PROT_READ; |
963 | if (permissions & eMemoryPermissionsWritable) |
964 | mach_prot |= VM_PROT_WRITE; |
965 | if (permissions & eMemoryPermissionsExecutable) |
966 | mach_prot |= VM_PROT_EXECUTE; |
967 | |
968 | err = ::mach_vm_protect(task, addr, size, 0, mach_prot); |
969 | if (err.Status() == KERN_SUCCESS) { |
970 | m_allocations.insert(std::make_pair(addr, size)); |
971 | return addr; |
972 | } |
973 | ::mach_vm_deallocate(task, addr, size); |
974 | } |
975 | return INVALID_NUB_ADDRESS; |
976 | } |
977 | |
978 | //---------------------------------------------------------------------- |
979 | // MachTask::DeallocateMemory |
980 | //---------------------------------------------------------------------- |
981 | nub_bool_t MachTask::DeallocateMemory(nub_addr_t addr) { |
982 | task_t task = TaskPort(); |
983 | if (task == TASK_NULL) |
984 | return false; |
985 | |
986 | // We have to stash away sizes for the allocations... |
987 | allocation_collection::iterator pos, end = m_allocations.end(); |
988 | for (pos = m_allocations.begin(); pos != end; pos++) { |
989 | if ((*pos).first == addr) { |
990 | size_t size = (*pos).second; |
991 | m_allocations.erase(pos); |
992 | #define ALWAYS_ZOMBIE_ALLOCATIONS 0 |
993 | if (ALWAYS_ZOMBIE_ALLOCATIONS || |
994 | getenv("DEBUGSERVER_ZOMBIE_ALLOCATIONS" )) { |
995 | ::mach_vm_protect(task, addr, size, 0, VM_PROT_NONE); |
996 | return true; |
997 | } else |
998 | return ::mach_vm_deallocate(task, addr, size) == KERN_SUCCESS; |
999 | } |
1000 | } |
1001 | return false; |
1002 | } |
1003 | |
1004 | //---------------------------------------------------------------------- |
1005 | // MachTask::ClearAllocations |
1006 | //---------------------------------------------------------------------- |
1007 | void MachTask::ClearAllocations() { |
1008 | m_allocations.clear(); |
1009 | } |
1010 | |
1011 | void MachTask::TaskPortChanged(task_t task) |
1012 | { |
1013 | m_task = task; |
1014 | |
1015 | // If we've just exec'd to a new process, and it |
1016 | // is started suspended, we'll need to do two |
1017 | // task_resume's to get the inferior process to |
1018 | // continue. |
1019 | if (m_exec_will_be_suspended) |
1020 | m_do_double_resume = true; |
1021 | else |
1022 | m_do_double_resume = false; |
1023 | m_exec_will_be_suspended = false; |
1024 | } |
1025 | |