| 1 | //===-- RNBContext.cpp ------------------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // Created by Greg Clayton on 12/12/07. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "RNBContext.h" |
| 14 | |
| 15 | #include <sstream> |
| 16 | #include <sys/stat.h> |
| 17 | |
| 18 | #if defined(__APPLE__) |
| 19 | #include <pthread.h> |
| 20 | #include <sched.h> |
| 21 | #endif |
| 22 | |
| 23 | #include "CFString.h" |
| 24 | #include "DNB.h" |
| 25 | #include "DNBLog.h" |
| 26 | #include "RNBRemote.h" |
| 27 | #include "MacOSX/MachException.h" |
| 28 | |
| 29 | // Destructor |
| 30 | RNBContext::~RNBContext() { SetProcessID(INVALID_NUB_PROCESS); } |
| 31 | |
| 32 | // RNBContext constructor |
| 33 | |
| 34 | const char *RNBContext::EnvironmentAtIndex(size_t index) { |
| 35 | if (index < m_env_vec.size()) |
| 36 | return m_env_vec[index].c_str(); |
| 37 | else |
| 38 | return NULL; |
| 39 | } |
| 40 | |
| 41 | static std::string GetEnvironmentKey(const std::string &env) { |
| 42 | std::string key = env.substr(pos: 0, n: env.find(c: '=')); |
| 43 | if (!key.empty() && key.back() == '=') |
| 44 | key.pop_back(); |
| 45 | return key; |
| 46 | } |
| 47 | |
| 48 | void RNBContext::PushEnvironmentIfNeeded(const char *arg) { |
| 49 | if (!arg) |
| 50 | return; |
| 51 | std::string arg_key = GetEnvironmentKey(env: arg); |
| 52 | |
| 53 | for (const std::string &entry: m_env_vec) { |
| 54 | if (arg_key == GetEnvironmentKey(env: entry)) |
| 55 | return; |
| 56 | } |
| 57 | m_env_vec.push_back(x: arg); |
| 58 | } |
| 59 | |
| 60 | const char *RNBContext::ArgumentAtIndex(size_t index) { |
| 61 | if (index < m_arg_vec.size()) |
| 62 | return m_arg_vec[index].c_str(); |
| 63 | else |
| 64 | return NULL; |
| 65 | } |
| 66 | |
| 67 | bool RNBContext::SetWorkingDirectory(const char *path) { |
| 68 | struct stat working_directory_stat; |
| 69 | if (::stat(file: path, buf: &working_directory_stat) != 0) { |
| 70 | m_working_directory.clear(); |
| 71 | return false; |
| 72 | } |
| 73 | m_working_directory.assign(s: path); |
| 74 | return true; |
| 75 | } |
| 76 | |
| 77 | void RNBContext::SetProcessID(nub_process_t pid) { |
| 78 | // Delete and events we created |
| 79 | if (m_pid != INVALID_NUB_PROCESS) { |
| 80 | StopProcessStatusThread(); |
| 81 | // Unregister this context as a client of the process's events. |
| 82 | } |
| 83 | // Assign our new process ID |
| 84 | m_pid = pid; |
| 85 | |
| 86 | if (pid != INVALID_NUB_PROCESS) { |
| 87 | StartProcessStatusThread(); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | void RNBContext::StartProcessStatusThread() { |
| 92 | DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called" , __FUNCTION__); |
| 93 | if ((m_events.GetEventBits() & event_proc_thread_running) == 0) { |
| 94 | int err = ::pthread_create(newthread: &m_pid_pthread, NULL, |
| 95 | start_routine: ThreadFunctionProcessStatus, arg: this); |
| 96 | if (err == 0) { |
| 97 | // Our thread was successfully kicked off, wait for it to |
| 98 | // set the started event so we can safely continue |
| 99 | m_events.WaitForSetEvents(event_proc_thread_running); |
| 100 | DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread got started!" , |
| 101 | __FUNCTION__); |
| 102 | } else { |
| 103 | DNBLogThreadedIf(LOG_RNB_PROC, |
| 104 | "RNBContext::%s thread failed to start: err = %i" , |
| 105 | __FUNCTION__, err); |
| 106 | m_events.ResetEvents(event_proc_thread_running); |
| 107 | m_events.SetEvents(event_proc_thread_exiting); |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | void RNBContext::StopProcessStatusThread() { |
| 113 | DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called" , __FUNCTION__); |
| 114 | if ((m_events.GetEventBits() & event_proc_thread_running) == |
| 115 | event_proc_thread_running) { |
| 116 | struct timespec timeout_abstime; |
| 117 | DNBTimer::OffsetTimeOfDay(&timeout_abstime, 2, 0); |
| 118 | // Wait for 2 seconds for the rx thread to exit |
| 119 | if (m_events.WaitForSetEvents(RNBContext::event_proc_thread_exiting, |
| 120 | &timeout_abstime) == |
| 121 | RNBContext::event_proc_thread_exiting) { |
| 122 | DNBLogThreadedIf(LOG_RNB_PROC, |
| 123 | "RNBContext::%s thread stopped as requeseted" , |
| 124 | __FUNCTION__); |
| 125 | } else { |
| 126 | DNBLogThreadedIf(LOG_RNB_PROC, |
| 127 | "RNBContext::%s thread did not stop in 2 seconds..." , |
| 128 | __FUNCTION__); |
| 129 | // Kill the RX thread??? |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | // This thread's sole purpose is to watch for any status changes in the |
| 135 | // child process. |
| 136 | void *RNBContext::ThreadFunctionProcessStatus(void *arg) { |
| 137 | RNBRemoteSP remoteSP(g_remoteSP); |
| 138 | RNBRemote *remote = remoteSP.get(); |
| 139 | if (remote == NULL) |
| 140 | return NULL; |
| 141 | RNBContext &ctx = remote->Context(); |
| 142 | |
| 143 | nub_process_t pid = ctx.ProcessID(); |
| 144 | DNBLogThreadedIf(LOG_RNB_PROC, |
| 145 | "RNBContext::%s (arg=%p, pid=%4.4x): thread starting..." , |
| 146 | __FUNCTION__, arg, pid); |
| 147 | ctx.Events().SetEvents(RNBContext::event_proc_thread_running); |
| 148 | |
| 149 | #if defined(__APPLE__) |
| 150 | pthread_setname_np("child process status watcher thread" ); |
| 151 | #if defined(__arm__) || defined(__arm64__) || defined(__aarch64__) |
| 152 | struct sched_param thread_param; |
| 153 | int thread_sched_policy; |
| 154 | if (pthread_getschedparam(pthread_self(), &thread_sched_policy, |
| 155 | &thread_param) == 0) { |
| 156 | thread_param.sched_priority = 47; |
| 157 | pthread_setschedparam(pthread_self(), thread_sched_policy, &thread_param); |
| 158 | } |
| 159 | #endif |
| 160 | #endif |
| 161 | |
| 162 | bool done = false; |
| 163 | while (!done) { |
| 164 | DNBLogThreadedIf(LOG_RNB_PROC, |
| 165 | "RNBContext::%s calling DNBProcessWaitForEvent(pid, " |
| 166 | "eEventProcessRunningStateChanged | " |
| 167 | "eEventProcessStoppedStateChanged | eEventStdioAvailable " |
| 168 | "| eEventProfileDataAvailable, true)..." , |
| 169 | __FUNCTION__); |
| 170 | nub_event_t pid_status_event = DNBProcessWaitForEvents( |
| 171 | pid, |
| 172 | event_mask: eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | |
| 173 | eEventStdioAvailable | eEventProfileDataAvailable, |
| 174 | wait_for_set: true, NULL); |
| 175 | DNBLogThreadedIf(LOG_RNB_PROC, |
| 176 | "RNBContext::%s calling DNBProcessWaitForEvent(pid, " |
| 177 | "eEventProcessRunningStateChanged | " |
| 178 | "eEventProcessStoppedStateChanged | eEventStdioAvailable " |
| 179 | "| eEventProfileDataAvailable, true) => 0x%8.8x" , |
| 180 | __FUNCTION__, pid_status_event); |
| 181 | |
| 182 | if (pid_status_event == 0) { |
| 183 | DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (pid=%4.4x) got ZERO back " |
| 184 | "from DNBProcessWaitForEvent...." , |
| 185 | __FUNCTION__, pid); |
| 186 | // done = true; |
| 187 | } else { |
| 188 | if (pid_status_event & eEventStdioAvailable) { |
| 189 | DNBLogThreadedIf( |
| 190 | LOG_RNB_PROC, |
| 191 | "RNBContext::%s (pid=%4.4x) got stdio available event...." , |
| 192 | __FUNCTION__, pid); |
| 193 | ctx.Events().SetEvents(RNBContext::event_proc_stdio_available); |
| 194 | // Wait for the main thread to consume this notification if it requested |
| 195 | // we wait for it |
| 196 | ctx.Events().WaitForResetAck(mask: RNBContext::event_proc_stdio_available); |
| 197 | } |
| 198 | |
| 199 | if (pid_status_event & eEventProfileDataAvailable) { |
| 200 | DNBLogThreadedIf( |
| 201 | LOG_RNB_PROC, |
| 202 | "RNBContext::%s (pid=%4.4x) got profile data event...." , |
| 203 | __FUNCTION__, pid); |
| 204 | ctx.Events().SetEvents(RNBContext::event_proc_profile_data); |
| 205 | // Wait for the main thread to consume this notification if it requested |
| 206 | // we wait for it |
| 207 | ctx.Events().WaitForResetAck(mask: RNBContext::event_proc_profile_data); |
| 208 | } |
| 209 | |
| 210 | if (pid_status_event & (eEventProcessRunningStateChanged | |
| 211 | eEventProcessStoppedStateChanged)) { |
| 212 | nub_state_t pid_state = DNBProcessGetState(pid); |
| 213 | DNBLogThreadedIf( |
| 214 | LOG_RNB_PROC, |
| 215 | "RNBContext::%s (pid=%4.4x) got process state change: %s" , |
| 216 | __FUNCTION__, pid, DNBStateAsString(pid_state)); |
| 217 | |
| 218 | // Let the main thread know there is a process state change to see |
| 219 | ctx.Events().SetEvents(RNBContext::event_proc_state_changed); |
| 220 | // Wait for the main thread to consume this notification if it requested |
| 221 | // we wait for it |
| 222 | ctx.Events().WaitForResetAck(mask: RNBContext::event_proc_state_changed); |
| 223 | |
| 224 | switch (pid_state) { |
| 225 | case eStateStopped: |
| 226 | break; |
| 227 | |
| 228 | case eStateInvalid: |
| 229 | case eStateExited: |
| 230 | case eStateDetached: |
| 231 | done = true; |
| 232 | break; |
| 233 | default: |
| 234 | break; |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | // Reset any events that we consumed. |
| 239 | DNBProcessResetEvents(pid, event_mask: pid_status_event); |
| 240 | } |
| 241 | } |
| 242 | DNBLogThreadedIf(LOG_RNB_PROC, |
| 243 | "RNBContext::%s (arg=%p, pid=%4.4x): thread exiting..." , |
| 244 | __FUNCTION__, arg, pid); |
| 245 | ctx.Events().ResetEvents(mask: event_proc_thread_running); |
| 246 | ctx.Events().SetEvents(event_proc_thread_exiting); |
| 247 | return NULL; |
| 248 | } |
| 249 | |
| 250 | const char *RNBContext::EventsAsString(nub_event_t events, std::string &s) { |
| 251 | s.clear(); |
| 252 | if (events & event_proc_state_changed) |
| 253 | s += "proc_state_changed " ; |
| 254 | if (events & event_proc_thread_running) |
| 255 | s += "proc_thread_running " ; |
| 256 | if (events & event_proc_thread_exiting) |
| 257 | s += "proc_thread_exiting " ; |
| 258 | if (events & event_proc_stdio_available) |
| 259 | s += "proc_stdio_available " ; |
| 260 | if (events & event_proc_profile_data) |
| 261 | s += "proc_profile_data " ; |
| 262 | if (events & event_read_packet_available) |
| 263 | s += "read_packet_available " ; |
| 264 | if (events & event_read_thread_running) |
| 265 | s += "read_thread_running " ; |
| 266 | if (events & event_read_thread_running) |
| 267 | s += "read_thread_running " ; |
| 268 | return s.c_str(); |
| 269 | } |
| 270 | |
| 271 | const char *RNBContext::LaunchStatusAsString(std::string &s) { |
| 272 | s.clear(); |
| 273 | |
| 274 | const char *err_str = m_launch_status.AsString(); |
| 275 | if (err_str) |
| 276 | s = err_str; |
| 277 | else { |
| 278 | char error_num_str[64]; |
| 279 | snprintf(s: error_num_str, maxlen: sizeof(error_num_str), format: "%u" , |
| 280 | m_launch_status.Status()); |
| 281 | s = error_num_str; |
| 282 | } |
| 283 | return s.c_str(); |
| 284 | } |
| 285 | |
| 286 | bool RNBContext::ProcessStateRunning() const { |
| 287 | nub_state_t pid_state = DNBProcessGetState(pid: m_pid); |
| 288 | return pid_state == eStateRunning || pid_state == eStateStepping; |
| 289 | } |
| 290 | |
| 291 | bool RNBContext::AddIgnoredException(const char *exception_name) { |
| 292 | exception_mask_t exc_mask = MachException::ExceptionMask(exception_name); |
| 293 | if (exc_mask == 0) |
| 294 | return false; |
| 295 | m_ignored_exceptions.push_back(exc_mask); |
| 296 | return true; |
| 297 | } |
| 298 | |
| 299 | void RNBContext::AddDefaultIgnoredExceptions() { |
| 300 | m_ignored_exceptions.push_back(EXC_MASK_BAD_ACCESS); |
| 301 | m_ignored_exceptions.push_back(EXC_MASK_BAD_INSTRUCTION); |
| 302 | m_ignored_exceptions.push_back(EXC_MASK_ARITHMETIC); |
| 303 | } |
| 304 | |