NPL: Compartmentalize arm64 single step workaround better

The main motivation for me doing this is being able to build an arm
android lldb-server against api level 9 headers, but it seems like a
good cleanup nonetheless.

The entirety of the cpu_set_t dance now resides in SingleStepCheck.cpp,
which is only built on arm64.

llvm-svn: 293046
This commit is contained in:
Pavel Labath 2017-01-25 11:19:45 +00:00
parent 2d0c5b0297
commit 8abd34f015
4 changed files with 105 additions and 132 deletions

View File

@ -220,63 +220,12 @@ Error NativeThreadLinux::Resume(uint32_t signo) {
reinterpret_cast<void *>(data));
}
void NativeThreadLinux::MaybePrepareSingleStepWorkaround() {
if (!SingleStepWorkaroundNeeded())
return;
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
if (sched_getaffinity(static_cast<::pid_t>(m_tid), sizeof m_original_cpu_set,
&m_original_cpu_set) != 0) {
// This should really not fail. But, just in case...
if (log) {
Error error(errno, eErrorTypePOSIX);
log->Printf(
"NativeThreadLinux::%s Unable to get cpu affinity for thread %" PRIx64
": %s",
__FUNCTION__, m_tid, error.AsCString());
}
return;
}
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set);
if (sched_setaffinity(static_cast<::pid_t>(m_tid), sizeof set, &set) != 0 &&
log) {
// This may fail in very locked down systems, if the thread is not allowed
// to run on
// cpu 0. If that happens, only thing we can do is it log it and continue...
Error error(errno, eErrorTypePOSIX);
log->Printf(
"NativeThreadLinux::%s Unable to set cpu affinity for thread %" PRIx64
": %s",
__FUNCTION__, m_tid, error.AsCString());
}
}
void NativeThreadLinux::MaybeCleanupSingleStepWorkaround() {
if (!SingleStepWorkaroundNeeded())
return;
if (sched_setaffinity(static_cast<::pid_t>(m_tid), sizeof m_original_cpu_set,
&m_original_cpu_set) != 0) {
Error error(errno, eErrorTypePOSIX);
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
log->Printf(
"NativeThreadLinux::%s Unable to reset cpu affinity for thread %" PRIx64
": %s",
__FUNCTION__, m_tid, error.AsCString());
}
}
Error NativeThreadLinux::SingleStep(uint32_t signo) {
const StateType new_state = StateType::eStateStepping;
MaybeLogStateChange(new_state);
m_state = new_state;
m_stop_info.reason = StopReason::eStopReasonNone;
MaybePrepareSingleStepWorkaround();
m_step_workaround = SingleStepWorkaround::Get(m_tid);
intptr_t data = 0;
if (signo != LLDB_INVALID_SIGNAL_NUMBER)
@ -338,7 +287,7 @@ bool NativeThreadLinux::IsStopped(int *signo) {
void NativeThreadLinux::SetStopped() {
if (m_state == StateType::eStateStepping)
MaybeCleanupSingleStepWorkaround();
m_step_workaround.reset();
const StateType new_state = StateType::eStateStopped;
MaybeLogStateChange(new_state);

View File

@ -10,11 +10,10 @@
#ifndef liblldb_NativeThreadLinux_H_
#define liblldb_NativeThreadLinux_H_
#include "SingleStepCheck.h"
#include "lldb/Host/common/NativeThreadProtocol.h"
#include "lldb/lldb-private-forward.h"
#include <sched.h>
#include <map>
#include <memory>
#include <string>
@ -94,10 +93,6 @@ private:
void SetStopped();
inline void MaybePrepareSingleStepWorkaround();
inline void MaybeCleanupSingleStepWorkaround();
// ---------------------------------------------------------------------
// Member Variables
// ---------------------------------------------------------------------
@ -107,7 +102,7 @@ private:
std::string m_stop_description;
using WatchpointIndexMap = std::map<lldb::addr_t, uint32_t>;
WatchpointIndexMap m_watchpoint_index_map;
cpu_set_t m_original_cpu_set; // For single-step workaround.
llvm::Optional<SingleStepWorkaround> m_step_workaround;
};
typedef std::shared_ptr<NativeThreadLinux> NativeThreadLinuxSP;

View File

@ -18,10 +18,12 @@
#include "llvm/Support/Compiler.h"
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
#include "lldb/Core/Error.h"
#include "lldb/Core/Log.h"
#include "lldb/Host/linux/Ptrace.h"
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_linux;
#if defined(__arm64__) || defined(__aarch64__)
@ -32,16 +34,14 @@ void LLVM_ATTRIBUTE_NORETURN Child() {
_exit(1);
// We just do an endless loop SIGSTOPPING ourselves until killed. The tracer
// will fiddle with our cpu
// affinities and monitor the behaviour.
// 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.
// 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)
;
}
@ -57,25 +57,16 @@ struct ChildDeleter {
}
};
} // end anonymous namespace
bool impl::SingleStepWorkaroundNeeded() {
bool WorkaroundNeeded() {
// 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.
// 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;
Log *log = ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD);
::pid_t child_pid = fork();
if (child_pid == -1) {
if (log) {
error.SetErrorToErrno();
log->Printf("%s failed to fork(): %s", __FUNCTION__, error.AsCString());
}
LLDB_LOG(log, "failed to fork(): {0}", Error(errno, eErrorTypePOSIX));
return false;
}
if (child_pid == 0)
@ -85,22 +76,16 @@ bool impl::SingleStepWorkaroundNeeded() {
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());
}
LLDB_LOG(log, "failed to get available cpus: {0}",
Error(errno, eErrorTypePOSIX));
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());
}
LLDB_LOG(log, "waitpid() failed (status = {0:x}): {1}", status,
Error(errno, eErrorTypePOSIX));
return false;
}
@ -113,46 +98,37 @@ bool impl::SingleStepWorkaroundNeeded() {
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());
}
LLDB_LOG(log, "failed to switch to cpu {0}: {1}", cpu,
Error(errno, eErrorTypePOSIX));
continue;
}
int status;
error = NativeProcessLinux::PtraceWrapper(PTRACE_SINGLESTEP, child_pid);
Error error =
NativeProcessLinux::PtraceWrapper(PTRACE_SINGLESTEP, child_pid);
if (error.Fail()) {
if (log)
log->Printf("%s single step failed: %s", __FUNCTION__,
error.AsCString());
LLDB_LOG(log, "single step failed: {0}", error);
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());
}
LLDB_LOG(log, "waitpid() failed (status = {0:x}): {1}", status,
Error(errno, eErrorTypePOSIX));
break;
}
if (WSTOPSIG(status) != SIGTRAP) {
if (log)
log->Printf("%s single stepping on cpu %d failed with status %x",
__FUNCTION__, cpu, status);
LLDB_LOG(log, "single stepping on cpu {0} failed with status {1:x}", 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__);
LLDB_LOG(log,
"SINGLE STEPPING ON FIRST CPU IS NOT WORKING. DEBUGGING "
"LIKELY TO BE UNRELIABLE.");
// No point in trying to fiddle with the affinities, just give it our best
// shot and see how it goes.
return false;
@ -161,6 +137,45 @@ bool impl::SingleStepWorkaroundNeeded() {
return cpu != CPU_SETSIZE;
}
#else // !arm64
bool impl::SingleStepWorkaroundNeeded() { return false; }
} // end anonymous namespace
llvm::Optional<SingleStepWorkaround> SingleStepWorkaround::Get(::pid_t tid) {
Log *log = ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_THREAD);
static bool workaround_needed = WorkaroundNeeded();
if (!workaround_needed) {
LLDB_LOG(log, "workaround for thread {0} not needed", tid);
return llvm::None;
}
cpu_set_t original_set;
if (sched_getaffinity(tid, sizeof original_set, &original_set) != 0) {
// This should really not fail. But, just in case...
LLDB_LOG(log, "Unable to get cpu affinity for thread {0}: {1}", tid,
Error(errno, eErrorTypePOSIX));
return llvm::None;
}
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set);
if (sched_setaffinity(tid, sizeof set, &set) != 0) {
// This may fail in very locked down systems, if the thread is not allowed
// to run on cpu 0. If that happens, only thing we can do is it log it and
// continue...
LLDB_LOG(log, "Unable to set cpu affinity for thread {0}: {1}", tid,
Error(errno, eErrorTypePOSIX));
}
LLDB_LOG(log, "workaround for thread {0} prepared", tid);
return SingleStepWorkaround(tid, original_set);
}
SingleStepWorkaround::~SingleStepWorkaround() {
if (sched_setaffinity(m_tid, sizeof m_original_set, &m_original_set) != 0) {
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
LLDB_LOG(log, "Unable to reset cpu affinity for thread {0}: {1}", m_tid,
Error(errno, eErrorTypePOSIX));
}
}
#endif

View File

@ -10,30 +10,44 @@
#ifndef liblldb_SingleStepCheck_H_
#define liblldb_SingleStepCheck_H_
#include "sched.h"
#include "llvm/ADT/Optional.h"
#include <sys/types.h>
namespace lldb_private {
namespace process_linux {
namespace impl {
extern bool SingleStepWorkaroundNeeded();
}
// arm64 linux had a bug which prevented single-stepping and watchpoints from
// working on non-boot
// cpus, due to them being incorrectly initialized after coming out of suspend.
// This issue is
// particularly affecting android M, which uses suspend ("doze mode") quite
// aggressively. This
// code detects that situation and makes single-stepping work by doing all the
// step operations on
// working on non-boot cpus, due to them being incorrectly initialized after
// coming out of suspend. This issue is particularly affecting android M, which
// uses suspend ("doze mode") quite aggressively. This code detects that
// situation and makes single-stepping work by doing all the step operations on
// the boot cpu.
//
// The underlying issue has been fixed in android N and linux 4.4. This code can
// be removed once
// these systems become obsolete.
inline bool SingleStepWorkaroundNeeded() {
static bool value = impl::SingleStepWorkaroundNeeded();
return value;
}
// be removed once these systems become obsolete.
#if defined(__arm64__) || defined(__aarch64__)
class SingleStepWorkaround {
::pid_t m_tid;
cpu_set_t m_original_set;
public:
SingleStepWorkaround(::pid_t tid, cpu_set_t original_set)
: m_tid(tid), m_original_set(original_set) {}
~SingleStepWorkaround();
static llvm::Optional<SingleStepWorkaround> Get(::pid_t tid);
};
#else
class SingleStepWorkaround {
public:
static llvm::Optional<SingleStepWorkaround> Get(::pid_t tid) {
return llvm::None;
}
};
#endif
} // end namespace process_linux
} // end namespace lldb_private