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