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