| //===-- RNBContext.cpp ------------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Created by Greg Clayton on 12/12/07. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "RNBContext.h" |
| |
| #include <sstream> |
| #include <sys/stat.h> |
| |
| #if defined(__APPLE__) |
| #include <pthread.h> |
| #include <sched.h> |
| #endif |
| |
| #include "CFString.h" |
| #include "DNB.h" |
| #include "DNBLog.h" |
| #include "RNBRemote.h" |
| |
| //---------------------------------------------------------------------- |
| // Destructor |
| //---------------------------------------------------------------------- |
| RNBContext::~RNBContext() { SetProcessID(INVALID_NUB_PROCESS); } |
| |
| //---------------------------------------------------------------------- |
| // RNBContext constructor |
| //---------------------------------------------------------------------- |
| |
| const char *RNBContext::EnvironmentAtIndex(size_t index) { |
| if (index < m_env_vec.size()) |
| return m_env_vec[index].c_str(); |
| else |
| return NULL; |
| } |
| |
| static std::string GetEnvironmentKey(const std::string &env) { |
| std::string key = env.substr(0, env.find('=')); |
| if (!key.empty() && key.back() == '=') |
| key.pop_back(); |
| return key; |
| } |
| |
| void RNBContext::PushEnvironmentIfNeeded(const char *arg) { |
| if (!arg) |
| return; |
| std::string arg_key = GetEnvironmentKey(arg); |
| |
| for (const std::string &entry: m_env_vec) { |
| if (arg_key == GetEnvironmentKey(entry)) |
| return; |
| } |
| m_env_vec.push_back(arg); |
| } |
| |
| const char *RNBContext::ArgumentAtIndex(size_t index) { |
| if (index < m_arg_vec.size()) |
| return m_arg_vec[index].c_str(); |
| else |
| return NULL; |
| } |
| |
| bool RNBContext::SetWorkingDirectory(const char *path) { |
| struct stat working_directory_stat; |
| if (::stat(path, &working_directory_stat) != 0) { |
| m_working_directory.clear(); |
| return false; |
| } |
| m_working_directory.assign(path); |
| return true; |
| } |
| |
| void RNBContext::SetProcessID(nub_process_t pid) { |
| // Delete and events we created |
| if (m_pid != INVALID_NUB_PROCESS) { |
| StopProcessStatusThread(); |
| // Unregister this context as a client of the process's events. |
| } |
| // Assign our new process ID |
| m_pid = pid; |
| |
| if (pid != INVALID_NUB_PROCESS) { |
| StartProcessStatusThread(); |
| } |
| } |
| |
| void RNBContext::StartProcessStatusThread() { |
| DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__); |
| if ((m_events.GetEventBits() & event_proc_thread_running) == 0) { |
| int err = ::pthread_create(&m_pid_pthread, NULL, |
| ThreadFunctionProcessStatus, this); |
| if (err == 0) { |
| // Our thread was successfully kicked off, wait for it to |
| // set the started event so we can safely continue |
| m_events.WaitForSetEvents(event_proc_thread_running); |
| DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread got started!", |
| __FUNCTION__); |
| } else { |
| DNBLogThreadedIf(LOG_RNB_PROC, |
| "RNBContext::%s thread failed to start: err = %i", |
| __FUNCTION__, err); |
| m_events.ResetEvents(event_proc_thread_running); |
| m_events.SetEvents(event_proc_thread_exiting); |
| } |
| } |
| } |
| |
| void RNBContext::StopProcessStatusThread() { |
| DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__); |
| if ((m_events.GetEventBits() & event_proc_thread_running) == |
| event_proc_thread_running) { |
| struct timespec timeout_abstime; |
| DNBTimer::OffsetTimeOfDay(&timeout_abstime, 2, 0); |
| // Wait for 2 seconds for the rx thread to exit |
| if (m_events.WaitForSetEvents(RNBContext::event_proc_thread_exiting, |
| &timeout_abstime) == |
| RNBContext::event_proc_thread_exiting) { |
| DNBLogThreadedIf(LOG_RNB_PROC, |
| "RNBContext::%s thread stopped as requeseted", |
| __FUNCTION__); |
| } else { |
| DNBLogThreadedIf(LOG_RNB_PROC, |
| "RNBContext::%s thread did not stop in 2 seconds...", |
| __FUNCTION__); |
| // Kill the RX thread??? |
| } |
| } |
| } |
| |
| //---------------------------------------------------------------------- |
| // This thread's sole purpose is to watch for any status changes in the |
| // child process. |
| //---------------------------------------------------------------------- |
| void *RNBContext::ThreadFunctionProcessStatus(void *arg) { |
| RNBRemoteSP remoteSP(g_remoteSP); |
| RNBRemote *remote = remoteSP.get(); |
| if (remote == NULL) |
| return NULL; |
| RNBContext &ctx = remote->Context(); |
| |
| nub_process_t pid = ctx.ProcessID(); |
| DNBLogThreadedIf(LOG_RNB_PROC, |
| "RNBContext::%s (arg=%p, pid=%4.4x): thread starting...", |
| __FUNCTION__, arg, pid); |
| ctx.Events().SetEvents(RNBContext::event_proc_thread_running); |
| |
| #if defined(__APPLE__) |
| pthread_setname_np("child process status watcher thread"); |
| #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 |
| #endif |
| |
| bool done = false; |
| while (!done) { |
| DNBLogThreadedIf(LOG_RNB_PROC, |
| "RNBContext::%s calling DNBProcessWaitForEvent(pid, " |
| "eEventProcessRunningStateChanged | " |
| "eEventProcessStoppedStateChanged | eEventStdioAvailable " |
| "| eEventProfileDataAvailable, true)...", |
| __FUNCTION__); |
| nub_event_t pid_status_event = DNBProcessWaitForEvents( |
| pid, |
| eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | |
| eEventStdioAvailable | eEventProfileDataAvailable, |
| true, NULL); |
| DNBLogThreadedIf(LOG_RNB_PROC, |
| "RNBContext::%s calling DNBProcessWaitForEvent(pid, " |
| "eEventProcessRunningStateChanged | " |
| "eEventProcessStoppedStateChanged | eEventStdioAvailable " |
| "| eEventProfileDataAvailable, true) => 0x%8.8x", |
| __FUNCTION__, pid_status_event); |
| |
| if (pid_status_event == 0) { |
| DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (pid=%4.4x) got ZERO back " |
| "from DNBProcessWaitForEvent....", |
| __FUNCTION__, pid); |
| // done = true; |
| } else { |
| if (pid_status_event & eEventStdioAvailable) { |
| DNBLogThreadedIf( |
| LOG_RNB_PROC, |
| "RNBContext::%s (pid=%4.4x) got stdio available event....", |
| __FUNCTION__, pid); |
| ctx.Events().SetEvents(RNBContext::event_proc_stdio_available); |
| // Wait for the main thread to consume this notification if it requested |
| // we wait for it |
| ctx.Events().WaitForResetAck(RNBContext::event_proc_stdio_available); |
| } |
| |
| if (pid_status_event & eEventProfileDataAvailable) { |
| DNBLogThreadedIf( |
| LOG_RNB_PROC, |
| "RNBContext::%s (pid=%4.4x) got profile data event....", |
| __FUNCTION__, pid); |
| ctx.Events().SetEvents(RNBContext::event_proc_profile_data); |
| // Wait for the main thread to consume this notification if it requested |
| // we wait for it |
| ctx.Events().WaitForResetAck(RNBContext::event_proc_profile_data); |
| } |
| |
| if (pid_status_event & (eEventProcessRunningStateChanged | |
| eEventProcessStoppedStateChanged)) { |
| nub_state_t pid_state = DNBProcessGetState(pid); |
| DNBLogThreadedIf( |
| LOG_RNB_PROC, |
| "RNBContext::%s (pid=%4.4x) got process state change: %s", |
| __FUNCTION__, pid, DNBStateAsString(pid_state)); |
| |
| // Let the main thread know there is a process state change to see |
| ctx.Events().SetEvents(RNBContext::event_proc_state_changed); |
| // Wait for the main thread to consume this notification if it requested |
| // we wait for it |
| ctx.Events().WaitForResetAck(RNBContext::event_proc_state_changed); |
| |
| switch (pid_state) { |
| case eStateStopped: |
| break; |
| |
| case eStateInvalid: |
| case eStateExited: |
| case eStateDetached: |
| done = true; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Reset any events that we consumed. |
| DNBProcessResetEvents(pid, pid_status_event); |
| } |
| } |
| DNBLogThreadedIf(LOG_RNB_PROC, |
| "RNBContext::%s (arg=%p, pid=%4.4x): thread exiting...", |
| __FUNCTION__, arg, pid); |
| ctx.Events().ResetEvents(event_proc_thread_running); |
| ctx.Events().SetEvents(event_proc_thread_exiting); |
| return NULL; |
| } |
| |
| const char *RNBContext::EventsAsString(nub_event_t events, std::string &s) { |
| s.clear(); |
| if (events & event_proc_state_changed) |
| s += "proc_state_changed "; |
| if (events & event_proc_thread_running) |
| s += "proc_thread_running "; |
| if (events & event_proc_thread_exiting) |
| s += "proc_thread_exiting "; |
| if (events & event_proc_stdio_available) |
| s += "proc_stdio_available "; |
| if (events & event_proc_profile_data) |
| s += "proc_profile_data "; |
| if (events & event_darwin_log_data_available) |
| s += "darwin_log_data_available "; |
| if (events & event_read_packet_available) |
| s += "read_packet_available "; |
| if (events & event_read_thread_running) |
| s += "read_thread_running "; |
| if (events & event_read_thread_running) |
| s += "read_thread_running "; |
| return s.c_str(); |
| } |
| |
| const char *RNBContext::LaunchStatusAsString(std::string &s) { |
| s.clear(); |
| |
| const char *err_str = m_launch_status.AsString(); |
| if (err_str) |
| s = err_str; |
| else { |
| char error_num_str[64]; |
| snprintf(error_num_str, sizeof(error_num_str), "%u", |
| m_launch_status.Status()); |
| s = error_num_str; |
| } |
| return s.c_str(); |
| } |
| |
| bool RNBContext::ProcessStateRunning() const { |
| nub_state_t pid_state = DNBProcessGetState(m_pid); |
| return pid_state == eStateRunning || pid_state == eStateStepping; |
| } |