llvm-project/lldb/source/Plugins/Process/Linux/SingleStepCheck.cpp

178 lines
4.8 KiB
C++

//===-- 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