aboutsummaryrefslogtreecommitdiff
path: root/source/Plugins/Process/Linux/SingleStepCheck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/Plugins/Process/Linux/SingleStepCheck.cpp')
-rw-r--r--source/Plugins/Process/Linux/SingleStepCheck.cpp177
1 files changed, 177 insertions, 0 deletions
diff --git a/source/Plugins/Process/Linux/SingleStepCheck.cpp b/source/Plugins/Process/Linux/SingleStepCheck.cpp
new file mode 100644
index 000000000000..8c557d4b6ff8
--- /dev/null
+++ b/source/Plugins/Process/Linux/SingleStepCheck.cpp
@@ -0,0 +1,177 @@
+//===-- SingleStepCheck.cpp ----------------------------------- -*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SingleStepCheck.h"
+
+#include <sched.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "NativeProcessLinux.h"
+
+#include "llvm/Support/Compiler.h"
+
+#include "lldb/Core/Error.h"
+#include "lldb/Core/Log.h"
+#include "lldb/Host/linux/Ptrace.h"
+
+using namespace lldb_private::process_linux;
+
+#if defined(__arm64__) || defined(__aarch64__)
+namespace
+{
+
+void LLVM_ATTRIBUTE_NORETURN
+Child()
+{
+ if (ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == -1)
+ _exit(1);
+
+ // We just do an endless loop SIGSTOPPING ourselves until killed. The tracer will fiddle with our cpu
+ // affinities and monitor the behaviour.
+ for (;;)
+ {
+ raise(SIGSTOP);
+
+ // Generate a bunch of instructions here, so that a single-step does not land in the
+ // raise() accidentally. If single-stepping works, we will be spinning in this loop. If
+ // it doesn't, we'll land in the raise() call above.
+ for (volatile unsigned i = 0; i < CPU_SETSIZE; ++i)
+ ;
+ }
+}
+
+struct ChildDeleter
+{
+ ::pid_t pid;
+
+ ~ChildDeleter()
+ {
+ int status;
+ kill(pid, SIGKILL); // Kill the child.
+ waitpid(pid, &status, __WALL); // Pick up the remains.
+ }
+};
+
+} // end anonymous namespace
+
+bool
+impl::SingleStepWorkaroundNeeded()
+{
+ // We shall spawn a child, and use it to verify the debug capabilities of the cpu. We shall
+ // iterate through the cpus, bind the child to each one in turn, and verify that
+ // single-stepping works on that cpu. A workaround is needed if we find at least one broken
+ // cpu.
+
+ Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
+ Error error;
+ ::pid_t child_pid = fork();
+ if (child_pid == -1)
+ {
+ if (log)
+ {
+ error.SetErrorToErrno();
+ log->Printf("%s failed to fork(): %s", __FUNCTION__, error.AsCString());
+ }
+ return false;
+ }
+ if (child_pid == 0)
+ Child();
+
+ ChildDeleter child_deleter{child_pid};
+ cpu_set_t available_cpus;
+ if (sched_getaffinity(child_pid, sizeof available_cpus, &available_cpus) == -1)
+ {
+ if (log)
+ {
+ error.SetErrorToErrno();
+ log->Printf("%s failed to get available cpus: %s", __FUNCTION__, error.AsCString());
+ }
+ return false;
+ }
+
+ int status;
+ ::pid_t wpid = waitpid(child_pid, &status, __WALL);
+ if (wpid != child_pid || !WIFSTOPPED(status))
+ {
+ if (log)
+ {
+ error.SetErrorToErrno();
+ log->Printf("%s waitpid() failed (status = %x): %s", __FUNCTION__, status, error.AsCString());
+ }
+ return false;
+ }
+
+ unsigned cpu;
+ for (cpu = 0; cpu < CPU_SETSIZE; ++cpu)
+ {
+ if (!CPU_ISSET(cpu, &available_cpus))
+ continue;
+
+ cpu_set_t cpus;
+ CPU_ZERO(&cpus);
+ CPU_SET(cpu, &cpus);
+ if (sched_setaffinity(child_pid, sizeof cpus, &cpus) == -1)
+ {
+ if (log)
+ {
+ error.SetErrorToErrno();
+ log->Printf("%s failed to switch to cpu %u: %s", __FUNCTION__, cpu, error.AsCString());
+ }
+ continue;
+ }
+
+ int status;
+ error = NativeProcessLinux::PtraceWrapper(PTRACE_SINGLESTEP, child_pid);
+ if (error.Fail())
+ {
+ if (log)
+ log->Printf("%s single step failed: %s", __FUNCTION__, error.AsCString());
+ break;
+ }
+
+ wpid = waitpid(child_pid, &status, __WALL);
+ if (wpid != child_pid || !WIFSTOPPED(status))
+ {
+ if (log)
+ {
+ error.SetErrorToErrno();
+ log->Printf("%s waitpid() failed (status = %x): %s", __FUNCTION__, status, error.AsCString());
+ }
+ break;
+ }
+ if (WSTOPSIG(status) != SIGTRAP)
+ {
+ if (log)
+ log->Printf("%s single stepping on cpu %d failed with status %x", __FUNCTION__, cpu, status);
+ break;
+ }
+ }
+
+ // cpu is either the index of the first broken cpu, or CPU_SETSIZE.
+ if (cpu == 0)
+ {
+ if (log)
+ log->Printf("%s SINGLE STEPPING ON FIRST CPU IS NOT WORKING. DEBUGGING LIKELY TO BE UNRELIABLE.",
+ __FUNCTION__);
+ // No point in trying to fiddle with the affinities, just give it our best shot and see how it goes.
+ return false;
+ }
+
+ return cpu != CPU_SETSIZE;
+}
+
+#else // !arm64
+bool
+impl::SingleStepWorkaroundNeeded()
+{
+ return false;
+}
+#endif