blob: 61ace434ec53e07a7d36997cff4c7e6ead48fd46 [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
Greg Claytoneae9cc62010-09-30 18:10:44 +0000217MachTask::TaskPortForProcessID (pid_t pid, DNBError &err, uint32_t num_retries, uint32_t usec_interval)
Chris Lattner30fdc8d2010-06-08 16:52:24 +0000218{
Greg Claytoneae9cc62010-09-30 18:10:44 +0000219 if (pid != INVALID_NUB_PROCESS)
220 {
221 DNBError err;
222 mach_port_t task_self = mach_task_self ();
223 task_t task = TASK_NULL;
224 for (uint32_t i=0; i<num_retries; i++)
225 {
226 err = ::task_for_pid ( task_self, pid, &task);
227
228 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
229 {
230 char str[1024];
231 ::snprintf (str,
232 sizeof(str),
233 "::task_for_pid ( target_tport = 0x%4.4x, pid = %d, &task ) => err = 0x%8.8x (%s)",
234 task_self,
235 pid,
236 err.Error(),
237 err.AsString() ? err.AsString() : "success");
238 if (err.Fail())
239 err.SetErrorString(str);
240 err.LogThreaded(str);
241 }
242
243 if (err.Success())
244 return task;
245
246 // Sleep a bit and try again
247 ::usleep (usec_interval);
248 }
249 }
250 return TASK_NULL;
Chris Lattner30fdc8d2010-06-08 16:52:24 +0000251}
252
253
254//----------------------------------------------------------------------
255// MachTask::BasicInfo
256//----------------------------------------------------------------------
257kern_return_t
258MachTask::BasicInfo(struct task_basic_info *info)
259{
260 return BasicInfo (TaskPort(), info);
261}
262
263//----------------------------------------------------------------------
264// MachTask::BasicInfo
265//----------------------------------------------------------------------
266kern_return_t
267MachTask::BasicInfo(task_t task, struct task_basic_info *info)
268{
269 if (info == NULL)
270 return KERN_INVALID_ARGUMENT;
271
272 DNBError err;
273 mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
274 err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count);
275 const bool log_process = DNBLogCheckLogBit(LOG_TASK);
276 if (log_process || err.Fail())
277 err.LogThreaded("::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count);
278 if (DNBLogCheckLogBit(LOG_TASK) && DNBLogCheckLogBit(LOG_VERBOSE) && err.Success())
279 {
280 float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
281 float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f;
282 DNBLogThreaded("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8x, resident_size = 0x%8.8x, user_time = %f, system_time = %f }",
283 info->suspend_count, info->virtual_size, info->resident_size, user, system);
284 }
285 return err.Error();
286}
287
288
289//----------------------------------------------------------------------
290// MachTask::IsValid
291//
292// Returns true if a task is a valid task port for a current process.
293//----------------------------------------------------------------------
294bool
295MachTask::IsValid () const
296{
297 return MachTask::IsValid(TaskPort());
298}
299
300//----------------------------------------------------------------------
301// MachTask::IsValid
302//
303// Returns true if a task is a valid task port for a current process.
304//----------------------------------------------------------------------
305bool
306MachTask::IsValid (task_t task)
307{
308 if (task != TASK_NULL)
309 {
310 struct task_basic_info task_info;
311 return BasicInfo(task, &task_info) == KERN_SUCCESS;
312 }
313 return false;
314}
315
316
317bool
318MachTask::StartExceptionThread(DNBError &err)
319{
320 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( )", __FUNCTION__);
321 task_t task = TaskPortForProcessID(err);
322 if (MachTask::IsValid(task))
323 {
324 // Got the mach port for the current process
325 mach_port_t task_self = mach_task_self ();
326
327 // Allocate an exception port that we will use to track our child process
328 err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port);
329 if (err.Fail())
330 return false;
331
332 // Add the ability to send messages on the new exception port
333 err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND);
334 if (err.Fail())
335 return false;
336
337 // Save the original state of the exception ports for our child process
338 SaveExceptionPortInfo();
339
340 // Set the ability to get all exceptions on this port
341 err = ::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
342 if (err.Fail())
343 return false;
344
345 // Create the exception thread
346 err = ::pthread_create (&m_exception_thread, NULL, MachTask::ExceptionThread, this);
347 return err.Success();
348 }
349 else
350 {
351 DNBLogError("MachTask::%s (): task invalid, exception thread start failed.", __FUNCTION__);
352 }
353 return false;
354}
355
356kern_return_t
357MachTask::ShutDownExcecptionThread()
358{
359 DNBError err;
360
361 err = RestoreExceptionPortInfo();
362
363 // NULL our our exception port and let our exception thread exit
364 mach_port_t exception_port = m_exception_port;
365 m_exception_port = NULL;
366
367 err.SetError(::pthread_cancel(m_exception_thread), DNBError::POSIX);
368 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
369 err.LogThreaded("::pthread_cancel ( thread = %p )", m_exception_thread);
370
371 err.SetError(::pthread_join(m_exception_thread, NULL), DNBError::POSIX);
372 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
373 err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread);
374
375 // Deallocate our exception port that we used to track our child process
376 mach_port_t task_self = mach_task_self ();
377 err = ::mach_port_deallocate (task_self, exception_port);
378 if (DNBLogCheckLogBit(LOG_TASK) || err.Fail())
379 err.LogThreaded("::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port);
380 exception_port = NULL;
381
382 return err.Error();
383}
384
385
386void *
387MachTask::ExceptionThread (void *arg)
388{
389 if (arg == NULL)
390 return NULL;
391
392 MachTask *mach_task = (MachTask*) arg;
393 MachProcess *mach_proc = mach_task->Process();
394 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s ( arg = %p ) starting thread...", __FUNCTION__, arg);
395
396 // We keep a count of the number of consecutive exceptions received so
397 // we know to grab all exceptions without a timeout. We do this to get a
398 // bunch of related exceptions on our exception port so we can process
399 // then together. When we have multiple threads, we can get an exception
400 // per thread and they will come in consecutively. The main loop in this
401 // thread can stop periodically if needed to service things related to this
402 // process.
403 // flag set in the options, so we will wait forever for an exception on
404 // our exception port. After we get one exception, we then will use the
405 // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
406 // exceptions for our process. After we have received the last pending
407 // exception, we will get a timeout which enables us to then notify
408 // our main thread that we have an exception bundle avaiable. We then wait
409 // for the main thread to tell this exception thread to start trying to get
410 // exceptions messages again and we start again with a mach_msg read with
411 // infinite timeout.
412 uint32_t num_exceptions_received = 0;
413 DNBError err;
414 task_t task = mach_task->TaskPort();
415 mach_msg_timeout_t periodic_timeout = 0;
416
417#if defined (__arm__)
418 mach_msg_timeout_t watchdog_elapsed = 0;
419 mach_msg_timeout_t watchdog_timeout = 60 * 1000;
420 pid_t pid = mach_proc->ProcessID();
421 CFReleaser<SBSWatchdogAssertionRef> watchdog;
422
423 if (mach_proc->ProcessUsingSpringBoard())
424 {
425 // Request a renewal for every 60 seconds if we attached using SpringBoard
426 watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60));
427 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get());
428
429 if (watchdog.get())
430 {
431 ::SBSWatchdogAssertionRenew (watchdog.get());
432
433 CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get());
434 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval);
435 if (watchdogRenewalInterval > 0.0)
436 {
437 watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000;
438 if (watchdog_timeout > 3000)
439 watchdog_timeout -= 1000; // Give us a second to renew our timeout
440 else if (watchdog_timeout > 1000)
441 watchdog_timeout -= 250; // Give us a quarter of a second to renew our timeout
442 }
443 }
444 if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout)
445 periodic_timeout = watchdog_timeout;
446 }
447#endif // #if defined (__arm__)
448
449 while (mach_task->ExceptionPortIsValid())
450 {
451 ::pthread_testcancel ();
452
453 MachException::Message exception_message;
454
455
456 if (num_exceptions_received > 0)
457 {
458 // No timeout, just receive as many exceptions as we can since we already have one and we want
459 // to get all currently available exceptions for this task
460 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0);
461 }
462 else if (periodic_timeout > 0)
463 {
464 // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms)
465 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout);
466 }
467 else
468 {
469 // We don't need to parse all current exceptions or stop periodically,
470 // just wait for an exception forever.
471 err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0);
472 }
473
474 if (err.Error() == MACH_RCV_INTERRUPTED)
475 {
476 // If we have no task port we should exit this thread
477 if (!mach_task->ExceptionPortIsValid())
478 {
479 DNBLogThreadedIf(LOG_EXCEPTIONS, "thread cancelled...");
480 break;
481 }
482
483 // Make sure our task is still valid
484 if (MachTask::IsValid(task))
485 {
486 // Task is still ok
487 DNBLogThreadedIf(LOG_EXCEPTIONS, "interrupted, but task still valid, continuing...");
488 continue;
489 }
490 else
491 {
492 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
493 mach_proc->SetState(eStateExited);
494 // Our task has died, exit the thread.
495 break;
496 }
497 }
498 else if (err.Error() == MACH_RCV_TIMED_OUT)
499 {
500 if (num_exceptions_received > 0)
501 {
502 // We were receiving all current exceptions with a timeout of zero
503 // it is time to go back to our normal looping mode
504 num_exceptions_received = 0;
505
506 // Notify our main thread we have a complete exception message
507 // bundle available.
508 mach_proc->ExceptionMessageBundleComplete();
509
510 // in case we use a timeout value when getting exceptions...
511 // Make sure our task is still valid
512 if (MachTask::IsValid(task))
513 {
514 // Task is still ok
515 DNBLogThreadedIf(LOG_EXCEPTIONS, "got a timeout, continuing...");
516 continue;
517 }
518 else
519 {
520 DNBLogThreadedIf(LOG_EXCEPTIONS, "task has exited...");
521 mach_proc->SetState(eStateExited);
522 // Our task has died, exit the thread.
523 break;
524 }
525 continue;
526 }
527
528#if defined (__arm__)
529 if (watchdog.get())
530 {
531 watchdog_elapsed += periodic_timeout;
532 if (watchdog_elapsed >= watchdog_timeout)
533 {
534 DNBLogThreadedIf(LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get());
535 ::SBSWatchdogAssertionRenew (watchdog.get());
536 watchdog_elapsed = 0;
537 }
538 }
539#endif
540 }
541 else if (err.Error() != KERN_SUCCESS)
542 {
543 DNBLogThreadedIf(LOG_EXCEPTIONS, "got some other error, do something about it??? nah, continuing for now...");
544 // TODO: notify of error?
545 }
546 else
547 {
548 if (exception_message.CatchExceptionRaise())
549 {
550 ++num_exceptions_received;
551 mach_proc->ExceptionMessageReceived(exception_message);
552 }
553 }
554 }
555
556#if defined (__arm__)
557 if (watchdog.get())
558 {
559 // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we
560 // all are up and running on systems that support it. The SBS framework has a #define
561 // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now
562 // so it should still build either way.
563 DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get());
564 ::SBSWatchdogAssertionRelease (watchdog.get());
565 }
566#endif // #if defined (__arm__)
567
568 DNBLogThreadedIf(LOG_EXCEPTIONS, "MachTask::%s (%p): thread exiting...", __FUNCTION__, arg);
569 return NULL;
570}
571
572
573// So the TASK_DYLD_INFO used to just return the address of the all image infos
574// as a single member called "all_image_info". Then someone decided it would be
575// a good idea to rename this first member to "all_image_info_addr" and add a
576// size member called "all_image_info_size". This of course can not be detected
577// using code or #defines. So to hack around this problem, we define our own
578// version of the TASK_DYLD_INFO structure so we can guarantee what is inside it.
579
580struct hack_task_dyld_info {
581 mach_vm_address_t all_image_info_addr;
582 mach_vm_size_t all_image_info_size;
583};
584
585nub_addr_t
586MachTask::GetDYLDAllImageInfosAddress (DNBError& err)
587{
588 struct hack_task_dyld_info dyld_info;
589 mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
590 // Make sure that COUNT isn't bigger than our hacked up struct hack_task_dyld_info.
591 // If it is, then make COUNT smaller to match.
592 if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t)))
593 count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t));
594
595 task_t task = TaskPortForProcessID (err);
596 if (err.Success())
597 {
598 err = ::task_info (task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
599 if (err.Success())
600 {
601 // We now have the address of the all image infos structure
602 return dyld_info.all_image_info_addr;
603 }
604 }
605 return INVALID_NUB_ADDRESS;
606}
607
608
609//----------------------------------------------------------------------
610// MachTask::AllocateMemory
611//----------------------------------------------------------------------
612nub_addr_t
613MachTask::AllocateMemory (size_t size, uint32_t permissions)
614{
615 mach_vm_address_t addr;
616 task_t task = TaskPort();
617 if (task == TASK_NULL)
618 return INVALID_NUB_ADDRESS;
619
620 DNBError err;
621 err = ::mach_vm_allocate (task, &addr, size, TRUE);
622 if (err.Error() == KERN_SUCCESS)
623 {
624 // Set the protections:
625 vm_prot_t mach_prot = 0;
626 if (permissions & eMemoryPermissionsReadable)
627 mach_prot |= VM_PROT_READ;
628 if (permissions & eMemoryPermissionsWritable)
629 mach_prot |= VM_PROT_WRITE;
630 if (permissions & eMemoryPermissionsExecutable)
631 mach_prot |= VM_PROT_EXECUTE;
632
633
634 err = ::mach_vm_protect (task, addr, size, 0, mach_prot);
635 if (err.Error() == KERN_SUCCESS)
636 {
637 m_allocations.insert (std::make_pair(addr, size));
638 return addr;
639 }
640 ::mach_vm_deallocate (task, addr, size);
641 }
642 return INVALID_NUB_ADDRESS;
643}
644
645//----------------------------------------------------------------------
646// MachTask::DeallocateMemory
647//----------------------------------------------------------------------
648nub_bool_t
649MachTask::DeallocateMemory (nub_addr_t addr)
650{
651 task_t task = TaskPort();
652 if (task == TASK_NULL)
653 return false;
654
655 // We have to stash away sizes for the allocations...
656 allocation_collection::iterator pos, end = m_allocations.end();
657 for (pos = m_allocations.begin(); pos != end; pos++)
658 {
659 if ((*pos).first == addr)
660 {
661 m_allocations.erase(pos);
662 return ::mach_vm_deallocate (task, (*pos).first, (*pos).second) == KERN_SUCCESS;
663 }
664
665 }
666 return false;
667}
668