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