[lldb] [llvm] [lldb] [lldb-server] Introduce Host/qnx and NativeProcessQNX (PR #97630)

Ayush Sahay via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 3 13:21:01 PDT 2024


https://github.com/ayushsahay1837 created https://github.com/llvm/llvm-project/pull/97630

This change provisions the _QNX_ host and process plugins, and is the fifth and final in a series of changes that look to facilitate remote debug of _AArch64_ targets on _QNX_.

_QNX Neutrino Real-Time Operating System_ is a commercial Unix-like real-time operating system primarily targeting the embedded systems market including automotive, medical devices, robotics, transportation, and industrial embedded systems.

The series of changes in question looks to provision support for –

- Launching a debuggee
- Attaching to a debuggee
- Having the debuggee come up stopped at the entry point
- Setting breakpoints
- Stopping at breakpoints
- Reading/writing contents of/to the debuggee's memory
- Reading/writing contents of/to the debuggee's registers
- Reading/writing contents of/to the debuggee's variables
- Resuming the debuggee's execution
- Single-stepping the debuggee's execution
- Interrupting the debuggee's execution
- Dumping information pertaining to the debuggee's stack trace

Kindly note that _ptrace_ isn't available on _QNX_. Instead, _devctl_ can be leveraged to observe and control the execution of a process under debug on _QNX_.

Any additional support (including the facilitation of execution of tests) will be the subject of future work.

>From 3452438c5abc7b1e00548bce2142ece395753fc2 Mon Sep 17 00:00:00 2001
From: Ayush Sahay <quic_asahay at quicinc.com>
Date: Thu, 11 Jan 2024 20:00:02 +0530
Subject: [PATCH] [lldb] [lldb-server] Introduce Host/qnx and NativeProcessQNX

Provision the QNX host and process plugins.
---
 lldb/cmake/modules/LLDBConfig.cmake           |    2 +-
 lldb/include/lldb/Host/HostInfo.h             |    3 +
 lldb/include/lldb/Host/posix/MainLoopPosix.h  |   20 +-
 lldb/include/lldb/Host/qnx/HostInfoQNX.h      |   24 +
 lldb/include/lldb/Host/qnx/Support.h          |   33 +
 lldb/source/Host/CMakeLists.txt               |    7 +
 lldb/source/Host/common/ProcessLaunchInfo.cpp |    3 +
 lldb/source/Host/common/PseudoTerminal.cpp    |   24 +
 lldb/source/Host/common/TCPSocket.cpp         |    3 +
 lldb/source/Host/posix/MainLoopPosix.cpp      |   58 +-
 .../Host/posix/ProcessLauncherPosixFork.cpp   |  192 ++-
 lldb/source/Host/qnx/Host.cpp                 |  183 +++
 lldb/source/Host/qnx/HostInfoQNX.cpp          |   22 +
 lldb/source/Host/qnx/Support.cpp              |   41 +
 lldb/source/Plugins/Process/CMakeLists.txt    |    3 +
 .../source/Plugins/Process/QNX/CMakeLists.txt |   16 +
 .../Plugins/Process/QNX/NativeProcessQNX.cpp  | 1066 +++++++++++++++++
 .../Plugins/Process/QNX/NativeProcessQNX.h    |  157 +++
 .../Process/QNX/NativeRegisterContextQNX.cpp  |   12 +
 .../Process/QNX/NativeRegisterContextQNX.h    |   37 +
 .../QNX/NativeRegisterContextQNX_arm64.cpp    |  344 ++++++
 .../QNX/NativeRegisterContextQNX_arm64.h      |   66 +
 .../Plugins/Process/QNX/NativeThreadQNX.cpp   |  141 +++
 .../Plugins/Process/QNX/NativeThreadQNX.h     |   73 ++
 .../Plugins/Process/Utility/CMakeLists.txt    |    1 +
 .../Plugins/Process/Utility/QNXSignals.cpp    |  140 +++
 .../Plugins/Process/Utility/QNXSignals.h      |   27 +
 lldb/source/Target/UnixSignals.cpp            |    3 +
 lldb/source/Utility/CMakeLists.txt            |    4 +
 lldb/tools/lldb-server/CMakeLists.txt         |    4 +
 lldb/tools/lldb-server/lldb-gdbserver.cpp     |    4 +
 llvm/include/llvm/Support/ExitCodes.h         |   10 +-
 llvm/lib/Support/Unix/Path.inc                |   12 +-
 llvm/lib/Support/Unix/Signals.inc             |    6 +
 34 files changed, 2725 insertions(+), 16 deletions(-)
 create mode 100644 lldb/include/lldb/Host/qnx/HostInfoQNX.h
 create mode 100644 lldb/include/lldb/Host/qnx/Support.h
 create mode 100644 lldb/source/Host/qnx/Host.cpp
 create mode 100644 lldb/source/Host/qnx/HostInfoQNX.cpp
 create mode 100644 lldb/source/Host/qnx/Support.cpp
 create mode 100644 lldb/source/Plugins/Process/QNX/CMakeLists.txt
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeProcessQNX.cpp
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeProcessQNX.h
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.cpp
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.h
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.cpp
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.h
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeThreadQNX.cpp
 create mode 100644 lldb/source/Plugins/Process/QNX/NativeThreadQNX.h
 create mode 100644 lldb/source/Plugins/Process/Utility/QNXSignals.cpp
 create mode 100644 lldb/source/Plugins/Process/Utility/QNXSignals.h

diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake
index a60921990cf77..139f1c98bd5a1 100644
--- a/lldb/cmake/modules/LLDBConfig.cmake
+++ b/lldb/cmake/modules/LLDBConfig.cmake
@@ -299,7 +299,7 @@ endif()
 
 # Figure out if lldb could use lldb-server.  If so, then we'll
 # ensure we build lldb-server when an lldb target is being built.
-if (CMAKE_SYSTEM_NAME MATCHES "Android|Darwin|FreeBSD|Linux|NetBSD|Windows")
+if (CMAKE_SYSTEM_NAME MATCHES "Android|Darwin|FreeBSD|Linux|NetBSD|Windows|QNX")
   set(LLDB_CAN_USE_LLDB_SERVER ON)
 else()
   set(LLDB_CAN_USE_LLDB_SERVER OFF)
diff --git a/lldb/include/lldb/Host/HostInfo.h b/lldb/include/lldb/Host/HostInfo.h
index b7010d69d88e7..82fa911ff1204 100644
--- a/lldb/include/lldb/Host/HostInfo.h
+++ b/lldb/include/lldb/Host/HostInfo.h
@@ -55,6 +55,9 @@
 #elif defined(__APPLE__)
 #include "lldb/Host/macosx/HostInfoMacOSX.h"
 #define HOST_INFO_TYPE HostInfoMacOSX
+#elif defined(__QNX__)
+#include "lldb/Host/qnx/HostInfoQNX.h"
+#define HOST_INFO_TYPE HostInfoQNX
 #else
 #include "lldb/Host/posix/HostInfoPosix.h"
 #define HOST_INFO_TYPE HostInfoPosix
diff --git a/lldb/include/lldb/Host/posix/MainLoopPosix.h b/lldb/include/lldb/Host/posix/MainLoopPosix.h
index 07497b7b8c259..6ff1c71f4d0c0 100644
--- a/lldb/include/lldb/Host/posix/MainLoopPosix.h
+++ b/lldb/include/lldb/Host/posix/MainLoopPosix.h
@@ -63,15 +63,30 @@ class MainLoopPosix : public MainLoopBase {
   class SignalHandle {
   public:
     ~SignalHandle() { m_mainloop.UnregisterSignal(m_signo, m_callback_it); }
+#if defined(__QNX__)
+    std::weak_ptr<siginfo_t> GetSiginfo() const { return m_siginfo; }
+#endif
 
   private:
     SignalHandle(MainLoopPosix &mainloop, int signo,
+#if defined(__QNX__)
+                 std::list<Callback>::iterator callback_it,
+                 std::weak_ptr<siginfo_t> siginfo)
+        : m_mainloop(mainloop), m_signo(signo), m_callback_it(callback_it),
+          m_siginfo(siginfo) {
+    }
+#else
                  std::list<Callback>::iterator callback_it)
-        : m_mainloop(mainloop), m_signo(signo), m_callback_it(callback_it) {}
+        : m_mainloop(mainloop), m_signo(signo), m_callback_it(callback_it) {
+    }
+#endif
 
     MainLoopPosix &m_mainloop;
     int m_signo;
     std::list<Callback>::iterator m_callback_it;
+#if defined(__QNX__)
+    std::weak_ptr<siginfo_t> m_siginfo;
+#endif
 
     friend class MainLoopPosix;
     SignalHandle(const SignalHandle &) = delete;
@@ -81,6 +96,9 @@ class MainLoopPosix : public MainLoopBase {
   struct SignalInfo {
     std::list<Callback> callbacks;
     struct sigaction old_action;
+#if defined(__QNX__)
+    std::shared_ptr<siginfo_t> siginfo;
+#endif
     bool was_blocked : 1;
   };
   class RunImpl;
diff --git a/lldb/include/lldb/Host/qnx/HostInfoQNX.h b/lldb/include/lldb/Host/qnx/HostInfoQNX.h
new file mode 100644
index 0000000000000..94fd51279ed53
--- /dev/null
+++ b/lldb/include/lldb/Host/qnx/HostInfoQNX.h
@@ -0,0 +1,24 @@
+//===-- HostInfoQNX.h -------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef lldb_Host_qnx_HostInfoQNX_h_
+#define lldb_Host_qnx_HostInfoQNX_h_
+
+#include "lldb/Host/posix/HostInfoPosix.h"
+
+namespace lldb_private {
+
+class HostInfoQNX : public HostInfoPosix {
+public:
+  static llvm::VersionTuple GetOSVersion();
+  static std::optional<std::string> GetOSBuildString();
+  static FileSpec GetProgramFileSpec();
+};
+} // namespace lldb_private
+
+#endif
diff --git a/lldb/include/lldb/Host/qnx/Support.h b/lldb/include/lldb/Host/qnx/Support.h
new file mode 100644
index 0000000000000..975c45c4d2ba6
--- /dev/null
+++ b/lldb/include/lldb/Host/qnx/Support.h
@@ -0,0 +1,33 @@
+//===-- Support.h -----------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_HOST_QNX_SUPPORT_H
+#define LLDB_HOST_QNX_SUPPORT_H
+
+#include <memory>
+
+#include "lldb/Host/File.h"
+#include "lldb/lldb-forward.h"
+
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+namespace lldb_private {
+
+llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+getProcFile(::pid_t pid, const llvm::Twine &file);
+
+llvm::Expected<lldb::FileUP>
+openProcFile(::pid_t pid, const llvm::Twine &file, File::OpenOptions options,
+             uint32_t permissions = lldb::eFilePermissionsFileDefault,
+             bool should_close_fd = true);
+
+} // namespace lldb_private
+
+#endif // #ifndef LLDB_HOST_QNX_SUPPORT_H
diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt
index c2e091ee8555b..205705a9a28df 100644
--- a/lldb/source/Host/CMakeLists.txt
+++ b/lldb/source/Host/CMakeLists.txt
@@ -133,6 +133,13 @@ else()
       openbsd/Host.cpp
       openbsd/HostInfoOpenBSD.cpp
       )
+
+  elseif (CMAKE_SYSTEM_NAME MATCHES "QNX")
+    add_host_subdirectory(qnx
+      qnx/Host.cpp
+      qnx/HostInfoQNX.cpp
+      qnx/Support.cpp
+      )
   endif()
 endif()
 
diff --git a/lldb/source/Host/common/ProcessLaunchInfo.cpp b/lldb/source/Host/common/ProcessLaunchInfo.cpp
index a1866b2a99fd8..d43b55ba0b3e6 100644
--- a/lldb/source/Host/common/ProcessLaunchInfo.cpp
+++ b/lldb/source/Host/common/ProcessLaunchInfo.cpp
@@ -213,7 +213,10 @@ llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() {
   // We really shouldn't be specifying platform specific flags that are
   // intended for a system call in generic code.  But this will have to
   // do for now.
+#if !defined(__QNX__)
+  // O_CLOEXEC is NOT a valid flag for posix_openpt on QNX.
   open_flags |= O_CLOEXEC;
+#endif
 #endif
   if (llvm::Error Err = m_pty->OpenFirstAvailablePrimary(open_flags))
     return Err;
diff --git a/lldb/source/Host/common/PseudoTerminal.cpp b/lldb/source/Host/common/PseudoTerminal.cpp
index d53327973eb27..bd183679655d2 100644
--- a/lldb/source/Host/common/PseudoTerminal.cpp
+++ b/lldb/source/Host/common/PseudoTerminal.cpp
@@ -72,6 +72,24 @@ llvm::Error PseudoTerminal::OpenFirstAvailablePrimary(int oflag) {
         std::error_code(errno, std::generic_category()));
   }
 
+#if defined(FD_CLOEXEC)
+  // Enable the close-on-exec flag for the primary file descriptor if it hasn't
+  // already been enabled.
+  // NB: In multithreaded programs, using fcntl to set the close-on-exec flag at
+  // the same time as another thread performs a fork plus execve risks a race
+  // condition that may unintentionally leak the file descriptor to the program
+  // executed in the child process but, apparently, we don't launch processes
+  // for debugging from within multithreaded contexts at the moment.
+  int flags = ::fcntl(m_primary_fd, F_GETFD);
+  if (flags == -1)
+    return llvm::errorCodeToError(
+        std::error_code(errno, std::generic_category()));
+  if (!(flags & FD_CLOEXEC))
+    if (::fcntl(m_primary_fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+      return llvm::errorCodeToError(
+          std::error_code(errno, std::generic_category()));
+#endif
+
   // Grant access to the secondary pseudo terminal
   if (::grantpt(m_primary_fd) < 0) {
     std::error_code EC(errno, std::generic_category());
@@ -124,7 +142,13 @@ std::string PseudoTerminal::GetSecondaryName() const {
     buf[0] = '\0';
     int r = ptsname_r(m_primary_fd, buf, sizeof(buf));
     UNUSED_IF_ASSERT_DISABLED(r);
+#if defined(__QNX__)
+    // ptsname_r returns a pointer to a null-terminated string containing the
+    // pathname of the corresponding slave device on success on QNX.
+    assert(r != 0);
+#else
     assert(r == 0);
+#endif
     return buf;
 #if defined(__APPLE__)
   } else {
diff --git a/lldb/source/Host/common/TCPSocket.cpp b/lldb/source/Host/common/TCPSocket.cpp
index df4737216ed3a..5efcffbb29d0f 100644
--- a/lldb/source/Host/common/TCPSocket.cpp
+++ b/lldb/source/Host/common/TCPSocket.cpp
@@ -26,6 +26,9 @@
 #include <arpa/inet.h>
 #include <netinet/tcp.h>
 #include <sys/socket.h>
+#if defined(__QNX__)
+#include <sys/select.h>
+#endif
 #endif
 
 #if defined(_WIN32)
diff --git a/lldb/source/Host/posix/MainLoopPosix.cpp b/lldb/source/Host/posix/MainLoopPosix.cpp
index 5fe4d015251c8..37edc016e99b8 100644
--- a/lldb/source/Host/posix/MainLoopPosix.cpp
+++ b/lldb/source/Host/posix/MainLoopPosix.cpp
@@ -20,12 +20,14 @@
 #include <vector>
 
 // Multiplexing is implemented using kqueue on systems that support it (BSD
-// variants including OSX). On linux we use ppoll, while android uses pselect
-// (ppoll is present but not implemented properly). On windows we use WSApoll
-// (which does not support signals).
+// variants including OSX). On linux we use ppoll, while android and QNX use
+// pselect (ppoll is present on Android but not implemented properly). On
+// windows we use WSApoll (which does not support signals).
 
 #if HAVE_SYS_EVENT_H
 #include <sys/event.h>
+#elif defined(__QNX__)
+#include <sys/select.h>
 #elif defined(__ANDROID__)
 #include <sys/syscall.h>
 #else
@@ -36,10 +38,17 @@ using namespace lldb;
 using namespace lldb_private;
 
 static sig_atomic_t g_signal_flags[NSIG];
+#if defined(__QNX__)
+static llvm::DenseMap<int, siginfo_t> g_signal_info;
+#endif
 
 static void SignalHandler(int signo, siginfo_t *info, void *) {
   assert(signo < NSIG);
   g_signal_flags[signo] = 1;
+#if defined(__QNX__)
+  siginfo_t siginfo(*info);
+  g_signal_info.insert({signo, siginfo});
+#endif
 }
 
 class MainLoopPosix::RunImpl {
@@ -59,7 +68,7 @@ class MainLoopPosix::RunImpl {
   int num_events = -1;
 
 #else
-#ifdef __ANDROID__
+#if defined(__ANDROID__) || defined(__QNX__)
   fd_set read_fd_set;
 #else
   std::vector<struct pollfd> read_fds;
@@ -113,7 +122,7 @@ void MainLoopPosix::RunImpl::ProcessEvents() {
 }
 #else
 MainLoopPosix::RunImpl::RunImpl(MainLoopPosix &loop) : loop(loop) {
-#ifndef __ANDROID__
+#if !defined(__ANDROID__) && !defined(__QNX__)
   read_fds.reserve(loop.m_read_fds.size());
 #endif
 }
@@ -129,7 +138,7 @@ sigset_t MainLoopPosix::RunImpl::get_sigmask() {
   return sigmask;
 }
 
-#ifdef __ANDROID__
+#if defined(__ANDROID__) || defined(__QNX__)
 Status MainLoopPosix::RunImpl::Poll() {
   // ppoll(2) is not supported on older all android versions. Also, older
   // versions android (API <= 19) implemented pselect in a non-atomic way, as a
@@ -144,6 +153,7 @@ Status MainLoopPosix::RunImpl::Poll() {
     nfds = std::max(nfds, fd.first + 1);
   }
 
+#if defined(__ANDROID__)
   union {
     sigset_t set;
     uint64_t pad;
@@ -157,6 +167,11 @@ Status MainLoopPosix::RunImpl::Poll() {
   } extra_data = {&kernel_sigset, sizeof(kernel_sigset)};
   if (syscall(__NR_pselect6, nfds, &read_fd_set, nullptr, nullptr, nullptr,
               &extra_data) == -1) {
+#else
+  // QNX.
+  sigset_t sigmask = get_sigmask();
+  if (pselect(nfds, &read_fd_set, nullptr, nullptr, nullptr, &sigmask) == -1) {
+#endif
     if (errno != EINTR)
       return Status(errno, eErrorTypePOSIX);
     else
@@ -188,7 +203,7 @@ Status MainLoopPosix::RunImpl::Poll() {
 #endif
 
 void MainLoopPosix::RunImpl::ProcessEvents() {
-#ifdef __ANDROID__
+#if defined(__ANDROID__) || defined(__QNX__)
   // Collect first all readable file descriptors into a separate vector and
   // then iterate over it to invoke callbacks. Iterating directly over
   // loop.m_read_fds is not possible because the callbacks can modify the
@@ -213,14 +228,28 @@ void MainLoopPosix::RunImpl::ProcessEvents() {
 
   std::vector<int> signals;
   for (const auto &entry : loop.m_signals)
+#if defined(__QNX__)
+    if (g_signal_flags[entry.first] != 0) {
+      signals.push_back(entry.first);
+      *(entry.second.siginfo.get()) = g_signal_info.find(entry.first)->second;
+    }
+#else
     if (g_signal_flags[entry.first] != 0)
       signals.push_back(entry.first);
+#endif
 
   for (const auto &signal : signals) {
     if (loop.m_terminate_request)
       return;
     g_signal_flags[signal] = 0;
+#if defined(__QNX__)
+    g_signal_info.erase(signal);
+#endif
     loop.ProcessSignal(signal);
+#if defined(__QNX__)
+    memset(loop.m_signals.find(signal)->second.siginfo.get(), 0,
+           sizeof(siginfo_t));
+#endif
   }
 }
 #endif
@@ -282,7 +311,12 @@ MainLoopPosix::RegisterSignal(int signo, const Callback &callback,
   if (signal_it != m_signals.end()) {
     auto callback_it = signal_it->second.callbacks.insert(
         signal_it->second.callbacks.end(), callback);
+#if defined(__QNX__)
+    std::weak_ptr<siginfo_t> siginfo = signal_it->second.siginfo;
+    return SignalHandleUP(new SignalHandle(*this, signo, callback_it, siginfo));
+#else
     return SignalHandleUP(new SignalHandle(*this, signo, callback_it));
+#endif
   }
 
   SignalInfo info;
@@ -315,11 +349,21 @@ MainLoopPosix::RegisterSignal(int signo, const Callback &callback,
   ret = pthread_sigmask(HAVE_SYS_EVENT_H ? SIG_UNBLOCK : SIG_BLOCK,
                         &new_action.sa_mask, &old_set);
   assert(ret == 0 && "pthread_sigmask failed");
+#if defined(__QNX__)
+  info.siginfo = std::make_shared<siginfo_t>();
+#endif
   info.was_blocked = sigismember(&old_set, signo);
   auto insert_ret = m_signals.insert({signo, info});
+#if defined(__QNX__)
+  std::weak_ptr<siginfo_t> siginfo = info.siginfo;
+#endif
 
   return SignalHandleUP(new SignalHandle(
+#if defined(__QNX__)
+      *this, signo, insert_ret.first->second.callbacks.begin(), siginfo));
+#else
       *this, signo, insert_ret.first->second.callbacks.begin()));
+#endif
 }
 
 void MainLoopPosix::UnregisterReadObject(IOObject::WaitableHandle handle) {
diff --git a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
index 0a832ebad13a7..b539dedcb8bcf 100644
--- a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
+++ b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp
@@ -17,7 +17,11 @@
 #include "llvm/Support/Errno.h"
 
 #include <climits>
+#if defined(__QNX__)
+#include <spawn.h>
+#else
 #include <sys/ptrace.h>
+#endif
 #include <sys/wait.h>
 #include <unistd.h>
 
@@ -35,6 +39,10 @@
 #include <sys/personality.h>
 #endif
 
+#if !defined(EOK)
+#define EOK 0 /* No error */
+#endif
+
 using namespace lldb;
 using namespace lldb_private;
 
@@ -193,7 +201,9 @@ struct ForkLaunchInfo {
     }
 
     // Start tracing this child that is about to exec.
+#if !defined(__QNX__)
     if (ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1)
+#endif
       ExitWithError(error_fd, "ptrace");
   }
 
@@ -222,6 +232,148 @@ struct ForkLaunchInfo {
 
 // End of code running in the child process.
 
+#if defined(__QNX__)
+static ::pid_t PosixSpawn(const ForkLaunchInfo &info,
+                          posix_spawn_file_actions_t &file_actions,
+                          posix_spawnattr_t &attr, Status &error) {
+  uint32_t flags = 0;
+  sigset_t set;
+  ::pid_t pid;
+  int ret;
+
+  if (info.separate_process_group) {
+    flags |= POSIX_SPAWN_SETPGROUP;
+    if (::posix_spawnattr_setpgroup(&attr, 0) != EOK) {
+      error.SetErrorStringWithFormatv("posix_spawnattr_setpgroup failed with "
+                                      "error message: {0}",
+                                      llvm::sys::StrError());
+      return LLDB_INVALID_PROCESS_ID;
+    }
+  }
+
+  for (const ForkFileAction &action : info.actions) {
+    switch (action.action) {
+    case FileAction::eFileActionClose:
+      if (::posix_spawn_file_actions_addclose(&file_actions, action.fd) !=
+          EOK) {
+        error.SetErrorStringWithFormatv("posix_spawn_file_actions_addclose "
+                                        "failed with error message: {0}",
+                                        llvm::sys::StrError());
+        return LLDB_INVALID_PROCESS_ID;
+      }
+      break;
+    case FileAction::eFileActionDuplicate:
+      if (::posix_spawn_file_actions_adddup2(&file_actions, action.fd,
+                                             action.arg) != EOK) {
+        error.SetErrorStringWithFormatv("posix_spawn_file_actions_adddup2 "
+                                        "failed with error message: {0}",
+                                        llvm::sys::StrError());
+        return LLDB_INVALID_PROCESS_ID;
+      }
+      break;
+    case FileAction::eFileActionOpen:
+      if (::posix_spawn_file_actions_addopen(&file_actions, action.fd,
+                                             action.path.c_str(), action.arg,
+                                             0666) != EOK) {
+        error.SetErrorStringWithFormatv("posix_spawn_file_actions_addopen "
+                                        "failed with error message: {0}",
+                                        llvm::sys::StrError());
+        return LLDB_INVALID_PROCESS_ID;
+      }
+      break;
+    case FileAction::eFileActionNone:
+      break;
+    }
+  }
+
+  // Change the working directory.
+  if (!info.wd.empty()) {
+    flags |= POSIX_SPAWN_SETCWD;
+
+    int dirfd =
+        llvm::sys::RetryAfterSignal(-1, ::open, info.wd.c_str(), O_DIRECTORY);
+
+    if (dirfd == -1) {
+      error.SetErrorStringWithFormatv("open failed with error message: {0}",
+                                      llvm::sys::StrError());
+      return LLDB_INVALID_PROCESS_ID;
+    }
+
+    if (::posix_spawnattr_setcwd_np(&attr, dirfd) != EOK) {
+      error.SetErrorStringWithFormatv("posix_spawnattr_setcwd_np failed with "
+                                      "error message: {0}",
+                                      llvm::sys::StrError());
+      return LLDB_INVALID_PROCESS_ID;
+    }
+  }
+
+  if (info.disable_aslr) {
+    if (::posix_spawnattr_setaslr(&attr, POSIX_SPAWN_ASLR_DISABLE) != EOK) {
+      error.SetErrorStringWithFormatv("posix_spawnattr_setaslr failed with "
+                                      "error message: {0}",
+                                      llvm::sys::StrError());
+      return LLDB_INVALID_PROCESS_ID;
+    }
+  }
+
+  // Clear the signal mask to prevent the child from being affected by any
+  // masking done by the parent.
+  flags |= POSIX_SPAWN_SETSIGMASK;
+
+  if (sigemptyset(&set) != 0) {
+    error.SetErrorStringWithFormatv("sigemptyset failed with error message: "
+                                    "{0}",
+                                    llvm::sys::StrError());
+    return LLDB_INVALID_PROCESS_ID;
+  }
+
+  if (::posix_spawnattr_setsigmask(&attr, &set) != EOK) {
+    error.SetErrorStringWithFormatv("posix_spawnattr_setsigmask failed with "
+                                    "error message: {0}",
+                                    llvm::sys::StrError());
+    return LLDB_INVALID_PROCESS_ID;
+  }
+
+  if (info.debug) {
+    // Hold the child process as if it had received a SIGSTOP as soon as it was
+    // spawned.
+    flags |= POSIX_SPAWN_HOLD;
+
+    // Do not inherit setgid powers.
+    flags |= POSIX_SPAWN_RESETIDS;
+
+    // posix_spawn returns EBADF when requested to close all open file
+    // descriptors other than STDIN, STDOUT, and STDERR.
+    // TODO: Close everything besides STDIN, STDOUT, and STDERR that doesn't
+    // have any file action to avoid leaking descriptors.
+  }
+
+  if (::posix_spawnattr_setxflags(&attr, flags) != EOK) {
+    error.SetErrorStringWithFormatv("posix_spawnattr_setxflags failed with "
+                                    "error message: {0}",
+                                    llvm::sys::StrError());
+    return LLDB_INVALID_PROCESS_ID;
+  }
+
+  ret = ::posix_spawn(&pid, info.argv[0], &file_actions, &attr,
+                      const_cast<char *const *>(info.argv), info.envp);
+
+  if (ret != EOK) {
+    // posix_spawn failed.
+    error.SetErrorStringWithFormatv("posix_spawn failed with error message: "
+                                    "{0}",
+                                    llvm::sys::StrError());
+    return LLDB_INVALID_PROCESS_ID;
+  }
+
+  // If an error occurs after posix_spawn returns successfully, then the child
+  // process exits with status 127.
+  // TODO: Wait for the child process if it exits on account of any error.
+
+  return pid; // No error. We're done.
+}
+#endif
+
 ForkFileAction::ForkFileAction(const FileAction &act)
     : action(act.GetAction()), fd(act.GetFD()), path(act.GetPath().str()),
       arg(act.GetActionArgument()) {}
@@ -257,15 +409,53 @@ ForkLaunchInfo::ForkLaunchInfo(const ProcessLaunchInfo &info)
 HostProcess
 ProcessLauncherPosixFork::LaunchProcess(const ProcessLaunchInfo &launch_info,
                                         Status &error) {
+#if !defined(__QNX__)
   // A pipe used by the child process to report errors.
   PipePosix pipe;
   const bool child_processes_inherit = false;
   error = pipe.CreateNew(child_processes_inherit);
   if (error.Fail())
     return HostProcess();
+#endif
 
   const ForkLaunchInfo fork_launch_info(launch_info);
+#if defined(__QNX__)
+  // A call to execve doesn't have the child process stopped by default on QNX.
+  // So, use posix_spawn instead.
+  posix_spawn_file_actions_t file_actions;
+  posix_spawnattr_t attr;
+  ::pid_t pid;
+
+  if (::posix_spawn_file_actions_init(&file_actions) != EOK) {
+    error.SetErrorStringWithFormatv("posix_spawn_file_actions_init failed with "
+                                    "error message: {0}",
+                                    llvm::sys::StrError());
+    return HostProcess();
+  }
+
+  if (::posix_spawnattr_init(&attr) != EOK) {
+    error.SetErrorStringWithFormatv("posix_spawnattr_init failed with error "
+                                    "message: {0}",
+                                    llvm::sys::StrError());
+    return HostProcess();
+  }
 
+  pid = PosixSpawn(fork_launch_info, file_actions, attr, error);
+
+  if (::posix_spawn_file_actions_destroy(&file_actions) != EOK) {
+    error.SetErrorStringWithFormatv("posix_spawn_file_actions_destroy failed "
+                                    "with error message: {0}",
+                                    llvm::sys::StrError());
+  }
+
+  if (::posix_spawnattr_destroy(&attr) != EOK) {
+    error.SetErrorStringWithFormatv("posix_spawnattr_destroy failed with error "
+                                    "message: {0}",
+                                    llvm::sys::StrError());
+  }
+
+  return HostProcess(pid);
+#else
   ::pid_t pid = ::fork();
   if (pid == -1) {
     // Fork failed
@@ -278,7 +468,6 @@ ProcessLauncherPosixFork::LaunchProcess(const ProcessLaunchInfo &launch_info,
     pipe.CloseReadFileDescriptor();
     ChildFunc(pipe.ReleaseWriteFileDescriptor(), fork_launch_info);
   }
-
   // parent process
 
   pipe.CloseWriteFileDescriptor();
@@ -302,4 +491,5 @@ ProcessLauncherPosixFork::LaunchProcess(const ProcessLaunchInfo &launch_info,
   llvm::sys::RetryAfterSignal(-1, waitpid, pid, nullptr, 0);
 
   return HostProcess();
+#endif
 }
diff --git a/lldb/source/Host/qnx/Host.cpp b/lldb/source/Host/qnx/Host.cpp
new file mode 100644
index 0000000000000..11d9a3f78ff92
--- /dev/null
+++ b/lldb/source/Host/qnx/Host.cpp
@@ -0,0 +1,183 @@
+//===-- source/Host/qnx/Host.cpp ------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include <dirent.h>
+
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/Host.h"
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Host/qnx/Support.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/ProcessInfo.h"
+
+#include "llvm/Object/ELF.h"
+
+extern "C" {
+extern char **environ;
+}
+
+using namespace lldb;
+using namespace lldb_private;
+
+static bool GetExecutableFile(::pid_t pid, ProcessInstanceInfo &process_info) {
+  auto buffer_or_error = getProcFile(pid, "exefile");
+
+  if (!buffer_or_error) {
+    return false;
+  }
+
+  llvm::StringRef exe_path = buffer_or_error.get()->getBuffer();
+
+  if (exe_path.empty()) {
+    return false;
+  }
+
+  process_info.GetExecutableFile().SetFile(exe_path, FileSpec::Style::native);
+
+  return true;
+}
+
+static bool GetArchitecture(ProcessInstanceInfo &process_info) {
+  Log *log = GetLog(LLDBLog::Host);
+
+  FileSpec executable_file = process_info.GetExecutableFile();
+
+  auto buffer_sp =
+      FileSystem::Instance().CreateDataBuffer(executable_file, 0x20, 0);
+  if (!buffer_sp) {
+    process_info.SetArchitecture(ArchSpec());
+    return false;
+  }
+
+  uint8_t exe_class =
+      llvm::object::getElfArchType(
+          {reinterpret_cast<const char *>(buffer_sp->GetBytes()),
+           size_t(buffer_sp->GetByteSize())})
+          .first;
+
+  switch (exe_class) {
+  case llvm::ELF::ELFCLASS32:
+    process_info.SetArchitecture(
+        HostInfo::GetArchitecture(HostInfo::eArchKind32));
+    return true;
+  case llvm::ELF::ELFCLASS64:
+    process_info.SetArchitecture(
+        HostInfo::GetArchitecture(HostInfo::eArchKind64));
+    return true;
+  default:
+    LLDB_LOG(log, "Unknown elf class ({0}) in file {1}", exe_class,
+             executable_file.GetPath());
+    process_info.SetArchitecture(ArchSpec());
+    return false;
+  }
+}
+
+static bool GetProcessArgs(::pid_t pid, ProcessInstanceInfo &process_info) {
+  auto buffer_or_error = getProcFile(pid, "cmdline");
+
+  if (!buffer_or_error)
+    return false;
+
+  std::unique_ptr<llvm::MemoryBuffer> cmdline = std::move(*buffer_or_error);
+
+  llvm::StringRef arg0, rest;
+  std::tie(arg0, rest) = cmdline->getBuffer().split('\0');
+  process_info.SetArg0(arg0);
+  while (!rest.empty()) {
+    llvm::StringRef arg;
+    std::tie(arg, rest) = rest.split('\0');
+    process_info.GetArguments().AppendArgument(arg);
+  }
+
+  return true;
+}
+
+static bool IsDirNumeric(const char *dname) {
+  for (; *dname; dname++) {
+    if (!isdigit(*dname))
+      return false;
+  }
+  return true;
+}
+
+uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
+                                 ProcessInstanceInfoList &process_infos) {
+  static const std::string procdir = "/proc/";
+
+  DIR *dirproc = opendir(procdir.c_str());
+  if (dirproc) {
+    struct dirent *direntry = nullptr;
+    const lldb::pid_t our_pid = getpid();
+
+    while ((direntry = readdir(dirproc)) != nullptr) {
+      struct stat statp;
+      std::string path = procdir + std::string(direntry->d_name);
+
+      if (stat(path.c_str(), &statp) == -1)
+        continue;
+
+      if (!S_ISDIR(statp.st_mode) || !IsDirNumeric(direntry->d_name))
+        continue;
+
+      lldb::pid_t pid = std::stoi(direntry->d_name);
+
+      // Skip this process.
+      if (pid == our_pid)
+        continue;
+
+      ProcessInstanceInfo process_info;
+      if (!GetProcessInfo(pid, process_info))
+        continue;
+
+      // TODO: Skip if process is under debug.
+
+      // TODO: Skip if process is a zombie.
+
+      // TODO: Match user if we're not matching all users and not running as
+      // root.
+
+      if (match_info.Matches(process_info)) {
+        process_infos.push_back(process_info);
+      }
+    }
+
+    closedir(dirproc);
+  }
+
+  return process_infos.size();
+}
+
+bool Host::GetProcessInfo(lldb::pid_t pid, ProcessInstanceInfo &process_info) {
+  Log *log = GetLog(LLDBLog::Host);
+
+  process_info.SetProcessID(pid);
+
+  int ret = true;
+
+  if (!GetExecutableFile(pid, process_info)) {
+    LLDB_LOG(log, "Failed to retrieve {0}'s executable file", pid);
+    ret = false;
+  }
+
+  if (!GetArchitecture(process_info)) {
+    LLDB_LOG(log, "Failed to retrieve {0}'s architecture", pid);
+    ret = false;
+  }
+
+  if (!GetProcessArgs(pid, process_info)) {
+    LLDB_LOG(log, "Failed to retrieve {0}'s arguments", pid);
+    ret = false;
+  }
+
+  // TODO: Get the process's environment, state, real group ID, effective group
+  // ID, user ID, effective user ID, and parent's process ID.
+
+  return ret;
+}
+
+Environment Host::GetEnvironment() { return Environment(environ); }
diff --git a/lldb/source/Host/qnx/HostInfoQNX.cpp b/lldb/source/Host/qnx/HostInfoQNX.cpp
new file mode 100644
index 0000000000000..b3dc7e9ab0421
--- /dev/null
+++ b/lldb/source/Host/qnx/HostInfoQNX.cpp
@@ -0,0 +1,22 @@
+//===-- HostInfoQNX.cpp ---------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/qnx/HostInfoQNX.h"
+
+using namespace lldb_private;
+
+llvm::VersionTuple HostInfoQNX::GetOSVersion() { return llvm::VersionTuple(); }
+
+std::optional<std::string> HostInfoQNX::GetOSBuildString() {
+  return std::nullopt;
+}
+
+FileSpec HostInfoQNX::GetProgramFileSpec() {
+  static FileSpec g_program_filespec;
+  return g_program_filespec;
+}
diff --git a/lldb/source/Host/qnx/Support.cpp b/lldb/source/Host/qnx/Support.cpp
new file mode 100644
index 0000000000000..fbb23af941833
--- /dev/null
+++ b/lldb/source/Host/qnx/Support.cpp
@@ -0,0 +1,41 @@
+//===-- Support.cpp -------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/qnx/Support.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+
+#include "llvm/Support/MemoryBuffer.h"
+
+using namespace lldb;
+
+llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+lldb_private::getProcFile(::pid_t pid, const llvm::Twine &file) {
+  Log *log = GetLog(LLDBLog::Host);
+  std::string path = ("/proc/" + llvm::Twine(pid) + "/" + file).str();
+  auto ret = llvm::MemoryBuffer::getFileAsStream(path);
+  if (!ret)
+    LLDB_LOG(log, "Failed to open {0}: {1}", path, ret.getError().message());
+  return ret;
+}
+
+llvm::Expected<FileUP> lldb_private::openProcFile(::pid_t pid,
+                                                  const llvm::Twine &file,
+                                                  File::OpenOptions options,
+                                                  uint32_t permissions,
+                                                  bool should_close_fd) {
+  Log *log = GetLog(LLDBLog::Host);
+  std::string path = ("/proc/" + llvm::Twine(pid) + "/" + file).str();
+  auto ret = FileSystem::Instance().Open(FileSpec(llvm::StringRef(path)),
+                                         options, permissions, should_close_fd);
+  if (!ret) {
+    LLDB_LOG_ERROR(log, ret.takeError(), "Failed to open {0}: {1}", path);
+  }
+  return ret;
+}
diff --git a/lldb/source/Plugins/Process/CMakeLists.txt b/lldb/source/Plugins/Process/CMakeLists.txt
index a51d0f7afd175..705ff6d611269 100644
--- a/lldb/source/Plugins/Process/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/CMakeLists.txt
@@ -11,6 +11,9 @@ elseif (CMAKE_SYSTEM_NAME MATCHES "Windows")
   add_subdirectory(Windows/Common)
 elseif (CMAKE_SYSTEM_NAME MATCHES "Darwin")
   add_subdirectory(MacOSX-Kernel)
+elseif (CMAKE_SYSTEM_NAME MATCHES "QNX")
+  add_subdirectory(QNX)
+  add_subdirectory(POSIX)
 endif()
 add_subdirectory(scripted)
 add_subdirectory(gdb-remote)
diff --git a/lldb/source/Plugins/Process/QNX/CMakeLists.txt b/lldb/source/Plugins/Process/QNX/CMakeLists.txt
new file mode 100644
index 0000000000000..66e7cf3262f1e
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_lldb_library(lldbPluginProcessQNX
+  NativeProcessQNX.cpp
+  NativeRegisterContextQNX.cpp
+  NativeRegisterContextQNX_arm64.cpp
+  NativeThreadQNX.cpp
+
+  LINK_LIBS
+    lldbHost
+    lldbSymbol
+    lldbTarget
+    lldbUtility
+    lldbPluginProcessPOSIX
+    lldbPluginProcessUtility
+  LINK_COMPONENTS
+    Support
+  )
diff --git a/lldb/source/Plugins/Process/QNX/NativeProcessQNX.cpp b/lldb/source/Plugins/Process/QNX/NativeProcessQNX.cpp
new file mode 100644
index 0000000000000..04c9234aedcb8
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeProcessQNX.cpp
@@ -0,0 +1,1066 @@
+//===-- NativeProcessQNX.cpp ----------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "NativeProcessQNX.h"
+
+#include <fcntl.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/neutrino.h>
+
+#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
+
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/HostProcess.h"
+#include "lldb/Host/posix/ProcessLauncherPosixFork.h"
+#include "lldb/Host/qnx/Support.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/State.h"
+
+#include "llvm/Support/Errno.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_qnx;
+using namespace llvm;
+
+// Simple helper function to ensure flags are enabled on the given file
+// descriptor.
+static Status EnsureFDFlags(int fd, int flags) {
+  Status error;
+
+  int status = fcntl(fd, F_GETFL);
+  if (status == -1) {
+    error.SetErrorToErrno();
+    return error;
+  }
+
+  if (fcntl(fd, F_SETFL, status | flags) == -1) {
+    error.SetErrorToErrno();
+    return error;
+  }
+
+  return error;
+}
+
+NativeProcessQNX::Manager::Manager(MainLoop &mainloop)
+    : NativeProcessProtocol::Manager(mainloop) {
+  Status error;
+  m_sigchld_handle = mainloop.RegisterSignal(
+      SIGCHLD, [this](MainLoopBase &) { SigchldHandler(); }, error);
+  if (!m_sigchld_handle || error.Fail())
+    return;
+}
+
+llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+NativeProcessQNX::Manager::Launch(ProcessLaunchInfo &launch_info,
+                                  NativeDelegate &native_delegate) {
+  Log *log = GetLog(POSIXLog::Process);
+
+  Status error;
+  ::pid_t pid = ProcessLauncherPosixFork()
+                    .LaunchProcess(launch_info, error)
+                    .GetProcessId();
+  LLDB_LOG(log, "pid = {0:x}", pid);
+  if (error.Fail()) {
+    LLDB_LOG(log, "failed to launch process: {0}", error);
+    return error.ToError();
+  }
+
+  LLDB_LOG(log, "Inferior started; in stopped state now");
+
+  ProcessInstanceInfo info;
+  if (!Host::GetProcessInfo(pid, info)) {
+    return llvm::make_error<StringError>("cannot get process architecture",
+                                         llvm::inconvertibleErrorCode());
+  }
+
+  // Set the architecture to the executable's architecture.
+  LLDB_LOG(log, "pid = {0:x}, detected architecture {1}", pid,
+           info.GetArchitecture().GetArchitectureName());
+
+  std::unique_ptr<NativeProcessQNX> process_up(new NativeProcessQNX(
+      pid, launch_info.GetPTY().ReleasePrimaryFileDescriptor(), native_delegate,
+      *this, info.GetArchitecture(), error));
+
+  if (error.Fail())
+    return error.ToError();
+
+  error = process_up->SetupTrace();
+  if (error.Fail())
+    return error.ToError();
+
+  for (const auto &thread : process_up->m_threads)
+    static_cast<NativeThreadQNX &>(*thread).SetStoppedBySignal(SIGSTOP);
+  process_up->SetState(StateType::eStateStopped, false);
+
+  return std::move(process_up);
+}
+
+llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+NativeProcessQNX::Manager::Attach(
+    lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate) {
+  Log *log = GetLog(POSIXLog::Process);
+  LLDB_LOG(log, "pid = {0:x}", pid);
+
+  // Retrieve the architecture for the running process.
+  ProcessInstanceInfo info;
+  if (!Host::GetProcessInfo(pid, info)) {
+    return llvm::make_error<StringError>("cannot get process architecture",
+                                         llvm::inconvertibleErrorCode());
+  }
+
+  Status error;
+
+  std::unique_ptr<NativeProcessQNX> process_up(new NativeProcessQNX(
+      pid, -1, native_delegate, *this, info.GetArchitecture(), error));
+
+  if (!error.Success()) {
+    return error.ToError();
+  }
+
+  error.Clear();
+
+  error = process_up->Attach();
+  if (!error.Success())
+    return error.ToError();
+
+  return std::move(process_up);
+}
+
+NativeProcessQNX::Extension
+NativeProcessQNX::Manager::GetSupportedExtensions() const {
+  // TODO: Enable the multiprocess, fork-events, and vfork-events extensions.
+  NativeProcessQNX::Extension supported =
+      Extension::pass_signals | Extension::auxv | Extension::libraries_svr4 |
+      Extension::siginfo_read;
+
+  return supported;
+}
+
+void NativeProcessQNX::Manager::SigchldHandler() {
+  // We can identify a debuggee that has stopped/terminated via siginfo_t.si_pid
+  // or a call to wait*. We have the server deliver a SIGCHLD to the debugger
+  // whenever a debuggee reaches a point of interest, though. So, siginfo_t.
+  // si_pid doesn't point to the debuggee anymore. Moreover, wait* doesn't wait
+  // for waitable debuggees that aren't children on QNX. So, we have siginfo_t.
+  // si_value.sival_int set to the debuggee's PID, MainLoopPosix::RegisterSignal
+  // provision a mechanism to save the signal information, and MainLoopPosix::
+  // SignalHandle expose the saved signal information.
+  ::pid_t pid;
+
+  if (m_sigchld_handle && !m_sigchld_handle.get()->GetSiginfo().expired())
+    pid = m_sigchld_handle.get()->GetSiginfo().lock()->si_value.sival_int;
+  else
+    return;
+
+  auto process_it =
+      std::find_if(m_processes.begin(), m_processes.end(),
+                   [pid](auto process) { return process->GetID() == pid; });
+
+  if (process_it == m_processes.end()) {
+    return;
+  }
+
+  procfs_status proc_status;
+  Status error =
+      DevctlWrapper((*process_it)->GetFileDescriptor(), DCMD_PROC_STATUS,
+                    &proc_status, sizeof(procfs_status), nullptr);
+
+  if (error.Fail())
+    return;
+
+  switch (proc_status.why) {
+  // TODO: Monitor fork, vfork, and spawn.
+  case _DEBUG_WHY_REQUESTED:
+    (*process_it)->MonitorInterrupt();
+    break;
+  case _DEBUG_WHY_SIGNALLED:
+    (*process_it)->MonitorCallback(proc_status);
+    break;
+  case _DEBUG_WHY_TERMINATED:
+    (*process_it)->MonitorExited(proc_status);
+    break;
+  case _DEBUG_WHY_THREAD:
+    (*process_it)->MonitorThread(proc_status);
+    break;
+  default:
+    break;
+  }
+}
+
+Status NativeProcessQNX::Resume(const ResumeActionList &resume_actions) {
+  Log *log = GetLog(POSIXLog::Process);
+
+  Status error;
+  procfs_run proc_run;
+
+  memset(&proc_run, 0, sizeof(procfs_run));
+  proc_run.flags =
+      _DEBUG_RUN_CLRSIG | _DEBUG_RUN_TRACE | _DEBUG_RUN_ARM | _DEBUG_RUN_THREAD;
+  sigfillset(&(proc_run.trace));
+
+  for (const auto &abs_thread : m_threads) {
+    if (!abs_thread) {
+      error.SetErrorString("thread list should not contain NULL threads");
+      return error;
+    }
+    NativeThreadQNX &thread = static_cast<NativeThreadQNX &>(*abs_thread);
+
+    const ResumeAction *action =
+        resume_actions.GetActionForThread(thread.GetID(), true);
+
+    if (action == nullptr) {
+      LLDB_LOG(log, "No action specified for thread {0} of process {1}",
+               thread.GetID(), GetID());
+      continue;
+    }
+
+    LLDB_LOG(
+        log,
+        "Processing resume action (state {0}, signal {1}) for thread {2} of "
+        "process {3}",
+        action->state, action->signal, thread.GetID(), GetID());
+
+    switch (action->state) {
+    case eStateRunning:
+      error = thread.Resume();
+      break;
+
+    case eStateStepping:
+      proc_run.flags |= _DEBUG_RUN_STEP | _DEBUG_RUN_CURTID;
+      proc_run.tid = thread.GetID();
+      error = thread.SingleStep();
+      break;
+
+    default:
+      error.SetErrorStringWithFormatv(
+          "NativeProcessQNX::%s (): Unexpected state %s specified for pid "
+          "%" PRIu64 ", tid %" PRIu64,
+          __FUNCTION__, StateAsCString(action->state), GetID(), thread.GetID());
+      return error;
+    }
+
+    if (error.Fail())
+      return error;
+
+    if (action->signal != LLDB_INVALID_SIGNAL_NUMBER) {
+      sigdelset(&(proc_run.trace), action->signal);
+      error = Signal(action->signal);
+
+      if (error.Fail())
+        return error;
+    }
+  }
+
+  error = DevctlWrapper(m_fd, DCMD_PROC_RUN, &proc_run, sizeof(procfs_run),
+                        nullptr);
+
+  if (error.Success())
+    SetState(eStateRunning, true);
+
+  return error;
+}
+
+Status NativeProcessQNX::Halt() {
+  procfs_status status;
+
+  return DevctlWrapper(m_fd, DCMD_PROC_STOP, &status, sizeof(procfs_status),
+                       nullptr);
+}
+
+Status NativeProcessQNX::Detach() {
+  Status error = m_file_up.get()->Close();
+
+  if (error.Fail())
+    return error;
+
+  m_fd = -1;
+
+  return error;
+}
+
+Status NativeProcessQNX::Signal(int signo) {
+  procfs_signal sig;
+
+  sig.tid = 0;
+  sig.signo = signo;
+  sig.code = 0;
+  sig.value = 0;
+
+  return DevctlWrapper(m_fd, DCMD_PROC_SIGNAL, &sig, sizeof(procfs_signal),
+                       nullptr);
+}
+
+Status NativeProcessQNX::Interrupt() { return Halt(); }
+
+Status NativeProcessQNX::Kill() {
+  Log *log = GetLog(POSIXLog::Process);
+
+  Status error;
+
+  switch (m_state) {
+  case StateType::eStateInvalid:
+  case StateType::eStateExited:
+  case StateType::eStateCrashed:
+  case StateType::eStateDetached:
+  case StateType::eStateUnloaded:
+    // Nothing to do; the process is already dead.
+    LLDB_LOG(log, "Current state of process {0}: {1}; ignoring", GetID(),
+             StateAsCString(m_state));
+    return error;
+
+  case StateType::eStateConnected:
+  case StateType::eStateAttaching:
+  case StateType::eStateLaunching:
+  case StateType::eStateStopped:
+  case StateType::eStateRunning:
+  case StateType::eStateStepping:
+  case StateType::eStateSuspended:
+    // We can try to kill a process in these states.
+    break;
+  }
+
+  error = Signal(SIGKILL);
+  if (error.Fail())
+    return error;
+
+  // Resume the debuggee.
+  procfs_run proc_run;
+  memset(&proc_run, 0, sizeof(procfs_run));
+  proc_run.flags =
+      _DEBUG_RUN_CLRSIG | _DEBUG_RUN_TRACE | _DEBUG_RUN_ARM | _DEBUG_RUN_THREAD;
+  sigfillset(&(proc_run.trace));
+  sigdelset(&(proc_run.trace), SIGKILL);
+
+  return DevctlWrapper(m_fd, DCMD_PROC_RUN, &proc_run, sizeof(procfs_run),
+                       nullptr);
+}
+
+Status NativeProcessQNX::GetMemoryRegionInfo(lldb::addr_t load_addr,
+                                             MemoryRegionInfo &range_info) {
+  Status error;
+
+  if (m_supports_mem_region == LazyBool::eLazyBoolNo) {
+    // We're done.
+    error.SetErrorString("unsupported");
+    return error;
+  }
+
+  error = PopulateMemoryRegionCache();
+  if (error.Fail())
+    return error;
+
+  lldb::addr_t prev_base_address = 0;
+  // TODO: There can be a ton of memory regions in case of numerous threads. The
+  // memory regions are expected to be sorted, though. So, identify the last
+  // region that is <= target memory address via binary search.
+  for (auto it = m_mem_region_cache.begin(); it != m_mem_region_cache.end();
+       ++it) {
+    MemoryRegionInfo &proc_entry_info = it->first;
+    // Sanity check the assumption that memory map entries are ascending.
+    if (proc_entry_info.GetRange().GetRangeBase() < prev_base_address) {
+      error.SetErrorString("unexpectedly detected descending memory map "
+                           "entries");
+      return error;
+    }
+    prev_base_address = proc_entry_info.GetRange().GetRangeBase();
+    // If the target memory address comes before this entry, then indicate
+    // distance to the next region.
+    if (load_addr < proc_entry_info.GetRange().GetRangeBase()) {
+      range_info.GetRange().SetRangeBase(load_addr);
+      range_info.GetRange().SetByteSize(
+          proc_entry_info.GetRange().GetRangeBase() - load_addr);
+      range_info.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
+      range_info.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
+      range_info.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
+      range_info.SetMapped(MemoryRegionInfo::OptionalBool::eNo);
+      return error;
+    } else if (proc_entry_info.GetRange().Contains(load_addr)) {
+      // The target memory address lies within the memory region that we're
+      // processing here.
+      range_info = proc_entry_info;
+      return error;
+    }
+    // The target memory address lies beyond the region that we've just parsed.
+  }
+  // If we've made it so far, then we didn't find any entry that contained the
+  // given address. So, set the load_addr as the base address and the amount of
+  // bytes between the load address and the end of the memory as the size.
+  range_info.GetRange().SetRangeBase(load_addr);
+  range_info.GetRange().SetRangeEnd(LLDB_INVALID_ADDRESS);
+  range_info.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
+  range_info.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
+  range_info.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
+  range_info.SetMapped(MemoryRegionInfo::OptionalBool::eNo);
+  return error;
+}
+
+Status NativeProcessQNX::ReadMemory(lldb::addr_t addr, void *buf, size_t size,
+                                    size_t &bytes_read) {
+  Log *log = GetLog(POSIXLog::Memory);
+
+  LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size);
+
+  bytes_read = size;
+  off_t offset = static_cast<off_t>(addr);
+
+  return m_file_up->Read(buf, bytes_read, offset);
+}
+
+Status NativeProcessQNX::WriteMemory(lldb::addr_t addr, const void *buf,
+                                     size_t size, size_t &bytes_written) {
+  Log *log = GetLog(POSIXLog::Memory);
+
+  LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size);
+
+  bytes_written = size;
+  off_t offset = static_cast<off_t>(addr);
+
+  // TODO: Determine whether or not we need to lock m_file.
+  return m_file_up->Write(buf, bytes_written, offset);
+}
+
+size_t NativeProcessQNX::UpdateThreads() {
+  // The list of a debuggee's threads, and its threads' states are expected to
+  // always be up to date. So, just return the thread count.
+  return m_threads.size();
+}
+
+Status NativeProcessQNX::SetBreakpoint(lldb::addr_t addr, uint32_t size,
+                                       bool hardware) {
+  // TODO: Provision support for setting hardware breakpoints.
+
+  (void)hardware;
+
+  procfs_break bkpt;
+  memset(&bkpt, 0x0, sizeof(procfs_break));
+  bkpt.type = _DEBUG_BREAK_EXEC;
+  bkpt.addr = static_cast<_Uintptr64t>(addr);
+  bkpt.size = 0;
+
+  return DevctlWrapper(m_fd, DCMD_PROC_BREAK, &bkpt, sizeof(procfs_break),
+                       nullptr);
+}
+
+Status NativeProcessQNX::RemoveBreakpoint(lldb::addr_t addr, bool hardware) {
+  // TODO: Provision support for removing hardware breakpoints.
+
+  (void)hardware;
+
+  procfs_break bkpt;
+  memset(&bkpt, 0x0, sizeof(procfs_break));
+  bkpt.type = _DEBUG_BREAK_EXEC;
+  bkpt.addr = static_cast<_Uintptr64t>(addr);
+  bkpt.size = -1;
+
+  return DevctlWrapper(m_fd, DCMD_PROC_BREAK, &bkpt, sizeof(procfs_break),
+                       nullptr);
+}
+
+Status NativeProcessQNX::GetLoadedModuleFileSpec(const char *module_path,
+                                                 FileSpec &file_spec) {
+  return Status("not implemented");
+}
+
+Status NativeProcessQNX::GetFileLoadAddress(const llvm::StringRef &file_name,
+                                            lldb::addr_t &load_addr) {
+  return Status("not implemented");
+}
+
+Status NativeProcessQNX::DevctlWrapper(int fd, int dcmd, void *dev_data_ptr,
+                                       size_t n_bytes, int *dev_info_ptr) {
+  Log *log = GetLog(POSIXLog::Ptrace);
+  Status error;
+  int ret;
+
+  ret = devctl(fd, dcmd, static_cast<caddr_t>(dev_data_ptr),
+               static_cast<size_t>(n_bytes), static_cast<int *>(dev_info_ptr));
+
+  if (ret != EOK)
+    error.SetError(ret, lldb::eErrorTypePOSIX);
+
+  LLDB_LOG(log, "devctl({0}, {1}, {2}, {3}, {4}) = {5:x}", fd, dcmd,
+           dev_data_ptr, n_bytes, dev_info_ptr, ret);
+
+  if (error.Fail())
+    LLDB_LOG(log, "devctl failed: {0}", error);
+
+  return error;
+}
+
+NativeProcessQNX::NativeProcessQNX(::pid_t pid, int terminal_fd,
+                                   NativeDelegate &delegate, Manager &manager,
+                                   const ArchSpec &arch, Status &error)
+    : NativeProcessELF(pid, terminal_fd, delegate), m_manager(manager),
+      m_arch(arch), m_file_up(nullptr), m_fd(-1) {
+  manager.AddProcess(*this);
+
+  if (m_terminal_fd != -1) {
+    error = EnsureFDFlags(m_terminal_fd, O_NONBLOCK);
+    if (!error.Success()) {
+      return;
+    }
+  }
+
+  llvm::Expected<FileUP> file_up =
+      openProcFile(m_pid, "as", File::eOpenOptionReadWrite);
+
+  if (!file_up) {
+    error = file_up.takeError();
+    return;
+  }
+
+  m_file_up = std::move(file_up.get());
+  m_fd = m_file_up.get()->GetDescriptor();
+}
+
+NativeThreadQNX &NativeProcessQNX::AddThread(lldb::tid_t thread_id) {
+  Log *log = GetLog(POSIXLog::Thread);
+
+  LLDB_LOG(log, "Adding thread {0} for process {1}", thread_id, GetID());
+
+  NativeThreadQNX *thread =
+      static_cast<NativeThreadQNX *>(GetThreadByIDUnlocked(thread_id));
+
+  if (!thread) {
+    // If this is the first thread, then save it as the current thread.
+    if (m_threads.empty())
+      SetCurrentThreadID(thread_id);
+
+    m_threads.push_back(std::make_unique<NativeThreadQNX>(*this, thread_id));
+    return static_cast<NativeThreadQNX &>(*m_threads.back());
+  }
+
+  return static_cast<NativeThreadQNX &>(*thread);
+}
+
+void NativeProcessQNX::RemoveThread(lldb::tid_t thread_id) {
+  Log *log = GetLog(POSIXLog::Thread);
+
+  LLDB_LOG(log, "Removing thread {0} for process {1}", thread_id, GetID());
+
+  if (!GetThreadByIDUnlocked(thread_id)) {
+    LLDB_LOG(log, "Attempting to remove a thread that doesn't exist");
+    return;
+  }
+
+  for (auto it = m_threads.begin(); it != m_threads.end(); ++it) {
+    if ((*it)->GetID() == thread_id) {
+      m_threads.erase(it);
+      break;
+    }
+  }
+
+  if (GetCurrentThreadID() == thread_id)
+    SetCurrentThreadID(m_threads.front()->GetID());
+}
+
+void NativeProcessQNX::MonitorInterrupt() {
+  // Interrupt all threads attached to the process.
+  for (const auto &thread : m_threads)
+    static_cast<NativeThreadQNX &>(*thread).SetStoppedBySignal(SIGINT, nullptr);
+
+  SetState(StateType::eStateStopped, true);
+}
+
+void NativeProcessQNX::MonitorCallback(procfs_status &proc_status) {
+  switch (proc_status.what) {
+  case SIGTRAP:
+    MonitorSIGTRAP(proc_status);
+  case SIGSTOP:
+    MonitorSIGSTOP();
+  default:
+    MonitorSignal(proc_status);
+  }
+}
+
+void NativeProcessQNX::MonitorSIGTRAP(procfs_status &proc_status) {
+  Log *log = GetLog(POSIXLog::Process);
+
+  NativeThreadQNX *thread = nullptr;
+  lldb::tid_t tid = static_cast<lldb::tid_t>(proc_status.tid);
+
+  for (const auto &t : m_threads) {
+    if (t->GetID() == tid)
+      thread = static_cast<NativeThreadQNX *>(t.get());
+    static_cast<NativeThreadQNX *>(t.get())->SetStoppedWithNoReason();
+  }
+
+  if (!thread)
+    LLDB_LOG(log, "Couldn't find thread {0} for pid {1}", tid, GetID());
+
+  switch (proc_status.info.si_code) {
+  case TRAP_BRKPT:
+    if (thread) {
+      thread->SetStoppedByBreakpoint();
+      FixupBreakpointPCAsNeeded(*thread);
+      SetCurrentThreadID(thread->GetID());
+    }
+
+    SetState(StateType::eStateStopped, true);
+    return;
+
+  case TRAP_TRACE:
+    if (thread) {
+      thread->SetStoppedByTrace();
+      SetCurrentThreadID(thread->GetID());
+    }
+
+    SetState(StateType::eStateStopped, true);
+    return;
+
+  default:
+    break;
+  }
+
+  // Encounterd either a user-generated SIGTRAP or an unknown event that would
+  // otherwise leave the debugger hanging.
+  LLDB_LOG(log, "Unknown SIGTRAP; passing to generic handler");
+  MonitorSignal(proc_status);
+}
+
+void NativeProcessQNX::MonitorSIGSTOP() {
+  // Stop all threads attached to the process.
+  for (const auto &thread : m_threads)
+    static_cast<NativeThreadQNX &>(*thread).SetStoppedBySignal(SIGSTOP,
+                                                               nullptr);
+
+  SetState(StateType::eStateStopped, true);
+}
+
+void NativeProcessQNX::MonitorSignal(procfs_status &proc_status) {
+  Log *log = GetLog(POSIXLog::Process);
+
+  int signo = proc_status.what;
+  LLDB_LOG(log, "Received signal {0} ({1})", Host::GetSignalAsCString(signo),
+           signo);
+
+  // Check if the debugger should just ignore this signal and resume the
+  // debuggee.
+  if (m_signals_to_ignore.contains(signo)) {
+    LLDB_LOG(log, "Ignoring signal {0} ({1})", Host::GetSignalAsCString(signo),
+             signo);
+
+    procfs_run proc_run;
+    memset(&proc_run, 0, sizeof(procfs_run));
+    proc_run.flags = _DEBUG_RUN_CLRSIG | _DEBUG_RUN_TRACE | _DEBUG_RUN_ARM |
+                     _DEBUG_RUN_THREAD;
+    sigfillset(&(proc_run.trace));
+
+    Status error = DevctlWrapper(m_fd, DCMD_PROC_RUN, &proc_run,
+                                 sizeof(procfs_run), nullptr);
+
+    if (error.Fail())
+      SetState(StateType::eStateInvalid);
+
+    return;
+  }
+
+  // Stop all threads attached to the process.
+  for (const auto &thread : m_threads)
+    static_cast<NativeThreadQNX &>(*thread).SetStoppedBySignal(signo, nullptr);
+
+  SetState(StateType::eStateStopped, true);
+}
+
+void NativeProcessQNX::MonitorExited(procfs_status &proc_status) {
+  Log *log = GetLog(POSIXLog::Process);
+
+  WaitStatus status(WaitStatus::Exit, proc_status.what);
+
+  LLDB_LOG(log, "Got exit signal({0}) , pid = {1}", status, proc_status.pid);
+
+  // Stop tracking all threads attached to the process.
+  m_threads.clear();
+
+  SetExitStatus(status, true);
+
+  // Notify delegate that our process has exited.
+  SetState(StateType::eStateExited, true);
+}
+
+void NativeProcessQNX::MonitorThread(procfs_status &proc_status) {
+  switch (proc_status.what) {
+  case _DEBUG_WHAT_DESTROYED:
+    RemoveThread(proc_status.blocked.thread_event.tid);
+    break;
+  case _DEBUG_WHAT_CREATED:
+    AddThread(proc_status.blocked.thread_event.tid);
+    break;
+  }
+
+  // Resume the debuggee.
+  procfs_run proc_run;
+  memset(&proc_run, 0, sizeof(procfs_run));
+  proc_run.flags =
+      _DEBUG_RUN_CLRSIG | _DEBUG_RUN_TRACE | _DEBUG_RUN_ARM | _DEBUG_RUN_THREAD;
+  sigfillset(&(proc_run.trace));
+
+  Status error = DevctlWrapper(m_fd, DCMD_PROC_RUN, &proc_run,
+                               sizeof(procfs_run), nullptr);
+
+  if (error.Fail())
+    SetState(StateType::eStateInvalid);
+}
+
+Status NativeProcessQNX::PopulateMemoryRegionCache() {
+  Log *log = GetLog(POSIXLog::Process);
+
+  Status error;
+
+  if (!m_mem_region_cache.empty()) {
+    LLDB_LOG(log, "Reusing {0} cached memory region entries",
+             m_mem_region_cache.size());
+    return error;
+  }
+
+  // If our cache is empty, then pull the latest memory regions. There should
+  // always be at least one memory region if memory region handling is
+  // supported.
+
+  int proc_map_ents;
+  error = DevctlWrapper(m_fd, DCMD_PROC_MAPINFO, NULL, 0, &proc_map_ents);
+
+  if (error.Fail())
+    return error;
+
+  procfs_mapinfo *proc_map_info =
+      (procfs_mapinfo *)malloc(sizeof(procfs_mapinfo) * proc_map_ents);
+
+  if (!proc_map_info) {
+    error.SetErrorToErrno();
+    return error;
+  }
+
+  error = DevctlWrapper(m_fd, DCMD_PROC_MAPINFO, proc_map_info,
+                        sizeof(procfs_mapinfo) * proc_map_ents, &proc_map_ents);
+
+  if (error.Fail())
+    return error;
+
+  for (int idx = 0; idx < proc_map_ents; idx++) {
+    MemoryRegionInfo info;
+    info.Clear();
+
+    info.GetRange().SetRangeBase(proc_map_info[idx].vaddr);
+    info.GetRange().SetRangeEnd(proc_map_info[idx].vaddr +
+                                proc_map_info[idx].size);
+    info.SetMapped(MemoryRegionInfo::OptionalBool::eYes);
+
+    if (proc_map_info[idx].flags & PROT_READ)
+      info.SetReadable(MemoryRegionInfo::OptionalBool::eYes);
+    else
+      info.SetReadable(MemoryRegionInfo::OptionalBool::eNo);
+
+    if (proc_map_info[idx].flags & PROT_WRITE)
+      info.SetWritable(MemoryRegionInfo::OptionalBool::eYes);
+    else
+      info.SetWritable(MemoryRegionInfo::OptionalBool::eNo);
+
+    if (proc_map_info[idx].flags & PROT_EXEC)
+      info.SetExecutable(MemoryRegionInfo::OptionalBool::eYes);
+    else
+      info.SetExecutable(MemoryRegionInfo::OptionalBool::eNo);
+
+    // 'path' in 'procfs_debuginfo' is a one-byte array. If you want to get the
+    // path, then you need to allocate more space for it.
+    struct {
+      procfs_debuginfo debug_info;
+      char buffer[_POSIX_PATH_MAX];
+    } map;
+
+    map.debug_info.vaddr = proc_map_info[idx].vaddr;
+    error = DevctlWrapper(m_fd, DCMD_PROC_MAPDEBUG, &map, sizeof(map), NULL);
+
+    if (error.Fail())
+      return error;
+
+    if (map.debug_info.path)
+      info.SetName(map.debug_info.path);
+
+    m_mem_region_cache.emplace_back(info,
+                                    FileSpec(info.GetName().GetCString()));
+  }
+
+  if (m_mem_region_cache.empty()) {
+    // Couldn't find any entries. This shouldn't happen. Assume that we don't
+    // support map entries.
+    LLDB_LOG(log, "Failed to find any vmmap entries, assuming no support "
+                  "for memory region metadata retrieval");
+    m_supports_mem_region = LazyBool::eLazyBoolNo;
+    error.SetErrorString("Not supported");
+    return error;
+  }
+
+  LLDB_LOG(log, "Read {0} memory region entries for process {1}",
+           m_mem_region_cache.size(), GetID());
+  // We support memory retrieval; remember that.
+  m_supports_mem_region = LazyBool::eLazyBoolYes;
+
+  return error;
+}
+
+Status NativeProcessQNX::Attach() {
+  // Attach to the requested process. An attach will cause the process to stop
+  // with a SIGSTOP.
+  procfs_status proc_status;
+  Status error = DevctlWrapper(m_fd, DCMD_PROC_STOP, &proc_status,
+                               sizeof(proc_status), nullptr);
+  if (error.Fail())
+    return error;
+
+  error = DevctlWrapper(m_fd, DCMD_PROC_WAITSTOP, &proc_status,
+                        sizeof(proc_status), nullptr);
+  if (error.Fail())
+    return error;
+
+  // Initialize threads and tracing status.
+  // NB: This needs to be called before we set the threads' states.
+  error = SetupTrace();
+  if (error.Fail())
+    return error;
+
+  for (const auto &thread : m_threads)
+    static_cast<NativeThreadQNX &>(*thread).SetStoppedBySignal(SIGSTOP);
+
+  // Let our process instance know that the thread has stopped.
+  SetCurrentThreadID(m_threads.front()->GetID());
+  SetState(StateType::eStateStopped, false);
+  return error;
+}
+
+llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+NativeProcessQNX::GetAuxvData() const {
+  // This is what a process's stack looks like after initialization on QNX -
+  //
+  //          |________________|
+  //          |                |
+  // addr ->  |      argc      |  _Uint64t      (Number of arguments)
+  //          |________________|
+  //          |                |
+  //          |    argv[0]     |  _Uint64t*     (Program Name)
+  //          |________________|
+  //          |                |
+  //          |    argv[1]     |  _Uint64t*
+  //          |________________|
+  //          |                |
+  //          |    argv[..]    |  _Uint64t*
+  //          |________________|
+  //          |                |
+  //          | argv[argc - 1] |  _Uint64t*
+  //          |________________|
+  //          |                |
+  //          |   argv[argc]   |  _Uint64t*     (NULL)
+  //          |________________|
+  //          |                |
+  //          |    envp[0]     |  _Uint64t*
+  //          |________________|
+  //          |                |
+  //          |    envp[1]     |  _Uint64t*
+  //          |________________|
+  //          |                |
+  //          |    envp[..]    |  _Uint64t*
+  //          |________________|
+  //          |                |
+  //          |   envp[term]   |  _Uint64t*     (NULL)
+  //          |________________|
+  //          |                |
+  //          |    auxv[0]     |  auxv_t
+  //          |________________|
+  //          |                |
+  //          |    auxv[1]     |  auxv_t
+  //          |________________|
+  //          |                |
+  //          |    auxv[..]    |  auxv_t
+  //          |________________|
+  //          |                |
+  //          |   auxv[term]   |  auxv_t        (AT_NULL)
+  //          |________________|
+  //          |                |
+  //
+  // So, seek past argc, argv, and envp to read auxv.
+
+  procfs_info proc_info;
+  Status error = DevctlWrapper(m_fd, DCMD_PROC_INFO, &proc_info,
+                               sizeof(proc_info), nullptr);
+  if (error.Fail())
+    return std::error_code(error.GetError(), std::generic_category());
+
+  // Get a pointer to the initial stack.
+  _Uint8t *addr = reinterpret_cast<_Uint8t *>(proc_info.initial_stack);
+
+  // Read argc, and seek past it.
+  _Uint64t argc;
+  uint32_t addr_size = GetAddressByteSize();
+  size_t bytes_read;
+
+  error = ReadMemory(reinterpret_cast<lldb::addr_t>(addr),
+                     reinterpret_cast<void *>(&argc), sizeof(argc), bytes_read);
+
+  if (error.Fail())
+    return std::error_code(error.GetError(), std::generic_category());
+
+  if (bytes_read != sizeof(argc))
+    return errorToErrorCode(llvm::createStringError(
+        llvm::inconvertibleErrorCode(),
+        "Attempted to read {0} bytes at {1:x} but only read {2}", sizeof(argc),
+        addr, bytes_read));
+
+  addr += addr_size;
+
+  // Seek past argv.
+  addr += ((argc + 1) * addr_size);
+
+  // Seek past envp.
+  _Uint64t envp;
+  do {
+    error =
+        ReadMemory(reinterpret_cast<lldb::addr_t>(addr),
+                   reinterpret_cast<void *>(&envp), sizeof(envp), bytes_read);
+
+    if (error.Fail())
+      return std::error_code(error.GetError(), std::generic_category());
+
+    if (bytes_read != sizeof(envp))
+      return errorToErrorCode(llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          "Attempted to read {0} bytes at {1:x} but only read {2}",
+          sizeof(envp), addr, bytes_read));
+
+    addr += addr_size;
+  } while (envp != 0);
+
+  // Read auxv.
+  auxv_t entry;
+  DataBufferHeap auxv_data(static_cast<lldb::offset_t>(0),
+                           static_cast<uint8_t>(0));
+  do {
+    error =
+        ReadMemory(reinterpret_cast<lldb::addr_t>(addr),
+                   reinterpret_cast<void *>(&entry), sizeof(entry), bytes_read);
+
+    if (error.Fail())
+      return std::error_code(error.GetError(), std::generic_category());
+
+    if (bytes_read != sizeof(entry))
+      return errorToErrorCode(llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          "Attempted to read {0} bytes at {1:x} but only read {2}",
+          sizeof(entry), addr, bytes_read));
+
+    auxv_data.AppendData(&entry, bytes_read);
+    addr += sizeof(entry);
+  } while (entry.a_type != AT_NULL);
+
+  std::unique_ptr<WritableMemoryBuffer> auxv =
+      llvm::WritableMemoryBuffer::getNewMemBuffer(auxv_data.GetByteSize());
+
+  std::memcpy(auxv->getBufferStart(), auxv_data.GetBytes(),
+              auxv->getBufferSize());
+
+  return auxv;
+}
+
+Status NativeProcessQNX::SetupTrace() {
+  // The debugger no longer receives SIGCHLD from the debuggee upon establishing
+  // a file descriptor to /proc/$pid/as. So, have the server deliver a SIGCHLD
+  // to the debugger whenever the debuggee reaches a point of interest. Also,
+  // have siginfo_t.si_value.sival_int set to the debuggee's PID so that we can
+  // identify the debuggee in NativeProcessQNX::Manager::SigchldHandler.
+  struct sigevent event;
+  Status error;
+  SIGEV_SIGNAL_VALUE_INT_INIT(&event, SIGCHLD, m_pid);
+  if (MsgRegisterEvent(&event, m_fd) != EOK) {
+    error.SetErrorToErrno();
+    return error;
+  }
+  error = DevctlWrapper(m_fd, DCMD_PROC_EVENT, &event, sizeof(struct sigevent),
+                        nullptr);
+  if (error.Fail())
+    return error;
+
+  // Let the debuggee run on detach (closure of last file descriptor to
+  // /proc/$pid/as).
+  int flags = _DEBUG_FLAG_RLC;
+  error =
+      DevctlWrapper(m_fd, DCMD_PROC_CLEAR_FLAG, &flags, sizeof(flags), nullptr);
+  if (error.Fail())
+    return error;
+
+  // TODO: Indicate interest in getting notified when the debuggee forks,
+  // vforks, spawns, or execs.
+
+  // The child process is held as if it had received a SIGSTOP as soon as it had
+  // been spawned. To resume this process, we must send it a SIGCONT. However,
+  // we don't want it to resume execution here.
+  Halt();
+  Signal(SIGCONT);
+
+  return ReinitializeThreads();
+}
+
+Status NativeProcessQNX::ReinitializeThreads() {
+  // Clear old threads.
+  m_threads.clear();
+
+  procfs_info proc_info;
+  Status error = DevctlWrapper(m_fd, DCMD_PROC_INFO, &proc_info,
+                               sizeof(proc_info), nullptr);
+  if (error.Fail())
+    return error;
+
+  _Uint32t num_threads = proc_info.num_threads;
+
+  // QNX returns information about the thread specified in the tid member of
+  // status if it has it; otherwise, it will either return information on the
+  // next available thread ID, or return something other than EOK to indicate
+  // that it's done.
+
+  procfs_status proc_status;
+  proc_status.tid = 0;
+
+  std::vector<pthread_t> tids;
+
+  for (int idx = 0; idx < num_threads; ++idx) {
+    // QNX starts numbering threads from 1.
+    proc_status.tid++;
+
+    error = DevctlWrapper(m_fd, DCMD_PROC_TIDSTATUS, &proc_status,
+                          sizeof(proc_status), nullptr);
+
+    if (error.Fail())
+      return error;
+
+    tids.push_back(proc_status.tid);
+  }
+
+  // Reinitialize threads from scratch and register them with the process.
+  for (pthread_t tid : tids) {
+    if (tid < 1) {
+      error.SetErrorString("tid < 1");
+      return error;
+    }
+    AddThread(tid);
+  }
+
+  return error;
+}
+
+Status NativeProcessQNX::ReadMemory(lldb::addr_t addr, void *buf, size_t size,
+                                    size_t &bytes_read) const {
+  Log *log = GetLog(POSIXLog::Memory);
+
+  LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size);
+
+  bytes_read = size;
+  off_t offset = static_cast<off_t>(addr);
+
+  return m_file_up->Read(buf, bytes_read, offset);
+}
diff --git a/lldb/source/Plugins/Process/QNX/NativeProcessQNX.h b/lldb/source/Plugins/Process/QNX/NativeProcessQNX.h
new file mode 100644
index 0000000000000..fe24d772ffdda
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeProcessQNX.h
@@ -0,0 +1,157 @@
+//===-- NativeProcessQNX.h ------------------------------------ -*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_NativeProcessQNX_H_
+#define liblldb_NativeProcessQNX_H_
+
+#include <sys/procfs.h>
+
+#include "Plugins/Process/POSIX/NativeProcessELF.h"
+#include "Plugins/Process/Utility/NativeProcessSoftwareSingleStep.h"
+
+#include "lldb/lldb-forward.h"
+
+#include "lldb/Target/MemoryRegionInfo.h"
+#include "llvm/ADT/SmallPtrSet.h"
+
+#include "NativeThreadQNX.h"
+
+namespace lldb_private {
+namespace process_qnx {
+/// \class NativeProcessQNX
+/// Manages communication with the inferior (debuggee) process.
+///
+/// Upon construction, this class prepares and launches an inferior process
+/// for debugging.
+///
+/// Changes in the inferior process state are broadcasted.
+class NativeProcessQNX : public NativeProcessELF {
+public:
+  class Manager : public NativeProcessProtocol::Manager {
+  public:
+    Manager(MainLoop &mainloop);
+
+    llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+    Launch(ProcessLaunchInfo &launch_info,
+           NativeDelegate &native_delegate) override;
+
+    llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+    Attach(lldb::pid_t pid, NativeDelegate &native_delegate) override;
+
+    Extension GetSupportedExtensions() const override;
+
+    void AddProcess(NativeProcessQNX &process) { m_processes.insert(&process); }
+
+    void RemoveProcess(NativeProcessQNX &process) {
+      m_processes.erase(&process);
+    }
+
+  private:
+    MainLoop::SignalHandleUP m_sigchld_handle;
+
+    llvm::SmallPtrSet<NativeProcessQNX *, 2> m_processes;
+
+    void SigchldHandler();
+  };
+
+  // NativeProcessProtocol interface.
+
+  ~NativeProcessQNX() override { m_manager.RemoveProcess(*this); }
+
+  Status Resume(const ResumeActionList &resume_actions) override;
+
+  Status Halt() override;
+
+  Status Detach() override;
+
+  Status Signal(int signo) override;
+
+  Status Interrupt() override;
+
+  Status Kill() override;
+
+  Status GetMemoryRegionInfo(lldb::addr_t load_addr,
+                             MemoryRegionInfo &range_info) override;
+
+  Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
+                    size_t &bytes_read) override;
+
+  Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
+                     size_t &bytes_written) override;
+
+  size_t UpdateThreads() override;
+
+  const ArchSpec &GetArchitecture() const override { return m_arch; }
+
+  Status SetBreakpoint(lldb::addr_t addr, uint32_t size,
+                       bool hardware) override;
+
+  Status RemoveBreakpoint(lldb::addr_t addr, bool hardware = false) override;
+
+  Status GetLoadedModuleFileSpec(const char *module_path,
+                                 FileSpec &file_spec) override;
+  Status GetFileLoadAddress(const llvm::StringRef &file_name,
+                            lldb::addr_t &load_addr) override;
+
+  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+  GetAuxvData() const override;
+
+  // Interface used by NativeRegisterContext-derived classes.
+
+  static Status DevctlWrapper(int fd, int dcmd, void *dev_data_ptr = nullptr,
+                              size_t n_bytes = 0, int *dev_info_ptr = nullptr);
+
+  int GetFileDescriptor() const { return m_fd; }
+
+private:
+  Manager &m_manager;
+  ArchSpec m_arch;
+  LazyBool m_supports_mem_region = eLazyBoolCalculate;
+  std::vector<std::pair<MemoryRegionInfo, FileSpec>> m_mem_region_cache;
+  lldb::FileUP m_file_up;
+  int m_fd;
+
+  // Private instance methods.
+
+  NativeProcessQNX(::pid_t pid, int terminal_fd, NativeDelegate &delegate,
+                   Manager &manager, const ArchSpec &arch, Status &status);
+
+  NativeThreadQNX &AddThread(lldb::tid_t thread_id);
+
+  void RemoveThread(lldb::tid_t thread_id);
+
+  void MonitorInterrupt();
+
+  void MonitorCallback(procfs_status &proc_status);
+
+  void MonitorSIGTRAP(procfs_status &proc_status);
+
+  void MonitorSIGSTOP();
+
+  void MonitorSignal(procfs_status &proc_status);
+
+  void MonitorExited(procfs_status &proc_status);
+
+  void MonitorThread(procfs_status &proc_status);
+
+  Status PopulateMemoryRegionCache();
+
+  Status Attach();
+
+  Status SetupTrace();
+
+  Status ReinitializeThreads();
+
+  Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
+                    size_t &bytes_read) const;
+};
+
+} // namespace process_qnx
+} // namespace lldb_private
+
+#endif // #ifndef liblldb_NativeProcessQNX_H_
diff --git a/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.cpp b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.cpp
new file mode 100644
index 0000000000000..22ce63be75bc5
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.cpp
@@ -0,0 +1,12 @@
+//===-- NativeRegisterContextQNX.cpp --------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "NativeRegisterContextQNX.h"
+
+using namespace lldb_private;
+using namespace lldb_private::process_qnx;
diff --git a/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.h b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.h
new file mode 100644
index 0000000000000..7c7e191a94eda
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX.h
@@ -0,0 +1,37 @@
+//===-- NativeRegisterContextQNX.h ------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef lldb_NativeRegisterContextQNX_h
+#define lldb_NativeRegisterContextQNX_h
+
+#include "lldb/Host/common/NativeThreadProtocol.h"
+
+#include "Plugins/Process/Utility/NativeRegisterContextRegisterInfo.h"
+
+namespace lldb_private {
+namespace process_qnx {
+
+class NativeThreadQNX;
+
+class NativeRegisterContextQNX
+    : public virtual NativeRegisterContextRegisterInfo {
+public:
+  // This function is implemented in the NativeRegisterContextQNX_*
+  // subclasses to create a new instance of the host specific
+  // NativeRegisterContextQNX. The implementations can't collide as only one
+  // NativeRegisterContextQNX_* variant should be compiled into the final
+  // executable.
+  static NativeRegisterContextQNX *
+  CreateHostNativeRegisterContextQNX(const ArchSpec &target_arch,
+                                     NativeThreadProtocol &native_thread);
+};
+
+} // namespace process_qnx
+} // namespace lldb_private
+
+#endif // #ifndef lldb_NativeRegisterContextQNX_h
diff --git a/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.cpp b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.cpp
new file mode 100644
index 0000000000000..75821f10b0bd8
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.cpp
@@ -0,0 +1,344 @@
+//===-- NativeRegisterContextQNX_arm64.cpp --------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#if defined(__aarch64__) && defined(__QNX__)
+
+#include "NativeRegisterContextQNX_arm64.h"
+
+#include <sys/procfs.h>
+
+#include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/RegisterValue.h"
+
+#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
+#include "Plugins/Process/QNX/NativeProcessQNX.h"
+#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_qnx;
+
+NativeRegisterContextQNX *
+NativeRegisterContextQNX::CreateHostNativeRegisterContextQNX(
+    const ArchSpec &target_arch, NativeThreadProtocol &native_thread) {
+  return new NativeRegisterContextQNX_arm64(target_arch, native_thread);
+}
+
+NativeRegisterContextQNX_arm64::NativeRegisterContextQNX_arm64(
+    const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
+    : NativeRegisterContextRegisterInfo(
+          native_thread, new RegisterInfoPOSIX_arm64(target_arch, 0)) {}
+
+uint32_t NativeRegisterContextQNX_arm64::GetRegisterSetCount() const {
+  return GetRegisterInfo().GetRegisterSetCount();
+}
+
+uint32_t NativeRegisterContextQNX_arm64::GetUserRegisterCount() const {
+  uint32_t count = 0;
+  for (uint32_t set_index = 0; set_index < GetRegisterSetCount(); ++set_index)
+    count += GetRegisterSet(set_index)->num_registers;
+  return count;
+}
+
+const RegisterSet *
+NativeRegisterContextQNX_arm64::GetRegisterSet(uint32_t set_index) const {
+  return GetRegisterInfo().GetRegisterSet(set_index);
+}
+
+Status
+NativeRegisterContextQNX_arm64::ReadRegister(const RegisterInfo *reg_info,
+                                             RegisterValue &reg_value) {
+  Status error;
+
+  if (!reg_info) {
+    error.SetErrorString("reg_info is NULL");
+    return error;
+  }
+
+  const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
+
+  if (reg == LLDB_INVALID_REGNUM) {
+    error.SetErrorStringWithFormat(
+        "no lldb regnum for %s",
+        reg_info && reg_info->name ? reg_info->name : "<unknown register>");
+    return error;
+  }
+
+  uint32_t set = GetRegisterInfo().GetRegisterSetFromRegisterIndex(reg);
+
+  switch (set) {
+  // TODO: Read alternate and performance registers.
+  case RegisterInfoPOSIX_arm64::GPRegSet:
+    if (reg_info->byte_offset + reg_info->byte_size >
+        sizeof(AARCH64_CPU_REGISTERS)) {
+      error.SetErrorString("reg_info->byte_offset + reg_info->byte_size > "
+                           "sizeof(AARCH64_CPU_REGISTERS)");
+      return error;
+    }
+    error = ReadGPR();
+    if (error.Fail())
+      return error;
+    reg_value.SetBytes(reinterpret_cast<_Uint8t *>(&m_cpu_reg_data) +
+                           reg_info->byte_offset,
+                       reg_info->byte_size, endian::InlHostByteOrder());
+    break;
+
+  case RegisterInfoPOSIX_arm64::FPRegSet:
+    if (reg_info->byte_offset + reg_info->byte_size >
+        sizeof(AARCH64_FPU_REGISTERS)) {
+      error.SetErrorString("reg_info->byte_offset + reg_info->byte_size > "
+                           "sizeof(AARCH64_FPU_REGISTERS)");
+      return error;
+    }
+    error = ReadFPR();
+    if (error.Fail())
+      return error;
+    reg_value.SetBytes(reinterpret_cast<_Uint8t *>(&m_fpu_reg_data) +
+                           reg_info->byte_offset,
+                       reg_info->byte_size, endian::InlHostByteOrder());
+    break;
+
+  default:
+    error.SetErrorString("unrecognized register set");
+  }
+
+  return error;
+}
+
+Status
+NativeRegisterContextQNX_arm64::WriteRegister(const RegisterInfo *reg_info,
+                                              const RegisterValue &reg_value) {
+  Status error;
+
+  if (!reg_info) {
+    error.SetErrorString("reg_info is NULL");
+    return error;
+  }
+
+  const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB];
+
+  if (reg == LLDB_INVALID_REGNUM) {
+    error.SetErrorStringWithFormat(
+        "no lldb regnum for %s",
+        reg_info && reg_info->name ? reg_info->name : "<unknown register>");
+    return error;
+  }
+
+  uint32_t set = GetRegisterInfo().GetRegisterSetFromRegisterIndex(reg);
+
+  switch (set) {
+  // TODO: Write to alternate and performance registers.
+  case RegisterInfoPOSIX_arm64::GPRegSet:
+    if (reg_info->byte_offset + reg_info->byte_size >
+        sizeof(AARCH64_CPU_REGISTERS)) {
+      error.SetErrorString("reg_info->byte_offset + reg_info->byte_size > "
+                           "sizeof(AARCH64_CPU_REGISTERS)");
+      return error;
+    }
+    ::memcpy(&m_cpu_reg_data.gpr + reg_info->byte_offset, reg_value.GetBytes(),
+             reg_info->byte_size);
+    error = WriteGPR();
+    if (error.Fail())
+      return error;
+    break;
+
+  case RegisterInfoPOSIX_arm64::FPRegSet:
+    if (reg_info->byte_offset + reg_info->byte_size >
+        sizeof(AARCH64_FPU_REGISTERS)) {
+      error.SetErrorString("reg_info->byte_offset + reg_info->byte_size > "
+                           "sizeof(AARCH64_FPU_REGISTERS)");
+      return error;
+    }
+    ::memcpy(&m_fpu_reg_data.reg + reg_info->byte_offset, reg_value.GetBytes(),
+             reg_info->byte_size);
+    error = WriteFPR();
+    if (error.Fail())
+      return error;
+    break;
+
+  default:
+    error.SetErrorString("unrecognized register set");
+  }
+
+  return error;
+}
+
+Status NativeRegisterContextQNX_arm64::ReadAllRegisterValues(
+    lldb::WritableDataBufferSP &data_sp) {
+  // TODO: Read alternate and performance registers.
+  Status error;
+  uint32_t reg_data_byte_size = sizeof(AARCH64_CPU_REGISTERS);
+
+  error = ReadGPR();
+  if (error.Fail())
+    return error;
+
+  reg_data_byte_size += sizeof(AARCH64_FPU_REGISTERS);
+
+  error = ReadFPR();
+  if (error.Fail())
+    return error;
+
+  data_sp.reset(new DataBufferHeap(reg_data_byte_size, 0));
+  uint8_t *dst = data_sp->GetBytes();
+  ::memcpy(dst, &m_cpu_reg_data.gpr, sizeof(AARCH64_CPU_REGISTERS));
+  dst += sizeof(AARCH64_CPU_REGISTERS);
+  ::memcpy(dst, &m_fpu_reg_data.reg, sizeof(AARCH64_FPU_REGISTERS));
+
+  return error;
+}
+
+Status NativeRegisterContextQNX_arm64::WriteAllRegisterValues(
+    const lldb::DataBufferSP &data_sp) {
+  Status error;
+
+  if (!data_sp) {
+    error.SetErrorString("data_sp is NULL");
+    return error;
+  }
+
+  if (data_sp->GetByteSize() !=
+      (sizeof(AARCH64_CPU_REGISTERS) + sizeof(AARCH64_FPU_REGISTERS))) {
+    error.SetErrorStringWithFormat(
+        "data_sp->GetByteSize() != "
+        "(sizeof(AARCH64_CPU_REGISTERS) + sizeof(AARCH64_FPU_REGISTERS)), "
+        "expected %" PRIu64 ", is %" PRIu64,
+        sizeof(AARCH64_CPU_REGISTERS) + sizeof(AARCH64_FPU_REGISTERS),
+        data_sp->GetByteSize());
+    return error;
+  }
+
+  const uint8_t *src = data_sp->GetBytes();
+  if (src == nullptr) {
+    error.SetErrorString("DataBuffer::GetBytes() returned nullptr");
+    return error;
+  }
+  ::memcpy(&m_cpu_reg_data.gpr, src, sizeof(AARCH64_CPU_REGISTERS));
+  error = WriteGPR();
+  if (error.Fail())
+    return error;
+
+  src += sizeof(AARCH64_CPU_REGISTERS);
+  ::memcpy(&m_fpu_reg_data.reg, src, sizeof(AARCH64_FPU_REGISTERS));
+  error = WriteFPR();
+  if (error.Fail())
+    return error;
+
+  return error;
+}
+
+Status NativeRegisterContextQNX_arm64::ReadGPR() {
+  procfs_greg greg;
+  memset(&greg, 0x0, sizeof(procfs_greg));
+
+  pthread_t tid = static_cast<pthread_t>(GetThreadID());
+  int greg_size;
+  Status error = NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_CURTHREAD, &tid, sizeof(tid), nullptr);
+
+  if (error.Fail())
+    return error;
+
+  error = NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_GETGREG, &greg, sizeof(procfs_greg), &greg_size);
+
+  if (error.Fail())
+    return error;
+
+  if (greg_size != sizeof(AARCH64_CPU_REGISTERS)) {
+    error.SetErrorString("greg_size != sizeof(AARCH64_CPU_REGISTERS)");
+    return error;
+  }
+
+  memcpy(&m_cpu_reg_data, &greg, sizeof(AARCH64_CPU_REGISTERS));
+
+  return error;
+}
+
+Status NativeRegisterContextQNX_arm64::WriteGPR() {
+  procfs_greg greg;
+  memcpy(&greg, &m_cpu_reg_data, sizeof(AARCH64_CPU_REGISTERS));
+
+  pthread_t tid = static_cast<pthread_t>(GetThreadID());
+  Status error = NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_CURTHREAD, &tid, sizeof(tid), nullptr);
+
+  if (error.Fail())
+    return error;
+
+  return NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_SETGREG, &greg, sizeof(procfs_greg), nullptr);
+}
+
+Status NativeRegisterContextQNX_arm64::ReadFPR() {
+  // If the thread hasn't used any floating-point arithmetic, then the read may
+  // fail because an FPU context hasn't been allocated yet.
+  procfs_fpreg fpreg;
+  memset(&fpreg, 0x0, sizeof(procfs_fpreg));
+
+  pthread_t tid = static_cast<pthread_t>(GetThreadID());
+  int fpreg_size;
+  Status error = NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_CURTHREAD, &tid, sizeof(tid), nullptr);
+
+  if (error.Fail())
+    return error;
+
+  error = NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_GETFPREG, &fpreg, sizeof(procfs_fpreg), &fpreg_size);
+
+  if (error.Fail())
+    return error;
+
+  if (fpreg_size != sizeof(AARCH64_FPU_REGISTERS)) {
+    error.SetErrorString("fpreg_size != sizeof(AARCH64_FPU_REGISTERS)");
+    return error;
+  }
+
+  memcpy(&m_fpu_reg_data, &fpreg, sizeof(AARCH64_FPU_REGISTERS));
+
+  return error;
+}
+
+Status NativeRegisterContextQNX_arm64::WriteFPR() {
+  procfs_fpreg fpreg;
+  memcpy(&fpreg, &m_fpu_reg_data, sizeof(AARCH64_FPU_REGISTERS));
+
+  pthread_t tid = static_cast<pthread_t>(GetThreadID());
+  Status error = NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_CURTHREAD, &tid, sizeof(tid), nullptr);
+
+  if (error.Fail())
+    return error;
+
+  return NativeProcessQNX::DevctlWrapper(
+      static_cast<NativeProcessQNX &>(GetThread().GetProcess())
+          .GetFileDescriptor(),
+      DCMD_PROC_SETFPREG, &fpreg, sizeof(procfs_fpreg), nullptr);
+}
+
+RegisterInfoPOSIX_arm64 &
+NativeRegisterContextQNX_arm64::GetRegisterInfo() const {
+  return static_cast<RegisterInfoPOSIX_arm64 &>(*m_register_info_interface_up);
+}
+
+#endif // defined(__aarch64__) && defined(__QNX__)
diff --git a/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.h b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.h
new file mode 100644
index 0000000000000..b37aee637d1cb
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeRegisterContextQNX_arm64.h
@@ -0,0 +1,66 @@
+//===-- NativeRegisterContextQNX_arm64.h ------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#if defined(__aarch64__) && defined(__QNX__)
+
+#ifndef lldb_NativeRegisterContextQNX_arm64_h
+#define lldb_NativeRegisterContextQNX_arm64_h
+
+#include <aarch64/context.h>
+
+#include "Plugins/Process/QNX/NativeRegisterContextQNX.h"
+#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
+
+namespace lldb_private {
+namespace process_qnx {
+
+class NativeProcessQNX;
+
+class NativeRegisterContextQNX_arm64 : public NativeRegisterContextQNX {
+public:
+  NativeRegisterContextQNX_arm64(const ArchSpec &target_arch,
+                                 NativeThreadProtocol &native_thread);
+
+  uint32_t GetRegisterSetCount() const override;
+
+  uint32_t GetUserRegisterCount() const override;
+
+  const RegisterSet *GetRegisterSet(uint32_t set_index) const override;
+
+  Status ReadRegister(const RegisterInfo *reg_info,
+                      RegisterValue &reg_value) override;
+
+  Status WriteRegister(const RegisterInfo *reg_info,
+                       const RegisterValue &reg_value) override;
+
+  Status ReadAllRegisterValues(lldb::WritableDataBufferSP &data_sp) override;
+
+  Status WriteAllRegisterValues(const lldb::DataBufferSP &data_sp) override;
+
+protected:
+  Status ReadGPR();
+
+  Status WriteGPR();
+
+  Status ReadFPR();
+
+  Status WriteFPR();
+
+private:
+  AARCH64_CPU_REGISTERS m_cpu_reg_data;
+  AARCH64_FPU_REGISTERS m_fpu_reg_data;
+
+  RegisterInfoPOSIX_arm64 &GetRegisterInfo() const;
+};
+
+} // namespace process_qnx
+} // namespace lldb_private
+
+#endif // #ifndef lldb_NativeRegisterContextQNX_arm64_h
+
+#endif // defined(__aarch64__) && defined(__QNX__)
diff --git a/lldb/source/Plugins/Process/QNX/NativeThreadQNX.cpp b/lldb/source/Plugins/Process/QNX/NativeThreadQNX.cpp
new file mode 100644
index 0000000000000..313e975b66ae8
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeThreadQNX.cpp
@@ -0,0 +1,141 @@
+//===-- NativeThreadQNX.cpp -----------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "NativeThreadQNX.h"
+#include "NativeRegisterContextQNX.h"
+
+#include "NativeProcessQNX.h"
+
+#include "Plugins/Process/POSIX/CrashReason.h"
+#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
+#include "lldb/Utility/State.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_qnx;
+
+NativeThreadQNX::NativeThreadQNX(NativeProcessQNX &process, lldb::tid_t tid)
+    : NativeThreadProtocol(process, tid), m_state(StateType::eStateInvalid),
+      m_stop_info(),
+      m_reg_context_up(
+          NativeRegisterContextQNX::CreateHostNativeRegisterContextQNX(
+              process.GetArchitecture(), *this)),
+      m_stop_description() {}
+
+Status NativeThreadQNX::Resume() {
+  m_state = StateType::eStateRunning;
+  m_stop_info.reason = StopReason::eStopReasonNone;
+  return Status();
+}
+
+Status NativeThreadQNX::SingleStep() {
+  m_state = StateType::eStateStepping;
+  m_stop_info.reason = StopReason::eStopReasonNone;
+  return Status();
+}
+
+void NativeThreadQNX::SetStoppedBySignal(uint32_t signo,
+                                         const siginfo_t *info) {
+  Log *log = GetLog(POSIXLog::Thread);
+  LLDB_LOG(log, "tid = {0} in called with signal {1}", GetID(), signo);
+
+  SetStopped();
+
+  m_stop_info.reason = StopReason::eStopReasonSignal;
+  m_stop_info.signo = signo;
+
+  m_stop_description.clear();
+  if (info) {
+    switch (signo) {
+    case SIGSEGV:
+    case SIGBUS:
+    case SIGFPE:
+    case SIGILL:
+      m_stop_description = GetCrashReasonString(*info);
+      break;
+    }
+  }
+}
+
+void NativeThreadQNX::SetStoppedByBreakpoint() {
+  SetStopped();
+  m_stop_info.reason = StopReason::eStopReasonBreakpoint;
+  m_stop_info.signo = SIGTRAP;
+}
+
+void NativeThreadQNX::SetStoppedByTrace() {
+  SetStopped();
+  m_stop_info.reason = StopReason::eStopReasonTrace;
+  m_stop_info.signo = SIGTRAP;
+}
+
+void NativeThreadQNX::SetStoppedWithNoReason() {
+  SetStopped();
+  m_stop_info.reason = StopReason::eStopReasonNone;
+  m_stop_info.signo = 0;
+}
+
+void NativeThreadQNX::SetStopped() {
+  const StateType new_state = StateType::eStateStopped;
+  m_state = new_state;
+  m_stop_description.clear();
+}
+
+std::string NativeThreadQNX::GetName() { return ""; }
+
+lldb::StateType NativeThreadQNX::GetState() { return m_state; }
+
+bool NativeThreadQNX::GetStopReason(ThreadStopInfo &stop_info,
+                                    std::string &description) {
+  Log *log = GetLog(POSIXLog::Thread);
+  description.clear();
+
+  switch (m_state) {
+  case eStateStopped:
+  case eStateCrashed:
+  case eStateExited:
+  case eStateSuspended:
+  case eStateUnloaded:
+    stop_info = m_stop_info;
+    description = m_stop_description;
+    return true;
+
+  case eStateInvalid:
+  case eStateConnected:
+  case eStateAttaching:
+  case eStateLaunching:
+  case eStateRunning:
+  case eStateStepping:
+  case eStateDetached:
+    LLDB_LOG(log, "tid = {0} in state {1} cannot answer stop reason", GetID(),
+             StateAsCString(m_state));
+    return false;
+  }
+  llvm_unreachable("unhandled StateType!");
+}
+
+NativeRegisterContextQNX &NativeThreadQNX::GetRegisterContext() {
+  return *m_reg_context_up;
+}
+
+Status NativeThreadQNX::SetWatchpoint(lldb::addr_t addr, size_t size,
+                                      uint32_t watch_flags, bool hardware) {
+  return Status("not implemented");
+}
+
+Status NativeThreadQNX::RemoveWatchpoint(lldb::addr_t addr) {
+  return Status("not implemented");
+}
+
+Status NativeThreadQNX::SetHardwareBreakpoint(lldb::addr_t addr, size_t size) {
+  return Status("not implemented");
+}
+
+Status NativeThreadQNX::RemoveHardwareBreakpoint(lldb::addr_t addr) {
+  return Status("not implemented");
+}
diff --git a/lldb/source/Plugins/Process/QNX/NativeThreadQNX.h b/lldb/source/Plugins/Process/QNX/NativeThreadQNX.h
new file mode 100644
index 0000000000000..f1fd97f05b90e
--- /dev/null
+++ b/lldb/source/Plugins/Process/QNX/NativeThreadQNX.h
@@ -0,0 +1,73 @@
+//===-- NativeThreadQNX.h ------------------------------------- -*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_NativeThreadQNX_H_
+#define liblldb_NativeThreadQNX_H_
+
+#include "lldb/Host/common/NativeThreadProtocol.h"
+
+#include "Plugins/Process/QNX/NativeRegisterContextQNX.h"
+
+namespace lldb_private {
+namespace process_qnx {
+
+class NativeProcessQNX;
+
+class NativeThreadQNX : public NativeThreadProtocol {
+  friend class NativeProcessQNX;
+
+public:
+  NativeThreadQNX(NativeProcessQNX &process, lldb::tid_t tid);
+
+  // NativeThreadProtocol Interface
+  std::string GetName() override;
+
+  lldb::StateType GetState() override;
+
+  bool GetStopReason(ThreadStopInfo &stop_info,
+                     std::string &description) override;
+
+  NativeRegisterContextQNX &GetRegisterContext() override;
+
+  Status SetWatchpoint(lldb::addr_t addr, size_t size, uint32_t watch_flags,
+                       bool hardware) override;
+
+  Status RemoveWatchpoint(lldb::addr_t addr) override;
+
+  Status SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override;
+
+  Status RemoveHardwareBreakpoint(lldb::addr_t addr) override;
+
+private:
+  // Interface for friend classes
+
+  Status Resume();
+
+  Status SingleStep();
+
+  void SetStoppedBySignal(uint32_t signo, const siginfo_t *info = nullptr);
+
+  void SetStoppedByBreakpoint();
+
+  void SetStoppedByTrace();
+
+  void SetStoppedWithNoReason();
+
+  void SetStopped();
+
+  // Member Variables
+  lldb::StateType m_state;
+  ThreadStopInfo m_stop_info;
+  std::unique_ptr<NativeRegisterContextQNX> m_reg_context_up;
+  std::string m_stop_description;
+};
+
+} // namespace process_qnx
+} // namespace lldb_private
+
+#endif // #ifndef liblldb_NativeThreadQNX_H_
diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt
index 5df4a9e5ac5c8..3eb0523cf28c4 100644
--- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt
@@ -13,6 +13,7 @@ add_lldb_library(lldbPluginProcessUtility
   NativeRegisterContextDBReg_x86.cpp
   NativeRegisterContextRegisterInfo.cpp
   NetBSDSignals.cpp
+  QNXSignals.cpp
   RegisterContext_x86.cpp
   RegisterContextDarwin_arm.cpp
   RegisterContextDarwin_arm64.cpp
diff --git a/lldb/source/Plugins/Process/Utility/QNXSignals.cpp b/lldb/source/Plugins/Process/Utility/QNXSignals.cpp
new file mode 100644
index 0000000000000..a79ed9ca9bf0a
--- /dev/null
+++ b/lldb/source/Plugins/Process/Utility/QNXSignals.cpp
@@ -0,0 +1,140 @@
+//===-- QNXSignals.cpp ----------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "QNXSignals.h"
+
+#if defined(__QNX__)
+#include <signal.h>
+#include <sys/siginfo.h>
+
+#define ADD_SIGCODE(signal_name, signal_value, code_name, code_value, ...)     \
+  static_assert(signal_name == signal_value,                                   \
+                "Value mismatch for signal number " #signal_name);             \
+  static_assert(code_name == code_value,                                       \
+                "Value mismatch for signal code " #code_name #code_value);     \
+  AddSignalCode(signal_value, code_value, __VA_ARGS__)
+#else
+#define ADD_SIGCODE(signal_name, signal_value, code_name, code_value, ...)     \
+  AddSignalCode(signal_value, code_value, __VA_ARGS__)
+#endif // defined(__QNX__)
+
+using namespace lldb_private;
+
+QNXSignals::QNXSignals() : UnixSignals() { Reset(); }
+
+void QNXSignals::Reset() {
+  m_signals.clear();
+  // clang-format off
+  //        SIGNO   NAME            SUPPRESS  STOP    NOTIFY  DESCRIPTION
+  //        ======  ==============  ========  ======  ======  ===================================================
+  AddSignal(1,      "SIGHUP",       false,    true,   true,   "hangup");
+  AddSignal(2,      "SIGINT",       true,     true,   true,   "interrupt");
+  AddSignal(3,      "SIGQUIT",      false,    true,   true,   "quit");
+
+  AddSignal(4,      "SIGILL",       false,    true,   true,   "illegal instruction");
+  ADD_SIGCODE(SIGILL, 4, ILL_ILLOPC, 1, "illegal opcode");
+  ADD_SIGCODE(SIGILL, 4, ILL_ILLOPN, 2, "illegal operand");
+  ADD_SIGCODE(SIGILL, 4, ILL_ILLADR, 3, "illegal addressing mode");
+  ADD_SIGCODE(SIGILL, 4, ILL_ILLTRP, 4, "illegal trap");
+  ADD_SIGCODE(SIGILL, 4, ILL_PRVOPC, 5, "privileged opcode");
+  ADD_SIGCODE(SIGILL, 4, ILL_PRVREG, 6, "privileged register");
+  ADD_SIGCODE(SIGILL, 4, ILL_COPROC, 7, "coprocessor error");
+  ADD_SIGCODE(SIGILL, 4, ILL_BADSTK, 8, "internal stack error");
+
+  AddSignal(5,      "SIGTRAP",      true,     true,   true,   "trace trap (not reset when caught)");
+  ADD_SIGCODE(SIGTRAP, 5, TRAP_BRKPT, 1, "Break Point");
+  ADD_SIGCODE(SIGTRAP, 5, TRAP_TRACE, 2, "Trace");
+  ADD_SIGCODE(SIGTRAP, 5, TRAP_KDBRK, 3, "Kdebug Break Point");
+  ADD_SIGCODE(SIGTRAP, 5, TRAP_CRASH, 4, "Crash");
+
+  AddSignal(6,      "SIGIOT",       false,    true,   true,   "IOT trap/abort()", "SIGABRT");
+  AddSignal(7,      "SIGEMT",       false,    true,   true,   "EMT instruction/Mutex deadlock", "SIGDEADLK");
+
+  AddSignal(8,      "SIGFPE",       false,    true,   true,   "floating point exception");
+  ADD_SIGCODE(SIGFPE, 8, FPE_INTDIV, 1,  "Integer divide by zero");
+  ADD_SIGCODE(SIGFPE, 8, FPE_INTOVF, 2,  "Integer overflow");
+  ADD_SIGCODE(SIGFPE, 8, FPE_FLTDIV, 3,  "Floating point divide by zero");
+  ADD_SIGCODE(SIGFPE, 8, FPE_FLTOVF, 4,  "Floating point overflow");
+  ADD_SIGCODE(SIGFPE, 8, FPE_FLTUND, 5,  "Floating point underflow");
+  ADD_SIGCODE(SIGFPE, 8, FPE_FLTRES, 6,  "Floating point inexact result");
+  ADD_SIGCODE(SIGFPE, 8, FPE_FLTINV, 7,  "Invalid floating point operation");
+  ADD_SIGCODE(SIGFPE, 8, FPE_FLTSUB, 8,  "Subscript out of range");
+  ADD_SIGCODE(SIGFPE, 8, FPE_NOFPU,  9,  "No FPU or emulator");
+  ADD_SIGCODE(SIGFPE, 8, FPE_NOMEM,  10, "No kernel space for FPU save area");
+
+  AddSignal(9,      "SIGKILL",      false,    true,   true,   "kill");
+
+  AddSignal(10,     "SIGBUS",       false,    true,   true,   "bus error");
+  ADD_SIGCODE(SIGBUS, 10, BUS_ADRALN,  1,  "Invalid address alignment");
+  ADD_SIGCODE(SIGBUS, 10, BUS_ADRERR,  2,  "Non-existant physical address");
+  ADD_SIGCODE(SIGBUS, 10, BUS_OBJERR,  3,  "Object specific hardware error (e.g. NMI parity error)");
+  ADD_SIGCODE(SIGBUS, 10, BUS_BADPAGE, 4,  "N/A");
+  ADD_SIGCODE(SIGBUS, 10, BUS_ENDOBJ,  5,  "N/A");
+  ADD_SIGCODE(SIGBUS, 10, BUS_EOTHER,  6,  "Other not translated error faults");
+  ADD_SIGCODE(SIGBUS, 10, BUS_ENOENT,  7,  "N/A");
+  ADD_SIGCODE(SIGBUS, 10, BUS_EAGAIN,  8,  "Transient fault - many times a no memory condition");
+  ADD_SIGCODE(SIGBUS, 10, BUS_ENOMEM,  9,  "No memory available to execute request");
+  ADD_SIGCODE(SIGBUS, 10, BUS_EFAULT,  10, "N/A");
+  ADD_SIGCODE(SIGBUS, 10, BUS_EINVAL,  11, "Invalid argument(s) passed");
+  ADD_SIGCODE(SIGBUS, 10, BUS_EACCES,  12, "N/A");
+  ADD_SIGCODE(SIGBUS, 10, BUS_EBADF,   13, "Invalid file descriptor");
+  ADD_SIGCODE(SIGBUS, 10, BUS_SRVERR,  50, "N/A");
+
+  AddSignal(11,     "SIGSEGV",      false,    true,   true,   "segmentation violation");
+  ADD_SIGCODE(SIGSEGV, 11, SEGV_MAPERR, 1, "Address not mapped");
+  ADD_SIGCODE(SIGSEGV, 11, SEGV_ACCERR, 2, "No permissions");
+  ADD_SIGCODE(SIGSEGV, 11, SEGV_STKERR, 3, "Stack exception");
+  ADD_SIGCODE(SIGSEGV, 11, SEGV_GPERR,  4, "General protection");
+  ADD_SIGCODE(SIGSEGV, 11, SEGV_IRQERR, 5, "Interrupt handler fault");
+
+  AddSignal(12,     "SIGSYS",       false,    true,   true,   "invalid system call");
+  AddSignal(13,     "SIGPIPE",      false,    true,   true,   "write to pipe with reading end closed");
+  AddSignal(14,     "SIGALRM",      false,    false,  false,  "alarm");
+  AddSignal(15,     "SIGTERM",      false,    true,   true,   "termination requested");
+  AddSignal(16,     "SIGUSR1",      false,    true,   true,   "user defined signal 1");
+  AddSignal(17,     "SIGUSR2",      false,    true,   true,   "user defined signal 2");
+
+  AddSignal(18,     "SIGCHLD",      false,    false,  true,   "child status has changed", "SIGCLD");
+  ADD_SIGCODE(SIGCHLD, 18, CLD_EXITED,    1, "Child has exited");
+  ADD_SIGCODE(SIGCHLD, 18, CLD_KILLED,    2, "Child was killed");
+  ADD_SIGCODE(SIGCHLD, 18, CLD_DUMPED,    3, "Child terminated abnormally");
+  ADD_SIGCODE(SIGCHLD, 18, CLD_TRAPPED,   4, "Traced child has trapped");
+  ADD_SIGCODE(SIGCHLD, 18, CLD_STOPPED,   5, "Child has stopped");
+  ADD_SIGCODE(SIGCHLD, 18, CLD_CONTINUED, 6, "Stopped child had continued");
+
+  AddSignal(19,     "SIGPWR",       false,    true,   true,   "power failure");
+  AddSignal(20,     "SIGWINCH",     false,    true,   true,   "window size changes");
+  AddSignal(21,     "SIGURG",       false,    true,   true,   "urgent data on socket");
+  AddSignal(22,     "SIGPOLL",      false,    true,   true,   "Pollable event / input/output ready", "SIGIO");
+  AddSignal(23,     "SIGSTOP",      true,     true,   true,   "process stop");
+  AddSignal(24,     "SIGTSTP",      false,    true,   true,   "tty stop");
+  AddSignal(25,     "SIGCONT",      false,    false,  true,   "process continue");
+  AddSignal(36,     "SIGTTIN",      false,    true,   true,   "background tty read");
+  AddSignal(27,     "SIGTTOU",      false,    true,   true,   "background tty write");
+  AddSignal(28,     "SIGVTALRM",    false,    true,   true,   "virtual time alarm");
+  AddSignal(29,     "SIGPROF",      false,    false,  false,  "profiling time alarm");
+  AddSignal(30,     "SIGXCPU",      false,    true,   true,   "CPU resource exceeded");
+  AddSignal(31,     "SIGXFSZ",      false,    true,   true,   "file size limit exceeded");
+  AddSignal(41,     "SIGRTMIN",     false,    false,  false,  "real time signal 0");
+  AddSignal(42,     "SIGRTMIN+1",   false,    false,  false,  "real time signal 1");
+  AddSignal(43,     "SIGRTMIN+2",   false,    false,  false,  "real time signal 2");
+  AddSignal(44,     "SIGRTMIN+3",   false,    false,  false,  "real time signal 3");
+  AddSignal(45,     "SIGRTMIN+4",   false,    false,  false,  "real time signal 4");
+  AddSignal(46,     "SIGRTMIN+5",   false,    false,  false,  "real time signal 5");
+  AddSignal(47,     "SIGRTMIN+6",   false,    false,  false,  "real time signal 6");
+  AddSignal(48,     "SIGRTMIN+7",   false,    false,  false,  "real time signal 7");
+  AddSignal(49,     "SIGRTMAX-7",   false,    false,  false,  "real time signal 8");
+  AddSignal(50,     "SIGRTMAX-6",   false,    false,  false,  "real time signal 9");
+  AddSignal(51,     "SIGRTMAX-5",   false,    false,  false,  "real time signal 10");
+  AddSignal(52,     "SIGRTMAX-4",   false,    false,  false,  "real time signal 11");
+  AddSignal(53,     "SIGRTMAX-3",   false,    false,  false,  "real time signal 12");
+  AddSignal(54,     "SIGRTMAX-2",   false,    false,  false,  "real time signal 13");
+  AddSignal(55,     "SIGRTMAX-1",   false,    false,  false,  "real time signal 14");
+  AddSignal(56,     "SIGRTMAX",     false,    false,  false,  "real time signal 15");
+  // clang-format on
+}
diff --git a/lldb/source/Plugins/Process/Utility/QNXSignals.h b/lldb/source/Plugins/Process/Utility/QNXSignals.h
new file mode 100644
index 0000000000000..7534e46cbb708
--- /dev/null
+++ b/lldb/source/Plugins/Process/Utility/QNXSignals.h
@@ -0,0 +1,27 @@
+//===-- QNXSignals.h --------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_QNXSIGNALS_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_QNXSIGNALS_H
+
+#include "lldb/Target/UnixSignals.h"
+
+namespace lldb_private {
+
+/// QNX specific set of Unix signals.
+class QNXSignals : public UnixSignals {
+public:
+  QNXSignals();
+
+private:
+  void Reset() override;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_QNXSIGNALS_H
diff --git a/lldb/source/Target/UnixSignals.cpp b/lldb/source/Target/UnixSignals.cpp
index e3c7a83ece073..fff2481943e4a 100644
--- a/lldb/source/Target/UnixSignals.cpp
+++ b/lldb/source/Target/UnixSignals.cpp
@@ -10,6 +10,7 @@
 #include "Plugins/Process/Utility/FreeBSDSignals.h"
 #include "Plugins/Process/Utility/LinuxSignals.h"
 #include "Plugins/Process/Utility/NetBSDSignals.h"
+#include "Plugins/Process/Utility/QNXSignals.h"
 #include "lldb/Host/HostInfo.h"
 #include "lldb/Utility/ArchSpec.h"
 #include <optional>
@@ -36,6 +37,8 @@ lldb::UnixSignalsSP UnixSignals::Create(const ArchSpec &arch) {
     return std::make_shared<FreeBSDSignals>();
   case llvm::Triple::NetBSD:
     return std::make_shared<NetBSDSignals>();
+  case llvm::Triple::QNX:
+    return std::make_shared<QNXSignals>();
   default:
     return std::make_shared<UnixSignals>();
   }
diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt
index e9954d66cd1a5..f38599cc52472 100644
--- a/lldb/source/Utility/CMakeLists.txt
+++ b/lldb/source/Utility/CMakeLists.txt
@@ -23,6 +23,10 @@ if (NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB )
     list(APPEND LLDB_SYSTEM_LIBS atomic)
 endif()
 
+if (CMAKE_SYSTEM_NAME MATCHES "QNX")
+    list(APPEND LLDB_SYSTEM_LIBS socket)
+endif()
+
 add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
   AddressableBits.cpp
   ArchSpec.cpp
diff --git a/lldb/tools/lldb-server/CMakeLists.txt b/lldb/tools/lldb-server/CMakeLists.txt
index 9030ed709a647..d7687e6a73941 100644
--- a/lldb/tools/lldb-server/CMakeLists.txt
+++ b/lldb/tools/lldb-server/CMakeLists.txt
@@ -24,6 +24,10 @@ else()
   list(APPEND LLDB_PLUGINS lldbPluginObjectFileELF)
 endif()
 
+if(CMAKE_SYSTEM_NAME MATCHES "QNX")
+  list(APPEND LLDB_PLUGINS lldbPluginProcessQNX)
+endif()
+
 if(APPLE_EMBEDDED)
   if(LLDB_CODESIGN_IDENTITY)
     # Use explicit LLDB identity
diff --git a/lldb/tools/lldb-server/lldb-gdbserver.cpp b/lldb/tools/lldb-server/lldb-gdbserver.cpp
index 563284730bc70..bde3ad9d9c0d3 100644
--- a/lldb/tools/lldb-server/lldb-gdbserver.cpp
+++ b/lldb/tools/lldb-server/lldb-gdbserver.cpp
@@ -45,6 +45,8 @@
 #include "Plugins/Process/NetBSD/NativeProcessNetBSD.h"
 #elif defined(_WIN32)
 #include "Plugins/Process/Windows/Common/NativeProcessWindows.h"
+#elif defined(__QNX__)
+#include "Plugins/Process/QNX/NativeProcessQNX.h"
 #endif
 
 #ifndef LLGS_PROGRAM_NAME
@@ -70,6 +72,8 @@ typedef process_freebsd::NativeProcessFreeBSD::Manager NativeProcessManager;
 typedef process_netbsd::NativeProcessNetBSD::Manager NativeProcessManager;
 #elif defined(_WIN32)
 typedef NativeProcessWindows::Manager NativeProcessManager;
+#elif defined(__QNX__)
+typedef process_qnx::NativeProcessQNX::Manager NativeProcessManager;
 #else
 // Dummy implementation to make sure the code compiles
 class NativeProcessManager : public NativeProcessProtocol::Manager {
diff --git a/llvm/include/llvm/Support/ExitCodes.h b/llvm/include/llvm/Support/ExitCodes.h
index 4eb5dedc688bc..f8876ac4e3712 100644
--- a/llvm/include/llvm/Support/ExitCodes.h
+++ b/llvm/include/llvm/Support/ExitCodes.h
@@ -20,11 +20,11 @@
 
 #if HAVE_SYSEXITS_H
 #include <sysexits.h>
-#elif __MVS__ || defined(_WIN32)
-// <sysexits.h> does not exist on z/OS and Windows. The only value used in LLVM
-// is EX_IOERR, which is used to signal a special error condition (broken pipe).
-// Define the macro with its usual value from BSD systems, which is chosen to
-// not clash with more standard exit codes like 1.
+#elif __MVS__ || defined(_WIN32) || defined(__QNX__)
+// <sysexits.h> does not exist on z/OS, Windows, and QNX. The only value used in
+// LLVM is EX_IOERR, which is used to signal a special error condition (broken
+// pipe). Define the macro with its usual value from BSD systems, which is
+// chosen to not clash with more standard exit codes like 1.
 #define EX_IOERR 74
 #elif LLVM_ON_UNIX
 #error Exit code EX_IOERR not available
diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc
index 6e679f74869f0..9cc889ea79c80 100644
--- a/llvm/lib/Support/Unix/Path.inc
+++ b/llvm/lib/Support/Unix/Path.inc
@@ -79,6 +79,9 @@ extern char **environ;
 #define STATVFS statvfs
 #define FSTATVFS fstatvfs
 #define STATVFS_F_FRSIZE(vfs) vfs.f_frsize
+#if defined(__QNX__)
+#include <sys/mount.h>
+#endif
 #else
 #if defined(__OpenBSD__) || defined(__FreeBSD__)
 #include <sys/mount.h>
@@ -112,7 +115,7 @@ typedef uint_t uint;
 #endif
 
 #if defined(__NetBSD__) || defined(__DragonFly__) || defined(__GNU__) ||       \
-    defined(__MVS__)
+    defined(__MVS__) || defined(__QNX__)
 #define STATVFS_F_FLAG(vfs) (vfs).f_flag
 #else
 #define STATVFS_F_FLAG(vfs) (vfs).f_flags
@@ -555,6 +558,13 @@ static bool is_local_impl(struct STATVFS &Vfs) {
   // The file system can have an arbitrary structure on z/OS; must go with the
   // conservative answer.
   return false;
+#elif defined(__QNX__)
+  // statvfs::f_basetype contains the null-terminated name of the target file
+  // system.
+  StringRef fstype(Vfs.f_basetype);
+  // TODO: Confirm if NFS_FS_TYPE and CIFS_FS_TYPE are the only remote file
+  // systems.
+  return !fstype.equals(NFS_FS_TYPE) && !fstype.equals(CIFS_FS_TYPE);
 #else
   return !!(STATVFS_F_FLAG(Vfs) & MNT_LOCAL);
 #endif
diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc
index 298fde1a387cc..4a6c0e34cae46 100644
--- a/llvm/lib/Support/Unix/Signals.inc
+++ b/llvm/lib/Support/Unix/Signals.inc
@@ -312,11 +312,17 @@ static void RegisterHandlers() { // Not signal-safe.
     switch (Kind) {
     case SignalKind::IsKill:
       NewHandler.sa_handler = SignalHandler;
+#if defined(HAVE_SIGALTSTACK)
       NewHandler.sa_flags = SA_NODEFER | SA_RESETHAND | SA_ONSTACK;
+#else
+      NewHandler.sa_flags = SA_NODEFER | SA_RESETHAND;
+#endif
       break;
     case SignalKind::IsInfo:
       NewHandler.sa_handler = InfoSignalHandler;
+#if defined(HAVE_SIGALTSTACK)
       NewHandler.sa_flags = SA_ONSTACK;
+#endif
       break;
     }
     sigemptyset(&NewHandler.sa_mask);



More information about the llvm-commits mailing list