1 | //===-- GDBRemoteCommunicationServerPlatform.cpp --------------------------===// |
---|---|
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 | #include "GDBRemoteCommunicationServerPlatform.h" |
10 | |
11 | #include <cerrno> |
12 | |
13 | #include <chrono> |
14 | #include <csignal> |
15 | #include <cstring> |
16 | #include <mutex> |
17 | #include <optional> |
18 | #include <sstream> |
19 | #include <thread> |
20 | |
21 | #include "llvm/Support/FileSystem.h" |
22 | #include "llvm/Support/JSON.h" |
23 | #include "llvm/Support/Threading.h" |
24 | |
25 | #include "lldb/Host/Config.h" |
26 | #include "lldb/Host/ConnectionFileDescriptor.h" |
27 | #include "lldb/Host/FileAction.h" |
28 | #include "lldb/Host/Host.h" |
29 | #include "lldb/Host/HostInfo.h" |
30 | #include "lldb/Interpreter/CommandCompletions.h" |
31 | #include "lldb/Target/Platform.h" |
32 | #include "lldb/Target/UnixSignals.h" |
33 | #include "lldb/Utility/GDBRemote.h" |
34 | #include "lldb/Utility/LLDBLog.h" |
35 | #include "lldb/Utility/Log.h" |
36 | #include "lldb/Utility/StreamString.h" |
37 | #include "lldb/Utility/StructuredData.h" |
38 | #include "lldb/Utility/TildeExpressionResolver.h" |
39 | #include "lldb/Utility/UriParser.h" |
40 | |
41 | #include "lldb/Utility/StringExtractorGDBRemote.h" |
42 | |
43 | using namespace lldb; |
44 | using namespace lldb_private::process_gdb_remote; |
45 | using namespace lldb_private; |
46 | |
47 | // GDBRemoteCommunicationServerPlatform constructor |
48 | GDBRemoteCommunicationServerPlatform::GDBRemoteCommunicationServerPlatform( |
49 | const Socket::SocketProtocol socket_protocol, uint16_t gdbserver_port) |
50 | : GDBRemoteCommunicationServerCommon(), m_socket_protocol(socket_protocol), |
51 | m_gdbserver_port(gdbserver_port) { |
52 | |
53 | RegisterMemberFunctionHandler( |
54 | packet_type: StringExtractorGDBRemote::eServerPacketType_qC, |
55 | handler: &GDBRemoteCommunicationServerPlatform::Handle_qC); |
56 | RegisterMemberFunctionHandler( |
57 | packet_type: StringExtractorGDBRemote::eServerPacketType_qGetWorkingDir, |
58 | handler: &GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir); |
59 | RegisterMemberFunctionHandler( |
60 | packet_type: StringExtractorGDBRemote::eServerPacketType_qLaunchGDBServer, |
61 | handler: &GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer); |
62 | RegisterMemberFunctionHandler( |
63 | packet_type: StringExtractorGDBRemote::eServerPacketType_qQueryGDBServer, |
64 | handler: &GDBRemoteCommunicationServerPlatform::Handle_qQueryGDBServer); |
65 | RegisterMemberFunctionHandler( |
66 | packet_type: StringExtractorGDBRemote::eServerPacketType_qKillSpawnedProcess, |
67 | handler: &GDBRemoteCommunicationServerPlatform::Handle_qKillSpawnedProcess); |
68 | RegisterMemberFunctionHandler( |
69 | packet_type: StringExtractorGDBRemote::eServerPacketType_qProcessInfo, |
70 | handler: &GDBRemoteCommunicationServerPlatform::Handle_qProcessInfo); |
71 | RegisterMemberFunctionHandler( |
72 | packet_type: StringExtractorGDBRemote::eServerPacketType_qPathComplete, |
73 | handler: &GDBRemoteCommunicationServerPlatform::Handle_qPathComplete); |
74 | RegisterMemberFunctionHandler( |
75 | packet_type: StringExtractorGDBRemote::eServerPacketType_QSetWorkingDir, |
76 | handler: &GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir); |
77 | RegisterMemberFunctionHandler( |
78 | packet_type: StringExtractorGDBRemote::eServerPacketType_jSignalsInfo, |
79 | handler: &GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo); |
80 | |
81 | RegisterPacketHandler(packet_type: StringExtractorGDBRemote::eServerPacketType_interrupt, |
82 | handler: [](StringExtractorGDBRemote packet, Status &error, |
83 | bool &interrupt, bool &quit) { |
84 | error = Status::FromErrorString(str: "interrupt received"); |
85 | interrupt = true; |
86 | return PacketResult::Success; |
87 | }); |
88 | } |
89 | |
90 | // Destructor |
91 | GDBRemoteCommunicationServerPlatform::~GDBRemoteCommunicationServerPlatform() = |
92 | default; |
93 | |
94 | Status GDBRemoteCommunicationServerPlatform::LaunchGDBServer( |
95 | const lldb_private::Args &args, lldb::pid_t &pid, std::string &socket_name, |
96 | shared_fd_t fd) { |
97 | std::ostringstream url; |
98 | if (fd == SharedSocket::kInvalidFD) { |
99 | if (m_socket_protocol == Socket::ProtocolTcp) { |
100 | // Just check that GDBServer exists. GDBServer must be launched after |
101 | // accepting the connection. |
102 | if (!GetDebugserverPath(platform: nullptr)) |
103 | return Status::FromErrorString(str: "unable to locate debugserver"); |
104 | return Status(); |
105 | } |
106 | |
107 | // debugserver does not accept the URL scheme prefix. |
108 | #if !defined(__APPLE__) |
109 | url << Socket::FindSchemeByProtocol(protocol: m_socket_protocol) << "://"; |
110 | #endif |
111 | socket_name = GetDomainSocketPath(prefix: "gdbserver").GetPath(); |
112 | url << socket_name; |
113 | } else { |
114 | if (m_socket_protocol != Socket::ProtocolTcp) |
115 | return Status::FromErrorString(str: "protocol must be tcp"); |
116 | } |
117 | |
118 | // Spawn a debugserver and try to get the port it listens to. |
119 | ProcessLaunchInfo debugserver_launch_info; |
120 | Log *log = GetLog(mask: LLDBLog::Platform); |
121 | LLDB_LOG(log, "Launching debugserver url='{0}', fd={1}...", url.str(), fd); |
122 | |
123 | // Do not run in a new session so that it can not linger after the platform |
124 | // closes. |
125 | debugserver_launch_info.SetLaunchInSeparateProcessGroup(false); |
126 | debugserver_launch_info.SetMonitorProcessCallback( |
127 | [](lldb::pid_t, int, int) {}); |
128 | |
129 | Status error = StartDebugserverProcess( |
130 | url: url.str().c_str(), platform: nullptr, launch_info&: debugserver_launch_info, port: nullptr, inferior_args: &args, pass_comm_fd: fd); |
131 | |
132 | if (error.Success()) { |
133 | pid = debugserver_launch_info.GetProcessID(); |
134 | AddSpawnedProcess(pid); |
135 | LLDB_LOGF(log, |
136 | "GDBRemoteCommunicationServerPlatform::%s() " |
137 | "debugserver launched successfully as pid %"PRIu64, |
138 | __FUNCTION__, pid); |
139 | } else { |
140 | LLDB_LOGF(log, |
141 | "GDBRemoteCommunicationServerPlatform::%s() " |
142 | "debugserver launch failed: %s", |
143 | __FUNCTION__, error.AsCString()); |
144 | } |
145 | return error; |
146 | } |
147 | |
148 | GDBRemoteCommunication::PacketResult |
149 | GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer( |
150 | StringExtractorGDBRemote &packet) { |
151 | // Spawn a local debugserver as a platform so we can then attach or launch a |
152 | // process... |
153 | |
154 | Log *log = GetLog(mask: LLDBLog::Platform); |
155 | LLDB_LOGF(log, "GDBRemoteCommunicationServerPlatform::%s() called", |
156 | __FUNCTION__); |
157 | |
158 | ConnectionFileDescriptor file_conn; |
159 | std::string hostname; |
160 | packet.SetFilePos(::strlen(s: "qLaunchGDBServer;")); |
161 | llvm::StringRef name; |
162 | llvm::StringRef value; |
163 | std::optional<uint16_t> port; |
164 | while (packet.GetNameColonValue(name, value)) { |
165 | if (name == "host") |
166 | hostname = std::string(value); |
167 | else if (name == "port") { |
168 | // Make the Optional valid so we can use its value |
169 | port = 0; |
170 | value.getAsInteger(Radix: 0, Result&: *port); |
171 | } |
172 | } |
173 | |
174 | // Ignore client's hostname and the port. |
175 | |
176 | lldb::pid_t debugserver_pid = LLDB_INVALID_PROCESS_ID; |
177 | std::string socket_name; |
178 | Status error = LaunchGDBServer(args: Args(), pid&: debugserver_pid, socket_name, |
179 | fd: SharedSocket::kInvalidFD); |
180 | if (error.Fail()) |
181 | return SendErrorResponse(error: 9); // EBADF |
182 | |
183 | StreamGDBRemote response; |
184 | uint16_t gdbserver_port = socket_name.empty() ? m_gdbserver_port : 0; |
185 | response.Printf(format: "pid:%"PRIu64 ";port:%u;", debugserver_pid, gdbserver_port); |
186 | if (!socket_name.empty()) { |
187 | response.PutCString(cstr: "socket_name:"); |
188 | response.PutStringAsRawHex8(s: socket_name); |
189 | response.PutChar(ch: ';'); |
190 | } |
191 | |
192 | PacketResult packet_result = SendPacketNoLock(payload: response.GetString()); |
193 | if (packet_result != PacketResult::Success) { |
194 | if (debugserver_pid != LLDB_INVALID_PROCESS_ID) |
195 | Host::Kill(pid: debugserver_pid, SIGINT); |
196 | } |
197 | return packet_result; |
198 | } |
199 | |
200 | GDBRemoteCommunication::PacketResult |
201 | GDBRemoteCommunicationServerPlatform::Handle_qQueryGDBServer( |
202 | StringExtractorGDBRemote &packet) { |
203 | namespace json = llvm::json; |
204 | |
205 | if (!m_pending_gdb_server_socket_name) |
206 | return SendErrorResponse(error: 4); |
207 | |
208 | json::Object server{{.K: "port", .V: m_pending_gdb_server_socket_name->empty() |
209 | ? m_gdbserver_port |
210 | : 0}}; |
211 | |
212 | if (!m_pending_gdb_server_socket_name->empty()) |
213 | server.try_emplace(K: "socket_name", Args&: *m_pending_gdb_server_socket_name); |
214 | |
215 | json::Array server_list; |
216 | server_list.push_back(E: std::move(server)); |
217 | |
218 | StreamGDBRemote response; |
219 | response.AsRawOstream() << std::move(server_list); |
220 | |
221 | StreamGDBRemote escaped_response; |
222 | escaped_response.PutEscapedBytes(s: response.GetString().data(), |
223 | src_len: response.GetSize()); |
224 | return SendPacketNoLock(payload: escaped_response.GetString()); |
225 | } |
226 | |
227 | GDBRemoteCommunication::PacketResult |
228 | GDBRemoteCommunicationServerPlatform::Handle_qKillSpawnedProcess( |
229 | StringExtractorGDBRemote &packet) { |
230 | packet.SetFilePos(::strlen(s: "qKillSpawnedProcess:")); |
231 | |
232 | lldb::pid_t pid = packet.GetU64(LLDB_INVALID_PROCESS_ID); |
233 | |
234 | // verify that we know anything about this pid. |
235 | if (!SpawnedProcessIsRunning(pid)) { |
236 | // not a pid we know about |
237 | return SendErrorResponse(error: 10); |
238 | } |
239 | |
240 | // go ahead and attempt to kill the spawned process |
241 | if (KillSpawnedProcess(pid)) |
242 | return SendOKResponse(); |
243 | else |
244 | return SendErrorResponse(error: 11); |
245 | } |
246 | |
247 | void GDBRemoteCommunicationServerPlatform::AddSpawnedProcess(lldb::pid_t pid) { |
248 | assert(pid != LLDB_INVALID_PROCESS_ID); |
249 | std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex); |
250 | m_spawned_pids.insert(x: pid); |
251 | } |
252 | |
253 | bool GDBRemoteCommunicationServerPlatform::SpawnedProcessIsRunning( |
254 | lldb::pid_t pid) { |
255 | std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex); |
256 | return (m_spawned_pids.find(x: pid) != m_spawned_pids.end()); |
257 | } |
258 | |
259 | bool GDBRemoteCommunicationServerPlatform::KillSpawnedProcess(lldb::pid_t pid) { |
260 | // make sure we know about this process |
261 | if (!SpawnedProcessIsRunning(pid)) { |
262 | // it seems the process has been finished recently |
263 | return true; |
264 | } |
265 | |
266 | // first try a SIGTERM (standard kill) |
267 | Host::Kill(pid, SIGTERM); |
268 | |
269 | // check if that worked |
270 | for (size_t i = 0; i < 10; ++i) { |
271 | if (!SpawnedProcessIsRunning(pid)) { |
272 | // it is now killed |
273 | return true; |
274 | } |
275 | std::this_thread::sleep_for(rtime: std::chrono::milliseconds(10)); |
276 | } |
277 | |
278 | if (!SpawnedProcessIsRunning(pid)) |
279 | return true; |
280 | |
281 | // the launched process still lives. Now try killing it again, this time |
282 | // with an unblockable signal. |
283 | Host::Kill(pid, SIGKILL); |
284 | |
285 | for (size_t i = 0; i < 10; ++i) { |
286 | if (!SpawnedProcessIsRunning(pid)) { |
287 | // it is now killed |
288 | return true; |
289 | } |
290 | std::this_thread::sleep_for(rtime: std::chrono::milliseconds(10)); |
291 | } |
292 | |
293 | // check one more time after the final sleep |
294 | return !SpawnedProcessIsRunning(pid); |
295 | } |
296 | |
297 | GDBRemoteCommunication::PacketResult |
298 | GDBRemoteCommunicationServerPlatform::Handle_qProcessInfo( |
299 | StringExtractorGDBRemote &packet) { |
300 | lldb::pid_t pid = m_process_launch_info.GetProcessID(); |
301 | m_process_launch_info.Clear(); |
302 | |
303 | if (pid == LLDB_INVALID_PROCESS_ID) |
304 | return SendErrorResponse(error: 1); |
305 | |
306 | ProcessInstanceInfo proc_info; |
307 | if (!Host::GetProcessInfo(pid, proc_info)) |
308 | return SendErrorResponse(error: 1); |
309 | |
310 | StreamString response; |
311 | CreateProcessInfoResponse_DebugServerStyle(proc_info, response); |
312 | return SendPacketNoLock(payload: response.GetString()); |
313 | } |
314 | |
315 | GDBRemoteCommunication::PacketResult |
316 | GDBRemoteCommunicationServerPlatform::Handle_qPathComplete( |
317 | StringExtractorGDBRemote &packet) { |
318 | packet.SetFilePos(::strlen(s: "qPathComplete:")); |
319 | const bool only_dir = (packet.GetHexMaxU32(little_endian: false, fail_value: 0) == 1); |
320 | if (packet.GetChar() != ',') |
321 | return SendErrorResponse(error: 85); |
322 | std::string path; |
323 | packet.GetHexByteString(str&: path); |
324 | |
325 | StringList matches; |
326 | StandardTildeExpressionResolver resolver; |
327 | if (only_dir) |
328 | CommandCompletions::DiskDirectories(partial_file_name: path, matches, Resolver&: resolver); |
329 | else |
330 | CommandCompletions::DiskFiles(partial_file_name: path, matches, Resolver&: resolver); |
331 | |
332 | StreamString response; |
333 | response.PutChar(ch: 'M'); |
334 | llvm::StringRef separator; |
335 | std::sort(first: matches.begin(), last: matches.end()); |
336 | for (const auto &match : matches) { |
337 | response << separator; |
338 | separator = ","; |
339 | // encode result strings into hex bytes to avoid unexpected error caused by |
340 | // special characters like '$'. |
341 | response.PutStringAsRawHex8(s: match.c_str()); |
342 | } |
343 | |
344 | return SendPacketNoLock(payload: response.GetString()); |
345 | } |
346 | |
347 | GDBRemoteCommunication::PacketResult |
348 | GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir( |
349 | StringExtractorGDBRemote &packet) { |
350 | |
351 | llvm::SmallString<64> cwd; |
352 | if (std::error_code ec = llvm::sys::fs::current_path(result&: cwd)) |
353 | return SendErrorResponse(error: ec.value()); |
354 | |
355 | StreamString response; |
356 | response.PutBytesAsRawHex8(src: cwd.data(), src_len: cwd.size()); |
357 | return SendPacketNoLock(payload: response.GetString()); |
358 | } |
359 | |
360 | GDBRemoteCommunication::PacketResult |
361 | GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir( |
362 | StringExtractorGDBRemote &packet) { |
363 | packet.SetFilePos(::strlen(s: "QSetWorkingDir:")); |
364 | std::string path; |
365 | packet.GetHexByteString(str&: path); |
366 | |
367 | if (std::error_code ec = llvm::sys::fs::set_current_path(path)) |
368 | return SendErrorResponse(error: ec.value()); |
369 | return SendOKResponse(); |
370 | } |
371 | |
372 | GDBRemoteCommunication::PacketResult |
373 | GDBRemoteCommunicationServerPlatform::Handle_qC( |
374 | StringExtractorGDBRemote &packet) { |
375 | // NOTE: lldb should now be using qProcessInfo for process IDs. This path |
376 | // here |
377 | // should not be used. It is reporting process id instead of thread id. The |
378 | // correct answer doesn't seem to make much sense for lldb-platform. |
379 | // CONSIDER: flip to "unsupported". |
380 | lldb::pid_t pid = m_process_launch_info.GetProcessID(); |
381 | |
382 | StreamString response; |
383 | response.Printf(format: "QC%"PRIx64, pid); |
384 | |
385 | // If we launch a process and this GDB server is acting as a platform, then |
386 | // we need to clear the process launch state so we can start launching |
387 | // another process. In order to launch a process a bunch or packets need to |
388 | // be sent: environment packets, working directory, disable ASLR, and many |
389 | // more settings. When we launch a process we then need to know when to clear |
390 | // this information. Currently we are selecting the 'qC' packet as that |
391 | // packet which seems to make the most sense. |
392 | if (pid != LLDB_INVALID_PROCESS_ID) { |
393 | m_process_launch_info.Clear(); |
394 | } |
395 | |
396 | return SendPacketNoLock(payload: response.GetString()); |
397 | } |
398 | |
399 | GDBRemoteCommunication::PacketResult |
400 | GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo( |
401 | StringExtractorGDBRemote &packet) { |
402 | StructuredData::Array signal_array; |
403 | |
404 | lldb::UnixSignalsSP signals = UnixSignals::CreateForHost(); |
405 | for (auto signo = signals->GetFirstSignalNumber(); |
406 | signo != LLDB_INVALID_SIGNAL_NUMBER; |
407 | signo = signals->GetNextSignalNumber(current_signal: signo)) { |
408 | auto dictionary = std::make_shared<StructuredData::Dictionary>(); |
409 | |
410 | dictionary->AddIntegerItem(key: "signo", value: signo); |
411 | dictionary->AddStringItem(key: "name", value: signals->GetSignalAsStringRef(signo)); |
412 | |
413 | bool suppress, stop, notify; |
414 | signals->GetSignalInfo(signo, should_suppress&: suppress, should_stop&: stop, should_notify&: notify); |
415 | dictionary->AddBooleanItem(key: "suppress", value: suppress); |
416 | dictionary->AddBooleanItem(key: "stop", value: stop); |
417 | dictionary->AddBooleanItem(key: "notify", value: notify); |
418 | |
419 | signal_array.Push(item: dictionary); |
420 | } |
421 | |
422 | StreamString response; |
423 | signal_array.Dump(s&: response); |
424 | return SendPacketNoLock(payload: response.GetString()); |
425 | } |
426 | |
427 | void GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped( |
428 | lldb::pid_t pid) { |
429 | std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex); |
430 | m_spawned_pids.erase(x: pid); |
431 | } |
432 | |
433 | Status GDBRemoteCommunicationServerPlatform::LaunchProcess() { |
434 | if (!m_process_launch_info.GetArguments().GetArgumentCount()) |
435 | return Status::FromErrorStringWithFormat( |
436 | format: "%s: no process command line specified to launch", __FUNCTION__); |
437 | |
438 | // specify the process monitor if not already set. This should generally be |
439 | // what happens since we need to reap started processes. |
440 | if (!m_process_launch_info.GetMonitorProcessCallback()) |
441 | m_process_launch_info.SetMonitorProcessCallback(std::bind( |
442 | f: &GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped, args: this, |
443 | args: std::placeholders::_1)); |
444 | |
445 | Status error = Host::LaunchProcess(launch_info&: m_process_launch_info); |
446 | if (!error.Success()) { |
447 | fprintf(stderr, format: "%s: failed to launch executable %s", __FUNCTION__, |
448 | m_process_launch_info.GetArguments().GetArgumentAtIndex(idx: 0)); |
449 | return error; |
450 | } |
451 | |
452 | printf(format: "Launched '%s' as process %"PRIu64 "...\n", |
453 | m_process_launch_info.GetArguments().GetArgumentAtIndex(idx: 0), |
454 | m_process_launch_info.GetProcessID()); |
455 | |
456 | // add to list of spawned processes. On an lldb-gdbserver, we would expect |
457 | // there to be only one. |
458 | const auto pid = m_process_launch_info.GetProcessID(); |
459 | AddSpawnedProcess(pid); |
460 | |
461 | return error; |
462 | } |
463 | |
464 | const FileSpec &GDBRemoteCommunicationServerPlatform::GetDomainSocketDir() { |
465 | static FileSpec g_domainsocket_dir; |
466 | static llvm::once_flag g_once_flag; |
467 | |
468 | llvm::call_once(flag&: g_once_flag, F: []() { |
469 | const char *domainsocket_dir_env = |
470 | ::getenv(name: "LLDB_DEBUGSERVER_DOMAINSOCKET_DIR"); |
471 | if (domainsocket_dir_env != nullptr) |
472 | g_domainsocket_dir = FileSpec(domainsocket_dir_env); |
473 | else |
474 | g_domainsocket_dir = HostInfo::GetProcessTempDir(); |
475 | }); |
476 | |
477 | return g_domainsocket_dir; |
478 | } |
479 | |
480 | FileSpec |
481 | GDBRemoteCommunicationServerPlatform::GetDomainSocketPath(const char *prefix) { |
482 | llvm::SmallString<128> socket_path; |
483 | llvm::SmallString<128> socket_name( |
484 | (llvm::StringRef(prefix) + ".%%%%%%").str()); |
485 | |
486 | FileSpec socket_path_spec(GetDomainSocketDir()); |
487 | socket_path_spec.AppendPathComponent(component: socket_name.c_str()); |
488 | |
489 | llvm::sys::fs::createUniqueFile(Model: socket_path_spec.GetPath().c_str(), |
490 | ResultPath&: socket_path); |
491 | return FileSpec(socket_path.c_str()); |
492 | } |
493 | |
494 | void GDBRemoteCommunicationServerPlatform::SetPendingGdbServer( |
495 | const std::string &socket_name) { |
496 | m_pending_gdb_server_socket_name = socket_name; |
497 | } |
498 |
Definitions
- GDBRemoteCommunicationServerPlatform
- ~GDBRemoteCommunicationServerPlatform
- LaunchGDBServer
- Handle_qLaunchGDBServer
- Handle_qQueryGDBServer
- Handle_qKillSpawnedProcess
- AddSpawnedProcess
- SpawnedProcessIsRunning
- KillSpawnedProcess
- Handle_qProcessInfo
- Handle_qPathComplete
- Handle_qGetWorkingDir
- Handle_QSetWorkingDir
- Handle_qC
- Handle_jSignalsInfo
- DebugserverProcessReaped
- LaunchProcess
- GetDomainSocketDir
- GetDomainSocketPath
Improve your Profiling and Debugging skills
Find out more