blob: ac1623ed1972e591cc38e13ea33534b28632d363 [file] [log] [blame]
//===-- ThreadMacOSX.cpp ----------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "ThreadMacOSX.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/DataExtractor.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/StopInfo.h"
#include "lldb/Target/Target.h"
#include "ProcessMacOSX.h"
#include "ProcessMacOSXLog.h"
#include "MachThreadContext.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Breakpoint/WatchpointLocation.h"
#include "lldb/Core/StreamString.h"
#include "lldb/Target/Unwind.h"
#include "UnwindMacOSXFrameBackchain.h"
using namespace lldb;
using namespace lldb_private;
//----------------------------------------------------------------------
// Thread Registers
//----------------------------------------------------------------------
ThreadMacOSX::ThreadMacOSX (ProcessMacOSX &process, lldb::tid_t tid) :
Thread(process, tid),
m_fp_pc_pairs(),
m_basic_info(),
m_suspend_count(0),
m_stop_exception(),
m_context()
{
ProcessMacOSX::CreateArchCalback create_arch_callback = process.GetArchCreateCallback();
assert(create_arch_callback != NULL);
m_context.reset(create_arch_callback(process.GetArchSpec(), *this));
assert(m_context.get() != NULL);
m_context->InitializeInstance();
::memset (&m_basic_info, 0, sizeof (m_basic_info));
::memset (&m_ident_info, 0, sizeof (m_ident_info));
::memset (&m_proc_threadinfo, 0, sizeof (m_proc_threadinfo));
ProcessMacOSXLog::LogIf(PD_LOG_THREAD | PD_LOG_VERBOSE, "ThreadMacOSX::ThreadMacOSX ( pid = %i, tid = 0x%4.4x, )", m_process.GetID(), GetID());
}
ThreadMacOSX::~ThreadMacOSX ()
{
DestroyThread();
}
#if defined (__i386__) || defined (__x86_64__)
#define MACH_SOFTWARE_BREAKPOINT_DATA_0 EXC_I386_BPT
#define MACH_TRAP_DATA_0 EXC_I386_SGL
#elif defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__)
#define MACH_SOFTWARE_BREAKPOINT_DATA_0 EXC_PPC_BREAKPOINT
#elif defined (__arm__)
#define MACH_SOFTWARE_BREAKPOINT_DATA_0 EXC_ARM_BREAKPOINT
#endif
StopInfoSP
ThreadMacOSX::GetPrivateStopReason ()
{
if (m_actual_stop_info_sp.get() == NULL || m_actual_stop_info_sp->IsValid() == false)
m_actual_stop_info_sp = GetStopException().GetStopInfo(*this);
return m_actual_stop_info_sp;
}
const char *
ThreadMacOSX::GetInfo ()
{
return GetBasicInfoAsString();
}
bool
ThreadMacOSX::GetIdentifierInfo ()
{
#ifdef THREAD_IDENTIFIER_INFO_COUNT
if (m_ident_info.thread_id == 0)
{
mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT;
return ::thread_info (GetID(), THREAD_IDENTIFIER_INFO, (thread_info_t) &m_ident_info, &count) == KERN_SUCCESS;
}
#else
//m_error.SetErrorString("Thread_info doesn't support THREAD_IDENTIFIER_INFO.");
#endif
return false;
}
const char *
ThreadMacOSX::GetDispatchQueueName()
{
if (GetIdentifierInfo ())
{
if (m_ident_info.dispatch_qaddr == 0)
return NULL;
uint8_t memory_buffer[8];
addr_t dispatch_queue_offsets_addr = LLDB_INVALID_ADDRESS;
DataExtractor data (memory_buffer, sizeof(memory_buffer),
m_process.GetTarget().GetArchitecture().GetByteOrder(),
m_process.GetTarget().GetArchitecture().GetAddressByteSize());
static ConstString g_dispatch_queue_offsets_symbol_name ("dispatch_queue_offsets");
const Symbol *dispatch_queue_offsets_symbol = NULL;
ModuleSP module_sp(m_process.GetTarget().GetImages().FindFirstModuleForFileSpec (FileSpec("libSystem.B.dylib", false)));
if (module_sp)
dispatch_queue_offsets_symbol = module_sp->FindFirstSymbolWithNameAndType (g_dispatch_queue_offsets_symbol_name, eSymbolTypeData);
if (dispatch_queue_offsets_symbol == NULL)
{
module_sp = m_process.GetTarget().GetImages().FindFirstModuleForFileSpec (FileSpec("libdispatch.dylib", false));
if (module_sp)
dispatch_queue_offsets_symbol = module_sp->FindFirstSymbolWithNameAndType (g_dispatch_queue_offsets_symbol_name, eSymbolTypeData);
}
if (dispatch_queue_offsets_symbol)
dispatch_queue_offsets_addr = dispatch_queue_offsets_symbol->GetValue().GetLoadAddress(&m_process.GetTarget());
if (dispatch_queue_offsets_addr == LLDB_INVALID_ADDRESS)
return NULL;
// Excerpt from src/queue_private.h
struct dispatch_queue_offsets_s
{
uint16_t dqo_version;
uint16_t dqo_label;
uint16_t dqo_label_size;
} dispatch_queue_offsets;
Error error;
if (m_process.ReadMemory (dispatch_queue_offsets_addr, memory_buffer, sizeof(dispatch_queue_offsets), error) == sizeof(dispatch_queue_offsets))
{
uint32_t data_offset = 0;
if (data.GetU16(&data_offset, &dispatch_queue_offsets.dqo_version, sizeof(dispatch_queue_offsets)/sizeof(uint16_t)))
{
if (m_process.ReadMemory (m_ident_info.dispatch_qaddr, &memory_buffer, data.GetAddressByteSize(), error) == data.GetAddressByteSize())
{
data_offset = 0;
lldb::addr_t queue_addr = data.GetAddress(&data_offset);
lldb::addr_t label_addr = queue_addr + dispatch_queue_offsets.dqo_label;
const size_t chunk_size = 32;
uint32_t label_pos = 0;
m_dispatch_queue_name.resize(chunk_size, '\0');
while (1)
{
size_t bytes_read = m_process.ReadMemory (label_addr + label_pos, &m_dispatch_queue_name[label_pos], chunk_size, error);
if (bytes_read <= 0)
break;
if (m_dispatch_queue_name.find('\0', label_pos) != std::string::npos)
break;
label_pos += bytes_read;
}
m_dispatch_queue_name.erase(m_dispatch_queue_name.find('\0'));
}
}
}
}
if (m_dispatch_queue_name.empty())
return NULL;
return m_dispatch_queue_name.c_str();
}
const char *
ThreadMacOSX::GetName ()
{
if (GetIdentifierInfo ())
::proc_pidinfo (m_process.GetID(), PROC_PIDTHREADINFO, m_ident_info.thread_handle, &m_proc_threadinfo, sizeof (m_proc_threadinfo));
// No thread name, lets return the queue name instead
if (m_proc_threadinfo.pth_name[0] == '\0')
return GetDispatchQueueName();
// Return the thread name if there was one
if (m_proc_threadinfo.pth_name[0])
return m_proc_threadinfo.pth_name;
return NULL;
}
bool
ThreadMacOSX::WillResume (StateType resume_state)
{
ThreadWillResume(resume_state);
Thread::WillResume(resume_state);
return true;
}
void
ThreadMacOSX::RefreshStateAfterStop()
{
// Invalidate all registers in our register context
GetRegisterContext()->InvalidateIfNeeded (false);
m_context->RefreshStateAfterStop();
// We may have suspended this thread so the primary thread could step
// without worrying about race conditions, so lets restore our suspend
// count.
RestoreSuspendCount();
// Update the basic information for a thread for suspend count reasons.
ThreadMacOSX::GetBasicInfo(GetID(), &m_basic_info);
m_suspend_count = m_basic_info.suspend_count;
m_basic_info_string.clear();
}
Unwind *
ThreadMacOSX::GetUnwinder ()
{
if (m_unwinder_ap.get() == NULL)
{
const ArchSpec target_arch (GetProcess().GetTarget().GetArchitecture ());
#if 0 // Not sure this is the right thing to do for native, but this will all go away with Jason's new
// unwinder anyway...
if (target_arch == ArchSpec("x86_64") || target_arch == ArchSpec("i386"))
{
m_unwinder_ap.reset (new UnwindLibUnwind (*this, GetGDBProcess().GetLibUnwindAddressSpace()));
}
else
#endif
{
m_unwinder_ap.reset (new UnwindMacOSXFrameBackchain (*this));
}
}
return m_unwinder_ap.get();
}
void
ThreadMacOSX::ClearStackFrames ()
{
m_fp_pc_pairs.clear();
Thread::ClearStackFrames();
}
int32_t
ThreadMacOSX::Suspend()
{
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD));
if (log && log->GetMask().Test(PD_LOG_VERBOSE))
log->Printf ("ThreadMacOSX::%s ( )", __FUNCTION__);
lldb::tid_t tid = GetID ();
if (ThreadIDIsValid(tid))
{
Error err(::thread_suspend (tid), eErrorTypeMachKernel);
if (err.Success())
m_suspend_count++;
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD);
if (log || err.Fail())
err.PutToLog(log.get(), "::thread_suspend (%4.4x)", tid);
}
return GetSuspendCount();
}
int32_t
ThreadMacOSX::Resume()
{
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD));
if (log && log->GetMask().Test(PD_LOG_VERBOSE))
log->Printf ("ThreadMacOSX::%s ()", __FUNCTION__);
lldb::tid_t tid = GetID ();
if (ThreadIDIsValid(tid))
{
while (m_suspend_count > 0)
{
Error err(::thread_resume (tid), eErrorTypeMachKernel);
if (err.Success())
m_suspend_count--;
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD);
if (log || err.Fail())
err.PutToLog(log.get(), "::thread_resume (%4.4x)", tid);
}
}
return GetSuspendCount();
}
bool
ThreadMacOSX::RestoreSuspendCount()
{
LogSP log (ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD));
if (log && log->GetMask().Test(PD_LOG_VERBOSE))
log->Printf ("ThreadMacOSX::%s ( )", __FUNCTION__);
Error err;
lldb::tid_t tid = GetID ();
if (ThreadIDIsValid(tid) == false)
return false;
else if (m_suspend_count > m_basic_info.suspend_count)
{
while (m_suspend_count > m_basic_info.suspend_count)
{
err = ::thread_resume (tid);
if (err.Success())
--m_suspend_count;
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD);
if (log || err.Fail())
err.PutToLog(log.get(), "::thread_resume (%4.4x)", tid);
}
}
else if (m_suspend_count < m_basic_info.suspend_count)
{
while (m_suspend_count < m_basic_info.suspend_count)
{
err = ::thread_suspend (tid);
if (err.Success())
--m_suspend_count;
log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD);
if (log || err.Fail())
err.PutToLog(log.get(), "::thread_suspend (%4.4x)", tid);
}
}
return m_suspend_count == m_basic_info.suspend_count;
}
const char *
ThreadMacOSX::GetBasicInfoAsString ()
{
if (m_basic_info_string.empty())
{
StreamString sstr;
struct thread_basic_info basicInfo;
lldb::tid_t tid = GetID ();
if (GetBasicInfo(tid, &basicInfo))
{
// char run_state_str[32];
// size_t run_state_str_size = sizeof(run_state_str);
// switch (basicInfo.run_state)
// {
// case TH_STATE_RUNNING: strncpy(run_state_str, "running", run_state_str_size); break;
// case TH_STATE_STOPPED: strncpy(run_state_str, "stopped", run_state_str_size); break;
// case TH_STATE_WAITING: strncpy(run_state_str, "waiting", run_state_str_size); break;
// case TH_STATE_UNINTERRUPTIBLE: strncpy(run_state_str, "uninterruptible", run_state_str_size); break;
// case TH_STATE_HALTED: strncpy(run_state_str, "halted", run_state_str_size); break;
// default: snprintf(run_state_str, run_state_str_size, "%d", basicInfo.run_state); break; // ???
// }
float user = (float)basicInfo.user_time.seconds + (float)basicInfo.user_time.microseconds / 1000000.0f;
float system = (float)basicInfo.user_time.seconds + (float)basicInfo.user_time.microseconds / 1000000.0f;
sstr.Printf("Thread 0x%4.4x: user=%f system=%f cpu=%d sleep_time=%d",
InferiorThreadID(),
user,
system,
basicInfo.cpu_usage,
basicInfo.sleep_time);
m_basic_info_string.assign (sstr.GetData(), sstr.GetSize());
}
}
if (m_basic_info_string.empty())
return NULL;
return m_basic_info_string.c_str();
}
//const uint8_t *
//ThreadMacOSX::SoftwareBreakpointOpcode (size_t break_op_size) const
//{
// return m_context->SoftwareBreakpointOpcode(break_op_size);
//}
lldb::tid_t
ThreadMacOSX::InferiorThreadID() const
{
mach_msg_type_number_t i;
mach_port_name_array_t names;
mach_port_type_array_t types;
mach_msg_type_number_t ncount, tcount;
lldb::tid_t inferior_tid = LLDB_INVALID_THREAD_ID;
task_t my_task = ::mach_task_self();
task_t task = GetMacOSXProcess().Task().GetTaskPort();
kern_return_t kret = ::mach_port_names (task, &names, &ncount, &types, &tcount);
if (kret == KERN_SUCCESS)
{
lldb::tid_t tid = GetID ();
for (i = 0; i < ncount; i++)
{
mach_port_t my_name;
mach_msg_type_name_t my_type;
kret = ::mach_port_extract_right (task, names[i], MACH_MSG_TYPE_COPY_SEND, &my_name, &my_type);
if (kret == KERN_SUCCESS)
{
::mach_port_deallocate (my_task, my_name);
if (my_name == tid)
{
inferior_tid = names[i];
break;
}
}
}
// Free up the names and types
::vm_deallocate (my_task, (vm_address_t) names, ncount * sizeof (mach_port_name_t));
::vm_deallocate (my_task, (vm_address_t) types, tcount * sizeof (mach_port_type_t));
}
return inferior_tid;
}
bool
ThreadMacOSX::GetBasicInfo(lldb::tid_t thread, struct thread_basic_info *basicInfoPtr)
{
if (ThreadIDIsValid(thread))
{
unsigned int info_count = THREAD_BASIC_INFO_COUNT;
kern_return_t err = ::thread_info (thread, THREAD_BASIC_INFO, (thread_info_t) basicInfoPtr, &info_count);
if (err == KERN_SUCCESS)
return true;
}
::memset (basicInfoPtr, 0, sizeof (struct thread_basic_info));
return false;
}
bool
ThreadMacOSX::ThreadIDIsValid (lldb::tid_t thread)
{
return thread != 0;
}
void
ThreadMacOSX::Dump(Log *log, uint32_t index)
{
const char * thread_run_state = NULL;
switch (m_basic_info.run_state)
{
case TH_STATE_RUNNING: thread_run_state = "running"; break; // 1 thread is running normally
case TH_STATE_STOPPED: thread_run_state = "stopped"; break; // 2 thread is stopped
case TH_STATE_WAITING: thread_run_state = "waiting"; break; // 3 thread is waiting normally
case TH_STATE_UNINTERRUPTIBLE: thread_run_state = "uninter"; break; // 4 thread is in an uninterruptible wait
case TH_STATE_HALTED: thread_run_state = "halted "; break; // 5 thread is halted at a
default: thread_run_state = "???"; break;
}
RegisterContext *reg_context = GetRegisterContext().get();
log->Printf ("thread[%u] %4.4x (%u): pc: 0x%8.8llx sp: 0x%8.8llx breakID: %d user: %d.%06.6d system: %d.%06.6d cpu: %d policy: %d run_state: %d (%s) flags: %d suspend_count: %d (current %d) sleep_time: %d",
index,
GetID (),
reg_context->GetPC (LLDB_INVALID_ADDRESS),
reg_context->GetSP (LLDB_INVALID_ADDRESS),
m_basic_info.user_time.seconds, m_basic_info.user_time.microseconds,
m_basic_info.system_time.seconds, m_basic_info.system_time.microseconds,
m_basic_info.cpu_usage,
m_basic_info.policy,
m_basic_info.run_state,
thread_run_state,
m_basic_info.flags,
m_basic_info.suspend_count, m_suspend_count,
m_basic_info.sleep_time);
//DumpRegisterState(0);
}
void
ThreadMacOSX::ThreadWillResume (StateType resume_state)
{
// Update the thread state to be the state we wanted when the task resumes
SetState (resume_state);
switch (resume_state)
{
case eStateSuspended:
Suspend();
break;
case eStateRunning:
case eStateStepping:
Resume();
break;
default:
break;
}
m_context->ThreadWillResume();
}
void
ThreadMacOSX::DidResume ()
{
// TODO: cache current stack frames for next time in case we can match things up??
ClearStackFrames();
m_stop_exception.Clear();
Thread::DidResume();
}
bool
ThreadMacOSX::ShouldStop(bool &step_more)
{
// TODO: REmove this after all is working, Process should be managing this
// for us.
//
// // See if this thread is at a breakpoint?
// lldb::user_id_t breakID = CurrentBreakpoint();
//
// if (LLDB_BREAK_ID_IS_VALID(breakID))
// {
// // This thread is sitting at a breakpoint, ask the breakpoint
// // if we should be stopping here.
// if (Process()->Breakpoints().ShouldStop(ProcessID(), ThreadID(), breakID))
// return true;
// else
// {
// // The breakpoint said we shouldn't stop, but we may have gotten
// // a signal or the user may have requested to stop in some other
// // way. Stop if we have a valid exception (this thread won't if
// // another thread was the reason this process stopped) and that
// // exception, is NOT a breakpoint exception (a common case would
// // be a SIGINT signal).
// if (GetStopException().IsValid() && !GetStopException().IsBreakpoint())
// return true;
// }
// }
// else
// {
if (m_context->StepNotComplete())
{
step_more = true;
return false;
}
// // The thread state is used to let us know what the thread was
// // trying to do. ThreadMacOSX::ThreadWillResume() will set the
// // thread state to various values depending if the thread was
// // the current thread and if it was to be single stepped, or
// // resumed.
// if (GetState() == eStateRunning)
// {
// // If our state is running, then we should continue as we are in
// // the process of stepping over a breakpoint.
// return false;
// }
// else
// {
// // Stop if we have any kind of valid exception for this
// // thread.
// if (GetStopException().IsValid())
// return true;
// }
// }
// return false;
return true;
}
bool
ThreadMacOSX::NotifyException(MachException::Data& exc)
{
if (m_stop_exception.IsValid())
{
// We may have more than one exception for a thread, but we need to
// only remember the one that we will say is the reason we stopped.
// We may have been single stepping and also gotten a signal exception,
// so just remember the most pertinent one.
if (m_stop_exception.IsBreakpoint())
m_stop_exception = exc;
}
else
{
m_stop_exception = exc;
}
// bool handled =
m_context->NotifyException(exc);
// if (!handled)
// {
// handled = true;
// lldb::addr_t pc = GetPC();
// lldb::user_id_t breakID = m_process.Breakpoints().FindIDCyAddress(pc);
// SetCurrentBreakpoint(breakID);
// switch (exc.exc_type)
// {
// case EXC_BAD_ACCESS:
// break;
// case EXC_BAD_INSTRUCTION:
// break;
// case EXC_ARITHMETIC:
// break;
// case EXC_EMULATION:
// break;
// case EXC_SOFTWARE:
// break;
// case EXC_BREAKPOINT:
// break;
// case EXC_SYSCALL:
// break;
// case EXC_MACH_SYSCALL:
// break;
// case EXC_RPC_ALERT:
// break;
// }
// }
// return handled;
return true;
}
RegisterContextSP
ThreadMacOSX::GetRegisterContext ()
{
if (m_reg_context_sp.get() == NULL)
m_reg_context_sp = CreateRegisterContextForFrame (NULL);
return m_reg_context_sp;
}
RegisterContextSP
ThreadMacOSX::CreateRegisterContextForFrame (StackFrame *frame)
{
return m_context->CreateRegisterContext (frame);
}
uint32_t
ThreadMacOSX::SetHardwareBreakpoint (const BreakpointSite *bp)
{
if (bp != NULL)
return GetRegisterContext()->SetHardwareBreakpoint(bp->GetLoadAddress(), bp->GetByteSize());
return LLDB_INVALID_INDEX32;
}
uint32_t
ThreadMacOSX::SetHardwareWatchpoint (const WatchpointLocation *wp)
{
if (wp != NULL)
return GetRegisterContext()->SetHardwareWatchpoint(wp->GetLoadAddress(), wp->GetByteSize(), wp->WatchpointRead(), wp->WatchpointWrite());
return LLDB_INVALID_INDEX32;
}
bool
ThreadMacOSX::SaveFrameZeroState (RegisterCheckpoint &checkpoint)
{
lldb::StackFrameSP frame_sp(GetStackFrameAtIndex (0));
if (frame_sp)
{
checkpoint.SetStackID(frame_sp->GetStackID());
return frame_sp->GetRegisterContext()->ReadAllRegisterValues (checkpoint.GetData());
}
return false;
}
bool
ThreadMacOSX::RestoreSaveFrameZero (const RegisterCheckpoint &checkpoint)
{
lldb::StackFrameSP frame_sp(GetStackFrameAtIndex (0));
if (frame_sp)
{
bool ret = frame_sp->GetRegisterContext()->WriteAllRegisterValues (checkpoint.GetData());
// Clear out all stack frames as our world just changed.
ClearStackFrames();
frame_sp->GetRegisterContext()->InvalidateIfNeeded(true);
return ret;
}
return false;
}
bool
ThreadMacOSX::ClearHardwareBreakpoint (const BreakpointSite *bp)
{
if (bp != NULL && bp->IsHardware())
return GetRegisterContext()->ClearHardwareBreakpoint(bp->GetHardwareIndex());
return false;
}
bool
ThreadMacOSX::ClearHardwareWatchpoint (const WatchpointLocation *wp)
{
if (wp != NULL && wp->IsHardware())
return GetRegisterContext()->ClearHardwareWatchpoint(wp->GetHardwareIndex());
return false;
}
size_t
ThreadMacOSX::GetStackFrameData(std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs)
{
lldb::StackFrameSP frame_sp(GetStackFrameAtIndex (0));
return m_context->GetStackFrameData(frame_sp.get(), fp_pc_pairs);
}
//void
//ThreadMacOSX::NotifyBreakpointChanged (const BreakpointSite *bp)
//{
// if (bp)
// {
// lldb::user_id_t breakID = bp->GetID();
// if (bp->IsEnabled())
// {
// if (bp->Address() == GetPC())
// {
// SetCurrentBreakpoint(breakID);
// }
// }
// else
// {
// if (CurrentBreakpoint() == breakID)
// {
// SetCurrentBreakpoint(LLDB_INVALID_BREAK_ID);
// }
// }
// }
//}
//