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
~RNBContext()30 RNBContext::~RNBContext() { SetProcessID(INVALID_NUB_PROCESS); }
31 
32 // RNBContext constructor
33 
EnvironmentAtIndex(size_t index)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 
GetEnvironmentKey(const std::string & env)41 static std::string GetEnvironmentKey(const std::string &env) {
42   std::string key = env.substr(0, env.find('='));
43   if (!key.empty() && key.back() == '=')
44     key.pop_back();
45   return key;
46 }
47 
PushEnvironmentIfNeeded(const char * arg)48 void RNBContext::PushEnvironmentIfNeeded(const char *arg) {
49   if (!arg)
50     return;
51   std::string arg_key = GetEnvironmentKey(arg);
52 
53   for (const std::string &entry: m_env_vec) {
54     if (arg_key == GetEnvironmentKey(entry))
55       return;
56   }
57   m_env_vec.push_back(arg);
58 }
59 
ArgumentAtIndex(size_t index)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 
SetWorkingDirectory(const char * path)67 bool RNBContext::SetWorkingDirectory(const char *path) {
68   struct stat working_directory_stat;
69   if (::stat(path, &working_directory_stat) != 0) {
70     m_working_directory.clear();
71     return false;
72   }
73   m_working_directory.assign(path);
74   return true;
75 }
76 
SetProcessID(nub_process_t pid)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 
StartProcessStatusThread()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(&m_pid_pthread, NULL,
95                                ThreadFunctionProcessStatus, 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 
StopProcessStatusThread()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.
ThreadFunctionProcessStatus(void * arg)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         eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged |
173             eEventStdioAvailable | eEventProfileDataAvailable,
174         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(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(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(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, 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(event_proc_thread_running);
246   ctx.Events().SetEvents(event_proc_thread_exiting);
247   return NULL;
248 }
249 
EventsAsString(nub_event_t events,std::string & s)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 
LaunchStatusAsString(std::string & s)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(error_num_str, sizeof(error_num_str), "%u",
280              m_launch_status.Status());
281     s = error_num_str;
282   }
283   return s.c_str();
284 }
285 
ProcessStateRunning() const286 bool RNBContext::ProcessStateRunning() const {
287   nub_state_t pid_state = DNBProcessGetState(m_pid);
288   return pid_state == eStateRunning || pid_state == eStateStepping;
289 }
290 
AddIgnoredException(const char * exception_name)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 
AddDefaultIgnoredExceptions()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