diff options
Diffstat (limited to 'source/Plugins/Process/Linux/SingleStepCheck.cpp')
-rw-r--r-- | source/Plugins/Process/Linux/SingleStepCheck.cpp | 177 |
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 |