//===-- 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 #include #include #include #include #include // 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 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 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 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(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 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 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(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 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(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(eExitTypeInvalid, -1, "failed to receive waitpid result", 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(eExitTypeInvalid, -1, "failed to receive waitpid result", 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); ExitType exit_type = eExitTypeInvalid; int exit_status = -1; if (WIFEXITED(status)) { exit_type = eExitTypeExit; exit_status = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { exit_type = eExitTypeSignal; exit_status = WTERMSIG(status); } SetExitStatus(exit_type, exit_status, nullptr, 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 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 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; }