[Lldb-commits] [lldb] [lldb] Add native process support for OpenBSD (PR #236288)
Brad Smith via lldb-commits
lldb-commits at lists.llvm.org
Mon Nov 2 17:06:06 PST 2026
================
@@ -0,0 +1,723 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "NativeProcessOpenBSD.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#define _DYN_LOADER
+#include <elf.h>
+#include <util.h>
+
+#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
+#include "lldb/Host/HostProcess.h"
+#include "lldb/Host/common/NativeRegisterContext.h"
+#include "lldb/Host/posix/ProcessLauncherPosixFork.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Utility/State.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Errno.h"
+
+#include <cstring>
+
+// System includes - They have to be included after framework includes because
+// they define some macros which collide with variable names in other modules
+// clang-format off
+#include <sys/types.h>
+#include <sys/ptrace.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+// clang-format on
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::process_openbsd;
+using namespace llvm;
+
+// Simple helper function to ensure flags are enabled on the given file
+// descriptor.
+static Status EnsureFDFlags(int fd, int flags) {
+ int status = fcntl(fd, F_GETFL);
+ if (status == -1)
+ return Status::FromErrno();
+
+ if (fcntl(fd, F_SETFL, status | flags) == -1)
+ return Status::FromErrno();
+
+ return Status();
+}
+
+// No architecture has a register context yet.
+static llvm::Error CheckArchitectureSupported() {
+ return llvm::createStringError(
+ "debugging is not supported on this architecture");
+}
+
+// Whether kern.global_ptrace allows attaching to processes that are not
+// children of the debugger.
+static bool IsGlobalPtraceEnabled() {
+ int mib[2] = {CTL_KERN, KERN_GLOBAL_PTRACE};
+ int value = 0;
+ size_t len = sizeof(value);
+ return ::sysctl(mib, 2, &value, &len, nullptr, 0) == 0 && value != 0;
+}
+
+// Public Static Methods
+
+llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+NativeProcessOpenBSD::Manager::Launch(ProcessLaunchInfo &launch_info,
+ NativeDelegate &native_delegate) {
+ Log *log = GetLog(POSIXLog::Process);
+
+ if (llvm::Error error = CheckArchitectureSupported())
+ return std::move(error);
+
+ Status status;
+ ::pid_t pid = ProcessLauncherPosixFork()
+ .LaunchProcess(launch_info, status)
+ .GetProcessId();
+ LLDB_LOG(log, "pid = {0:x}", pid);
+ if (status.Fail()) {
+ LLDB_LOG(log, "failed to launch process: {0}", status);
+ return status.ToError();
+ }
+
+ // Wait for the child process to trap on its call to execve.
+ int wstatus;
+ ::pid_t wpid = llvm::sys::RetryAfterSignal(-1, ::waitpid, pid, &wstatus, 0);
+ assert(wpid == pid);
+ UNUSED_IF_ASSERT_DISABLED(wpid);
+ if (!WIFSTOPPED(wstatus)) {
+ LLDB_LOG(log, "Could not sync with inferior process: wstatus={0}",
+ WaitStatus::Decode(wstatus));
+ return llvm::createStringError("could not sync with inferior process");
+ }
+ LLDB_LOG(log, "inferior started, now in stopped state");
+
+ ProcessInstanceInfo Info;
+ if (!Host::GetProcessInfo(pid, Info)) {
+ return llvm::createStringError("cannot get process architecture");
+ }
+
+ // Set the architecture to the exe architecture.
+ LLDB_LOG(log, "pid = {0:x}, detected architecture {1}", pid,
+ Info.GetArchitecture().GetArchitectureName());
+
+ std::unique_ptr<NativeProcessOpenBSD> process_up(new NativeProcessOpenBSD(
+ pid, launch_info.GetPTY().ReleasePrimaryFileDescriptor(), native_delegate,
+ Info.GetArchitecture(), m_mainloop));
+
+ status = process_up->RefreshThreads();
+ if (status.Fail())
+ return status.ToError();
+
+ process_up->m_trapped_tid = process_up->GetTrappedThreadID();
+ for (const auto &thread : process_up->m_threads)
+ static_cast<NativeThreadOpenBSD &>(*thread).SetStoppedBySignal(SIGSTOP);
+ process_up->SetState(StateType::eStateStopped, false);
+
+ return std::move(process_up);
+}
+
+llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+NativeProcessOpenBSD::Manager::Attach(
+ lldb::pid_t pid, NativeProcessProtocol::NativeDelegate &native_delegate) {
+
+ Log *log = GetLog(POSIXLog::Process);
+ LLDB_LOG(log, "pid = {0:x}", pid);
+
+ if (llvm::Error error = CheckArchitectureSupported())
+ return std::move(error);
+
+ // Retrieve the architecture for the running process.
+ ProcessInstanceInfo Info;
+ if (!Host::GetProcessInfo(pid, Info)) {
+ return llvm::createStringError("cannot get process architecture");
+ }
+
+ std::unique_ptr<NativeProcessOpenBSD> process_up(new NativeProcessOpenBSD(
+ pid, -1, native_delegate, Info.GetArchitecture(), m_mainloop));
+
+ Status status = process_up->Attach();
+ if (!status.Success())
+ return status.ToError();
+
+ return std::move(process_up);
+}
+
+NativeProcessOpenBSD::Extension
+NativeProcessOpenBSD::Manager::GetSupportedExtensions() const {
+ Extension supported =
+ Extension::pass_signals | Extension::auxv | Extension::libraries_svr4;
+ return supported;
+}
+
+// Public Instance Methods
+
+NativeProcessOpenBSD::NativeProcessOpenBSD(::pid_t pid, int terminal_fd,
+ NativeDelegate &delegate,
+ const ArchSpec &arch,
+ MainLoop &mainloop)
+ : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch) {
+ if (m_terminal_fd != -1) {
+ Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK);
+ assert(status.Success());
+ }
+
+ Status status;
+ m_sigchld_handle = mainloop.RegisterSignal(
+ SIGCHLD, [this](MainLoopBase &) { SigchldHandler(); }, status);
+ assert(m_sigchld_handle && status.Success());
+}
+
+// Handles all waitpid events from the inferior process.
+void NativeProcessOpenBSD::MonitorCallback(lldb::pid_t pid, int signal) {
+ // OpenBSD does not report thread creation or exit, so resynchronize the
+ // thread list every time the process stops.
+ Status error = RefreshThreads();
+ if (error.Fail()) {
+ Log *log = GetLog(POSIXLog::Thread);
+ LLDB_LOG(log, "failed to refresh threads for pid {0}: {1}", pid, error);
+ }
+
+ switch (signal) {
+ case SIGTRAP:
+ return MonitorSIGTRAP(pid);
+ default:
+ return MonitorSignal(pid, signal);
+ }
+}
+
+void NativeProcessOpenBSD::MonitorExited(lldb::pid_t pid, WaitStatus status) {
+ Log *log = GetLog(POSIXLog::Process);
+
+ LLDB_LOG(log, "got exit signal({0}) , pid = {1}", status, pid);
+
+ /* Stop Tracking All Threads attached to Process */
+ m_threads.clear();
+
+ SetExitStatus(status, true);
+
+ // Notify delegate that our process has exited.
+ SetState(StateType::eStateExited, true);
+}
+
+void NativeProcessOpenBSD::MonitorSIGTRAP(lldb::pid_t pid) {
+ // OpenBSD reports a plain SIGTRAP for both a completed PT_STEP and a
+ // software breakpoint, so use what the last resume requested to tell them
+ // apart: it is a step only if the thread that trapped is the one that was
+ // being stepped.
+ m_trapped_tid = GetTrappedThreadID();
+
+ const bool stepped =
+ m_step_tid != LLDB_INVALID_THREAD_ID && m_step_tid == m_trapped_tid;
+ m_step_tid = LLDB_INVALID_THREAD_ID;
+
+ for (const auto &thread : m_threads) {
+ auto &t = static_cast<NativeThreadOpenBSD &>(*thread);
+ if (t.GetID() != m_trapped_tid) {
+ t.SetStoppedWithNoReason();
+ } else if (stepped) {
+ t.SetStoppedByTrace();
+ } else {
+ t.SetStoppedByBreakpoint();
+ FixupBreakpointPCAsNeeded(t);
+ }
+ }
+ if (m_trapped_tid != LLDB_INVALID_THREAD_ID)
+ SetCurrentThreadID(m_trapped_tid);
+ SetState(StateType::eStateStopped, true);
+}
+
+void NativeProcessOpenBSD::MonitorSignal(lldb::pid_t pid, int signal) {
+ m_trapped_tid = GetTrappedThreadID();
+ m_step_tid = LLDB_INVALID_THREAD_ID;
+
+ for (const auto &thread : m_threads) {
+ auto &t = static_cast<NativeThreadOpenBSD &>(*thread);
+ if (t.GetID() == m_trapped_tid)
+ t.SetStoppedBySignal(signal);
+ else
+ t.SetStoppedWithNoReason();
+ }
+ if (m_trapped_tid != LLDB_INVALID_THREAD_ID)
+ SetCurrentThreadID(m_trapped_tid);
+ SetState(StateType::eStateStopped, true);
+}
+
+Status NativeProcessOpenBSD::PtraceWrapper(int req, lldb::pid_t pid, void *addr,
+ int data, int *result) {
+ Log *log = GetLog(POSIXLog::Ptrace);
+ Status error;
+ int ret;
+
+ errno = 0;
+ ret =
+ ptrace(req, static_cast<::pid_t>(pid), static_cast<caddr_t>(addr), data);
+
+ if (ret == -1)
+ error = Status::FromErrno();
+
+ if (result)
+ *result = ret;
+
+ LLDB_LOG(log, "ptrace({0}, {1}, {2}, {3})={4:x}", req, pid, addr, data, ret);
+
+ if (error.Fail())
+ LLDB_LOG(log, "ptrace() failed: {0}", error);
+
+ return error;
+}
+
+Status NativeProcessOpenBSD::Resume(const ResumeActionList &resume_actions) {
+ Log *log = GetLog(POSIXLog::Process);
+ LLDB_LOG(log, "pid {0}", GetID());
+
+ // OpenBSD's ptrace(2) can resume either the whole process or, when given a
+ // thread ID, only the thread that reported the last stop (the others stay
+ // suspended). It cannot suspend individual threads, so map lldb's
+ // per-thread actions onto those two modes.
+ NativeThreadOpenBSD *step_thread = nullptr;
+ std::vector<NativeThreadOpenBSD *> run_threads;
+ bool any_suspended = false;
+ int signal = 0;
+
+ for (const auto &abs_thread : m_threads) {
+ auto &thread = static_cast<NativeThreadOpenBSD &>(*abs_thread);
+ const ResumeAction *action =
+ resume_actions.GetActionForThread(thread.GetID(), true);
+ if (action == nullptr) {
+ any_suspended = true;
+ continue;
+ }
+
+ switch (action->state) {
+ case eStateRunning:
+ run_threads.push_back(&thread);
+ break;
+ case eStateStepping:
+ if (step_thread)
+ return Status::FromErrorString(
+ "OpenBSD can only single-step one thread at a time");
+ step_thread = &thread;
+ break;
+ case eStateSuspended:
+ case eStateStopped:
+ any_suspended = true;
+ break;
+ default:
+ return Status::FromErrorStringWithFormat(
+ "NativeProcessOpenBSD::%s (): unexpected state %s specified "
+ "for pid %" PRIu64 ", tid %" PRIu64,
+ __FUNCTION__, StateAsCString(action->state), GetID(), thread.GetID());
+ }
+
+ if (action->signal != LLDB_INVALID_SIGNAL_NUMBER)
+ signal = action->signal;
+ }
+
+ lldb::pid_t target = GetID();
+ int req;
+ if (step_thread) {
+#ifdef PT_STEP
+ if (step_thread->GetID() != m_trapped_tid)
+ return Status::FromErrorStringWithFormat(
+ "OpenBSD can only single-step the thread that reported the last "
+ "stop (tid %" PRIu64 ")",
+ m_trapped_tid);
+ req = PT_STEP;
+ // With the thread ID only the stepping thread runs, which is what lldb
+ // wants when the other threads are suspended (e.g. stepping over a
+ // breakpoint). With the process ID the other threads run as well.
+ if (run_threads.empty())
+ target = step_thread->GetID();
+#else
+ return Status::FromErrorString(
+ "NativeProcessOpenBSD stepping not supported on this platform");
+#endif
+ } else if (run_threads.empty()) {
+ LLDB_LOG(log, "no threads to resume for pid {0}", GetID());
+ return Status();
+ } else {
+ req = PT_CONTINUE;
+ if (any_suspended) {
+ if (run_threads.size() == 1 &&
+ run_threads.front()->GetID() == m_trapped_tid)
+ target = m_trapped_tid;
+ else
+ LLDB_LOG(log,
+ "cannot suspend individual threads, resuming all threads "
+ "of pid {0}",
+ GetID());
+ }
+ }
+
+ Status error = PtraceWrapper(req, target, (void *)1, signal);
+ if (error.Fail())
+ return error;
+
+ const bool whole_process = target == GetID();
+ for (const auto &abs_thread : m_threads) {
+ auto &thread = static_cast<NativeThreadOpenBSD &>(*abs_thread);
+ if (&thread == step_thread)
+ thread.SetStepping();
+ else if (whole_process || thread.GetID() == target)
+ thread.SetRunning();
+ }
+ m_step_tid = step_thread ? step_thread->GetID() : LLDB_INVALID_THREAD_ID;
+ SetState(step_thread ? eStateStepping : eStateRunning, true);
+
+ return Status();
+}
+
+Status NativeProcessOpenBSD::Halt() {
+ // Do not try to stop a process that's already stopped, this may cause
+ // the SIGSTOP to get queued and stop the process again once resumed.
+ if (StateIsStoppedState(m_state, false))
+ return Status();
+ if (kill(GetID(), SIGSTOP) != 0)
+ return Status::FromErrno();
+ return Status();
+}
+
+Status NativeProcessOpenBSD::Detach() {
+ Status error;
+
+ // Stop monitoring the inferior.
+ m_sigchld_handle.reset();
+
+ // Tell ptrace to detach from the process.
+ if (GetID() == LLDB_INVALID_PROCESS_ID)
+ return error;
+
+ return PtraceWrapper(PT_DETACH, GetID());
+}
+
+Status NativeProcessOpenBSD::Signal(int signo) {
+ if (kill(GetID(), signo) != 0)
+ return Status::FromErrno();
+ return Status();
+}
+
+Status NativeProcessOpenBSD::Kill() {
+ Log *log = GetLog(POSIXLog::Process);
+ LLDB_LOG(log, "pid {0}", GetID());
+
+ 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, "ignored for PID {0} due to current state: {1}", 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;
+ }
+
+ // A process stopped by ptrace(2) does not act on SIGKILL until it is
+ // resumed, so use PT_KILL, which resumes it with SIGKILL. PT_KILL fails on
+ // a running process, so signal that one directly.
+ if (StateIsStoppedState(m_state, false))
+ return PtraceWrapper(PT_KILL, GetID());
+
+ if (kill(GetID(), SIGKILL) != 0)
+ return Status::FromErrno();
+
+ return error;
+}
+
+llvm::Expected<lldb::addr_t>
+NativeProcessOpenBSD::AllocateMemory(size_t size, uint32_t permissions) {
+ return llvm::make_error<UnimplementedError>();
+}
+
+llvm::Error NativeProcessOpenBSD::DeallocateMemory(lldb::addr_t addr) {
+ return llvm::make_error<UnimplementedError>();
+}
+
+size_t NativeProcessOpenBSD::UpdateThreads() { return m_threads.size(); }
+
+Status NativeProcessOpenBSD::SetBreakpoint(lldb::addr_t addr, uint32_t size,
+ bool hardware) {
+ if (hardware)
+ return Status::FromErrorString(
+ "NativeProcessOpenBSD does not support hardware breakpoints");
+ else
+ return SetSoftwareBreakpoint(addr, size);
+}
+
+Status NativeProcessOpenBSD::GetLoadedModuleFileSpec(const char *module_path,
+ FileSpec &file_spec) {
+ return Status::FromErrorString("Unimplemented");
+}
+
+Status
+NativeProcessOpenBSD::GetFileLoadAddress(const llvm::StringRef &file_name,
+ lldb::addr_t &load_addr) {
+ return Status::FromErrorString("Unimplemented");
+}
+
+void NativeProcessOpenBSD::SigchldHandler() {
+ Log *log = GetLog(POSIXLog::Process);
+ // Process all pending waitpid notifications.
+ int status;
+ ::pid_t wait_pid =
+ llvm::sys::RetryAfterSignal(-1, waitpid, GetID(), &status, WNOHANG);
+
+ if (wait_pid == 0)
+ return; // We are done.
+
+ if (wait_pid == -1) {
+ Status error(errno, eErrorTypePOSIX);
+ LLDB_LOG(log, "waitpid ({0}, &status, _) failed: {1}", GetID(), error);
+ return;
+ }
+
+ WaitStatus wait_status = WaitStatus::Decode(status);
+ bool exited = wait_status.type == WaitStatus::Exit ||
+ (wait_status.type == WaitStatus::Signal &&
+ wait_pid == static_cast<::pid_t>(GetID()));
+
+ LLDB_LOG(log,
+ "waitpid ({0}, &status, _) => pid = {1}, status = {2}, exited = {3}",
+ GetID(), wait_pid, status, exited);
+
+ if (exited)
+ MonitorExited(wait_pid, wait_status);
+ else {
+ assert(wait_status.type == WaitStatus::Stop);
+ MonitorCallback(wait_pid, wait_status.status);
+ }
+}
+
+bool NativeProcessOpenBSD::HasThreadNoLock(lldb::tid_t thread_id) {
+ for (const auto &thread : m_threads) {
+ assert(thread && "thread list should not contain NULL threads");
----------------
brad0 wrote:
No idea what you're pointing out.
https://github.com/llvm/llvm-project/pull/236288
More information about the lldb-commits
mailing list