1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 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
|