//===-- RNBContext.cpp ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Created by Greg Clayton on 12/12/07.
//
//===----------------------------------------------------------------------===//
#include "RNBContext.h"
#include <sstream>
#include <sys/stat.h>
#if defined(__APPLE__)
#include <pthread.h>
#include <sched.h>
#endif
#include "CFString.h"
#include "DNB.h"
#include "DNBLog.h"
#include "RNBRemote.h"
#include "MacOSX/MachException.h"
// Destructor
RNBContext::~RNBContext() { SetProcessID(INVALID_NUB_PROCESS); }
// RNBContext constructor
const char *RNBContext::EnvironmentAtIndex(size_t index) {
if (index < m_env_vec.size())
return m_env_vec[index].c_str();
else
return NULL;
}
static std::string GetEnvironmentKey(const std::string &env) {
std::string key = env.substr(0, env.find('='));
if (!key.empty() && key.back() == '=')
key.pop_back();
return key;
}
void RNBContext::PushEnvironmentIfNeeded(const char *arg) {
if (!arg)
return;
std::string arg_key = GetEnvironmentKey(arg);
for (const std::string &entry: m_env_vec) {
if (arg_key == GetEnvironmentKey(entry))
return;
}
m_env_vec.push_back(arg);
}
const char *RNBContext::ArgumentAtIndex(size_t index) {
if (index < m_arg_vec.size())
return m_arg_vec[index].c_str();
else
return NULL;
}
bool RNBContext::SetWorkingDirectory(const char *path) {
struct stat working_directory_stat;
if (::stat(path, &working_directory_stat) != 0) {
m_working_directory.clear();
return false;
}
m_working_directory.assign(path);
return true;
}
void RNBContext::SetProcessID(nub_process_t pid) {
// Delete and events we created
if (m_pid != INVALID_NUB_PROCESS) {
StopProcessStatusThread();
// Unregister this context as a client of the process's events.
}
// Assign our new process ID
m_pid = pid;
if (pid != INVALID_NUB_PROCESS) {
StartProcessStatusThread();
}
}
void RNBContext::StartProcessStatusThread() {
DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__);
if ((m_events.GetEventBits() & event_proc_thread_running) == 0) {
int err = ::pthread_create(&m_pid_pthread, NULL,
ThreadFunctionProcessStatus, this);
if (err == 0) {
// Our thread was successfully kicked off, wait for it to
// set the started event so we can safely continue
m_events.WaitForSetEvents(event_proc_thread_running);
DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s thread got started!",
__FUNCTION__);
} else {
DNBLogThreadedIf(LOG_RNB_PROC,
"RNBContext::%s thread failed to start: err = %i",
__FUNCTION__, err);
m_events.ResetEvents(event_proc_thread_running);
m_events.SetEvents(event_proc_thread_exiting);
}
}
}
void RNBContext::StopProcessStatusThread() {
DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s called", __FUNCTION__);
if ((m_events.GetEventBits() & event_proc_thread_running) ==
event_proc_thread_running) {
struct timespec timeout_abstime;
DNBTimer::OffsetTimeOfDay(&timeout_abstime, 2, 0);
// Wait for 2 seconds for the rx thread to exit
if (m_events.WaitForSetEvents(RNBContext::event_proc_thread_exiting,
&timeout_abstime) ==
RNBContext::event_proc_thread_exiting) {
DNBLogThreadedIf(LOG_RNB_PROC,
"RNBContext::%s thread stopped as requeseted",
__FUNCTION__);
} else {
DNBLogThreadedIf(LOG_RNB_PROC,
"RNBContext::%s thread did not stop in 2 seconds...",
__FUNCTION__);
// Kill the RX thread???
}
}
}
// This thread's sole purpose is to watch for any status changes in the
// child process.
void *RNBContext::ThreadFunctionProcessStatus(void *arg) {
RNBRemoteSP remoteSP(g_remoteSP);
RNBRemote *remote = remoteSP.get();
if (remote == NULL)
return NULL;
RNBContext &ctx = remote->Context();
nub_process_t pid = ctx.ProcessID();
DNBLogThreadedIf(LOG_RNB_PROC,
"RNBContext::%s (arg=%p, pid=%4.4x): thread starting...",
__FUNCTION__, arg, pid);
ctx.Events().SetEvents(RNBContext::event_proc_thread_running);
#if defined(__APPLE__)
pthread_setname_np("child process status watcher thread");
#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
struct sched_param thread_param;
int thread_sched_policy;
if (pthread_getschedparam(pthread_self(), &thread_sched_policy,
&thread_param) == 0) {
thread_param.sched_priority = 47;
pthread_setschedparam(pthread_self(), thread_sched_policy, &thread_param);
}
#endif
#endif
bool done = false;
while (!done) {
DNBLogThreadedIf(LOG_RNB_PROC,
"RNBContext::%s calling DNBProcessWaitForEvent(pid, "
"eEventProcessRunningStateChanged | "
"eEventProcessStoppedStateChanged | eEventStdioAvailable "
"| eEventProfileDataAvailable, true)...",
__FUNCTION__);
nub_event_t pid_status_event = DNBProcessWaitForEvents(
pid,
eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged |
eEventStdioAvailable | eEventProfileDataAvailable,
true, NULL);
DNBLogThreadedIf(LOG_RNB_PROC,
"RNBContext::%s calling DNBProcessWaitForEvent(pid, "
"eEventProcessRunningStateChanged | "
"eEventProcessStoppedStateChanged | eEventStdioAvailable "
"| eEventProfileDataAvailable, true) => 0x%8.8x",
__FUNCTION__, pid_status_event);
if (pid_status_event == 0) {
DNBLogThreadedIf(LOG_RNB_PROC, "RNBContext::%s (pid=%4.4x) got ZERO back "
"from DNBProcessWaitForEvent....",
__FUNCTION__, pid);
// done = true;
} else {
if (pid_status_event & eEventStdioAvailable) {
DNBLogThreadedIf(
LOG_RNB_PROC,
"RNBContext::%s (pid=%4.4x) got stdio available event....",
__FUNCTION__, pid);
ctx.Events().SetEvents(RNBContext::event_proc_stdio_available);
// Wait for the main thread to consume this notification if it requested
// we wait for it
ctx.Events().WaitForResetAck(RNBContext::event_proc_stdio_available);
}
if (pid_status_event & eEventProfileDataAvailable) {
DNBLogThreadedIf(
LOG_RNB_PROC,
"RNBContext::%s (pid=%4.4x) got profile data event....",
__FUNCTION__, pid);
ctx.Events().SetEvents(RNBContext::event_proc_profile_data);
// Wait for the main thread to consume this notification if it requested
// we wait for it
ctx.Events().WaitForResetAck(RNBContext::event_proc_profile_data);
}
if (pid_status_event & (eEventProcessRunningStateChanged |
eEventProcessStoppedStateChanged)) {
nub_state_t pid_state = DNBProcessGetState(pid);
DNBLogThreadedIf(
LOG_RNB_PROC,
"RNBContext::%s (pid=%4.4x) got process state change: %s",
__FUNCTION__, pid, DNBStateAsString(pid_state));
// Let the main thread know there is a process state change to see
ctx.Events().SetEvents(RNBContext::event_proc_state_changed);
// Wait for the main thread to consume this notification if it requested
// we wait for it
ctx.Events().WaitForResetAck(RNBContext::event_proc_state_changed);
switch (pid_state) {
case eStateStopped:
break;
case eStateInvalid:
case eStateExited:
case eStateDetached:
done = true;
break;
default:
break;
}
}
// Reset any events that we consumed.
DNBProcessResetEvents(pid, pid_status_event);
}
}
DNBLogThreadedIf(LOG_RNB_PROC,
"RNBContext::%s (arg=%p, pid=%4.4x): thread exiting...",
__FUNCTION__, arg, pid);
ctx.Events().ResetEvents(event_proc_thread_running);
ctx.Events().SetEvents(event_proc_thread_exiting);
return NULL;
}
const char *RNBContext::EventsAsString(nub_event_t events, std::string &s) {
s.clear();
if (events & event_proc_state_changed)
s += "proc_state_changed ";
if (events & event_proc_thread_running)
s += "proc_thread_running ";
if (events & event_proc_thread_exiting)
s += "proc_thread_exiting ";
if (events & event_proc_stdio_available)
s += "proc_stdio_available ";
if (events & event_proc_profile_data)
s += "proc_profile_data ";
if (events & event_read_packet_available)
s += "read_packet_available ";
if (events & event_read_thread_running)
s += "read_thread_running ";
if (events & event_read_thread_running)
s += "read_thread_running ";
return s.c_str();
}
const char *RNBContext::LaunchStatusAsString(std::string &s) {
s.clear();
const char *err_str = m_launch_status.AsString();
if (err_str)
s = err_str;
else {
char error_num_str[64];
snprintf(error_num_str, sizeof(error_num_str), "%u",
m_launch_status.Status());
s = error_num_str;
}
return s.c_str();
}
bool RNBContext::ProcessStateRunning() const {
nub_state_t pid_state = DNBProcessGetState(m_pid);
return pid_state == eStateRunning || pid_state == eStateStepping;
}
bool RNBContext::AddIgnoredException(const char *exception_name) {
exception_mask_t exc_mask = MachException::ExceptionMask(exception_name);
if (exc_mask == 0)
return false;
m_ignored_exceptions.push_back(exc_mask);
return true;
}
void RNBContext::AddDefaultIgnoredExceptions() {
m_ignored_exceptions.push_back(EXC_MASK_BAD_ACCESS);
m_ignored_exceptions.push_back(EXC_MASK_BAD_INSTRUCTION);
m_ignored_exceptions.push_back(EXC_MASK_ARITHMETIC);
}