#include "SingleStepCheck.h"
#include <csignal>
#include <sched.h>
#include <sys/wait.h>
#include <unistd.h>
#include "NativeProcessLinux.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Errno.h"
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
#include "lldb/Host/linux/Ptrace.h"
#include "lldb/Utility/Status.h"
usingnamespacelldb;
usingnamespacelldb_private;
usingnamespacelldb_private::process_linux;
#if defined(__arm64__) || defined(__aarch64__)
namespace {
[[noreturn]] void Child() {
if (ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == -1)
_exit(1);
for (;;) {
raise(SIGSTOP);
for (volatile unsigned i = 0; i < CPU_SETSIZE; ++i)
;
}
}
struct ChildDeleter {
::pid_t pid;
~ChildDeleter() {
int status;
kill(pid, SIGKILL);
llvm::sys::RetryAfterSignal(-1, waitpid, pid, &status, __WALL);
}
};
bool WorkaroundNeeded() {
Log *log = GetLog(POSIXLog::Thread);
::pid_t child_pid = fork();
if (child_pid == -1) {
LLDB_LOG(log, "failed to fork(): {0}", Status(errno, eErrorTypePOSIX));
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) {
LLDB_LOG(log, "failed to get available cpus: {0}",
Status(errno, eErrorTypePOSIX));
return false;
}
int status;
::pid_t wpid = llvm::sys::RetryAfterSignal(-1, waitpid,
child_pid, &status, __WALL);
if (wpid != child_pid || !WIFSTOPPED(status)) {
LLDB_LOG(log, "waitpid() failed (status = {0:x}): {1}", status,
Status(errno, eErrorTypePOSIX));
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) {
LLDB_LOG(log, "failed to switch to cpu {0}: {1}", cpu,
Status(errno, eErrorTypePOSIX));
continue;
}
int status;
Status error =
NativeProcessLinux::PtraceWrapper(PTRACE_SINGLESTEP, child_pid);
if (error.Fail()) {
LLDB_LOG(log, "single step failed: {0}", error);
break;
}
wpid = llvm::sys::RetryAfterSignal(-1, waitpid,
child_pid, &status, __WALL);
if (wpid != child_pid || !WIFSTOPPED(status)) {
LLDB_LOG(log, "waitpid() failed (status = {0:x}): {1}", status,
Status(errno, eErrorTypePOSIX));
break;
}
if (WSTOPSIG(status) != SIGTRAP) {
LLDB_LOG(log, "single stepping on cpu {0} failed with status {1:x}", cpu,
status);
break;
}
}
if (cpu == 0) {
LLDB_LOG(log,
"SINGLE STEPPING ON FIRST CPU IS NOT WORKING. DEBUGGING "
"LIKELY TO BE UNRELIABLE.");
return false;
}
return cpu != CPU_SETSIZE;
}
}
std::unique_ptr<SingleStepWorkaround> SingleStepWorkaround::Get(::pid_t tid) {
Log *log = GetLog(POSIXLog::Thread);
static bool workaround_needed = WorkaroundNeeded();
if (!workaround_needed) {
LLDB_LOG(log, "workaround for thread {0} not needed", tid);
return nullptr;
}
cpu_set_t original_set;
if (sched_getaffinity(tid, sizeof original_set, &original_set) != 0) {
LLDB_LOG(log, "Unable to get cpu affinity for thread {0}: {1}", tid,
Status(errno, eErrorTypePOSIX));
return nullptr;
}
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set);
if (sched_setaffinity(tid, sizeof set, &set) != 0) {
LLDB_LOG(log, "Unable to set cpu affinity for thread {0}: {1}", tid,
Status(errno, eErrorTypePOSIX));
}
LLDB_LOG(log, "workaround for thread {0} prepared", tid);
return std::make_unique<SingleStepWorkaround>(tid, original_set);
}
SingleStepWorkaround::~SingleStepWorkaround() {
Log *log = GetLog(POSIXLog::Thread);
LLDB_LOG(log, "Removing workaround");
if (sched_setaffinity(m_tid, sizeof m_original_set, &m_original_set) != 0) {
LLDB_LOG(log, "Unable to reset cpu affinity for thread {0}: {1}", m_tid,
Status(errno, eErrorTypePOSIX));
}
}
#endif