You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
14 KiB
453 lines
14 KiB
4 months ago
|
//===-- NativeThreadLinux.cpp ---------------------------------------------===//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "NativeThreadLinux.h"
|
||
|
|
||
|
#include <signal.h>
|
||
|
#include <sstream>
|
||
|
|
||
|
#include "NativeProcessLinux.h"
|
||
|
#include "NativeRegisterContextLinux.h"
|
||
|
#include "SingleStepCheck.h"
|
||
|
|
||
|
#include "lldb/Host/HostNativeThread.h"
|
||
|
#include "lldb/Host/linux/Ptrace.h"
|
||
|
#include "lldb/Host/linux/Support.h"
|
||
|
#include "lldb/Utility/LLDBAssert.h"
|
||
|
#include "lldb/Utility/Log.h"
|
||
|
#include "lldb/Utility/State.h"
|
||
|
#include "lldb/lldb-enumerations.h"
|
||
|
|
||
|
#include "llvm/ADT/SmallString.h"
|
||
|
|
||
|
#include "Plugins/Process/POSIX/CrashReason.h"
|
||
|
|
||
|
#include <sys/syscall.h>
|
||
|
// Try to define a macro to encapsulate the tgkill syscall
|
||
|
#define tgkill(pid, tid, sig) \
|
||
|
syscall(__NR_tgkill, static_cast<::pid_t>(pid), static_cast<::pid_t>(tid), \
|
||
|
sig)
|
||
|
|
||
|
using namespace lldb;
|
||
|
using namespace lldb_private;
|
||
|
using namespace lldb_private::process_linux;
|
||
|
|
||
|
namespace {
|
||
|
void LogThreadStopInfo(Log &log, const ThreadStopInfo &stop_info,
|
||
|
const char *const header) {
|
||
|
switch (stop_info.reason) {
|
||
|
case eStopReasonNone:
|
||
|
log.Printf("%s: %s no stop reason", __FUNCTION__, header);
|
||
|
return;
|
||
|
case eStopReasonTrace:
|
||
|
log.Printf("%s: %s trace, stopping signal 0x%" PRIx32, __FUNCTION__, header,
|
||
|
stop_info.details.signal.signo);
|
||
|
return;
|
||
|
case eStopReasonBreakpoint:
|
||
|
log.Printf("%s: %s breakpoint, stopping signal 0x%" PRIx32, __FUNCTION__,
|
||
|
header, stop_info.details.signal.signo);
|
||
|
return;
|
||
|
case eStopReasonWatchpoint:
|
||
|
log.Printf("%s: %s watchpoint, stopping signal 0x%" PRIx32, __FUNCTION__,
|
||
|
header, stop_info.details.signal.signo);
|
||
|
return;
|
||
|
case eStopReasonSignal:
|
||
|
log.Printf("%s: %s signal 0x%02" PRIx32, __FUNCTION__, header,
|
||
|
stop_info.details.signal.signo);
|
||
|
return;
|
||
|
case eStopReasonException:
|
||
|
log.Printf("%s: %s exception type 0x%02" PRIx64, __FUNCTION__, header,
|
||
|
stop_info.details.exception.type);
|
||
|
return;
|
||
|
case eStopReasonExec:
|
||
|
log.Printf("%s: %s exec, stopping signal 0x%" PRIx32, __FUNCTION__, header,
|
||
|
stop_info.details.signal.signo);
|
||
|
return;
|
||
|
case eStopReasonPlanComplete:
|
||
|
log.Printf("%s: %s plan complete", __FUNCTION__, header);
|
||
|
return;
|
||
|
case eStopReasonThreadExiting:
|
||
|
log.Printf("%s: %s thread exiting", __FUNCTION__, header);
|
||
|
return;
|
||
|
case eStopReasonInstrumentation:
|
||
|
log.Printf("%s: %s instrumentation", __FUNCTION__, header);
|
||
|
return;
|
||
|
default:
|
||
|
log.Printf("%s: %s invalid stop reason %" PRIu32, __FUNCTION__, header,
|
||
|
static_cast<uint32_t>(stop_info.reason));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NativeThreadLinux::NativeThreadLinux(NativeProcessLinux &process,
|
||
|
lldb::tid_t tid)
|
||
|
: NativeThreadProtocol(process, tid), m_state(StateType::eStateInvalid),
|
||
|
m_stop_info(),
|
||
|
m_reg_context_up(
|
||
|
NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
|
||
|
process.GetArchitecture(), *this)),
|
||
|
m_stop_description() {}
|
||
|
|
||
|
std::string NativeThreadLinux::GetName() {
|
||
|
NativeProcessLinux &process = GetProcess();
|
||
|
|
||
|
auto BufferOrError = getProcFile(process.GetID(), GetID(), "comm");
|
||
|
if (!BufferOrError)
|
||
|
return "";
|
||
|
return std::string(BufferOrError.get()->getBuffer().rtrim('\n'));
|
||
|
}
|
||
|
|
||
|
lldb::StateType NativeThreadLinux::GetState() { return m_state; }
|
||
|
|
||
|
bool NativeThreadLinux::GetStopReason(ThreadStopInfo &stop_info,
|
||
|
std::string &description) {
|
||
|
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
|
||
|
|
||
|
description.clear();
|
||
|
|
||
|
switch (m_state) {
|
||
|
case eStateStopped:
|
||
|
case eStateCrashed:
|
||
|
case eStateExited:
|
||
|
case eStateSuspended:
|
||
|
case eStateUnloaded:
|
||
|
if (log)
|
||
|
LogThreadStopInfo(*log, m_stop_info, "m_stop_info in thread:");
|
||
|
stop_info = m_stop_info;
|
||
|
description = m_stop_description;
|
||
|
if (log)
|
||
|
LogThreadStopInfo(*log, stop_info, "returned stop_info:");
|
||
|
|
||
|
return true;
|
||
|
|
||
|
case eStateInvalid:
|
||
|
case eStateConnected:
|
||
|
case eStateAttaching:
|
||
|
case eStateLaunching:
|
||
|
case eStateRunning:
|
||
|
case eStateStepping:
|
||
|
case eStateDetached:
|
||
|
if (log) {
|
||
|
LLDB_LOGF(log,
|
||
|
"NativeThreadLinux::%s tid %" PRIu64
|
||
|
" in state %s cannot answer stop reason",
|
||
|
__FUNCTION__, GetID(), StateAsCString(m_state));
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
llvm_unreachable("unhandled StateType!");
|
||
|
}
|
||
|
|
||
|
Status NativeThreadLinux::SetWatchpoint(lldb::addr_t addr, size_t size,
|
||
|
uint32_t watch_flags, bool hardware) {
|
||
|
if (!hardware)
|
||
|
return Status("not implemented");
|
||
|
if (m_state == eStateLaunching)
|
||
|
return Status();
|
||
|
Status error = RemoveWatchpoint(addr);
|
||
|
if (error.Fail())
|
||
|
return error;
|
||
|
uint32_t wp_index =
|
||
|
m_reg_context_up->SetHardwareWatchpoint(addr, size, watch_flags);
|
||
|
if (wp_index == LLDB_INVALID_INDEX32)
|
||
|
return Status("Setting hardware watchpoint failed.");
|
||
|
m_watchpoint_index_map.insert({addr, wp_index});
|
||
|
return Status();
|
||
|
}
|
||
|
|
||
|
Status NativeThreadLinux::RemoveWatchpoint(lldb::addr_t addr) {
|
||
|
auto wp = m_watchpoint_index_map.find(addr);
|
||
|
if (wp == m_watchpoint_index_map.end())
|
||
|
return Status();
|
||
|
uint32_t wp_index = wp->second;
|
||
|
m_watchpoint_index_map.erase(wp);
|
||
|
if (m_reg_context_up->ClearHardwareWatchpoint(wp_index))
|
||
|
return Status();
|
||
|
return Status("Clearing hardware watchpoint failed.");
|
||
|
}
|
||
|
|
||
|
Status NativeThreadLinux::SetHardwareBreakpoint(lldb::addr_t addr,
|
||
|
size_t size) {
|
||
|
if (m_state == eStateLaunching)
|
||
|
return Status();
|
||
|
|
||
|
Status error = RemoveHardwareBreakpoint(addr);
|
||
|
if (error.Fail())
|
||
|
return error;
|
||
|
|
||
|
uint32_t bp_index = m_reg_context_up->SetHardwareBreakpoint(addr, size);
|
||
|
|
||
|
if (bp_index == LLDB_INVALID_INDEX32)
|
||
|
return Status("Setting hardware breakpoint failed.");
|
||
|
|
||
|
m_hw_break_index_map.insert({addr, bp_index});
|
||
|
return Status();
|
||
|
}
|
||
|
|
||
|
Status NativeThreadLinux::RemoveHardwareBreakpoint(lldb::addr_t addr) {
|
||
|
auto bp = m_hw_break_index_map.find(addr);
|
||
|
if (bp == m_hw_break_index_map.end())
|
||
|
return Status();
|
||
|
|
||
|
uint32_t bp_index = bp->second;
|
||
|
if (m_reg_context_up->ClearHardwareBreakpoint(bp_index)) {
|
||
|
m_hw_break_index_map.erase(bp);
|
||
|
return Status();
|
||
|
}
|
||
|
|
||
|
return Status("Clearing hardware breakpoint failed.");
|
||
|
}
|
||
|
|
||
|
Status NativeThreadLinux::Resume(uint32_t signo) {
|
||
|
const StateType new_state = StateType::eStateRunning;
|
||
|
MaybeLogStateChange(new_state);
|
||
|
m_state = new_state;
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonNone;
|
||
|
m_stop_description.clear();
|
||
|
|
||
|
// If watchpoints have been set, but none on this thread, then this is a new
|
||
|
// thread. So set all existing watchpoints.
|
||
|
if (m_watchpoint_index_map.empty()) {
|
||
|
NativeProcessLinux &process = GetProcess();
|
||
|
|
||
|
const auto &watchpoint_map = process.GetWatchpointMap();
|
||
|
m_reg_context_up->ClearAllHardwareWatchpoints();
|
||
|
for (const auto &pair : watchpoint_map) {
|
||
|
const auto &wp = pair.second;
|
||
|
SetWatchpoint(wp.m_addr, wp.m_size, wp.m_watch_flags, wp.m_hardware);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set all active hardware breakpoint on all threads.
|
||
|
if (m_hw_break_index_map.empty()) {
|
||
|
NativeProcessLinux &process = GetProcess();
|
||
|
|
||
|
const auto &hw_breakpoint_map = process.GetHardwareBreakpointMap();
|
||
|
m_reg_context_up->ClearAllHardwareBreakpoints();
|
||
|
for (const auto &pair : hw_breakpoint_map) {
|
||
|
const auto &bp = pair.second;
|
||
|
SetHardwareBreakpoint(bp.m_addr, bp.m_size);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
intptr_t data = 0;
|
||
|
|
||
|
if (signo != LLDB_INVALID_SIGNAL_NUMBER)
|
||
|
data = signo;
|
||
|
|
||
|
return NativeProcessLinux::PtraceWrapper(PTRACE_CONT, GetID(), nullptr,
|
||
|
reinterpret_cast<void *>(data));
|
||
|
}
|
||
|
|
||
|
Status NativeThreadLinux::SingleStep(uint32_t signo) {
|
||
|
const StateType new_state = StateType::eStateStepping;
|
||
|
MaybeLogStateChange(new_state);
|
||
|
m_state = new_state;
|
||
|
m_stop_info.reason = StopReason::eStopReasonNone;
|
||
|
|
||
|
if(!m_step_workaround) {
|
||
|
// If we already hava a workaround inplace, don't reset it. Otherwise, the
|
||
|
// destructor of the existing instance will run after the new instance has
|
||
|
// fetched the cpu mask, and the thread will end up with the wrong mask.
|
||
|
m_step_workaround = SingleStepWorkaround::Get(m_tid);
|
||
|
}
|
||
|
|
||
|
intptr_t data = 0;
|
||
|
if (signo != LLDB_INVALID_SIGNAL_NUMBER)
|
||
|
data = signo;
|
||
|
|
||
|
// If hardware single-stepping is not supported, we just do a continue. The
|
||
|
// breakpoint on the next instruction has been setup in
|
||
|
// NativeProcessLinux::Resume.
|
||
|
return NativeProcessLinux::PtraceWrapper(
|
||
|
GetProcess().SupportHardwareSingleStepping() ? PTRACE_SINGLESTEP
|
||
|
: PTRACE_CONT,
|
||
|
m_tid, nullptr, reinterpret_cast<void *>(data));
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetStoppedBySignal(uint32_t signo,
|
||
|
const siginfo_t *info) {
|
||
|
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
|
||
|
LLDB_LOGF(log, "NativeThreadLinux::%s called with signal 0x%02" PRIx32,
|
||
|
__FUNCTION__, signo);
|
||
|
|
||
|
SetStopped();
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonSignal;
|
||
|
m_stop_info.details.signal.signo = signo;
|
||
|
|
||
|
m_stop_description.clear();
|
||
|
if (info) {
|
||
|
switch (signo) {
|
||
|
case SIGSEGV:
|
||
|
case SIGBUS:
|
||
|
case SIGFPE:
|
||
|
case SIGILL:
|
||
|
// In case of MIPS64 target, SI_KERNEL is generated for invalid 64bit
|
||
|
// address.
|
||
|
const auto reason =
|
||
|
(info->si_signo == SIGBUS && info->si_code == SI_KERNEL)
|
||
|
? CrashReason::eInvalidAddress
|
||
|
: GetCrashReason(*info);
|
||
|
m_stop_description = GetCrashReasonString(reason, *info);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool NativeThreadLinux::IsStopped(int *signo) {
|
||
|
if (!StateIsStoppedState(m_state, false))
|
||
|
return false;
|
||
|
|
||
|
// If we are stopped by a signal, return the signo.
|
||
|
if (signo && m_state == StateType::eStateStopped &&
|
||
|
m_stop_info.reason == StopReason::eStopReasonSignal) {
|
||
|
*signo = m_stop_info.details.signal.signo;
|
||
|
}
|
||
|
|
||
|
// Regardless, we are stopped.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetStopped() {
|
||
|
if (m_state == StateType::eStateStepping)
|
||
|
m_step_workaround.reset();
|
||
|
|
||
|
// On every stop, clear any cached register data structures
|
||
|
GetRegisterContext().InvalidateAllRegisters();
|
||
|
|
||
|
const StateType new_state = StateType::eStateStopped;
|
||
|
MaybeLogStateChange(new_state);
|
||
|
m_state = new_state;
|
||
|
m_stop_description.clear();
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetStoppedByExec() {
|
||
|
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
|
||
|
LLDB_LOGF(log, "NativeThreadLinux::%s()", __FUNCTION__);
|
||
|
|
||
|
SetStopped();
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonExec;
|
||
|
m_stop_info.details.signal.signo = SIGSTOP;
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetStoppedByBreakpoint() {
|
||
|
SetStopped();
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonBreakpoint;
|
||
|
m_stop_info.details.signal.signo = SIGTRAP;
|
||
|
m_stop_description.clear();
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetStoppedByWatchpoint(uint32_t wp_index) {
|
||
|
SetStopped();
|
||
|
|
||
|
lldbassert(wp_index != LLDB_INVALID_INDEX32 && "wp_index cannot be invalid");
|
||
|
|
||
|
std::ostringstream ostr;
|
||
|
ostr << m_reg_context_up->GetWatchpointAddress(wp_index) << " ";
|
||
|
ostr << wp_index;
|
||
|
|
||
|
/*
|
||
|
* MIPS: Last 3bits of the watchpoint address are masked by the kernel. For
|
||
|
* example:
|
||
|
* 'n' is at 0x120010d00 and 'm' is 0x120010d04. When a watchpoint is set at
|
||
|
* 'm', then
|
||
|
* watch exception is generated even when 'n' is read/written. To handle this
|
||
|
* case,
|
||
|
* find the base address of the load/store instruction and append it in the
|
||
|
* stop-info
|
||
|
* packet.
|
||
|
*/
|
||
|
ostr << " " << m_reg_context_up->GetWatchpointHitAddress(wp_index);
|
||
|
|
||
|
m_stop_description = ostr.str();
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonWatchpoint;
|
||
|
m_stop_info.details.signal.signo = SIGTRAP;
|
||
|
}
|
||
|
|
||
|
bool NativeThreadLinux::IsStoppedAtBreakpoint() {
|
||
|
return GetState() == StateType::eStateStopped &&
|
||
|
m_stop_info.reason == StopReason::eStopReasonBreakpoint;
|
||
|
}
|
||
|
|
||
|
bool NativeThreadLinux::IsStoppedAtWatchpoint() {
|
||
|
return GetState() == StateType::eStateStopped &&
|
||
|
m_stop_info.reason == StopReason::eStopReasonWatchpoint;
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetStoppedByTrace() {
|
||
|
SetStopped();
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonTrace;
|
||
|
m_stop_info.details.signal.signo = SIGTRAP;
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetStoppedWithNoReason() {
|
||
|
SetStopped();
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonNone;
|
||
|
m_stop_info.details.signal.signo = 0;
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::SetExited() {
|
||
|
const StateType new_state = StateType::eStateExited;
|
||
|
MaybeLogStateChange(new_state);
|
||
|
m_state = new_state;
|
||
|
|
||
|
m_stop_info.reason = StopReason::eStopReasonThreadExiting;
|
||
|
}
|
||
|
|
||
|
Status NativeThreadLinux::RequestStop() {
|
||
|
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
|
||
|
|
||
|
NativeProcessLinux &process = GetProcess();
|
||
|
|
||
|
lldb::pid_t pid = process.GetID();
|
||
|
lldb::tid_t tid = GetID();
|
||
|
|
||
|
LLDB_LOGF(log,
|
||
|
"NativeThreadLinux::%s requesting thread stop(pid: %" PRIu64
|
||
|
", tid: %" PRIu64 ")",
|
||
|
__FUNCTION__, pid, tid);
|
||
|
|
||
|
Status err;
|
||
|
errno = 0;
|
||
|
if (::tgkill(pid, tid, SIGSTOP) != 0) {
|
||
|
err.SetErrorToErrno();
|
||
|
LLDB_LOGF(log,
|
||
|
"NativeThreadLinux::%s tgkill(%" PRIu64 ", %" PRIu64
|
||
|
", SIGSTOP) failed: %s",
|
||
|
__FUNCTION__, pid, tid, err.AsCString());
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
void NativeThreadLinux::MaybeLogStateChange(lldb::StateType new_state) {
|
||
|
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD));
|
||
|
// If we're not logging, we're done.
|
||
|
if (!log)
|
||
|
return;
|
||
|
|
||
|
// If this is a state change to the same state, we're done.
|
||
|
lldb::StateType old_state = m_state;
|
||
|
if (new_state == old_state)
|
||
|
return;
|
||
|
|
||
|
LLDB_LOG(log, "pid={0}, tid={1}: changing from state {2} to {3}",
|
||
|
m_process.GetID(), GetID(), old_state, new_state);
|
||
|
}
|
||
|
|
||
|
NativeProcessLinux &NativeThreadLinux::GetProcess() {
|
||
|
return static_cast<NativeProcessLinux &>(m_process);
|
||
|
}
|