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