blob: 68d858c014ce0a454cfba2805f2a2a68ee3deb4c [file] [log] [blame]
Chris Lattner30fdc8d2010-06-08 16:52:24 +00001//===-- 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
25// C++ Includes
26// Other libraries and framework includes
27// Project includes
28#include "CFUtils.h"
29#include "DNB.h"
30#include "DNBError.h"
31#include "DNBLog.h"
32#include "MachProcess.h"
33#include "DNBDataRef.h"
34
35#if defined (__arm__)
36
37#include <CoreFoundation/CoreFoundation.h>
38#include <SpringBoardServices/SpringBoardServer.h>
39#include <SpringBoardServices/SBSWatchdogAssertion.h>
40
41#endif
42
43//----------------------------------------------------------------------
44// MachTask constructor
45//----------------------------------------------------------------------
46MachTask::MachTask(MachProcess *process) :
47 m_process (process),
48 m_task (TASK_NULL),
49 m_vm_memory (),
50 m_exception_thread (0),
51 m_exception_port (MACH_PORT_NULL)
52{
53 memset(&m_exc_port_info, 0, sizeof(m_exc_port_info));
54
55}
56
57//----------------------------------------------------------------------
58// Destructor
59//----------------------------------------------------------------------
60MachTask::~MachTask()
61{
62 Clear();
63}
64
65
66//----------------------------------------------------------------------
67// MachTask::Suspend
68//----------------------------------------------------------------------
69kern_return_t
70MachTask::Suspend()
71{
72 DNBError err;
73 task_t task = TaskPort();
74 err = ::task_suspend (task);
75 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
76 err.LogThreaded("::task_suspend ( target_task = 0x%4.4x )", task);
77 return err.Error();
78}
79
80
81//----------------------------------------------------------------------
82// MachTask::Resume
83//----------------------------------------------------------------------
84kern_return_t
85MachTask::Resume()
86{
87 struct task_basic_info task_info;
88 task_t task = TaskPort();
89
90 DNBError err;
91 err = BasicInfo(task, &task_info);
92
93 if (err.Success())
94 {
95 integer_t i;
96 for (i=0; i<task_info.suspend_count; i++)
97 {
98 err = ::task_resume (task);
99 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
100 err.LogThreaded("::task_resume ( target_task = 0x%4.4x )", task);
101 }
102 }
103 return err.Error();
104}
105
106//----------------------------------------------------------------------
107// MachTask::ExceptionPort
108//----------------------------------------------------------------------
109mach_port_t
110MachTask::ExceptionPort() const
111{
112 return m_exception_port;
113}
114
115//----------------------------------------------------------------------
116// MachTask::ExceptionPortIsValid
117//----------------------------------------------------------------------
118bool
119MachTask::ExceptionPortIsValid() const
120{
121 return MACH_PORT_VALID(m_exception_port);
122}
123
124
125//----------------------------------------------------------------------
126// MachTask::Clear
127//----------------------------------------------------------------------
128void
129MachTask::Clear()
130{
131 // Do any cleanup needed for this task
132 m_task = TASK_NULL;
133 m_exception_thread = 0;
134 m_exception_port = MACH_PORT_NULL;
135
136}
137
138
139//----------------------------------------------------------------------
140// MachTask::SaveExceptionPortInfo
141//----------------------------------------------------------------------
142kern_return_t
143MachTask::SaveExceptionPortInfo()
144{
145 return m_exc_port_info.Save(TaskPort());
146}
147
148//----------------------------------------------------------------------
149// MachTask::RestoreExceptionPortInfo
150//----------------------------------------------------------------------
151kern_return_t
152MachTask::RestoreExceptionPortInfo()
153{
154 return m_exc_port_info.Restore(TaskPort());
155}
156
157
158//----------------------------------------------------------------------
159// MachTask::ReadMemory
160//----------------------------------------------------------------------
161nub_size_t
162MachTask::ReadMemory (nub_addr_t addr, nub_size_t size, void *buf)
163{
164 nub_size_t n = 0;
165 task_t task = TaskPort();
166 if (task != TASK_NULL)
167 {
168 n = m_vm_memory.Read(task, addr, buf, size);
169
170 DNBLogThreadedIf(LOG_MEMORY, "MachTask::ReadMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes read", (uint64_t)addr, size, buf, n);
171 if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8))
172 {
173 DNBDataRef data((uint8_t*)buf, n, false);
174 data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16);
175 }
176 }
177 return n;
178}
179
180
181//----------------------------------------------------------------------
182// MachTask::WriteMemory
183//----------------------------------------------------------------------
184nub_size_t
185MachTask::WriteMemory (nub_addr_t addr, nub_size_t size, const void *buf)
186{
187 nub_size_t n = 0;
188 task_t task = TaskPort();
189 if (task != TASK_NULL)
190 {
191 n = m_vm_memory.Write(task, addr, buf, size);
192 DNBLogThreadedIf(LOG_MEMORY, "MachTask::WriteMemory ( addr = 0x%8.8llx, size = %zu, buf = %8.8p) => %u bytes written", (uint64_t)addr, size, buf, n);
193 if (DNBLogCheckLogBit(LOG_MEMORY_DATA_LONG) || (DNBLogCheckLogBit(LOG_MEMORY_DATA_SHORT) && size <= 8))
194 {
195 DNBDataRef data((uint8_t*)buf, n, false);
196 data.Dump(0, n, addr, DNBDataRef::TypeUInt8, 16);
197 }
198 }
199 return n;
200}
201
202//----------------------------------------------------------------------
203// MachTask::TaskPortForProcessID
204//----------------------------------------------------------------------
205task_t
206MachTask::TaskPortForProcessID (DNBError &err)
207{
208 if (m_task == TASK_NULL && m_process != NULL)
209 m_task = MachTask::TaskPortForProcessID(m_process->ProcessID(), err);
210 return m_task;
211}
212
213//----------------------------------------------------------------------
214// MachTask::TaskPortForProcessID
215//----------------------------------------------------------------------
216task_t
217MachTask::TaskPortForProcessID (pid_t pid, DNBError &err)
218{
219 task_t task = TASK_NULL;
220 if (pid != INVALID_NUB_PROCESS)
221 {
222 mach_port_t task_self = mach_task_self ();
223 err = ::task_for_pid ( task_self, pid, &task);
224 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
225 {
226 char str[1024];
227 ::snprintf (str,
228 sizeof(str),
229 "::task_for_pid ( task_self, pid = %d, task => TASK_NULL (0x%4.4x) ) uid=%u, euid=%u gid=%u egid=%u (%s)",
230 pid,
231 task,
232 getuid(),
233 geteuid(),
234 getgid(),
235 getegid(),
236 err.AsString() ? err.AsString() : "success");
237 if (err.Fail())
238 err.SetErrorString(str);
239 err.LogThreaded(str);
240 }
241 }
242 return task;
243}
244
245
246//----------------------------------------------------------------------
247// MachTask::BasicInfo
248//----------------------------------------------------------------------
249kern_return_t
250MachTask::BasicInfo(struct task_basic_info *info)
251{
252 return BasicInfo (TaskPort(), info);
253}
254
255//----------------------------------------------------------------------
256// MachTask::BasicInfo
257//----------------------------------------------------------------------
258kern_return_t
259MachTask::BasicInfo(task_t task, struct task_basic_info *info)
260{
261 if (info == NULL)
262 return KERN_INVALID_ARGUMENT;
263
264 DNBError err;
265 mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
266 err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count);
267 const bool log_process = DNBLogCheckLogBit(LOG_TASK);
268 if (log_process || err.Fail())
269 err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count);
270 if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && err.Success())
271 {
272 float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
273 float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
274 DNBLogThreaded("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8x, resident_size = 0x%8.8x, user_time = %f, system_time = %f }",
275 info->suspend_count, info->virtual_size, info->resident_size, user, system);
276 }
277 return err.Error();
278}
279
280
281//----------------------------------------------------------------------
282// MachTask::IsValid
283//
284// Returns true if a task is a valid task port for a current process.
285//----------------------------------------------------------------------
286bool
287MachTask::IsValid () const
288{
289 return MachTask::IsValid(TaskPort());
290}
291
292//----------------------------------------------------------------------
293// MachTask::IsValid
294//
295// Returns true if a task is a valid task port for a current process.
296//----------------------------------------------------------------------
297bool
298MachTask::IsValid (task_t task)
299{
300 if (task != TASK_NULL)
301 {
302 struct task_basic_info task_info;
303 return BasicInfo(task, &task_info) == KERN_SUCCESS;
304 }
305 return false;
306}
307
308
309bool
310MachTask::StartExceptionThread(DNBError &err)
311{
312 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__);
313 task_t task = TaskPortForProcessID(err);
314 if (MachTask::IsValid(task))
315 {
316 // Got the mach port for the current process
317 mach_port_t task_self = mach_task_self ();
318
319 // Allocate an exception port that we will use to track our child process
320 err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port);
321 if (err.Fail())
322 return false;
323
324 // Add the ability to send messages on the new exception port
325 err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND);
326 if (err.Fail())
327 return false;
328
329 // Save the original state of the exception ports for our child process
330 SaveExceptionPortInfo();
331
332 // Set the ability to get all exceptions on this port
333 err = ::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
334 if (err.Fail())
335 return false;
336
337 // Create the exception thread
338 err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this);
339 return err.Success();
340 }
341 else
342 {
343 DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__);
344 }
345 return false;
346}
347
348kern_return_t
349MachTask::ShutDownExcecptionThread()
350{
351 DNBError err;
352
353 err = RestoreExceptionPortInfo();
354
355 // NULL our our exception port and let our exception thread exit
356 mach_port_t exception_port = m_exception_port;
357 m_exception_port = NULL;
358
359 err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX);
360 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
361 err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread);
362
363 err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX);
364 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
365 err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread);
366
367 // Deallocate our exception port that we used to track our child process
368 mach_port_t task_self = mach_task_self ();
369 err = ::mach_port_deallocate (task_self, exception_port);
370 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
371 err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port);
372 exception_port = NULL;
373
374 return err.Error();
375}
376
377
378void *
379MachTask::ExceptionThread (void *arg)
380{
381 if (arg == NULL)
382 return NULL;
383
384 MachTask *mach_task = (MachTask*) arg;
385 MachProcess *mach_proc = mach_task->Process();
386 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__, arg);
387
388 // We keep a count of the number of consecutive exceptions received so
389 // we know to grab all exceptions without a timeout. We do this to get a
390 // bunch of related exceptions on our exception port so we can process
391 // then together. When we have multiple threads, we can get an exception
392 // per thread and they will come in consecutively. The main loop in this
393 // thread can stop periodically if needed to service things related to this
394 // process.
395 // flag set in the options, so we will wait forever for an exception on
396 // our exception port. After we get one exception, we then will use the
397 // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
398 // exceptions for our process. After we have received the last pending
399 // exception, we will get a timeout which enables us to then notify
400 // our main thread that we have an exception bundle avaiable. We then wait
401 // for the main thread to tell this exception thread to start trying to get
402 // exceptions messages again and we start again with a mach_msg read with
403 // infinite timeout.
404 uint32_t num_exceptions_received = 0;
405 DNBError err;
406 task_t task = mach_task->TaskPort();
407 mach_msg_timeout_t periodic_timeout = 0;
408
409#if defined (__arm__)
410 mach_msg_timeout_t watchdog_elapsed = 0;
411 mach_msg_timeout_t watchdog_timeout = 60 * 1000;
412 pid_t pid = mach_proc->ProcessID();
413 CFReleaser<SBSWatchdogAssertionRef> watchdog;
414
415 if (mach_proc->ProcessUsingSpringBoard())
416 {
417 // Request a renewal for every 60 seconds if we attached using SpringBoard
418 watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60));
419 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get());
420
421 if (watchdog.get())
422 {
423 ::SBSWatchdogAssertionRenew (watchdog.get());
424
425 CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get());
426 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval);
427 if (watchdogRenewalInterval > 0.0)
428 {
429 watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000;
430 if (watchdog_timeout > 3000)
431 watchdog_timeout -= 1000; // Give us a second to renew our timeout
432 else if (watchdog_timeout > 1000)
433 watchdog_timeout -= 250; // Give us a quarter of a second to renew our timeout
434 }
435 }
436 if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout)
437 periodic_timeout = watchdog_timeout;
438 }
439#endif // #if defined (__arm__)
440
441 while (mach_task->ExceptionPortIsValid())
442 {
443 ::pthread_testcancel ();
444
445 MachException::Message exception_message;
446
447
448 if (num_exceptions_received > 0)
449 {
450 // No timeout, just receive as many exceptions as we can since we already have one and we want
451 // to get all currently available exceptions for this task
452 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0);
453 }
454 else if (periodic_timeout > 0)
455 {
456 // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms)
457 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout);
458 }
459 else
460 {
461 // We don't need to parse all current exceptions or stop periodically,
462 // just wait for an exception forever.
463 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0);
464 }
465
466 if (err.Error() == MACH_RCV_INTERRUPTED)
467 {
468 // If we have no task port we should exit this thread
469 if (!mach_task->ExceptionPortIsValid())
470 {
471 DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled...");
472 break;
473 }
474
475 // Make sure our task is still valid
476 if (MachTask::IsValid(task))
477 {
478 // Task is still ok
479 DNBLogThreadedIf(LOG_EXCEPTIONS, "interrupted, but task still valid, continuing...");
480 continue;
481 }
482 else
483 {
484 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
485 mach_proc->SetState(eStateExited);
486 // Our task has died, exit the thread.
487 break;
488 }
489 }
490 else if (err.Error() == MACH_RCV_TIMED_OUT)
491 {
492 if (num_exceptions_received > 0)
493 {
494 // We were receiving all current exceptions with a timeout of zero
495 // it is time to go back to our normal looping mode
496 num_exceptions_received = 0;
497
498 // Notify our main thread we have a complete exception message
499 // bundle available.
500 mach_proc->ExceptionMessageBundleComplete();
501
502 // in case we use a timeout value when getting exceptions...
503 // Make sure our task is still valid
504 if (MachTask::IsValid(task))
505 {
506 // Task is still ok
507 DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing...");
508 continue;
509 }
510 else
511 {
512 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
513 mach_proc->SetState(eStateExited);
514 // Our task has died, exit the thread.
515 break;
516 }
517 continue;
518 }
519
520#if defined (__arm__)
521 if (watchdog.get())
522 {
523 watchdog_elapsed += periodic_timeout;
524 if (watchdog_elapsed >= watchdog_timeout)
525 {
526 DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get());
527 ::SBSWatchdogAssertionRenew (watchdog.get());
528 watchdog_elapsed = 0;
529 }
530 }
531#endif
532 }
533 else if (err.Error() != KERN_SUCCESS)
534 {
535 DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something about it??? nah, continuing for now...");
536 // TODO: notify of error?
537 }
538 else
539 {
540 if (exception_message.CatchExceptionRaise())
541 {
542 ++num_exceptions_received;
543 mach_proc->ExceptionMessageReceived(exception_message);
544 }
545 }
546 }
547
548#if defined (__arm__)
549 if (watchdog.get())
550 {
551 // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we
552 // all are up and running on systems that support it. The SBS framework has a #define
553 // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now
554 // so it should still build either way.
555 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get());
556 ::SBSWatchdogAssertionRelease (watchdog.get());
557 }
558#endif // #if defined (__arm__)
559
560 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting...", __FUNCTION__, arg);
561 return NULL;
562}
563
564
565// So the TASK_DYLD_INFO used to just return the address of the all image infos
566// as a single member called "all_image_info". Then someone decided it would be
567// a good idea to rename this first member to "all_image_info_addr" and add a
568// size member called "all_image_info_size". This of course can not be detected
569// using code or #defines. So to hack around this problem, we define our own
570// version of the TASK_DYLD_INFO structure so we can guarantee what is inside it.
571
572struct hack_task_dyld_info {
573 mach_vm_address_t all_image_info_addr;
574 mach_vm_size_t all_image_info_size;
575};
576
577nub_addr_t
578MachTask::GetDYLDAllImageInfosAddress (DNBError& err)
579{
580 struct hack_task_dyld_info dyld_info;
581 mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
582 // Make sure that COUNT isn't bigger than our hacked up struct hack_task_dyld_info.
583 // If it is, then make COUNT smaller to match.
584 if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)))
585 count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t));
586
587 task_t task = TaskPortForProcessID (err);
588 if (err.Success())
589 {
590 err = ::task_info (task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
591 if (err.Success())
592 {
593 // We now have the address of the all image infos structure
594 return dyld_info.all_image_info_addr;
595 }
596 }
597 return INVALID_NUB_ADDRESS;
598}
599
600
601//----------------------------------------------------------------------
602// MachTask::AllocateMemory
603//----------------------------------------------------------------------
604nub_addr_t
605MachTask::AllocateMemory (size_t size, uint32_t permissions)
606{
607 mach_vm_address_t addr;
608 task_t task = TaskPort();
609 if (task == TASK_NULL)
610 return INVALID_NUB_ADDRESS;
611
612 DNBError err;
613 err = ::mach_vm_allocate (task, &addr, size, TRUE);
614 if (err.Error() == KERN_SUCCESS)
615 {
616 // Set the protections:
617 vm_prot_t mach_prot = 0;
618 if (permissions & eMemoryPermissionsReadable)
619 mach_prot |= VM_PROT_READ;
620 if (permissions & eMemoryPermissionsWritable)
621 mach_prot |= VM_PROT_WRITE;
622 if (permissions & eMemoryPermissionsExecutable)
623 mach_prot |= VM_PROT_EXECUTE;
624
625
626 err = ::mach_vm_protect (task, addr, size, 0, mach_prot);
627 if (err.Error() == KERN_SUCCESS)
628 {
629 m_allocations.insert (std::make_pair(addr, size));
630 return addr;
631 }
632 ::mach_vm_deallocate (task, addr, size);
633 }
634 return INVALID_NUB_ADDRESS;
635}
636
637//----------------------------------------------------------------------
638// MachTask::DeallocateMemory
639//----------------------------------------------------------------------
640nub_bool_t
641MachTask::DeallocateMemory (nub_addr_t addr)
642{
643 task_t task = TaskPort();
644 if (task == TASK_NULL)
645 return false;
646
647 // We have to stash away sizes for the allocations...
648 allocation_collection::iterator pos, end = m_allocations.end();
649 for (pos = m_allocations.begin(); pos != end; pos++)
650 {
651 if ((*pos).first == addr)
652 {
653 m_allocations.erase(pos);
654 return ::mach_vm_deallocate (task, (*pos).first, (*pos).second) == KERN_SUCCESS;
655 }
656
657 }
658 return false;
659}
660