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 |
30 | RNBContext::~RNBContext() { SetProcessID(INVALID_NUB_PROCESS); } |
31 | |
32 | // RNBContext constructor |
33 | |
34 | const 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 | |
41 | static std::string GetEnvironmentKey(const std::string &env) { |
42 | std::string key = env.substr(pos: 0, n: env.find(c: '=')); |
43 | if (!key.empty() && key.back() == '=') |
44 | key.pop_back(); |
45 | return key; |
46 | } |
47 | |
48 | void RNBContext::PushEnvironmentIfNeeded(const char *arg) { |
49 | if (!arg) |
50 | return; |
51 | std::string arg_key = GetEnvironmentKey(env: arg); |
52 | |
53 | for (const std::string &entry: m_env_vec) { |
54 | if (arg_key == GetEnvironmentKey(env: entry)) |
55 | return; |
56 | } |
57 | m_env_vec.push_back(x: arg); |
58 | } |
59 | |
60 | const 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 | |
67 | bool RNBContext::SetWorkingDirectory(const char *path) { |
68 | struct stat working_directory_stat; |
69 | if (::stat(file: path, buf: &working_directory_stat) != 0) { |
70 | m_working_directory.clear(); |
71 | return false; |
72 | } |
73 | m_working_directory.assign(s: path); |
74 | return true; |
75 | } |
76 | |
77 | void 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 | |
91 | void 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(newthread: &m_pid_pthread, NULL, |
95 | start_routine: ThreadFunctionProcessStatus, arg: 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(mask: 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(mask: event_proc_thread_running); |
107 | m_events.SetEvents(event_proc_thread_exiting); |
108 | } |
109 | } |
110 | } |
111 | |
112 | void 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(mask: RNBContext::event_proc_thread_exiting, |
120 | timeout_abstime: &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. |
136 | void *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 | event_mask: eEventProcessRunningStateChanged | eEventProcessStoppedStateChanged | |
173 | eEventStdioAvailable | eEventProfileDataAvailable, |
174 | wait_for_set: 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(mask: 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(mask: 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(mask: 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, event_mask: 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(mask: event_proc_thread_running); |
246 | ctx.Events().SetEvents(event_proc_thread_exiting); |
247 | return NULL; |
248 | } |
249 | |
250 | const 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 | |
271 | const 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(s: error_num_str, maxlen: sizeof(error_num_str), format: "%u" , |
280 | m_launch_status.Status()); |
281 | s = error_num_str; |
282 | } |
283 | return s.c_str(); |
284 | } |
285 | |
286 | bool RNBContext::ProcessStateRunning() const { |
287 | nub_state_t pid_state = DNBProcessGetState(pid: m_pid); |
288 | return pid_state == eStateRunning || pid_state == eStateStepping; |
289 | } |
290 | |
291 | bool 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 | |
299 | void 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 | |