forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			1567 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			1567 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- NativeProcessDarwin.cpp ---------------------------------*- C++ -*-===//
 | |
| //
 | |
| //                     The LLVM Compiler Infrastructure
 | |
| //
 | |
| // This file is distributed under the University of Illinois Open Source
 | |
| // License. See LICENSE.TXT for details.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "NativeProcessDarwin.h"
 | |
| 
 | |
| // C includes
 | |
| #include <mach/mach_init.h>
 | |
| #include <mach/mach_traps.h>
 | |
| #include <sys/ptrace.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/sysctl.h>
 | |
| #include <sys/types.h>
 | |
| 
 | |
| // C++ includes
 | |
| // LLDB includes
 | |
| #include "lldb/Core/State.h"
 | |
| #include "lldb/Host/PseudoTerminal.h"
 | |
| #include "lldb/Target/ProcessLaunchInfo.h"
 | |
| #include "lldb/Utility/Log.h"
 | |
| #include "lldb/Utility/StreamString.h"
 | |
| 
 | |
| #include "CFBundle.h"
 | |
| #include "CFString.h"
 | |
| #include "DarwinProcessLauncher.h"
 | |
| 
 | |
| #include "MachException.h"
 | |
| 
 | |
| #include "llvm/Support/FileSystem.h"
 | |
| 
 | |
| using namespace lldb;
 | |
| using namespace lldb_private;
 | |
| using namespace lldb_private::process_darwin;
 | |
| using namespace lldb_private::darwin_process_launcher;
 | |
| 
 | |
| // -----------------------------------------------------------------------------
 | |
| // Hidden Impl
 | |
| // -----------------------------------------------------------------------------
 | |
| 
 | |
| namespace {
 | |
| struct hack_task_dyld_info {
 | |
|   mach_vm_address_t all_image_info_addr;
 | |
|   mach_vm_size_t all_image_info_size;
 | |
| };
 | |
| }
 | |
| 
 | |
| // -----------------------------------------------------------------------------
 | |
| // Public Static Methods
 | |
| // -----------------------------------------------------------------------------
 | |
| 
 | |
| Status NativeProcessProtocol::Launch(
 | |
|     ProcessLaunchInfo &launch_info,
 | |
|     NativeProcessProtocol::NativeDelegate &native_delegate, MainLoop &mainloop,
 | |
|     NativeProcessProtocolSP &native_process_sp) {
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   Status error;
 | |
| 
 | |
|   // Verify the working directory is valid if one was specified.
 | |
|   FileSpec working_dir(launch_info.GetWorkingDirectory());
 | |
|   if (working_dir &&
 | |
|       (!working_dir.ResolvePath() ||
 | |
|        !llvm::sys::fs::is_directory(working_dir.GetPath())) {
 | |
|     error.SetErrorStringWithFormat("No such file or directory: %s",
 | |
|                                    working_dir.GetCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Launch the inferior.
 | |
|   int pty_master_fd = -1;
 | |
|   LaunchFlavor launch_flavor = LaunchFlavor::Default;
 | |
| 
 | |
|   error = LaunchInferior(launch_info, &pty_master_fd, &launch_flavor);
 | |
| 
 | |
|   // Handle launch failure.
 | |
|   if (!error.Success()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() failed to launch process: "
 | |
|                   "%s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Handle failure to return a pid.
 | |
|   if (launch_info.GetProcessID() == LLDB_INVALID_PROCESS_ID) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() launch succeeded but no "
 | |
|                   "pid was returned!  Aborting.",
 | |
|                   __FUNCTION__);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Create the Darwin native process impl.
 | |
|   std::shared_ptr<NativeProcessDarwin> np_darwin_sp(
 | |
|       new NativeProcessDarwin(launch_info.GetProcessID(), pty_master_fd));
 | |
|   if (!np_darwin_sp->RegisterNativeDelegate(native_delegate)) {
 | |
|     native_process_sp.reset();
 | |
|     error.SetErrorStringWithFormat("failed to register the native delegate");
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Finalize the processing needed to debug the launched process with
 | |
|   // a NativeProcessDarwin instance.
 | |
|   error = np_darwin_sp->FinalizeLaunch(launch_flavor, mainloop);
 | |
|   if (!error.Success()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() aborting, failed to finalize"
 | |
|                   " the launching of the process: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Return the process and process id to the caller through the launch args.
 | |
|   native_process_sp = np_darwin_sp;
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessProtocol::Attach(
 | |
|     lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate,
 | |
|     MainLoop &mainloop, NativeProcessProtocolSP &native_process_sp) {
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(pid = %" PRIi64 ")", __FUNCTION__,
 | |
|                 pid);
 | |
| 
 | |
|   // Retrieve the architecture for the running process.
 | |
|   ArchSpec process_arch;
 | |
|   Status error = ResolveProcessArchitecture(pid, process_arch);
 | |
|   if (!error.Success())
 | |
|     return error;
 | |
| 
 | |
|   // TODO get attach to return this value.
 | |
|   const int pty_master_fd = -1;
 | |
|   std::shared_ptr<NativeProcessDarwin> native_process_darwin_sp(
 | |
|       new NativeProcessDarwin(pid, pty_master_fd));
 | |
| 
 | |
|   if (!native_process_darwin_sp->RegisterNativeDelegate(native_delegate)) {
 | |
|     error.SetErrorStringWithFormat("failed to register the native "
 | |
|                                    "delegate");
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   native_process_darwin_sp->AttachToInferior(mainloop, pid, error);
 | |
|   if (!error.Success())
 | |
|     return error;
 | |
| 
 | |
|   native_process_sp = native_process_darwin_sp;
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| // -----------------------------------------------------------------------------
 | |
| // ctor/dtor
 | |
| // -----------------------------------------------------------------------------
 | |
| 
 | |
| NativeProcessDarwin::NativeProcessDarwin(lldb::pid_t pid, int pty_master_fd)
 | |
|     : NativeProcessProtocol(pid), m_task(TASK_NULL), m_did_exec(false),
 | |
|       m_cpu_type(0), m_exception_port(MACH_PORT_NULL), m_exc_port_info(),
 | |
|       m_exception_thread(nullptr), m_exception_messages_mutex(),
 | |
|       m_sent_interrupt_signo(0), m_auto_resume_signo(0), m_thread_list(),
 | |
|       m_thread_actions(), m_waitpid_pipe(), m_waitpid_thread(nullptr),
 | |
|       m_waitpid_reader_handle() {
 | |
|   // TODO add this to the NativeProcessProtocol constructor.
 | |
|   m_terminal_fd = pty_master_fd;
 | |
| }
 | |
| 
 | |
| NativeProcessDarwin::~NativeProcessDarwin() {}
 | |
| 
 | |
| // -----------------------------------------------------------------------------
 | |
| // Instance methods
 | |
| // -----------------------------------------------------------------------------
 | |
| 
 | |
| Status NativeProcessDarwin::FinalizeLaunch(LaunchFlavor launch_flavor,
 | |
|                                            MainLoop &main_loop) {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
| #if 0
 | |
|     m_path = path;
 | |
|     size_t i;
 | |
|     char const *arg;
 | |
|     for (i=0; (arg = argv[i]) != NULL; i++)
 | |
|         m_args.push_back(arg);
 | |
| #endif
 | |
| 
 | |
|   error = StartExceptionThread();
 | |
|   if (!error.Success()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): failure starting the "
 | |
|                   "mach exception port monitor thread: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
| 
 | |
|     // Terminate the inferior process.  There's nothing meaningful we can
 | |
|     // do if we can't receive signals and exceptions.  Since we launched
 | |
|     // the process, it's fair game for us to kill it.
 | |
|     ::ptrace(PT_KILL, m_pid, 0, 0);
 | |
|     SetState(eStateExited);
 | |
| 
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   StartSTDIOThread();
 | |
| 
 | |
|   if (launch_flavor == LaunchFlavor::PosixSpawn) {
 | |
|     SetState(eStateAttaching);
 | |
|     errno = 0;
 | |
|     int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
 | |
|     if (err == 0) {
 | |
|       // m_flags |= eMachProcessFlagsAttached;
 | |
|       if (log)
 | |
|         log->Printf("NativeProcessDarwin::%s(): successfully spawned "
 | |
|                     "process with pid %" PRIu64,
 | |
|                     __FUNCTION__, m_pid);
 | |
|     } else {
 | |
|       error.SetErrorToErrno();
 | |
|       SetState(eStateExited);
 | |
|       if (log)
 | |
|         log->Printf("NativeProcessDarwin::%s(): error: failed to "
 | |
|                     "attach to spawned pid %" PRIu64 " (error=%d (%s))",
 | |
|                     __FUNCTION__, m_pid, (int)error.GetError(),
 | |
|                     error.AsCString());
 | |
|       return error;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(): new pid is %" PRIu64 "...",
 | |
|                 __FUNCTION__, m_pid);
 | |
| 
 | |
|   // Spawn a thread to reap our child inferior process...
 | |
|   error = StartWaitpidThread(main_loop);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): failed to start waitpid() "
 | |
|                   "thread: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     kill(SIGKILL, static_cast<::pid_t>(m_pid));
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   if (TaskPortForProcessID(error) == TASK_NULL) {
 | |
|     // We failed to get the task for our process ID which is bad.
 | |
|     // Kill our process; otherwise, it will be stopped at the entry
 | |
|     // point and get reparented to someone else and never go away.
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): could not get task port "
 | |
|                   "for process, sending SIGKILL and exiting: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     kill(SIGKILL, static_cast<::pid_t>(m_pid));
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Indicate that we're stopped, as we always launch suspended.
 | |
|   SetState(eStateStopped);
 | |
| 
 | |
|   // Success.
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::SaveExceptionPortInfo() {
 | |
|   return m_exc_port_info.Save(m_task);
 | |
| }
 | |
| 
 | |
| bool NativeProcessDarwin::ProcessUsingSpringBoard() const {
 | |
|   // TODO implement flags
 | |
|   // return (m_flags & eMachProcessFlagsUsingSBS) != 0;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool NativeProcessDarwin::ProcessUsingBackBoard() const {
 | |
|   // TODO implement flags
 | |
|   // return (m_flags & eMachProcessFlagsUsingBKS) != 0;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // Called by the exception thread when an exception has been received from
 | |
| // our process. The exception message is completely filled and the exception
 | |
| // data has already been copied.
 | |
| void NativeProcessDarwin::ExceptionMessageReceived(
 | |
|     const MachException::Message &message) {
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
 | |
| 
 | |
|   std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
 | |
|   if (m_exception_messages.empty()) {
 | |
|     // Suspend the task the moment we receive our first exception message.
 | |
|     SuspendTask();
 | |
|   }
 | |
| 
 | |
|   // Use a locker to automatically unlock our mutex in case of exceptions
 | |
|   // Add the exception to our internal exception stack
 | |
|   m_exception_messages.push_back(message);
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(): new queued message count: %lu",
 | |
|                 __FUNCTION__, m_exception_messages.size());
 | |
| }
 | |
| 
 | |
| void *NativeProcessDarwin::ExceptionThread(void *arg) {
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
 | |
|   if (!arg) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): cannot run mach exception "
 | |
|                   "thread, mandatory process arg was null",
 | |
|                   __FUNCTION__);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return reinterpret_cast<NativeProcessDarwin *>(arg)->DoExceptionThread();
 | |
| }
 | |
| 
 | |
| void *NativeProcessDarwin::DoExceptionThread() {
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(arg=%p) starting thread...",
 | |
|                 __FUNCTION__, this);
 | |
| 
 | |
|   pthread_setname_np("exception monitoring thread");
 | |
| 
 | |
|   // Ensure we don't get CPU starved.
 | |
|   MaybeRaiseThreadPriority();
 | |
| 
 | |
|   // We keep a count of the number of consecutive exceptions received so
 | |
|   // we know to grab all exceptions without a timeout. We do this to get a
 | |
|   // bunch of related exceptions on our exception port so we can process
 | |
|   // then together. When we have multiple threads, we can get an exception
 | |
|   // per thread and they will come in consecutively. The main loop in this
 | |
|   // thread can stop periodically if needed to service things related to this
 | |
|   // process.
 | |
|   //
 | |
|   // [did we lose some words here?]
 | |
|   //
 | |
|   // flag set in the options, so we will wait forever for an exception on
 | |
|   // 0 our exception port. After we get one exception, we then will use the
 | |
|   // MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
 | |
|   // exceptions for our process. After we have received the last pending
 | |
|   // exception, we will get a timeout which enables us to then notify
 | |
|   // our main thread that we have an exception bundle available. We then wait
 | |
|   // for the main thread to tell this exception thread to start trying to get
 | |
|   // exceptions messages again and we start again with a mach_msg read with
 | |
|   // infinite timeout.
 | |
|   //
 | |
|   // We choose to park a thread on this, rather than polling, because the
 | |
|   // polling is expensive.  On devices, we need to minimize overhead caused
 | |
|   // by the process monitor.
 | |
|   uint32_t num_exceptions_received = 0;
 | |
|   Status error;
 | |
|   task_t task = m_task;
 | |
|   mach_msg_timeout_t periodic_timeout = 0;
 | |
| 
 | |
| #if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS)
 | |
|   mach_msg_timeout_t watchdog_elapsed = 0;
 | |
|   mach_msg_timeout_t watchdog_timeout = 60 * 1000;
 | |
|   ::pid_t pid = (::pid_t)process->GetID();
 | |
|   CFReleaser<SBSWatchdogAssertionRef> watchdog;
 | |
| 
 | |
|   if (process->ProcessUsingSpringBoard()) {
 | |
|     // Request a renewal for every 60 seconds if we attached using
 | |
|     // SpringBoard.
 | |
|     watchdog.reset(::SBSWatchdogAssertionCreateForPID(nullptr, pid, 60));
 | |
|     if (log)
 | |
|       log->Printf("::SBSWatchdogAssertionCreateForPID(NULL, %4.4x, 60) "
 | |
|                   "=> %p",
 | |
|                   pid, watchdog.get());
 | |
| 
 | |
|     if (watchdog.get()) {
 | |
|       ::SBSWatchdogAssertionRenew(watchdog.get());
 | |
| 
 | |
|       CFTimeInterval watchdogRenewalInterval =
 | |
|           ::SBSWatchdogAssertionGetRenewalInterval(watchdog.get());
 | |
|       if (log)
 | |
|         log->Printf("::SBSWatchdogAssertionGetRenewalInterval(%p) => "
 | |
|                     "%g seconds",
 | |
|                     watchdog.get(), watchdogRenewalInterval);
 | |
|       if (watchdogRenewalInterval > 0.0) {
 | |
|         watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000;
 | |
|         if (watchdog_timeout > 3000) {
 | |
|           // Give us a second to renew our timeout.
 | |
|           watchdog_timeout -= 1000;
 | |
|         } else if (watchdog_timeout > 1000) {
 | |
|           // Give us a quarter of a second to renew our timeout.
 | |
|           watchdog_timeout -= 250;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout)
 | |
|       periodic_timeout = watchdog_timeout;
 | |
|   }
 | |
| #endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS)
 | |
| 
 | |
| #ifdef WITH_BKS
 | |
|   CFReleaser<BKSWatchdogAssertionRef> watchdog;
 | |
|   if (process->ProcessUsingBackBoard()) {
 | |
|     ::pid_t pid = process->GetID();
 | |
|     CFAllocatorRef alloc = kCFAllocatorDefault;
 | |
|     watchdog.reset(::BKSWatchdogAssertionCreateForPID(alloc, pid));
 | |
|   }
 | |
| #endif // #ifdef WITH_BKS
 | |
| 
 | |
|   // Do we want to use a weak pointer to the NativeProcessDarwin here, in
 | |
|   // which case we can guarantee we don't whack the process monitor if we
 | |
|   // race between this thread and the main one on shutdown?
 | |
|   while (IsExceptionPortValid()) {
 | |
|     ::pthread_testcancel();
 | |
| 
 | |
|     MachException::Message exception_message;
 | |
| 
 | |
|     if (num_exceptions_received > 0) {
 | |
|       // We don't want a timeout here, just receive as many exceptions as
 | |
|       // we can since we already have one.  We want to get all currently
 | |
|       // available exceptions for this task at once.
 | |
|       error = exception_message.Receive(
 | |
|           GetExceptionPort(),
 | |
|           MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0);
 | |
|     } else if (periodic_timeout > 0) {
 | |
|       // We need to stop periodically in this loop, so try and get a mach
 | |
|       // message with a valid timeout (ms).
 | |
|       error = exception_message.Receive(GetExceptionPort(),
 | |
|                                         MACH_RCV_MSG | MACH_RCV_INTERRUPT |
 | |
|                                             MACH_RCV_TIMEOUT,
 | |
|                                         periodic_timeout);
 | |
|     } else {
 | |
|       // We don't need to parse all current exceptions or stop
 | |
|       // periodically, just wait for an exception forever.
 | |
|       error = exception_message.Receive(GetExceptionPort(),
 | |
|                                         MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0);
 | |
|     }
 | |
| 
 | |
|     if (error.Success()) {
 | |
|       // We successfully received an exception.
 | |
|       if (exception_message.CatchExceptionRaise(task)) {
 | |
|         ++num_exceptions_received;
 | |
|         ExceptionMessageReceived(exception_message);
 | |
|       }
 | |
|     } else {
 | |
|       if (error.GetError() == MACH_RCV_INTERRUPTED) {
 | |
|         // We were interrupted.
 | |
| 
 | |
|         // If we have no task port we should exit this thread, as it implies
 | |
|         // the inferior went down.
 | |
|         if (!IsExceptionPortValid()) {
 | |
|           if (log)
 | |
|             log->Printf("NativeProcessDarwin::%s(): the inferior "
 | |
|                         "exception port is no longer valid, "
 | |
|                         "canceling exception thread...",
 | |
|                         __FUNCTION__);
 | |
|           // Should we be setting a process state here?
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         // Make sure the inferior task is still valid.
 | |
|         if (IsTaskValid()) {
 | |
|           // Task is still ok.
 | |
|           if (log)
 | |
|             log->Printf("NativeProcessDarwin::%s(): interrupted, but "
 | |
|                         "the inferior task iss till valid, "
 | |
|                         "continuing...",
 | |
|                         __FUNCTION__);
 | |
|           continue;
 | |
|         } else {
 | |
|           // The inferior task is no longer valid.  Time to exit as
 | |
|           // the process has gone away.
 | |
|           if (log)
 | |
|             log->Printf("NativeProcessDarwin::%s(): the inferior task "
 | |
|                         "has exited, and so will we...",
 | |
|                         __FUNCTION__);
 | |
|           // Does this race at all with our waitpid()?
 | |
|           SetState(eStateExited);
 | |
|           break;
 | |
|         }
 | |
|       } else if (error.GetError() == MACH_RCV_TIMED_OUT) {
 | |
|         // We timed out when waiting for exceptions.
 | |
| 
 | |
|         if (num_exceptions_received > 0) {
 | |
|           // We were receiving all current exceptions with a timeout of
 | |
|           // zero.  It is time to go back to our normal looping mode.
 | |
|           num_exceptions_received = 0;
 | |
| 
 | |
|           // Notify our main thread we have a complete exception message
 | |
|           // bundle available.  Get the possibly updated task port back
 | |
|           // from the process in case we exec'ed and our task port
 | |
|           // changed.
 | |
|           task = ExceptionMessageBundleComplete();
 | |
| 
 | |
|           // In case we use a timeout value when getting exceptions,
 | |
|           // make sure our task is still valid.
 | |
|           if (IsTaskValid(task)) {
 | |
|             // Task is still ok.
 | |
|             if (log)
 | |
|               log->Printf("NativeProcessDarwin::%s(): got a timeout, "
 | |
|                           "continuing...",
 | |
|                           __FUNCTION__);
 | |
|             continue;
 | |
|           } else {
 | |
|             // The inferior task is no longer valid.  Time to exit as
 | |
|             // the process has gone away.
 | |
|             if (log)
 | |
|               log->Printf("NativeProcessDarwin::%s(): the inferior "
 | |
|                           "task has exited, and so will we...",
 | |
|                           __FUNCTION__);
 | |
|             // Does this race at all with our waitpid()?
 | |
|             SetState(eStateExited);
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
| 
 | |
| #if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS)
 | |
|         if (watchdog.get()) {
 | |
|           watchdog_elapsed += periodic_timeout;
 | |
|           if (watchdog_elapsed >= watchdog_timeout) {
 | |
|             if (log)
 | |
|               log->Printf("SBSWatchdogAssertionRenew(%p)", watchdog.get());
 | |
|             ::SBSWatchdogAssertionRenew(watchdog.get());
 | |
|             watchdog_elapsed = 0;
 | |
|           }
 | |
|         }
 | |
| #endif
 | |
|       } else {
 | |
|         if (log)
 | |
|           log->Printf("NativeProcessDarwin::%s(): continuing after "
 | |
|                       "receiving an unexpected error: %u (%s)",
 | |
|                       __FUNCTION__, error.GetError(), error.AsCString());
 | |
|         // TODO: notify of error?
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #if defined(WITH_SPRINGBOARD) && !defined(WITH_BKS)
 | |
|   if (watchdog.get()) {
 | |
|     // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel
 | |
|     // when we
 | |
|     // all are up and running on systems that support it. The SBS framework has
 | |
|     // a #define
 | |
|     // that will forward SBSWatchdogAssertionRelease to
 | |
|     // SBSWatchdogAssertionCancel for now
 | |
|     // so it should still build either way.
 | |
|     DNBLogThreadedIf(LOG_TASK, "::SBSWatchdogAssertionRelease(%p)",
 | |
|                      watchdog.get());
 | |
|     ::SBSWatchdogAssertionRelease(watchdog.get());
 | |
|   }
 | |
| #endif // #if defined (WITH_SPRINGBOARD) && !defined (WITH_BKS)
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(%p): thread exiting...", __FUNCTION__,
 | |
|                 this);
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::StartExceptionThread() {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s() called", __FUNCTION__);
 | |
| 
 | |
|   // Make sure we've looked up the inferior port.
 | |
|   TaskPortForProcessID(error);
 | |
| 
 | |
|   // Ensure the inferior task is valid.
 | |
|   if (!IsTaskValid()) {
 | |
|     error.SetErrorStringWithFormat("cannot start exception thread: "
 | |
|                                    "task 0x%4.4x is not valid",
 | |
|                                    m_task);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Get the mach port for the process monitor.
 | |
|   mach_port_t task_self = mach_task_self();
 | |
| 
 | |
|   // Allocate an exception port that we will use to track our child process
 | |
|   auto mach_err = ::mach_port_allocate(task_self, MACH_PORT_RIGHT_RECEIVE,
 | |
|                                        &m_exception_port);
 | |
|   error.SetError(mach_err, eErrorTypeMachKernel);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): mach_port_allocate("
 | |
|                   "task_self=0x%4.4x, MACH_PORT_RIGHT_RECEIVE, "
 | |
|                   "&m_exception_port) failed: %u (%s)",
 | |
|                   __FUNCTION__, task_self, error.GetError(), error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Add the ability to send messages on the new exception port
 | |
|   mach_err = ::mach_port_insert_right(
 | |
|       task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND);
 | |
|   error.SetError(mach_err, eErrorTypeMachKernel);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): mach_port_insert_right("
 | |
|                   "task_self=0x%4.4x, m_exception_port=0x%4.4x, "
 | |
|                   "m_exception_port=0x%4.4x, MACH_MSG_TYPE_MAKE_SEND) "
 | |
|                   "failed: %u (%s)",
 | |
|                   __FUNCTION__, task_self, m_exception_port, m_exception_port,
 | |
|                   error.GetError(), error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Save the original state of the exception ports for our child process.
 | |
|   error = SaveExceptionPortInfo();
 | |
|   if (error.Fail() || (m_exc_port_info.mask == 0)) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): SaveExceptionPortInfo() "
 | |
|                   "failed, cannot install exception handler: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Set the ability to get all exceptions on this port.
 | |
|   mach_err = ::task_set_exception_ports(
 | |
|       m_task, m_exc_port_info.mask, m_exception_port,
 | |
|       EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
 | |
|   error.SetError(mach_err, eErrorTypeMachKernel);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("::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) failed: "
 | |
|                   "%u (%s)",
 | |
|                   m_task, m_exc_port_info.mask, m_exception_port,
 | |
|                   (EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES), THREAD_STATE_NONE,
 | |
|                   error.GetError(), error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Create the exception thread.
 | |
|   auto pthread_err =
 | |
|       ::pthread_create(&m_exception_thread, nullptr, ExceptionThread, this);
 | |
|   error.SetError(pthread_err, eErrorTypePOSIX);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): failed to create Mach "
 | |
|                   "exception-handling thread: %u (%s)",
 | |
|                   __FUNCTION__, error.GetError(), error.AsCString());
 | |
|   }
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| lldb::addr_t
 | |
| NativeProcessDarwin::GetDYLDAllImageInfosAddress(Status &error) const {
 | |
|   error.Clear();
 | |
| 
 | |
|   struct hack_task_dyld_info dyld_info;
 | |
|   mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
 | |
|   // Make sure that COUNT isn't bigger than our hacked up struct
 | |
|   // hack_task_dyld_info.  If it is, then make COUNT smaller to match.
 | |
|   if (count > (sizeof(struct hack_task_dyld_info) / sizeof(natural_t))) {
 | |
|     count = (sizeof(struct hack_task_dyld_info) / sizeof(natural_t));
 | |
|   }
 | |
| 
 | |
|   TaskPortForProcessID(error);
 | |
|   if (error.Fail())
 | |
|     return LLDB_INVALID_ADDRESS;
 | |
| 
 | |
|   auto mach_err =
 | |
|       ::task_info(m_task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
 | |
|   error.SetError(mach_err, eErrorTypeMachKernel);
 | |
|   if (error.Success()) {
 | |
|     // We now have the address of the all image infos structure.
 | |
|     return dyld_info.all_image_info_addr;
 | |
|   }
 | |
| 
 | |
|   // We don't have it.
 | |
|   return LLDB_INVALID_ADDRESS;
 | |
| }
 | |
| 
 | |
| uint32_t NativeProcessDarwin::GetCPUTypeForLocalProcess(::pid_t pid) {
 | |
|   int mib[CTL_MAXNAME] = {
 | |
|       0,
 | |
|   };
 | |
|   size_t len = CTL_MAXNAME;
 | |
| 
 | |
|   if (::sysctlnametomib("sysctl.proc_cputype", mib, &len))
 | |
|     return 0;
 | |
| 
 | |
|   mib[len] = pid;
 | |
|   len++;
 | |
| 
 | |
|   cpu_type_t cpu;
 | |
|   size_t cpu_len = sizeof(cpu);
 | |
|   if (::sysctl(mib, static_cast<u_int>(len), &cpu, &cpu_len, 0, 0))
 | |
|     cpu = 0;
 | |
|   return cpu;
 | |
| }
 | |
| 
 | |
| uint32_t NativeProcessDarwin::GetCPUType() const {
 | |
|   if (m_cpu_type == 0 && m_pid != 0)
 | |
|     m_cpu_type = GetCPUTypeForLocalProcess(m_pid);
 | |
|   return m_cpu_type;
 | |
| }
 | |
| 
 | |
| task_t NativeProcessDarwin::ExceptionMessageBundleComplete() {
 | |
|   // We have a complete bundle of exceptions for our child process.
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
 | |
| 
 | |
|   std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(): processing %lu exception "
 | |
|                 "messages.",
 | |
|                 __FUNCTION__, m_exception_messages.size());
 | |
| 
 | |
|   if (m_exception_messages.empty()) {
 | |
|     // Not particularly useful...
 | |
|     return m_task;
 | |
|   }
 | |
| 
 | |
|   bool auto_resume = false;
 | |
|   m_did_exec = false;
 | |
| 
 | |
|   // First check for any SIGTRAP and make sure we didn't exec
 | |
|   const task_t task = m_task;
 | |
|   size_t i;
 | |
|   if (m_pid != 0) {
 | |
|     bool received_interrupt = false;
 | |
|     uint32_t num_task_exceptions = 0;
 | |
|     for (i = 0; i < m_exception_messages.size(); ++i) {
 | |
|       if (m_exception_messages[i].state.task_port != task) {
 | |
|         // This is an exception that is not for our inferior, ignore.
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // This is an exception for the inferior.
 | |
|       ++num_task_exceptions;
 | |
|       const int signo = m_exception_messages[i].state.SoftSignal();
 | |
|       if (signo == SIGTRAP) {
 | |
|         // SIGTRAP could mean that we exec'ed. We need to check the
 | |
|         // dyld all_image_infos.infoArray to see if it is NULL and if
 | |
|         // so, say that we exec'ed.
 | |
|         const addr_t aii_addr = GetDYLDAllImageInfosAddress(error);
 | |
|         if (aii_addr == LLDB_INVALID_ADDRESS)
 | |
|           break;
 | |
| 
 | |
|         const addr_t info_array_count_addr = aii_addr + 4;
 | |
|         uint32_t info_array_count = 0;
 | |
|         size_t bytes_read = 0;
 | |
|         Status read_error;
 | |
|         read_error = ReadMemory(info_array_count_addr, // source addr
 | |
|                                 &info_array_count,     // dest addr
 | |
|                                 4,                     // byte count
 | |
|                                 bytes_read);           // #bytes read
 | |
|         if (read_error.Success() && (bytes_read == 4)) {
 | |
|           if (info_array_count == 0) {
 | |
|             // We got the all infos address, and there are zero
 | |
|             // entries.  We think we exec'd.
 | |
|             m_did_exec = true;
 | |
| 
 | |
|             // Force the task port to update itself in case the
 | |
|             // task port changed after exec
 | |
|             const task_t old_task = m_task;
 | |
|             const bool force_update = true;
 | |
|             const task_t new_task = TaskPortForProcessID(error, force_update);
 | |
|             if (old_task != new_task) {
 | |
|               if (log)
 | |
|                 log->Printf("exec: inferior task port changed "
 | |
|                             "from 0x%4.4x to 0x%4.4x",
 | |
|                             old_task, new_task);
 | |
|             }
 | |
|           }
 | |
|         } else {
 | |
|           if (log)
 | |
|             log->Printf("NativeProcessDarwin::%s() warning: "
 | |
|                         "failed to read all_image_infos."
 | |
|                         "infoArrayCount from 0x%8.8llx",
 | |
|                         __FUNCTION__, info_array_count_addr);
 | |
|         }
 | |
|       } else if ((m_sent_interrupt_signo != 0) &&
 | |
|                  (signo == m_sent_interrupt_signo)) {
 | |
|         // We just received the interrupt that we sent to ourselves.
 | |
|         received_interrupt = true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (m_did_exec) {
 | |
|       cpu_type_t process_cpu_type = GetCPUTypeForLocalProcess(m_pid);
 | |
|       if (m_cpu_type != process_cpu_type) {
 | |
|         if (log)
 | |
|           log->Printf("NativeProcessDarwin::%s(): arch changed from "
 | |
|                       "0x%8.8x to 0x%8.8x",
 | |
|                       __FUNCTION__, m_cpu_type, process_cpu_type);
 | |
|         m_cpu_type = process_cpu_type;
 | |
|         // TODO figure out if we need to do something here.
 | |
|         // DNBArchProtocol::SetArchitecture (process_cpu_type);
 | |
|       }
 | |
|       m_thread_list.Clear();
 | |
| 
 | |
|       // TODO hook up breakpoints.
 | |
|       // m_breakpoints.DisableAll();
 | |
|     }
 | |
| 
 | |
|     if (m_sent_interrupt_signo != 0) {
 | |
|       if (received_interrupt) {
 | |
|         if (log)
 | |
|           log->Printf("NativeProcessDarwin::%s(): process "
 | |
|                       "successfully interrupted with signal %i",
 | |
|                       __FUNCTION__, m_sent_interrupt_signo);
 | |
| 
 | |
|         // Mark that we received the interrupt signal
 | |
|         m_sent_interrupt_signo = 0;
 | |
|         // Now check if we had a case where:
 | |
|         // 1 - We called NativeProcessDarwin::Interrupt() but we stopped
 | |
|         //     for another reason.
 | |
|         // 2 - We called NativeProcessDarwin::Resume() (but still
 | |
|         //     haven't gotten the interrupt signal).
 | |
|         // 3 - We are now incorrectly stopped because we are handling
 | |
|         //     the interrupt signal we missed.
 | |
|         // 4 - We might need to resume if we stopped only with the
 | |
|         //     interrupt signal that we never handled.
 | |
|         if (m_auto_resume_signo != 0) {
 | |
|           // Only auto_resume if we stopped with _only_ the interrupt
 | |
|           // signal.
 | |
|           if (num_task_exceptions == 1) {
 | |
|             auto_resume = true;
 | |
|             if (log)
 | |
|               log->Printf("NativeProcessDarwin::%s(): auto "
 | |
|                           "resuming due to unhandled interrupt "
 | |
|                           "signal %i",
 | |
|                           __FUNCTION__, m_auto_resume_signo);
 | |
|           }
 | |
|           m_auto_resume_signo = 0;
 | |
|         }
 | |
|       } else {
 | |
|         if (log)
 | |
|           log->Printf("NativeProcessDarwin::%s(): didn't get signal "
 | |
|                       "%i after MachProcess::Interrupt()",
 | |
|                       __FUNCTION__, m_sent_interrupt_signo);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Let all threads recover from stopping and do any clean up based
 | |
|   // on the previous thread state (if any).
 | |
|   m_thread_list.ProcessDidStop(*this);
 | |
| 
 | |
|   // Let each thread know of any exceptions
 | |
|   for (i = 0; i < m_exception_messages.size(); ++i) {
 | |
|     // Let the thread list forward all exceptions on down to each thread.
 | |
|     if (m_exception_messages[i].state.task_port == task) {
 | |
|       // This exception is for our inferior.
 | |
|       m_thread_list.NotifyException(m_exception_messages[i].state);
 | |
|     }
 | |
| 
 | |
|     if (log) {
 | |
|       StreamString stream;
 | |
|       m_exception_messages[i].Dump(stream);
 | |
|       stream.Flush();
 | |
|       log->PutCString(stream.GetString().c_str());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (log) {
 | |
|     StreamString stream;
 | |
|     m_thread_list.Dump(stream);
 | |
|     stream.Flush();
 | |
|     log->PutCString(stream.GetString().c_str());
 | |
|   }
 | |
| 
 | |
|   bool step_more = false;
 | |
|   if (m_thread_list.ShouldStop(step_more) && (auto_resume == false)) {
 | |
| // TODO - need to hook up event system here. !!!!
 | |
| #if 0
 | |
|         // Wait for the eEventProcessRunningStateChanged event to be reset
 | |
|         // before changing state to stopped to avoid race condition with
 | |
|         // very fast start/stops.
 | |
|         struct timespec timeout;
 | |
| 
 | |
|         //DNBTimer::OffsetTimeOfDay(&timeout, 0, 250 * 1000);   // Wait for 250 ms
 | |
|         DNBTimer::OffsetTimeOfDay(&timeout, 1, 0);  // Wait for 250 ms
 | |
|         m_events.WaitForEventsToReset(eEventProcessRunningStateChanged,
 | |
|                                       &timeout);
 | |
| #endif
 | |
|     SetState(eStateStopped);
 | |
|   } else {
 | |
|     // Resume without checking our current state.
 | |
|     PrivateResume();
 | |
|   }
 | |
| 
 | |
|   return m_task;
 | |
| }
 | |
| 
 | |
| void NativeProcessDarwin::StartSTDIOThread() {
 | |
|   // TODO implement
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::StartWaitpidThread(MainLoop &main_loop) {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   // Strategy: create a thread that sits on waitpid(), waiting for the
 | |
|   // inferior process to die, reaping it in the process.  Arrange for
 | |
|   // the thread to have a pipe file descriptor that it can send a byte
 | |
|   // over when the waitpid completes.  Have the main loop have a read
 | |
|   // object for the other side of the pipe, and have the callback for
 | |
|   // the read do the process termination message sending.
 | |
| 
 | |
|   // Create a single-direction communication channel.
 | |
|   const bool child_inherits = false;
 | |
|   error = m_waitpid_pipe.CreateNew(child_inherits);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): failed to create waitpid "
 | |
|                   "communication pipe: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Hook up the waitpid reader callback.
 | |
| 
 | |
|   // TODO make PipePOSIX derive from IOObject.  This is goofy here.
 | |
|   const bool transfer_ownership = false;
 | |
|   auto io_sp = IOObjectSP(
 | |
|       new File(m_waitpid_pipe.GetReadFileDescriptor(), transfer_ownership));
 | |
|   m_waitpid_reader_handle = main_loop.RegisterReadObject(
 | |
|       io_sp, [this](MainLoopBase &) { HandleWaitpidResult(); }, error);
 | |
| 
 | |
|   // Create the thread.
 | |
|   auto pthread_err =
 | |
|       ::pthread_create(&m_waitpid_thread, nullptr, WaitpidThread, this);
 | |
|   error.SetError(pthread_err, eErrorTypePOSIX);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): failed to create waitpid "
 | |
|                   "handling thread: %u (%s)",
 | |
|                   __FUNCTION__, error.GetError(), error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| void *NativeProcessDarwin::WaitpidThread(void *arg) {
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
|   if (!arg) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): cannot run waitpid "
 | |
|                   "thread, mandatory process arg was null",
 | |
|                   __FUNCTION__);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return reinterpret_cast<NativeProcessDarwin *>(arg)->DoWaitpidThread();
 | |
| }
 | |
| 
 | |
| void NativeProcessDarwin::MaybeRaiseThreadPriority() {
 | |
| #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
 | |
|   struct sched_param thread_param;
 | |
|   int thread_sched_policy;
 | |
|   if (pthread_getschedparam(pthread_self(), &thread_sched_policy,
 | |
|                             &thread_param) == 0) {
 | |
|     thread_param.sched_priority = 47;
 | |
|     pthread_setschedparam(pthread_self(), thread_sched_policy, &thread_param);
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void *NativeProcessDarwin::DoWaitpidThread() {
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   if (m_pid == LLDB_INVALID_PROCESS_ID) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): inferior process ID is "
 | |
|                   "not set, cannot waitpid on it",
 | |
|                   __FUNCTION__);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Name the thread.
 | |
|   pthread_setname_np("waitpid thread");
 | |
| 
 | |
|   // Ensure we don't get CPU starved.
 | |
|   MaybeRaiseThreadPriority();
 | |
| 
 | |
|   Status error;
 | |
|   int status = -1;
 | |
| 
 | |
|   while (1) {
 | |
|     // Do a waitpid.
 | |
|     ::pid_t child_pid = ::waitpid(m_pid, &status, 0);
 | |
|     if (child_pid < 0)
 | |
|       error.SetErrorToErrno();
 | |
|     if (error.Fail()) {
 | |
|       if (error.GetError() == EINTR) {
 | |
|         // This is okay, we can keep going.
 | |
|         if (log)
 | |
|           log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
 | |
|                       ", &status, 0) interrupted, continuing",
 | |
|                       __FUNCTION__, m_pid);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // This error is not okay, abort.
 | |
|       if (log)
 | |
|         log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
 | |
|                     ", &status, 0) aborting due to error: %u (%s)",
 | |
|                     __FUNCTION__, m_pid, error.GetError(), error.AsCString());
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // Log the successful result.
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
 | |
|                   ", &status, 0) => %i, status = %i",
 | |
|                   __FUNCTION__, m_pid, child_pid, status);
 | |
| 
 | |
|     // Handle the result.
 | |
|     if (WIFSTOPPED(status)) {
 | |
|       if (log)
 | |
|         log->Printf("NativeProcessDarwin::%s(): waitpid(pid = %" PRIu64
 | |
|                     ") received a stop, continuing waitpid() loop",
 | |
|                     __FUNCTION__, m_pid);
 | |
|       continue;
 | |
|     } else // if (WIFEXITED(status) || WIFSIGNALED(status))
 | |
|     {
 | |
|       if (log)
 | |
|         log->Printf("NativeProcessDarwin::%s(pid = %" PRIu64 "): "
 | |
|                     "waitpid thread is setting exit status for pid = "
 | |
|                     "%i to %i",
 | |
|                     __FUNCTION__, m_pid, child_pid, status);
 | |
| 
 | |
|       error = SendInferiorExitStatusToMainLoop(child_pid, status);
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // We should never exit as long as our child process is alive.  If we
 | |
|   // get here, something completely unexpected went wrong and we should exit.
 | |
|   if (log)
 | |
|     log->Printf(
 | |
|         "NativeProcessDarwin::%s(): internal error: waitpid thread "
 | |
|         "exited out of its main loop in an unexpected way. pid = %" PRIu64
 | |
|         ". Sending exit status of -1.",
 | |
|         __FUNCTION__, m_pid);
 | |
| 
 | |
|   error = SendInferiorExitStatusToMainLoop((::pid_t)m_pid, -1);
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::SendInferiorExitStatusToMainLoop(::pid_t pid,
 | |
|                                                              int status) {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   size_t bytes_written = 0;
 | |
| 
 | |
|   // Send the pid.
 | |
|   error = m_waitpid_pipe.Write(&pid, sizeof(pid), bytes_written);
 | |
|   if (error.Fail() || (bytes_written < sizeof(pid))) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() - failed to write "
 | |
|                   "waitpid exiting pid to the pipe.  Client will not "
 | |
|                   "hear about inferior exit status!",
 | |
|                   __FUNCTION__);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Send the status.
 | |
|   bytes_written = 0;
 | |
|   error = m_waitpid_pipe.Write(&status, sizeof(status), bytes_written);
 | |
|   if (error.Fail() || (bytes_written < sizeof(status))) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() - failed to write "
 | |
|                   "waitpid exit result to the pipe.  Client will not "
 | |
|                   "hear about inferior exit status!",
 | |
|                   __FUNCTION__);
 | |
|   }
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::HandleWaitpidResult() {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   // Read the pid.
 | |
|   const bool notify_status = true;
 | |
| 
 | |
|   ::pid_t pid = -1;
 | |
|   size_t bytes_read = 0;
 | |
|   error = m_waitpid_pipe.Read(&pid, sizeof(pid), bytes_read);
 | |
|   if (error.Fail() || (bytes_read < sizeof(pid))) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() - failed to read "
 | |
|                   "waitpid exiting pid from the pipe.  Will notify "
 | |
|                   "as if parent process died with exit status -1.",
 | |
|                   __FUNCTION__);
 | |
|     SetExitStatus(WaitStatus(WaitStatus::Exit, -1), notify_status);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Read the status.
 | |
|   int status = -1;
 | |
|   error = m_waitpid_pipe.Read(&status, sizeof(status), bytes_read);
 | |
|   if (error.Fail() || (bytes_read < sizeof(status))) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() - failed to read "
 | |
|                   "waitpid exit status from the pipe.  Will notify "
 | |
|                   "as if parent process died with exit status -1.",
 | |
|                   __FUNCTION__);
 | |
|     SetExitStatus(WaitStatus(WaitStatus::Exit, -1), notify_status);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Notify the monitor that our state has changed.
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(): main loop received waitpid "
 | |
|                 "exit status info: pid=%i (%s), status=%i",
 | |
|                 __FUNCTION__, pid,
 | |
|                 (pid == m_pid) ? "the inferior" : "not the inferior", status);
 | |
| 
 | |
|   SetExitStatus(WaitStatus::Decode(status), notify_status);
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| task_t NativeProcessDarwin::TaskPortForProcessID(Status &error,
 | |
|                                                  bool force) const {
 | |
|   if ((m_task == TASK_NULL) || force) {
 | |
|     Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
|     if (m_pid == LLDB_INVALID_PROCESS_ID) {
 | |
|       if (log)
 | |
|         log->Printf("NativeProcessDarwin::%s(): cannot get task due "
 | |
|                     "to invalid pid",
 | |
|                     __FUNCTION__);
 | |
|       return TASK_NULL;
 | |
|     }
 | |
| 
 | |
|     const uint32_t num_retries = 10;
 | |
|     const uint32_t usec_interval = 10000;
 | |
| 
 | |
|     mach_port_t task_self = mach_task_self();
 | |
|     task_t task = TASK_NULL;
 | |
| 
 | |
|     for (uint32_t i = 0; i < num_retries; i++) {
 | |
|       kern_return_t err = ::task_for_pid(task_self, m_pid, &task);
 | |
|       if (err == 0) {
 | |
|         // Succeeded.  Save and return it.
 | |
|         error.Clear();
 | |
|         m_task = task;
 | |
|         log->Printf("NativeProcessDarwin::%s(): ::task_for_pid("
 | |
|                     "stub_port = 0x%4.4x, pid = %llu, &task) "
 | |
|                     "succeeded: inferior task port = 0x%4.4x",
 | |
|                     __FUNCTION__, task_self, m_pid, m_task);
 | |
|         return m_task;
 | |
|       } else {
 | |
|         // Failed to get the task for the inferior process.
 | |
|         error.SetError(err, eErrorTypeMachKernel);
 | |
|         if (log) {
 | |
|           log->Printf("NativeProcessDarwin::%s(): ::task_for_pid("
 | |
|                       "stub_port = 0x%4.4x, pid = %llu, &task) "
 | |
|                       "failed, err = 0x%8.8x (%s)",
 | |
|                       __FUNCTION__, task_self, m_pid, err, error.AsCString());
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // Sleep a bit and try again
 | |
|       ::usleep(usec_interval);
 | |
|     }
 | |
| 
 | |
|     // We failed to get the task for the inferior process.
 | |
|     // Ensure that it is cleared out.
 | |
|     m_task = TASK_NULL;
 | |
|   }
 | |
|   return m_task;
 | |
| }
 | |
| 
 | |
| void NativeProcessDarwin::AttachToInferior(MainLoop &mainloop, lldb::pid_t pid,
 | |
|                                            Status &error) {
 | |
|   error.SetErrorString("TODO: implement");
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::PrivateResume() {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
 | |
|   m_auto_resume_signo = m_sent_interrupt_signo;
 | |
| 
 | |
|   if (log) {
 | |
|     if (m_auto_resume_signo)
 | |
|       log->Printf("NativeProcessDarwin::%s(): task 0x%x resuming (with "
 | |
|                   "unhandled interrupt signal %i)...",
 | |
|                   __FUNCTION__, m_task, m_auto_resume_signo);
 | |
|     else
 | |
|       log->Printf("NativeProcessDarwin::%s(): task 0x%x resuming...",
 | |
|                   __FUNCTION__, m_task);
 | |
|   }
 | |
| 
 | |
|   error = ReplyToAllExceptions();
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): aborting, failed to "
 | |
|                   "reply to exceptions: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
|   //    bool stepOverBreakInstruction = step;
 | |
| 
 | |
|   // Let the thread prepare to resume and see if any threads want us to
 | |
|   // step over a breakpoint instruction (ProcessWillResume will modify
 | |
|   // the value of stepOverBreakInstruction).
 | |
|   m_thread_list.ProcessWillResume(*this, m_thread_actions);
 | |
| 
 | |
|   // Set our state accordingly
 | |
|   if (m_thread_actions.NumActionsWithState(eStateStepping))
 | |
|     SetState(eStateStepping);
 | |
|   else
 | |
|     SetState(eStateRunning);
 | |
| 
 | |
|   // Now resume our task.
 | |
|   error = ResumeTask();
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::ReplyToAllExceptions() {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
 | |
| 
 | |
|   TaskPortForProcessID(error);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): no task port, aborting",
 | |
|                   __FUNCTION__);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   std::lock_guard<std::recursive_mutex> locker(m_exception_messages_mutex);
 | |
|   if (m_exception_messages.empty()) {
 | |
|     // We're done.
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   size_t index = 0;
 | |
|   for (auto &message : m_exception_messages) {
 | |
|     if (log) {
 | |
|       log->Printf("NativeProcessDarwin::%s(): replying to exception "
 | |
|                   "%zu...",
 | |
|                   __FUNCTION__, index++);
 | |
|     }
 | |
| 
 | |
|     int thread_reply_signal = 0;
 | |
| 
 | |
|     const tid_t tid =
 | |
|         m_thread_list.GetThreadIDByMachPortNumber(message.state.thread_port);
 | |
|     const ResumeAction *action = nullptr;
 | |
|     if (tid != LLDB_INVALID_THREAD_ID)
 | |
|       action = m_thread_actions.GetActionForThread(tid, false);
 | |
| 
 | |
|     if (action) {
 | |
|       thread_reply_signal = action->signal;
 | |
|       if (thread_reply_signal)
 | |
|         m_thread_actions.SetSignalHandledForThread(tid);
 | |
|     }
 | |
| 
 | |
|     error = message.Reply(m_pid, m_task, thread_reply_signal);
 | |
|     if (error.Fail() && log) {
 | |
|       // We log any error here, but we don't stop the exception
 | |
|       // response handling.
 | |
|       log->Printf("NativeProcessDarwin::%s(): failed to reply to "
 | |
|                   "exception: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|       error.Clear();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Erase all exception message as we should have used and replied
 | |
|   // to them all already.
 | |
|   m_exception_messages.clear();
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::ResumeTask() {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   TaskPortForProcessID(error);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): failed to get task port "
 | |
|                   "for process when attempting to resume: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
|   if (m_task == TASK_NULL) {
 | |
|     error.SetErrorString("task port retrieval succeeded but task port is "
 | |
|                          "null when attempting to resume the task");
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s(): requesting resume of task "
 | |
|                 "0x%4.4x",
 | |
|                 __FUNCTION__, m_task);
 | |
| 
 | |
|   // Get the BasicInfo struct to verify that we're suspended before we try
 | |
|   // to resume the task.
 | |
|   struct task_basic_info task_info;
 | |
|   error = GetTaskBasicInfo(m_task, &task_info);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): failed to get task "
 | |
|                   "BasicInfo when attempting to resume: %s",
 | |
|                   __FUNCTION__, error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // task_resume isn't counted like task_suspend calls are, so if the
 | |
|   // task is not suspended, don't try and resume it since it is already
 | |
|   // running
 | |
|   if (task_info.suspend_count > 0) {
 | |
|     auto mach_err = ::task_resume(m_task);
 | |
|     error.SetError(mach_err, eErrorTypeMachKernel);
 | |
|     if (log) {
 | |
|       if (error.Success())
 | |
|         log->Printf("::task_resume(target_task = 0x%4.4x): success", m_task);
 | |
|       else
 | |
|         log->Printf("::task_resume(target_task = 0x%4.4x) error: %s", m_task,
 | |
|                     error.AsCString());
 | |
|     }
 | |
|   } else {
 | |
|     if (log)
 | |
|       log->Printf("::task_resume(target_task = 0x%4.4x): ignored, "
 | |
|                   "already running",
 | |
|                   m_task);
 | |
|   }
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| bool NativeProcessDarwin::IsTaskValid() const {
 | |
|   if (m_task == TASK_NULL)
 | |
|     return false;
 | |
| 
 | |
|   struct task_basic_info task_info;
 | |
|   return GetTaskBasicInfo(m_task, &task_info).Success();
 | |
| }
 | |
| 
 | |
| bool NativeProcessDarwin::IsTaskValid(task_t task) const {
 | |
|   if (task == TASK_NULL)
 | |
|     return false;
 | |
| 
 | |
|   struct task_basic_info task_info;
 | |
|   return GetTaskBasicInfo(task, &task_info).Success();
 | |
| }
 | |
| 
 | |
| mach_port_t NativeProcessDarwin::GetExceptionPort() const {
 | |
|   return m_exception_port;
 | |
| }
 | |
| 
 | |
| bool NativeProcessDarwin::IsExceptionPortValid() const {
 | |
|   return MACH_PORT_VALID(m_exception_port);
 | |
| }
 | |
| 
 | |
| Status
 | |
| NativeProcessDarwin::GetTaskBasicInfo(task_t task,
 | |
|                                       struct task_basic_info *info) const {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   // Validate args.
 | |
|   if (info == NULL) {
 | |
|     error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): mandatory "
 | |
|                                    "info arg is null",
 | |
|                                    __FUNCTION__);
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // Grab the task if we don't already have it.
 | |
|   if (task == TASK_NULL) {
 | |
|     error.SetErrorStringWithFormat("NativeProcessDarwin::%s(): given task "
 | |
|                                    "is invalid",
 | |
|                                    __FUNCTION__);
 | |
|   }
 | |
| 
 | |
|   mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
 | |
|   auto err = ::task_info(m_task, TASK_BASIC_INFO, (task_info_t)info, &count);
 | |
|   error.SetError(err, eErrorTypeMachKernel);
 | |
|   if (error.Fail()) {
 | |
|     if (log)
 | |
|       log->Printf("::task_info(target_task = 0x%4.4x, "
 | |
|                   "flavor = TASK_BASIC_INFO, task_info_out => %p, "
 | |
|                   "task_info_outCnt => %u) failed: %u (%s)",
 | |
|                   m_task, info, count, error.GetError(), error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   Log *verbose_log(
 | |
|       GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | LIBLLDB_LOG_VERBOSE));
 | |
|   if (verbose_log) {
 | |
|     float user = (float)info->user_time.seconds +
 | |
|                  (float)info->user_time.microseconds / 1000000.0f;
 | |
|     float system = (float)info->user_time.seconds +
 | |
|                    (float)info->user_time.microseconds / 1000000.0f;
 | |
|     verbose_log->Printf("task_basic_info = { suspend_count = %i, "
 | |
|                         "virtual_size = 0x%8.8llx, resident_size = "
 | |
|                         "0x%8.8llx, user_time = %f, system_time = %f }",
 | |
|                         info->suspend_count, (uint64_t)info->virtual_size,
 | |
|                         (uint64_t)info->resident_size, user, system);
 | |
|   }
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::SuspendTask() {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   if (m_task == TASK_NULL) {
 | |
|     error.SetErrorString("task port is null, cannot suspend task");
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s() failed: %s", __FUNCTION__,
 | |
|                   error.AsCString());
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   auto mach_err = ::task_suspend(m_task);
 | |
|   error.SetError(mach_err, eErrorTypeMachKernel);
 | |
|   if (error.Fail() && log)
 | |
|     log->Printf("::task_suspend(target_task = 0x%4.4x)", m_task);
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::Resume(const ResumeActionList &resume_actions) {
 | |
|   Status error;
 | |
|   Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
 | |
| 
 | |
|   if (log)
 | |
|     log->Printf("NativeProcessDarwin::%s() called", __FUNCTION__);
 | |
| 
 | |
|   if (CanResume()) {
 | |
|     m_thread_actions = resume_actions;
 | |
|     error = PrivateResume();
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   auto state = GetState();
 | |
|   if (state == eStateRunning) {
 | |
|     if (log)
 | |
|       log->Printf("NativeProcessDarwin::%s(): task 0x%x is already "
 | |
|                   "running, ignoring...",
 | |
|                   __FUNCTION__, TaskPortForProcessID(error));
 | |
|     return error;
 | |
|   }
 | |
| 
 | |
|   // We can't resume from this state.
 | |
|   error.SetErrorStringWithFormat("task 0x%x has state %s, can't resume",
 | |
|                                  TaskPortForProcessID(error),
 | |
|                                  StateAsCString(state));
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::Halt() {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::Detach() {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::Signal(int signo) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::Interrupt() {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::Kill() {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::GetMemoryRegionInfo(lldb::addr_t load_addr,
 | |
|                                                 MemoryRegionInfo &range_info) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::ReadMemory(lldb::addr_t addr, void *buf,
 | |
|                                        size_t size, size_t &bytes_read) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::ReadMemoryWithoutTrap(lldb::addr_t addr, void *buf,
 | |
|                                                   size_t size,
 | |
|                                                   size_t &bytes_read) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::WriteMemory(lldb::addr_t addr, const void *buf,
 | |
|                                         size_t size, size_t &bytes_written) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::AllocateMemory(size_t size, uint32_t permissions,
 | |
|                                            lldb::addr_t &addr) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::DeallocateMemory(lldb::addr_t addr) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| lldb::addr_t NativeProcessDarwin::GetSharedLibraryInfoAddress() {
 | |
|   return LLDB_INVALID_ADDRESS;
 | |
| }
 | |
| 
 | |
| size_t NativeProcessDarwin::UpdateThreads() { return 0; }
 | |
| 
 | |
| bool NativeProcessDarwin::GetArchitecture(ArchSpec &arch) const {
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::SetBreakpoint(lldb::addr_t addr, uint32_t size,
 | |
|                                           bool hardware) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| void NativeProcessDarwin::DoStopIDBumped(uint32_t newBumpId) {}
 | |
| 
 | |
| Status NativeProcessDarwin::GetLoadedModuleFileSpec(const char *module_path,
 | |
|                                                     FileSpec &file_spec) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| Status NativeProcessDarwin::GetFileLoadAddress(const llvm::StringRef &file_name,
 | |
|                                                lldb::addr_t &load_addr) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| // -----------------------------------------------------------------
 | |
| // NativeProcessProtocol protected interface
 | |
| // -----------------------------------------------------------------
 | |
| Status NativeProcessDarwin::GetSoftwareBreakpointTrapOpcode(
 | |
|     size_t trap_opcode_size_hint, size_t &actual_opcode_size,
 | |
|     const uint8_t *&trap_opcode_bytes) {
 | |
|   Status error;
 | |
|   error.SetErrorString("TODO: implement");
 | |
|   return error;
 | |
| }
 |