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
30RNBContext::~RNBContext() { SetProcessID(INVALID_NUB_PROCESS); }
31
32// RNBContext constructor
33
34const 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
41static 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
48void 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
60const 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
67bool 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
77void 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
91void 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
112void 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.
136void *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
250const 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
271const 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
286bool RNBContext::ProcessStateRunning() const {
287  nub_state_t pid_state = DNBProcessGetState(m_pid);
288  return pid_state == eStateRunning || pid_state == eStateStepping;
289}
290
291bool 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
299void 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