diff options
35 files changed, 1869 insertions, 74 deletions
diff --git a/lldb/source/Host/common/Host.cpp b/lldb/source/Host/common/Host.cpp index bc2b0414dbb..7bfcb635c4c 100644 --- a/lldb/source/Host/common/Host.cpp +++ b/lldb/source/Host/common/Host.cpp @@ -173,7 +173,11 @@ MonitorChildProcessThreadFunction (void *arg) if (errno == EINTR) continue; else + { + if (log) + log->Printf ("%s (arg = %p) thread exiting because waitpid failed (%s)...", __FUNCTION__, arg, strerror(errno)); break; + } } else if (wait_pid > 0) { @@ -190,8 +194,7 @@ MonitorChildProcessThreadFunction (void *arg) { exit_status = WEXITSTATUS(status); status_cstr = "EXITED"; - if (wait_pid == pid) - exited = true; + exited = true; } else if (WIFSIGNALED(status)) { @@ -230,12 +233,20 @@ MonitorChildProcessThreadFunction (void *arg) callback_return = callback (callback_baton, wait_pid, exited, signal, exit_status); // If our process exited, then this thread should exit - if (exited) + if (exited && wait_pid == pid) + { + if (log) + log->Printf ("%s (arg = %p) thread exiting because pid received exit signal...", __FUNCTION__, arg); break; + } // If the callback returns true, it means this process should // exit if (callback_return) + { + if (log) + log->Printf ("%s (arg = %p) thread exiting because callback returned true...", __FUNCTION__, arg); break; + } } } } diff --git a/lldb/source/Plugins/Process/Linux/ProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/ProcessLinux.cpp index 5d93502823e..8c0a2a240f2 100644 --- a/lldb/source/Plugins/Process/Linux/ProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/ProcessLinux.cpp @@ -64,7 +64,7 @@ ProcessLinux::Initialize() // Constructors and destructors. ProcessLinux::ProcessLinux(Target& target, Listener &listener) - : ProcessPOSIX(target, listener) + : ProcessPOSIX(target, listener), m_stopping_threads(false) { #if 0 // FIXME: Putting this code in the ctor and saving the byte order in a @@ -134,3 +134,39 @@ ProcessLinux::EnablePluginLogging(Stream *strm, Args &command) { return NULL; } + +// ProcessPOSIX override +void +ProcessLinux::StopAllThreads(lldb::tid_t stop_tid) +{ + // If a breakpoint occurs while we're stopping threads, we'll get back + // here, but we don't want to do it again. Only the MonitorChildProcess + // thread calls this function, so we don't need to protect this flag. + if (m_stopping_threads) + return; + m_stopping_threads = true; + + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); + if (log) + log->Printf ("ProcessLinux::%s() stopping all threads", __FUNCTION__); + + // Walk the thread list and stop the other threads. The thread that caused + // the stop should already be marked as stopped before we get here. + Mutex::Locker thread_list_lock(m_thread_list.GetMutex()); + + uint32_t thread_count = m_thread_list.GetSize(false); + for (uint32_t i = 0; i < thread_count; ++i) + { + POSIXThread *thread = static_cast<POSIXThread*>( + m_thread_list.GetThreadAtIndex(i, false).get()); + assert(thread); + lldb::tid_t tid = thread->GetID(); + if (!StateIsStoppedState(thread->GetState(), false)) + m_monitor->StopThread(tid); + } + + m_stopping_threads = false; + + if (log) + log->Printf ("ProcessLinux::%s() finished", __FUNCTION__); +} diff --git a/lldb/source/Plugins/Process/Linux/ProcessLinux.h b/lldb/source/Plugins/Process/Linux/ProcessLinux.h index 7e8b593d603..d7f338fe984 100644 --- a/lldb/source/Plugins/Process/Linux/ProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/ProcessLinux.h @@ -84,11 +84,19 @@ public: return m_linux_signals; } + //------------------------------------------------------------------ + // ProcessPOSIX overrides + //------------------------------------------------------------------ + virtual void + StopAllThreads(lldb::tid_t stop_tid); + private: /// Linux-specific signal set. LinuxSignals m_linux_signals; + // Flag to avoid recursion when stopping all threads. + bool m_stopping_threads; }; #endif // liblldb_MacOSXProcess_H_ diff --git a/lldb/source/Plugins/Process/Linux/ProcessMonitor.cpp b/lldb/source/Plugins/Process/Linux/ProcessMonitor.cpp index f2577e26227..0ba8840ad16 100644 --- a/lldb/source/Plugins/Process/Linux/ProcessMonitor.cpp +++ b/lldb/source/Plugins/Process/Linux/ProcessMonitor.cpp @@ -17,6 +17,7 @@ #include <unistd.h> #include <sys/ptrace.h> #include <sys/socket.h> +#include <sys/syscall.h> #include <sys/types.h> #include <sys/wait.h> @@ -52,6 +53,10 @@ #define TRAP_HWBKPT 4 #endif +// Try to define a macro to encapsulate the tgkill syscall +// fall back on kill() if tgkill isn't available +#define tgkill(pid, tid, sig) syscall(SYS_tgkill, pid, tid, sig) + using namespace lldb_private; // FIXME: this code is host-dependent with respect to types and @@ -724,7 +729,13 @@ ResumeOperation::Execute(ProcessMonitor *monitor) data = m_signo; if (PTRACE(PTRACE_CONT, m_tid, NULL, (void*)data, 0)) + { + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); + + if (log) + log->Printf ("ResumeOperation (%" PRIu64 ") failed: %s", m_tid, strerror(errno)); m_result = false; + } else m_result = true; } @@ -1348,8 +1359,21 @@ ProcessMonitor::MonitorCallback(void *callback_baton, siginfo_t info; int ptrace_err; + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); + + if (exited) + { + if (log) + log->Printf ("ProcessMonitor::%s() got exit signal, tid = %" PRIu64, __FUNCTION__, pid); + message = ProcessMessage::Exit(pid, status); + process->SendMessage(message); + return pid == process->GetID(); + } + if (!monitor->GetSignalInfo(pid, &info, ptrace_err)) { if (ptrace_err == EINVAL) { + if (log) + log->Printf ("ProcessMonitor::%s() resuming from group-stop", __FUNCTION__); // inferior process is in 'group-stop', so deliver SIGSTOP signal if (!monitor->Resume(pid, SIGSTOP)) { assert(0 && "SIGSTOP delivery failed while in 'group-stop' state"); @@ -1358,8 +1382,11 @@ ProcessMonitor::MonitorCallback(void *callback_baton, } else { // ptrace(GETSIGINFO) failed (but not due to group-stop). Most likely, // this means the child pid is gone (or not being debugged) therefore - // stop the monitor thread. - stop_monitoring = true; + // stop the monitor thread if this is the main pid. + if (log) + log->Printf ("ProcessMonitor::%s() GetSignalInfo failed: %s, tid = %" PRIu64 ", signal = %d, status = %d", + __FUNCTION__, strerror(ptrace_err), pid, signal, status); + stop_monitoring = pid == monitor->m_process->GetID(); } } else { @@ -1375,7 +1402,7 @@ ProcessMonitor::MonitorCallback(void *callback_baton, } process->SendMessage(message); - stop_monitoring = !process->IsAlive(); + stop_monitoring = false; } return stop_monitoring; @@ -1387,6 +1414,8 @@ ProcessMonitor::MonitorSIGTRAP(ProcessMonitor *monitor, { ProcessMessage message; + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); + assert(monitor); assert(info && info->si_signo == SIGTRAP && "Unexpected child signal!"); @@ -1403,6 +1432,9 @@ ProcessMonitor::MonitorSIGTRAP(ProcessMonitor *monitor, case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): { + if (log) + log->Printf ("ProcessMonitor::%s() received thread creation event, code = %d", __FUNCTION__, info->si_code ^ SIGTRAP); + unsigned long tid = 0; if (!monitor->GetEventMessage(pid, &tid)) tid = -1; @@ -1417,27 +1449,35 @@ ProcessMonitor::MonitorSIGTRAP(ProcessMonitor *monitor, case (SIGTRAP | (PTRACE_EVENT_EXIT << 8)): { - // The inferior process is about to exit. Maintain the process in a - // state of "limbo" until we are explicitly commanded to detach, - // destroy, resume, etc. + // The inferior process or one of its threads is about to exit. + // Maintain the process or thread in a state of "limbo" until we are + // explicitly commanded to detach, destroy, resume, etc. unsigned long data = 0; if (!monitor->GetEventMessage(pid, &data)) data = -1; + if (log) + log->Printf ("ProcessMonitor::%s() received exit event, data = %lx, pid = %" PRIu64, __FUNCTION__, data, pid); message = ProcessMessage::Limbo(pid, (data >> 8)); break; } case 0: case TRAP_TRACE: + if (log) + log->Printf ("ProcessMonitor::%s() received trace event, pid = %" PRIu64, __FUNCTION__, pid); message = ProcessMessage::Trace(pid); break; case SI_KERNEL: case TRAP_BRKPT: + if (log) + log->Printf ("ProcessMonitor::%s() received breakpoint event, pid = %" PRIu64, __FUNCTION__, pid); message = ProcessMessage::Break(pid); break; case TRAP_HWBKPT: + if (log) + log->Printf ("ProcessMonitor::%s() received watchpoint event, pid = %" PRIu64, __FUNCTION__, pid); message = ProcessMessage::Watch(pid, (lldb::addr_t)info->si_addr); break; } @@ -1452,6 +1492,8 @@ ProcessMonitor::MonitorSignal(ProcessMonitor *monitor, ProcessMessage message; int signo = info->si_signo; + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); + // POSIX says that process behaviour is undefined after it ignores a SIGFPE, // SIGILL, SIGSEGV, or SIGBUS *unless* that signal was generated by a // kill(2) or raise(3). Similarly for tgkill(2) on Linux. @@ -1462,12 +1504,22 @@ ProcessMonitor::MonitorSignal(ProcessMonitor *monitor, // Similarly, ACK signals generated by this monitor. if (info->si_code == SI_TKILL || info->si_code == SI_USER) { + if (log) + log->Printf ("ProcessMonitor::%s() received signal %s with code %s, pid = %" PRIu64, + __FUNCTION__, + monitor->m_process->GetUnixSignals().GetSignalAsCString (signo), + (info->si_code == SI_TKILL ? "SI_TKILL" : "SI_USER"), + info->si_pid); + if (info->si_pid == getpid()) return ProcessMessage::SignalDelivered(pid, signo); else return ProcessMessage::Signal(pid, signo); } + if (log) + log->Printf ("ProcessMonitor::%s() received signal %s", __FUNCTION__, monitor->m_process->GetUnixSignals().GetSignalAsCString (signo)); + if (signo == SIGSEGV) { lldb::addr_t fault_addr = reinterpret_cast<lldb::addr_t>(info->si_addr); ProcessMessage::CrashReason reason = GetCrashReasonForSIGSEGV(info); @@ -1497,6 +1549,144 @@ ProcessMonitor::MonitorSignal(ProcessMonitor *monitor, return ProcessMessage::Signal(pid, signo); } +bool +ProcessMonitor::StopThread(lldb::tid_t tid) +{ + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); + + // FIXME: Try to use tgkill or tkill + int ret = tgkill(m_pid, tid, SIGSTOP); + if (log) + log->Printf ("ProcessMonitor::%s(bp) stopping thread, tid = %" PRIu64 ", ret = %d", __FUNCTION__, tid, ret); + + // This can happen if a thread exited while we were trying to stop it. That's OK. + // We'll get the signal for that later. + if (ret < 0) + return false; + + // Wait for the thread to stop + while (true) + { + int status = -1; + if (log) + log->Printf ("ProcessMonitor::%s(bp) waitpid...", __FUNCTION__); + lldb::pid_t wait_pid = ::waitpid (-1*m_pid, &status, __WALL); + if (log) + log->Printf ("ProcessMonitor::%s(bp) waitpid, pid = %" PRIu64 ", status = %d", __FUNCTION__, wait_pid, status); + + if (wait_pid == -1) + { + // If we got interrupted by a signal (in our process, not the + // inferior) try again. + if (errno == EINTR) + continue; + else + return false; // This is bad, but there's nothing we can do. + } + + // If this is a thread exit, we won't get any more information. + if (WIFEXITED(status)) + { + m_process->SendMessage(ProcessMessage::Exit(wait_pid, WEXITSTATUS(status))); + if (wait_pid == tid) + return true; + continue; + } + + siginfo_t info; + int ptrace_err; + if (!GetSignalInfo(wait_pid, &info, ptrace_err)) + { + if (log) + { + log->Printf ("ProcessMonitor::%s() GetSignalInfo failed.", __FUNCTION__); + + // This would be a particularly interesting case + if (ptrace_err == EINVAL) + log->Printf ("ProcessMonitor::%s() in group-stop", __FUNCTION__); + } + return false; + } + + // Handle events from other threads + if (log) + log->Printf ("ProcessMonitor::%s(bp) handling event, tid == %d", __FUNCTION__, wait_pid); + + ProcessMessage message; + if (info.si_signo == SIGTRAP) + message = MonitorSIGTRAP(this, &info, wait_pid); + else + message = MonitorSignal(this, &info, wait_pid); + + POSIXThread *thread = static_cast<POSIXThread*>(m_process->GetThreadList().FindThreadByID(wait_pid).get()); + + // When a new thread is created, we may get a SIGSTOP for the new thread + // just before we get the SIGTRAP that we use to add the thread to our + // process thread list. We don't need to worry about that signal here. + assert(thread || message.GetKind() == ProcessMessage::eSignalMessage); + + if (!thread) + { + m_process->SendMessage(message); + continue; + } + + switch (message.GetKind()) + { + case ProcessMessage::eInvalidMessage: + break; + + // These need special handling because we don't want to send a + // resume even if we already sent a SIGSTOP to this thread. In + // this case the resume will cause the thread to disappear. It is + // unlikely that we'll ever get eExitMessage here, but the same + // reasoning applies. + case ProcessMessage::eLimboMessage: + case ProcessMessage::eExitMessage: + if (log) + log->Printf ("ProcessMonitor::%s(bp) handling message", __FUNCTION__); + // SendMessage will set the thread state as needed. + m_process->SendMessage(message); + // If this is the thread we're waiting for, stop waiting. Even + // though this wasn't the signal we expected, it's the last + // signal we'll see while this thread is alive. + if (wait_pid == tid) + return true; + break; + + case ProcessMessage::eSignalDeliveredMessage: + // This is the stop we're expecting. + if (wait_pid == tid && WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP && info.si_code == SI_TKILL) + { + if (log) + log->Printf ("ProcessMonitor::%s(bp) received signal, done waiting", __FUNCTION__); + thread->SetState(lldb::eStateStopped); + return true; + } + // else fall-through + case ProcessMessage::eSignalMessage: + case ProcessMessage::eBreakpointMessage: + case ProcessMessage::eTraceMessage: + case ProcessMessage::eWatchpointMessage: + case ProcessMessage::eCrashMessage: + case ProcessMessage::eNewThreadMessage: + if (log) + log->Printf ("ProcessMonitor::%s(bp) handling message", __FUNCTION__); + // SendMessage will set the thread state as needed. + m_process->SendMessage(message); + // This isn't the stop we were expecting, but the thread is + // stopped. SendMessage will handle processing of this event, + // but we need to resume here to get the stop we are waiting + // for (otherwise the thread will stop again immediately when + // we try to resume). + if (wait_pid == tid) + Resume(wait_pid, eResumeSignalNone); + break; + } + } + return false; +} + ProcessMessage::CrashReason ProcessMonitor::GetCrashReasonForSIGSEGV(const siginfo_t *info) { @@ -1808,8 +1998,15 @@ bool ProcessMonitor::Resume(lldb::tid_t tid, uint32_t signo) { bool result; + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); + + if (log) + log->Printf ("ProcessMonitor::%s() resuming thread = %" PRIu64 " with signal %s", __FUNCTION__, tid, + m_process->GetUnixSignals().GetSignalAsCString (signo)); ResumeOperation op(tid, signo, result); DoOperation(&op); + if (log) + log->Printf ("ProcessMonitor::%s() resuming result = %s", __FUNCTION__, result ? "true" : "false"); return result; } diff --git a/lldb/source/Plugins/Process/Linux/ProcessMonitor.h b/lldb/source/Plugins/Process/Linux/ProcessMonitor.h index 5563991d26c..529503e9df0 100644 --- a/lldb/source/Plugins/Process/Linux/ProcessMonitor.h +++ b/lldb/source/Plugins/Process/Linux/ProcessMonitor.h @@ -63,6 +63,11 @@ public: ~ProcessMonitor(); + enum ResumeSignals + { + eResumeSignalNone = 0 + }; + /// Provides the process number of debugee. lldb::pid_t GetPID() const { return m_pid; } @@ -172,6 +177,9 @@ public: lldb_private::Error Detach(); + /// Stops the requested thread and waits for the stop signal. + bool + StopThread(lldb::tid_t tid); private: ProcessLinux *m_process; diff --git a/lldb/source/Plugins/Process/POSIX/POSIXThread.cpp b/lldb/source/Plugins/Process/POSIX/POSIXThread.cpp index e08414f8e57..bdc874eec41 100644 --- a/lldb/source/Plugins/Process/POSIX/POSIXThread.cpp +++ b/lldb/source/Plugins/Process/POSIX/POSIXThread.cpp @@ -17,6 +17,7 @@ // Project includes #include "lldb/Breakpoint/Watchpoint.h" #include "lldb/Core/Debugger.h" +#include "lldb/Core/State.h" #include "lldb/Host/Host.h" #include "lldb/Target/Process.h" #include "lldb/Target/StopInfo.h" @@ -63,15 +64,24 @@ POSIXThread::GetMonitor() void POSIXThread::RefreshStateAfterStop() { + // Invalidate all registers in our register context. We don't set "force" to + // true because the stop reply packet might have had some register values + // that were expedited and these will already be copied into the register + // context by the time this function gets called. The KDPRegisterContext + // class has been made smart enough to detect when it needs to invalidate + // which registers are valid by putting hooks in the register read and + // register supply functions where they check the process stop ID and do + // the right thing. + //if (StateIsStoppedState(GetState()) + { + const bool force = false; + GetRegisterContext()->InvalidateIfNeeded (force); + } + // FIXME: This should probably happen somewhere else. + SetResumeState(eStateRunning); Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_THREAD)); - if (log && log->GetMask().Test(POSIX_LOG_VERBOSE)) - log->Printf ("POSIXThread::%s ()", __FUNCTION__); - - // Let all threads recover from stopping and do any clean up based - // on the previous thread state (if any). - ProcessSP base = GetProcess(); - ProcessPOSIX &process = static_cast<ProcessPOSIX&>(*base); - process.GetThreadList().RefreshStateAfterStop(); + if (log) + log->Printf ("POSIXThread::%s (tid = %" PRIi64 ") setting thread resume state to running", __FUNCTION__, GetID()); } const char * @@ -157,11 +167,20 @@ POSIXThread::GetUnwinder() void POSIXThread::WillResume(lldb::StateType resume_state) { - // TODO: the line below shouldn't really be done, but + Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_THREAD)); + if (log) + log->Printf ("POSIXThread::%s (tid = %" PRIi64 ") setting thread resume state to %s", __FUNCTION__, GetID(), StateAsCString(resume_state)); + // TODO: the line below shouldn't really be done, but // the POSIXThread might rely on this so I will leave this in for now SetResumeState(resume_state); } +void +POSIXThread::DidStop() +{ + // Don't set the thread state to stopped unless we really stopped. +} + bool POSIXThread::Resume() { @@ -170,8 +189,9 @@ POSIXThread::Resume() bool status; Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_THREAD)); - if (log && log->GetMask().Test(POSIX_LOG_VERBOSE)) - log->Printf ("POSIXThread::%s ()", __FUNCTION__); + if (log) + log->Printf ("POSIXThread::%s (), resume_state = %s", __FUNCTION__, + StateAsCString(resume_state)); switch (resume_state) { @@ -211,10 +231,14 @@ POSIXThread::Notify(const ProcessMessage &message) assert(false && "Unexpected message kind!"); break; + case ProcessMessage::eExitMessage: + // Nothing to be done. + break; + case ProcessMessage::eLimboMessage: LimboNotify(message); break; - + case ProcessMessage::eSignalMessage: SignalNotify(message); break; @@ -318,6 +342,9 @@ POSIXThread::BreakNotify(const ProcessMessage &message) lldb::break_id_t bp_id = bp_site->GetID(); assert(bp_site && bp_site->ValidForThisThread(this)); + // Make this thread the selected thread + GetProcess()->GetThreadList().SetSelectedThreadByID(GetID()); + m_breakpoint = bp_site; SetStopInfo (StopInfo::CreateStopReasonWithBreakpointSiteID(*this, bp_id)); } @@ -408,6 +435,7 @@ POSIXThread::SignalDeliveredNotify(const ProcessMessage &message) void POSIXThread::CrashNotify(const ProcessMessage &message) { + // FIXME: Update stop reason as per bugzilla 14598 int signo = message.GetSignal(); assert(message.GetKind() == ProcessMessage::eCrashMessage); diff --git a/lldb/source/Plugins/Process/POSIX/POSIXThread.h b/lldb/source/Plugins/Process/POSIX/POSIXThread.h index 8d353aac588..fca9ea97e72 100644 --- a/lldb/source/Plugins/Process/POSIX/POSIXThread.h +++ b/lldb/source/Plugins/Process/POSIX/POSIXThread.h @@ -39,6 +39,10 @@ public: virtual void WillResume(lldb::StateType resume_state); + // This notifies the thread when a private stop occurs. + virtual void + DidStop (); + const char * GetInfo(); @@ -108,6 +112,7 @@ private: void SignalDeliveredNotify(const ProcessMessage &message); void CrashNotify(const ProcessMessage &message); void ThreadNotify(const ProcessMessage &message); + void ExitNotify(const ProcessMessage &message); lldb_private::Unwind * GetUnwinder(); diff --git a/lldb/source/Plugins/Process/POSIX/ProcessMessage.h b/lldb/source/Plugins/Process/POSIX/ProcessMessage.h index 2107e92a130..c76ca01489e 100644 --- a/lldb/source/Plugins/Process/POSIX/ProcessMessage.h +++ b/lldb/source/Plugins/Process/POSIX/ProcessMessage.h @@ -117,11 +117,16 @@ public: return message; } - /// Indicates that the thread @p tid was spawned. + /// Indicates that the thread @p child_tid was spawned. static ProcessMessage NewThread(lldb::tid_t parent_tid, lldb::tid_t child_tid) { return ProcessMessage(parent_tid, eNewThreadMessage, child_tid); } + /// Indicates that the thread @p tid is about to exit with status @p status. + static ProcessMessage Exit(lldb::tid_t tid, int status) { + return ProcessMessage(tid, eExitMessage, status); + } + int GetExitStatus() const { assert(GetKind() == eExitMessage || GetKind() == eLimboMessage); return m_status; diff --git a/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.cpp b/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.cpp index 471843b3f99..b2db43e3597 100644 --- a/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.cpp +++ b/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.cpp @@ -75,6 +75,7 @@ ProcessPOSIX::ProcessPOSIX(Target& target, Listener &listener) m_byte_order(lldb::endian::InlHostByteOrder()), m_monitor(NULL), m_module(NULL), + m_message_mutex (Mutex::eMutexTypeRecursive), m_in_limbo(false), m_exit_now(false) { @@ -374,31 +375,58 @@ ProcessPOSIX::SendMessage(const ProcessMessage &message) { Mutex::Locker lock(m_message_mutex); + POSIXThread *thread = static_cast<POSIXThread*>( + m_thread_list.FindThreadByID(message.GetTID(), false).get()); + switch (message.GetKind()) { case ProcessMessage::eInvalidMessage: return; case ProcessMessage::eLimboMessage: - m_in_limbo = true; - m_exit_status = message.GetExitStatus(); - if (m_exit_now) + assert(thread); + thread->SetState(eStateStopped); + if (message.GetTID() == GetID()) { - SetPrivateState(eStateExited); - m_monitor->Detach(); + m_in_limbo = true; + m_exit_status = message.GetExitStatus(); + if (m_exit_now) + { + SetPrivateState(eStateExited); + m_monitor->Detach(); + } + else + { + StopAllThreads(message.GetTID()); + SetPrivateState(eStateStopped); + } } else + { + StopAllThreads(message.GetTID()); SetPrivateState(eStateStopped); + } break; case ProcessMessage::eExitMessage: - m_exit_status = message.GetExitStatus(); - SetExitStatus(m_exit_status, NULL); + assert(thread); + thread->SetState(eStateExited); + // FIXME: I'm not sure we need to do this. + if (message.GetTID() == GetID()) + { + m_exit_status = message.GetExitStatus(); + SetExitStatus(m_exit_status, NULL); + } break; - case ProcessMessage::eTraceMessage: case ProcessMessage::eBreakpointMessage: + case ProcessMessage::eTraceMessage: case ProcessMessage::eWatchpointMessage: + case ProcessMessage::eNewThreadMessage: + case ProcessMessage::eCrashMessage: + assert(thread); + thread->SetState(eStateStopped); + StopAllThreads(message.GetTID()); SetPrivateState(eStateStopped); break; @@ -408,6 +436,9 @@ ProcessPOSIX::SendMessage(const ProcessMessage &message) lldb::tid_t tid = message.GetTID(); lldb::tid_t pid = GetID(); if (tid == pid) { + assert(thread); + thread->SetState(eStateStopped); + StopAllThreads(message.GetTID()); SetPrivateState(eStateStopped); break; } else { @@ -416,51 +447,65 @@ ProcessPOSIX::SendMessage(const ProcessMessage &message) } } - case ProcessMessage::eCrashMessage: - // FIXME: Update stop reason as per bugzilla 14598 - SetPrivateState(eStateStopped); - break; - - case ProcessMessage::eNewThreadMessage: - SetPrivateState(eStateStopped); - break; } m_message_queue.push(message); } +void +ProcessPOSIX::StopAllThreads(lldb::tid_t stop_tid) +{ + // FIXME: Will this work the same way on FreeBSD and Linux? +} + void ProcessPOSIX::RefreshStateAfterStop() { Log *log (ProcessPOSIXLog::GetLogIfAllCategoriesSet (POSIX_LOG_PROCESS)); if (log && log->GetMask().Test(POSIX_LOG_VERBOSE)) - log->Printf ("ProcessPOSIX::%s()", __FUNCTION__); + log->Printf ("ProcessPOSIX::%s(), message_queue size = %d", __FUNCTION__, (int)m_message_queue.size()); Mutex::Locker lock(m_message_mutex); - if (m_message_queue.empty()) - return; - ProcessMessage &message = m_message_queue.front(); + // This method used to only handle one message. Changing it to loop allows + // it to handle the case where we hit a breakpoint while handling a different + // breakpoint. + while (!m_message_queue.empty()) + { + ProcessMessage &message = m_message_queue.front(); - // Resolve the thread this message corresponds to and pass it along. - // FIXME: we're really dealing with the pid here. This should get - // fixed when this code is fixed to handle multiple threads. - lldb::tid_t tid = message.GetTID(); - if (log) - log->Printf ("ProcessPOSIX::%s() pid = %" PRIi64, __FUNCTION__, tid); - POSIXThread *thread = static_cast<POSIXThread*>( - GetThreadList().FindThreadByID(tid, false).get()); + // Resolve the thread this message corresponds to and pass it along. + lldb::tid_t tid = message.GetTID(); + if (log) + log->Printf ("ProcessPOSIX::%s(), message_queue size = %d, pid = %" PRIi64, __FUNCTION__, (int)m_message_queue.size(), tid); + POSIXThread *thread = static_cast<POSIXThread*>( + GetThreadList().FindThreadByID(tid, false).get()); - if (message.GetKind() == ProcessMessage::eNewThreadMessage) { - ThreadSP thread_sp; - thread_sp.reset(new POSIXThread(*this, message.GetChildTID())); - m_thread_list.AddThread(thread_sp); - } + if (message.GetKind() == ProcessMessage::eNewThreadMessage) + { + if (log) + log->Printf ("ProcessPOSIX::%s() adding thread, tid = %" PRIi64, __FUNCTION__, message.GetChildTID()); + ThreadSP thread_sp; + thread_sp.reset(new POSIXThread(*this, message.GetChildTID())); + m_thread_list.AddThread(thread_sp); + } + + m_thread_list.RefreshStateAfterStop(); - assert(thread); - thread->Notify(message); + if (thread) + thread->Notify(message); - m_message_queue.pop(); + if (message.GetKind() == ProcessMessage::eExitMessage) + { + // FIXME: We should tell the user about this, but the limbo message is probably better for that. + if (log) + log->Printf ("ProcessPOSIX::%s() removing thread, tid = %" PRIi64, __FUNCTION__, tid); + ThreadSP thread_sp = m_thread_list.RemoveThreadByID(tid, false); + thread_sp.reset(); + } + + m_message_queue.pop(); + } } bool diff --git a/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.h b/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.h index ce31d8f5a7c..b540805d496 100644 --- a/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.h +++ b/lldb/source/Plugins/Process/POSIX/ProcessPOSIX.h @@ -152,6 +152,11 @@ public: GetFilePath(const lldb_private::ProcessLaunchInfo::FileAction *file_action, const char *default_path); + /// Stops all threads in the process. + /// The \p stop_tid parameter indicates the thread which initiated the stop. + virtual void + StopAllThreads(lldb::tid_t stop_tid); + protected: /// Target byte order. lldb::ByteOrder m_byte_order; diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 5ed9a68bdb4..1127446be51 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -1720,7 +1720,7 @@ Process::SetPrivateState (StateType new_state) else m_private_run_lock.WriteLock(); } - + if (state_changed) { m_private_state.SetValueNoLock (new_state); diff --git a/lldb/test/functionalities/thread/break_after_join/Makefile b/lldb/test/functionalities/thread/break_after_join/Makefile new file mode 100644 index 00000000000..26db4816b6e --- /dev/null +++ b/lldb/test/functionalities/thread/break_after_join/Makefile @@ -0,0 +1,4 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/thread/break_after_join/TestBreakAfterJoin.py b/lldb/test/functionalities/thread/break_after_join/TestBreakAfterJoin.py new file mode 100644 index 00000000000..9bc14f09666 --- /dev/null +++ b/lldb/test/functionalities/thread/break_after_join/TestBreakAfterJoin.py @@ -0,0 +1,102 @@ +""" +Test number of threads. +""" + +import os, time +import unittest2 +import lldb +from lldbtest import * +import lldbutil + +class BreakpointAfterJoinTestCase(TestBase): + + mydir = os.path.join("functionalities", "thread", "break_after_join") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_with_dsym(self): + """Test breakpoint handling after a thread join.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.breakpoint_after_join_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @skipIfLinux # Fails intermittently, llvm.org/pr16170" + @dwarf_test + def test_with_dwarf(self): + """Test breakpoint handling after a thread join.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.breakpoint_after_join_test() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line number for our breakpoint. + self.breakpoint = line_number('main.cpp', '// Set breakpoint here') + + def breakpoint_after_join_test(self): + """Test breakpoint handling after a thread join.""" + exe = os.path.join(os.getcwd(), "a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + # This should create a breakpoint in the main thread. + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.breakpoint, num_expected_locations=1) + + # The breakpoint list should show 1 location. + self.expect("breakpoint list -f", "Breakpoint location shown correctly", + substrs = ["1: file ='main.cpp', line = %d, locations = 1" % self.breakpoint]) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs = ['stopped', + 'stop reason = breakpoint']) + + # Get the target process + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + + # The exit probably occured during breakpoint handling, but it isn't + # guaranteed. The main thing we're testing here is that the debugger + # handles this cleanly is some way. + + # Get the number of threads + num_threads = process.GetNumThreads() + + # Make sure we see six threads + self.assertTrue(num_threads == 6, 'Number of expected threads and actual threads do not match.') + + # Get the thread objects + thread1 = process.GetThreadAtIndex(0) + thread2 = process.GetThreadAtIndex(1) + thread3 = process.GetThreadAtIndex(2) + thread4 = process.GetThreadAtIndex(3) + thread5 = process.GetThreadAtIndex(4) + thread6 = process.GetThreadAtIndex(5) + + # Make sure all threads are stopped + self.assertTrue(thread1.IsStopped(), "Thread 1 didn't stop during breakpoint") + self.assertTrue(thread2.IsStopped(), "Thread 2 didn't stop during breakpoint") + self.assertTrue(thread3.IsStopped(), "Thread 3 didn't stop during breakpoint") + self.assertTrue(thread4.IsStopped(), "Thread 4 didn't stop during breakpoint") + self.assertTrue(thread5.IsStopped(), "Thread 5 didn't stop during breakpoint") + self.assertTrue(thread6.IsStopped(), "Thread 6 didn't stop during breakpoint") + + # Run to completion + self.runCmd("continue") + + # If the process hasn't exited, collect some information + if process.GetState() != lldb.eStateExited: + self.runCmd("thread list") + self.runCmd("process status") + + # At this point, the inferior process should have exited. + self.assertTrue(process.GetState() == lldb.eStateExited, PROCESS_EXITED) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/functionalities/thread/break_after_join/main.cpp b/lldb/test/functionalities/thread/break_after_join/main.cpp new file mode 100644 index 00000000000..97649761e0d --- /dev/null +++ b/lldb/test/functionalities/thread/break_after_join/main.cpp @@ -0,0 +1,125 @@ +//===-- main.cpp ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This test is intended to create a situation in which one thread will exit +// while a breakpoint is being handled in another thread. This may not always +// happen because it's possible that the exiting thread will exit before the +// breakpoint is hit. The test case should be flexible enough to treat that +// as success. + +#include <pthread.h> +#include <unistd.h> +#include <atomic> + +volatile int g_test = 0; + +// Note that although hogging the CPU while waiting for a variable to change +// would be terrible in production code, it's great for testing since it +// avoids a lot of messy context switching to get multiple threads synchronized. +#define do_nothing() + +#define pseudo_barrier_wait(bar) \ + --bar; \ + while (bar > 0) \ + do_nothing(); + +#define pseudo_barrier_init(bar, count) (bar = count) + +// A barrier to synchronize all the threads. +std::atomic_int g_barrier1; + +// A barrier to keep the threads from exiting until after the breakpoint has +// been passed. +std::atomic_int g_barrier2; + +void * +break_thread_func (void *input) +{ + // Wait until all the threads are running + pseudo_barrier_wait(g_barrier1); + + // Wait for the join thread to join + usleep(50); + + // Do something + g_test++; // Set breakpoint here + + // Synchronize after the breakpoint + pseudo_barrier_wait(g_barrier2); + + // Return + return NULL; +} + +void * +wait_thread_func (void *input) +{ + // Wait until the entire first group of threads is running + pseudo_barrier_wait(g_barrier1); + + // Wait until the breakpoint has been passed + pseudo_barrier_wait(g_barrier2); + + // Return + return NULL; +} + +void * +join_thread_func (void *input) +{ + pthread_t *thread_to_join = (pthread_t*)input; + + // Sync up with the rest of the threads. + pseudo_barrier_wait(g_barrier1); + + // Join the other thread + pthread_join(*thread_to_join, NULL); + + // Return + return NULL; +} + +int main () +{ + pthread_t thread_1; + pthread_t thread_2; + pthread_t thread_3; + pthread_t thread_4; + pthread_t thread_5; + + // The first barrier waits for the non-joining threads to start. + // This thread will also participate in that barrier. + // The idea here is to guarantee that the joining thread will be + // last in the internal list maintained by the debugger. + pseudo_barrier_init(g_barrier1, 5); + + // The second barrier keeps the waiting threads around until the breakpoint + // has been passed. + pseudo_barrier_init(g_barrier2, 4); + + // Create a thread to hit the breakpoint + pthread_create (&thread_1, NULL, break_thread_func, NULL); + + // Create more threads to slow the debugger down during processing. + pthread_create (&thread_2, NULL, wait_thread_func, NULL); + pthread_create (&thread_3, NULL, wait_thread_func, NULL); + pthread_create (&thread_4, NULL, wait_thread_func, NULL); + + // Create a thread to join the breakpoint thread + pthread_create (&thread_5, NULL, join_thread_func, &thread_4); + + // Wait for the threads to finish + pthread_join(thread_5, NULL); + pthread_join(thread_4, NULL); + pthread_join(thread_3, NULL); + pthread_join(thread_2, NULL); + pthread_join(thread_1, NULL); + + return 0; +} diff --git a/lldb/test/functionalities/thread/create_during_step/Makefile b/lldb/test/functionalities/thread/create_during_step/Makefile new file mode 100644 index 00000000000..26db4816b6e --- /dev/null +++ b/lldb/test/functionalities/thread/create_during_step/Makefile @@ -0,0 +1,4 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/thread/create_during_step/TestCreateDuringStep.py b/lldb/test/functionalities/thread/create_during_step/TestCreateDuringStep.py new file mode 100644 index 00000000000..e2010f0555a --- /dev/null +++ b/lldb/test/functionalities/thread/create_during_step/TestCreateDuringStep.py @@ -0,0 +1,157 @@ +""" +Test number of threads. +""" + +import os, time +import unittest2 +import lldb +from lldbtest import * +import lldbutil + +class CreateDuringStepTestCase(TestBase): + + mydir = os.path.join("functionalities", "thread", "create_during_step") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_step_inst_with_dsym(self): + """Test thread creation during step-inst handling.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.create_during_step_inst_test() + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_step_over_with_dsym(self): + """Test thread creation during step-over handling.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.create_during_step_over_test() + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_step_in_with_dsym(self): + """Test thread creation during step-in handling.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.create_during_step_in_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dwarf_test + def test_step_inst_with_dwarf(self): + """Test thread creation during step-inst handling.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.create_during_step_inst_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @expectedFailureLinux("llvm.org/pr16171") # step in/over don't behave in the multithreaded case + @dwarf_test + def test_step_over_with_dwarf(self): + """Test thread creation during step-over handling.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.create_during_step_over_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @expectedFailureLinux("llvm.org/pr16171") # step in/over don't behave in the multithreaded case + @dwarf_test + def test_step_in_with_dwarf(self): + """Test thread creation during step-in handling.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.create_during_step_in_test() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers to break and continue. + self.breakpoint = line_number('main.cpp', '// Set breakpoint here') + self.continuepoint = line_number('main.cpp', '// Continue from here') + + def create_during_step_inst_test(self): + """Test thread creation while using step-inst.""" + self.create_during_step_base("thread step-inst -m all-threads") + + def create_during_step_over_test(self): + """Test thread creation while using step-over.""" + self.create_during_step_base("thread step-over -m all-threads") + + def create_during_step_in_test(self): + """Test thread creation while using step-in.""" + self.create_during_step_base("thread step-in -m all-threads") + + def create_during_step_base(self, step_cmd): + """Test thread creation while using step-in.""" + exe = os.path.join(os.getcwd(), "a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + # This should create a breakpoint in the stepping thread. + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.breakpoint, num_expected_locations=1) + + # The breakpoint list should show 1 location. + self.expect("breakpoint list -f", "Breakpoint location shown correctly", + substrs = ["1: file ='main.cpp', line = %d, locations = 1" % self.breakpoint]) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs = ['stopped', + 'stop reason = breakpoint']) + + # Get the target process + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + + # Get the number of threads + num_threads = process.GetNumThreads() + + # Make sure we see only two threads + self.assertTrue(num_threads == 2, 'Number of expected threads and actual threads do not match.') + + # Get the thread objects + thread1 = process.GetThreadAtIndex(0) + thread2 = process.GetThreadAtIndex(1) + + # Make sure both threads are stopped + self.assertTrue(thread1.IsStopped(), "Thread 1 didn't stop during breakpoint") + self.assertTrue(thread2.IsStopped(), "Thread 2 didn't stop during breakpoint") + + # Keep stepping until we've reached our designated continue point + stepping_thread = process.GetSelectedThread() + current_line = self.breakpoint + while current_line != self.continuepoint: + self.runCmd(step_cmd) + + # The thread creation may change the selected thread. + # If it does, we just change it back here. + if stepping_thread != process.GetSelectedThread(): + process.SetSelectedThread(stepping_thread) + + frame = stepping_thread.GetFrameAtIndex(0) + current_line = frame.GetLineEntry().GetLine() + + # Make sure we're still where we thought we were + self.assertTrue(current_line >= self.breakpoint, "Stepped to unexpected line, " + str(current_line)) + self.assertTrue(current_line <= self.continuepoint, "Stepped to unexpected line, " + str(current_line)) + + # Update the number of threads + num_threads = process.GetNumThreads() + + # Check to see that we increased the number of threads as expected + self.assertTrue(num_threads == 3, 'Number of expected threads and actual threads do not match after thread exit.') + + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs = ['stopped', + 'top reason = instruction step int']) + + # Run to completion + self.runCmd("process continue") + + # At this point, the inferior process should have exited. + self.assertTrue(process.GetState() == lldb.eStateExited, PROCESS_EXITED) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/functionalities/thread/create_during_step/main.cpp b/lldb/test/functionalities/thread/create_during_step/main.cpp new file mode 100644 index 00000000000..f64831be5cb --- /dev/null +++ b/lldb/test/functionalities/thread/create_during_step/main.cpp @@ -0,0 +1,92 @@ +//===-- main.cpp ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This test is intended to create a situation in which one thread will be +// created while a the debugger is stepping in another thread. + +#include <pthread.h> +#include <atomic> + +// Note that although hogging the CPU while waiting for a variable to change +// would be terrible in production code, it's great for testing since it +// avoids a lot of messy context switching to get multiple threads synchronized. +#define do_nothing() + +#define pseudo_barrier_wait(bar) \ + --bar; \ + while (bar > 0) \ + do_nothing(); + +#define pseudo_barrier_init(bar, count) (bar = count) + +std::atomic_int g_barrier; + +volatile int g_thread_created = 0; +volatile int g_test = 0; + +void * +step_thread_func (void *input) +{ + g_test = 0; // Set breakpoint here + + while (!g_thread_created) + g_test++; + + // One more time to provide a continue point + g_test++; // Continue from here + + // Return + return NULL; +} + +void * +create_thread_func (void *input) +{ + pthread_t *step_thread = (pthread_t*)input; + + // Wait until the main thread knows this thread is started. + pseudo_barrier_wait(g_barrier); + + // Wait until the other thread is done. + pthread_join(*step_thread, NULL); + + // Return + return NULL; +} + +int main () +{ + pthread_t thread_1; + pthread_t thread_2; + + // Use a simple count to simulate a barrier. + pseudo_barrier_init(g_barrier, 2); + + // Create a thread to hit the breakpoint. + pthread_create (&thread_1, NULL, step_thread_func, NULL); + + // Wait until the step thread is stepping + while (g_test < 1) + do_nothing(); + + // Create a thread to exit while we're stepping. + pthread_create (&thread_2, NULL, create_thread_func, &thread_1); + + // Wait until that thread is started + pseudo_barrier_wait(g_barrier); + + // Let the stepping thread know the other thread is there + g_thread_created = 1; + + // Wait for the threads to finish. + pthread_join(thread_2, NULL); + pthread_join(thread_1, NULL); + + return 0; +} diff --git a/lldb/test/functionalities/thread/exit_during_break/Makefile b/lldb/test/functionalities/thread/exit_during_break/Makefile new file mode 100644 index 00000000000..26db4816b6e --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_break/Makefile @@ -0,0 +1,4 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/thread/exit_during_break/TestExitDuringBreak.py b/lldb/test/functionalities/thread/exit_during_break/TestExitDuringBreak.py new file mode 100644 index 00000000000..7c0a29bad53 --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_break/TestExitDuringBreak.py @@ -0,0 +1,94 @@ +""" +Test number of threads. +""" + +import os, time +import unittest2 +import lldb +from lldbtest import * +import lldbutil + +class ExitDuringBreakpointTestCase(TestBase): + + mydir = os.path.join("functionalities", "thread", "exit_during_break") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_with_dsym(self): + """Test thread exit during breakpoint handling.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.exit_during_breakpoint_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dwarf_test + def test_with_dwarf(self): + """Test thread exit during breakpoint handling.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.exit_during_breakpoint_test() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line number for our breakpoint. + self.breakpoint = line_number('main.cpp', '// Set breakpoint here') + + def exit_during_breakpoint_test(self): + """Test thread exit during breakpoint handling.""" + exe = os.path.join(os.getcwd(), "a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + # This should create a breakpoint in the main thread. + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.breakpoint, num_expected_locations=1) + + # The breakpoint list should show 1 location. + self.expect("breakpoint list -f", "Breakpoint location shown correctly", + substrs = ["1: file ='main.cpp', line = %d, locations = 1" % self.breakpoint]) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs = ['stopped', + 'stop reason = breakpoint']) + + # Get the target process + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + + # The exit probably occured during breakpoint handling, but it isn't + # guaranteed. The main thing we're testing here is that the debugger + # handles this cleanly is some way. + + # Get the number of threads + num_threads = process.GetNumThreads() + + # Make sure we see at least five threads + self.assertTrue(num_threads >= 5, 'Number of expected threads and actual threads do not match.') + + # Get the thread objects + thread1 = process.GetThreadAtIndex(0) + thread2 = process.GetThreadAtIndex(1) + thread3 = process.GetThreadAtIndex(2) + thread4 = process.GetThreadAtIndex(3) + thread5 = process.GetThreadAtIndex(4) + + # Make sure all threads are stopped + self.assertTrue(thread1.IsStopped(), "Thread 1 didn't stop during breakpoint") + self.assertTrue(thread2.IsStopped(), "Thread 2 didn't stop during breakpoint") + self.assertTrue(thread3.IsStopped(), "Thread 3 didn't stop during breakpoint") + self.assertTrue(thread4.IsStopped(), "Thread 4 didn't stop during breakpoint") + self.assertTrue(thread5.IsStopped(), "Thread 5 didn't stop during breakpoint") + + # Run to completion + self.runCmd("continue") + + # At this point, the inferior process should have exited. + self.assertTrue(process.GetState() == lldb.eStateExited, PROCESS_EXITED) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/functionalities/thread/exit_during_break/main.cpp b/lldb/test/functionalities/thread/exit_during_break/main.cpp new file mode 100644 index 00000000000..f900194b0a5 --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_break/main.cpp @@ -0,0 +1,135 @@ +//===-- main.cpp ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This test is intended to create a situation in which one thread will exit +// while a breakpoint is being handled in another thread. This may not always +// happen because it's possible that the exiting thread will exit before the +// breakpoint is hit. The test case should be flexible enough to treat that +// as success. + +#include <pthread.h> +#include <unistd.h> +#include <atomic> + +volatile int g_test = 0; + +// Note that although hogging the CPU while waiting for a variable to change +// would be terrible in production code, it's great for testing since it +// avoids a lot of messy context switching to get multiple threads synchronized. +#define do_nothing() + +#define pseudo_barrier_wait(bar) \ + --bar; \ + while (bar > 0) \ + do_nothing(); + +#define pseudo_barrier_init(bar, count) (bar = count) + +// A barrier to synchronize all the threads except the one that will exit. +std::atomic_int g_barrier1; + +// A barrier to synchronize all the threads including the one that will exit. +std::atomic_int g_barrier2; + +// A barrier to keep the first group of threads from exiting until after the +// breakpoint has been passed. +std::atomic_int g_barrier3; + +void * +break_thread_func (void *input) +{ + // Wait until the entire first group of threads is running + pseudo_barrier_wait(g_barrier1); + + // Wait for the exiting thread to start + pseudo_barrier_wait(g_barrier2); + + // Do something + g_test++; // Set breakpoint here + + // Synchronize after the breakpoint + pseudo_barrier_wait(g_barrier3); + + // Return + return NULL; +} + +void * +wait_thread_func (void *input) +{ + // Wait until the entire first group of threads is running + pseudo_barrier_wait(g_barrier1); + + // Wait for the exiting thread to start + pseudo_barrier_wait(g_barrier2); + + // Wait until the breakpoint has been passed + pseudo_barrier_wait(g_barrier3); + + // Return + return NULL; +} + +void * +exit_thread_func (void *input) +{ + // Sync up with the rest of the threads. + pseudo_barrier_wait(g_barrier2); + + // Try to make sure this thread doesn't exit until the breakpoint is hit. + usleep(1); + + // Return + return NULL; +} + +int main () +{ + pthread_t thread_1; + pthread_t thread_2; + pthread_t thread_3; + pthread_t thread_4; + pthread_t thread_5; + + // The first barrier waits for the non-exiting threads to start. + // This thread will also participate in that barrier. + // The idea here is to guarantee that the exiting thread will be + // last in the internal list maintained by the debugger. + pseudo_barrier_init(g_barrier1, 5); + + // The second break synchronyizes thread exection with the breakpoint. + pseudo_barrier_init(g_barrier2, 5); + + // The third barrier keeps the waiting threads around until the breakpoint + // has been passed. + pseudo_barrier_init(g_barrier3, 4); + + // Create a thread to hit the breakpoint + pthread_create (&thread_1, NULL, break_thread_func, NULL); + + // Create more threads to slow the debugger down during processing. + pthread_create (&thread_2, NULL, wait_thread_func, NULL); + pthread_create (&thread_3, NULL, wait_thread_func, NULL); + pthread_create (&thread_4, NULL, wait_thread_func, NULL); + + // Wait for all these threads to get started. + pseudo_barrier_wait(g_barrier1); + + // Create a thread to exit during the breakpoint + pthread_create (&thread_5, NULL, exit_thread_func, NULL); + + // Wait for the threads to finish + pthread_join(thread_5, NULL); + pthread_join(thread_4, NULL); + pthread_join(thread_3, NULL); + pthread_join(thread_2, NULL); + pthread_join(thread_1, NULL); + + return 0; +} diff --git a/lldb/test/functionalities/thread/exit_during_break/main.d.23591 b/lldb/test/functionalities/thread/exit_during_break/main.d.23591 new file mode 100644 index 00000000000..f5194df7b90 --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_break/main.d.23591 @@ -0,0 +1,39 @@ +main.o: main.cpp /usr/include/pthread.h /usr/include/features.h \ + /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h /usr/include/sched.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stddef.h \ + /usr/include/time.h /usr/include/x86_64-linux-gnu/bits/sched.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/bits/timex.h /usr/include/xlocale.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \ + /usr/include/x86_64-linux-gnu/bits/setjmp.h /usr/include/stdio.h \ + /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/unistd.h \ + /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ + /usr/include/x86_64-linux-gnu/bits/environments.h \ + /usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h \ + /usr/include/x86_64-linux-gnu/sys/types.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/atomic \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/c++0x_warning.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_base.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/c++config.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/os_defines.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/cpu_defines.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdbool.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdint.h \ + /usr/include/stdint.h /usr/include/x86_64-linux-gnu/bits/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_0.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_2.h diff --git a/lldb/test/functionalities/thread/exit_during_break/main.d.23604 b/lldb/test/functionalities/thread/exit_during_break/main.d.23604 new file mode 100644 index 00000000000..f5194df7b90 --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_break/main.d.23604 @@ -0,0 +1,39 @@ +main.o: main.cpp /usr/include/pthread.h /usr/include/features.h \ + /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h /usr/include/sched.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stddef.h \ + /usr/include/time.h /usr/include/x86_64-linux-gnu/bits/sched.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/bits/timex.h /usr/include/xlocale.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \ + /usr/include/x86_64-linux-gnu/bits/setjmp.h /usr/include/stdio.h \ + /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/unistd.h \ + /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ + /usr/include/x86_64-linux-gnu/bits/environments.h \ + /usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h \ + /usr/include/x86_64-linux-gnu/sys/types.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/atomic \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/c++0x_warning.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_base.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/c++config.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/os_defines.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/cpu_defines.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdbool.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdint.h \ + /usr/include/stdint.h /usr/include/x86_64-linux-gnu/bits/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_0.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_2.h diff --git a/lldb/test/functionalities/thread/exit_during_break/main.d.23614 b/lldb/test/functionalities/thread/exit_during_break/main.d.23614 new file mode 100644 index 00000000000..f5194df7b90 --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_break/main.d.23614 @@ -0,0 +1,39 @@ +main.o: main.cpp /usr/include/pthread.h /usr/include/features.h \ + /usr/include/x86_64-linux-gnu/bits/predefs.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h /usr/include/sched.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stddef.h \ + /usr/include/time.h /usr/include/x86_64-linux-gnu/bits/sched.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/bits/timex.h /usr/include/xlocale.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \ + /usr/include/x86_64-linux-gnu/bits/setjmp.h /usr/include/stdio.h \ + /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/unistd.h \ + /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ + /usr/include/x86_64-linux-gnu/bits/environments.h \ + /usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h \ + /usr/include/x86_64-linux-gnu/sys/types.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/sigset.h \ + /usr/include/x86_64-linux-gnu/sys/sysmacros.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/atomic \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/c++0x_warning.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_base.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/c++config.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/os_defines.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/x86_64-linux-gnu/bits/cpu_defines.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdbool.h \ + /home/akaylor/clang-install/bin/../lib/clang/3.3/include/stdint.h \ + /usr/include/stdint.h /usr/include/x86_64-linux-gnu/bits/wchar.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_0.h \ + /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../include/c++/4.6/bits/atomic_2.h diff --git a/lldb/test/functionalities/thread/exit_during_step/Makefile b/lldb/test/functionalities/thread/exit_during_step/Makefile new file mode 100644 index 00000000000..26db4816b6e --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_step/Makefile @@ -0,0 +1,4 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/thread/exit_during_step/TestExitDuringStep.py b/lldb/test/functionalities/thread/exit_during_step/TestExitDuringStep.py new file mode 100644 index 00000000000..b6968cdf13f --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_step/TestExitDuringStep.py @@ -0,0 +1,113 @@ +""" +Test number of threads. +""" + +import os, time +import unittest2 +import lldb +from lldbtest import * +import lldbutil + +class ExitDuringStepTestCase(TestBase): + + mydir = os.path.join("functionalities", "thread", "exit_during_step") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_with_dsym(self): + """Test thread exit during step handling.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.exit_during_step_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dwarf_test + def test_with_dwarf(self): + """Test thread exit during step handling.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.exit_during_step_test() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers to break and continue. + self.breakpoint = line_number('main.cpp', '// Set breakpoint here') + self.continuepoint = line_number('main.cpp', '// Continue from here') + + def exit_during_step_test(self): + """Test thread exit during step handling.""" + exe = os.path.join(os.getcwd(), "a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + # This should create a breakpoint in the main thread. + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.breakpoint, num_expected_locations=1) + + # The breakpoint list should show 1 location. + self.expect("breakpoint list -f", "Breakpoint location shown correctly", + substrs = ["1: file ='main.cpp', line = %d, locations = 1" % self.breakpoint]) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs = ['stopped', + 'stop reason = breakpoint']) + + # Get the target process + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + + # Get the number of threads + num_threads = process.GetNumThreads() + + # Make sure we see all three threads + self.assertTrue(num_threads == 3, 'Number of expected threads and actual threads do not match.') + + # Get the thread objects + thread1 = process.GetThreadAtIndex(0) + thread2 = process.GetThreadAtIndex(1) + thread3 = process.GetThreadAtIndex(2) + + # Make sure all threads are stopped + self.assertTrue(thread1.IsStopped(), "Thread 1 didn't stop during breakpoint") + self.assertTrue(thread2.IsStopped(), "Thread 2 didn't stop during breakpoint") + self.assertTrue(thread3.IsStopped(), "Thread 3 didn't stop during breakpoint") + + # Keep stepping until we've reached our designated continue point + stepping_thread = process.GetSelectedThread() + current_line = self.breakpoint + stepping_frame = stepping_thread.GetFrameAtIndex(0) + self.assertTrue(current_line == stepping_frame.GetLineEntry().GetLine(), "Starting line for stepping doesn't match breakpoint line.") + while current_line != self.continuepoint: + self.runCmd("thread step-inst -m all-threads") + + if stepping_thread != process.GetSelectedThread(): + process.SetSelectedThread(stepping_thread) + + frame = stepping_thread.GetFrameAtIndex(0) + + current_line = frame.GetLineEntry().GetLine() + + self.assertTrue(current_line >= self.breakpoint, "Stepped to unexpected line, " + str(current_line)) + self.assertTrue(current_line <= self.continuepoint, "Stepped to unexpected line, " + str(current_line)) + + self.runCmd("thread list") + + # Update the number of threads + num_threads = process.GetNumThreads() + + # Check to see that we reduced the number of threads as expected + self.assertTrue(num_threads == 2, 'Number of expected threads and actual threads do not match after thread exit.') + + # Run to completion + self.runCmd("continue") + + # At this point, the inferior process should have exited. + self.assertTrue(process.GetState() == lldb.eStateExited, PROCESS_EXITED) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/functionalities/thread/exit_during_step/main.cpp b/lldb/test/functionalities/thread/exit_during_step/main.cpp new file mode 100644 index 00000000000..99492123fde --- /dev/null +++ b/lldb/test/functionalities/thread/exit_during_step/main.cpp @@ -0,0 +1,91 @@ +//===-- main.cpp ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This test is intended to create a situation in which one thread will exit +// while a the debugger is stepping in another thread. + +#include <pthread.h> +#include <unistd.h> + +// Note that although hogging the CPU while waiting for a variable to change +// would be terrible in production code, it's great for testing since it +// avoids a lot of messy context switching to get multiple threads synchronized. +#define do_nothing() + +#define pseudo_barrier_wait(bar) \ + --bar; \ + while (bar > 0) \ + do_nothing(); + +#define pseudo_barrier_init(bar, count) (bar = count) + +// A barrier to synchronize thread start. +volatile int g_barrier; + +volatile int g_thread_exited = 0; + +volatile int g_test = 0; + +void * +step_thread_func (void *input) +{ + // Wait until both threads are started. + pseudo_barrier_wait(g_barrier); + + g_test = 0; // Set breakpoint here + + while (!g_thread_exited) + g_test++; + + // One more time to provide a continue point + g_test++; // Continue from here + + // Return + return NULL; +} + +void * +exit_thread_func (void *input) +{ + // Wait until both threads are started. + pseudo_barrier_wait(g_barrier); + + // Wait until the other thread is stepping. + while (g_test == 0) + do_nothing(); + + // Return + return NULL; +} + +int main () +{ + pthread_t thread_1; + pthread_t thread_2; + + // Synchronize thread start so that doesn't happen during stepping. + pseudo_barrier_init(g_barrier, 2); + + // Create a thread to hit the breakpoint. + pthread_create (&thread_1, NULL, step_thread_func, NULL); + + // Create a thread to exit while we're stepping. + pthread_create (&thread_2, NULL, exit_thread_func, NULL); + + // Wait for the exit thread to finish. + pthread_join(thread_2, NULL); + + // Let the stepping thread know the other thread is gone. + g_thread_exited = 1; + + // Wait for the stepping thread to finish. + pthread_join(thread_1, NULL); + + return 0; +} diff --git a/lldb/test/functionalities/thread/multi_break/Makefile b/lldb/test/functionalities/thread/multi_break/Makefile new file mode 100644 index 00000000000..26db4816b6e --- /dev/null +++ b/lldb/test/functionalities/thread/multi_break/Makefile @@ -0,0 +1,4 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/thread/multi_break/TestMultipleBreakpoints.py b/lldb/test/functionalities/thread/multi_break/TestMultipleBreakpoints.py new file mode 100644 index 00000000000..a042284d40b --- /dev/null +++ b/lldb/test/functionalities/thread/multi_break/TestMultipleBreakpoints.py @@ -0,0 +1,90 @@ +""" +Test number of threads. +""" + +import os, time +import unittest2 +import lldb +from lldbtest import * +import lldbutil + +class MultipleBreakpointTestCase(TestBase): + + mydir = os.path.join("functionalities", "thread", "multi_break") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_with_dsym(self): + """Test simultaneous breakpoints in multiple threads.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.multiple_breakpoint_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dwarf_test + def test_with_dwarf(self): + """Test simultaneous breakpoints in multiple threads.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.multiple_breakpoint_test() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line number for our breakpoint. + self.breakpoint = line_number('main.cpp', '// Set breakpoint here') + + def multiple_breakpoint_test(self): + """Test simultaneous breakpoints in multiple threads.""" + exe = os.path.join(os.getcwd(), "a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + # This should create a breakpoint in the main thread. + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.breakpoint, num_expected_locations=1) + + # The breakpoint list should show 1 location. + self.expect("breakpoint list -f", "Breakpoint location shown correctly", + substrs = ["1: file ='main.cpp', line = %d, locations = 1" % self.breakpoint]) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + # The breakpoint may be hit in either thread 2 or thread 3. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs = ['stopped', + 'stop reason = breakpoint']) + + # Get the target process + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + + # Get the number of threads + num_threads = process.GetNumThreads() + + # Make sure we see all three threads + self.assertTrue(num_threads == 3, 'Number of expected threads and actual threads do not match.') + + # Get the thread objects + thread1 = process.GetThreadAtIndex(0) + thread2 = process.GetThreadAtIndex(1) + thread3 = process.GetThreadAtIndex(2) + + # Make sure both threads are stopped + self.assertTrue(thread1.IsStopped(), "Primary thread didn't stop during breakpoint") + self.assertTrue(thread2.IsStopped(), "Secondary thread didn't stop during breakpoint") + self.assertTrue(thread3.IsStopped(), "Tertiary thread didn't stop during breakpoint") + + # Delete the first breakpoint then continue + self.runCmd("breakpoint delete 1") + + # Run to completion + self.runCmd("continue") + + # At this point, the inferior process should have exited. + self.assertTrue(process.GetState() == lldb.eStateExited, PROCESS_EXITED) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/functionalities/thread/multi_break/main.cpp b/lldb/test/functionalities/thread/multi_break/main.cpp new file mode 100644 index 00000000000..e567e544957 --- /dev/null +++ b/lldb/test/functionalities/thread/multi_break/main.cpp @@ -0,0 +1,64 @@ +//===-- main.cpp ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This test is intended to create a situation in which a breakpoint will be +// hit in two threads at nearly the same moment. The expected result is that +// the breakpoint in the second thread will be hit while the breakpoint handler +// in the first thread is trying to stop all threads. + +#include <pthread.h> +#include <atomic> + +// Note that although hogging the CPU while waiting for a variable to change +// would be terrible in production code, it's great for testing since it +// avoids a lot of messy context switching to get multiple threads synchronized. +#define do_nothing() + +#define pseudo_barrier_wait(bar) \ + --bar; \ + while (bar > 0) \ + do_nothing(); + +#define pseudo_barrier_init(bar, count) (bar = count) + +std::atomic_int g_barrier; + +volatile int g_test = 0; + +void * +thread_func (void *input) +{ + // Wait until both threads are running + pseudo_barrier_wait(g_barrier); + + // Do something + g_test++; // Set breakpoint here + + // Return + return NULL; +} + +int main () +{ + pthread_t thread_1; + pthread_t thread_2; + + // Don't let either thread do anything until they're both ready. + pseudo_barrier_init(g_barrier, 2); + + // Create two threads + pthread_create (&thread_1, NULL, thread_func, NULL); + pthread_create (&thread_2, NULL, thread_func, NULL); + + // Wait for the threads to finish + pthread_join(thread_1, NULL); + pthread_join(thread_2, NULL); + + return 0; +} diff --git a/lldb/test/functionalities/thread/state/Makefile b/lldb/test/functionalities/thread/state/Makefile index 0cde994bf94..3b5a8a30fa4 100644 --- a/lldb/test/functionalities/thread/state/Makefile +++ b/lldb/test/functionalities/thread/state/Makefile @@ -1,5 +1,4 @@ LEVEL = ../../../make C_SOURCES := main.c -LD_EXTRAS := -lpthread include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/thread/state/TestThreadStates.py b/lldb/test/functionalities/thread/state/TestThreadStates.py index 22b51bf1a95..fe9fb204408 100644 --- a/lldb/test/functionalities/thread/state/TestThreadStates.py +++ b/lldb/test/functionalities/thread/state/TestThreadStates.py @@ -8,7 +8,7 @@ import lldb from lldbtest import * import lldbutil -class StopThreadsTestCase(TestBase): +class ThreadStateTestCase(TestBase): mydir = os.path.join("functionalities", "thread", "state") @@ -16,54 +16,54 @@ class StopThreadsTestCase(TestBase): @dsym_test def test_state_after_breakpoint_with_dsym(self): """Test thread state after breakpoint.""" - self.buildDsym() + self.buildDsym(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_state_after_breakpoint_test() @dwarf_test def test_state_after_breakpoint_with_dwarf(self): """Test thread state after breakpoint.""" - self.buildDwarf() + self.buildDwarf(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_state_after_breakpoint_test() @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") @dsym_test def test_state_after_continue_with_dsym(self): """Test thread state after continue.""" - self.buildDsym() + self.buildDsym(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_state_after_continue_test() @dwarf_test def test_state_after_continue_with_dwarf(self): """Test thread state after continue.""" - self.buildDwarf() + self.buildDwarf(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_state_after_continue_test() @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") @dsym_test def test_state_after_expression_with_dsym(self): """Test thread state after expression.""" - self.buildDsym() + self.buildDsym(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_state_after_continue_test() @dwarf_test def test_state_after_expression_with_dwarf(self): """Test thread state after expression.""" - self.buildDwarf() + self.buildDwarf(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_state_after_continue_test() @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") @dsym_test - @unittest2.expectedFailure("llvm.org/pr15824") # thread states not properly maintained + @unittest2.expectedFailure("llvm.org/pr16172") # thread states not properly maintained def test_process_interrupt_with_dsym(self): """Test process interrupt.""" - self.buildDsym() + self.buildDsym(dictionary=self.getBuildFlags(use_cpp11=False)) self.process_interrupt_test() @dwarf_test - @unittest2.expectedFailure("llvm.org/pr15824") # thread states not properly maintained + @unittest2.expectedFailure("llvm.org/pr16712") # thread states not properly maintained def test_process_interrupt_with_dwarf(self): """Test process interrupt.""" - self.buildDwarf() + self.buildDwarf(dictionary=self.getBuildFlags(use_cpp11=False)) self.process_interrupt_test() @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") @@ -71,14 +71,14 @@ class StopThreadsTestCase(TestBase): @unittest2.expectedFailure("llvm.org/pr15824") # thread states not properly maintained def test_process_state_with_dsym(self): """Test thread states (comprehensive).""" - self.buildDsym() + self.buildDsym(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_states_test() @dwarf_test @unittest2.expectedFailure("llvm.org/pr15824") # thread states not properly maintained def test_process_state_with_dwarf(self): """Test thread states (comprehensive).""" - self.buildDwarf() + self.buildDwarf(dictionary=self.getBuildFlags(use_cpp11=False)) self.thread_states_test() def setUp(self): diff --git a/lldb/test/functionalities/thread/thread_exit/Makefile b/lldb/test/functionalities/thread/thread_exit/Makefile new file mode 100644 index 00000000000..26db4816b6e --- /dev/null +++ b/lldb/test/functionalities/thread/thread_exit/Makefile @@ -0,0 +1,4 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/functionalities/thread/thread_exit/TestThreadExit.py b/lldb/test/functionalities/thread/thread_exit/TestThreadExit.py new file mode 100644 index 00000000000..7fd4f91db0b --- /dev/null +++ b/lldb/test/functionalities/thread/thread_exit/TestThreadExit.py @@ -0,0 +1,132 @@ +""" +Test number of threads. +""" + +import os, time +import unittest2 +import lldb +from lldbtest import * +import lldbutil + +class ThreadExitTestCase(TestBase): + + mydir = os.path.join("functionalities", "thread", "thread_exit") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dsym_test + def test_with_dsym(self): + """Test thread exit handling.""" + self.buildDsym(dictionary=self.getBuildFlags()) + self.thread_exit_test() + + @expectedFailureDarwin("llvm.org/pr15824") # thread states not properly maintained + @dwarf_test + def test_with_dwarf(self): + """Test thread exit handling.""" + self.buildDwarf(dictionary=self.getBuildFlags()) + self.thread_exit_test() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers for our breakpoints. + self.break_1 = line_number('main.cpp', '// Set first breakpoint here') + self.break_2 = line_number('main.cpp', '// Set second breakpoint here') + self.break_3 = line_number('main.cpp', '// Set third breakpoint here') + self.break_4 = line_number('main.cpp', '// Set fourth breakpoint here') + + def thread_exit_test(self): + """Test thread exit handling.""" + exe = os.path.join(os.getcwd(), "a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + # This should create a breakpoint with 1 location. + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.break_1, num_expected_locations=1) + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.break_2, num_expected_locations=1) + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.break_3, num_expected_locations=1) + lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.break_4, num_expected_locations=1) + + # The breakpoint list should show 1 locations. + self.expect("breakpoint list -f", "Breakpoint location shown correctly", + substrs = ["1: file ='main.cpp', line = %d, locations = 1" % self.break_1, + "2: file ='main.cpp', line = %d, locations = 1" % self.break_2, + "3: file ='main.cpp', line = %d, locations = 1" % self.break_3, + "4: file ='main.cpp', line = %d, locations = 1" % self.break_4]) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint 1. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT + " 1", + substrs = ['stopped', + '* thread #1', + 'stop reason = breakpoint 1', + 'thread #2']) + + # Get the target process + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + + # Get the number of threads + num_threads = process.GetNumThreads() + + self.assertTrue(num_threads == 2, 'Number of expected threads and actual threads do not match at breakpoint 1.') + + # Run to the second breakpoint + self.runCmd("continue") + + # The stop reason of the thread should be breakpoint 1. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT + " 2", + substrs = ['stopped', + 'thread #1', + '* thread #2', + 'stop reason = breakpoint 2', + 'thread #3']) + + # Update the number of threads + num_threads = process.GetNumThreads() + + self.assertTrue(num_threads == 3, 'Number of expected threads and actual threads do not match at breakpoint 2.') + + # Run to the third breakpoint + self.runCmd("continue") + + # The stop reason of the thread should be breakpoint 3. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT + " 3", + substrs = ['stopped', + '* thread #1', + 'stop reason = breakpoint 3', + 'thread #3', + ]) + + # Update the number of threads + num_threads = process.GetNumThreads() + + self.assertTrue(num_threads == 2, 'Number of expected threads and actual threads do not match at breakpoint 3.') + + # Run to the fourth breakpoint + self.runCmd("continue") + + # The stop reason of the thread should be breakpoint 4. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT + " 4", + substrs = ['stopped', + '* thread #1', + 'stop reason = breakpoint 4']) + + # Update the number of threads + num_threads = process.GetNumThreads() + + self.assertTrue(num_threads == 1, 'Number of expected threads and actual threads do not match at breakpoint 4.') + + # Run to completion + self.runCmd("continue") + + # At this point, the inferior process should have exited. + self.assertTrue(process.GetState() == lldb.eStateExited, PROCESS_EXITED) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/functionalities/thread/thread_exit/main.cpp b/lldb/test/functionalities/thread/thread_exit/main.cpp new file mode 100644 index 00000000000..94ed517eff4 --- /dev/null +++ b/lldb/test/functionalities/thread/thread_exit/main.cpp @@ -0,0 +1,89 @@ +//===-- main.cpp ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This test verifies the correct handling of child thread exits. + +#include <pthread.h> +#include <atomic> + +// Note that although hogging the CPU while waiting for a variable to change +// would be terrible in production code, it's great for testing since it +// avoids a lot of messy context switching to get multiple threads synchronized. +#define do_nothing() + +#define pseudo_barrier_wait(bar) \ + --bar; \ + while (bar > 0) \ + do_nothing(); + +#define pseudo_barrier_init(bar, count) (bar = count) + +std::atomic_int g_barrier1; +std::atomic_int g_barrier2; +std::atomic_int g_barrier3; + +void * +thread1 (void *input) +{ + // Synchronize with the main thread. + pseudo_barrier_wait(g_barrier1); + + // Synchronize with the main thread and thread2. + pseudo_barrier_wait(g_barrier2); + + // Return + return NULL; // Set second breakpoint here +} + +void * +thread2 (void *input) +{ + // Synchronize with thread1 and the main thread. + pseudo_barrier_wait(g_barrier2); + + // Synchronize with the main thread. + pseudo_barrier_wait(g_barrier3); + + // Return + return NULL; +} + +int main () +{ + pthread_t thread_1; + pthread_t thread_2; + pthread_t thread_3; + + pseudo_barrier_init(g_barrier1, 2); + pseudo_barrier_init(g_barrier2, 3); + pseudo_barrier_init(g_barrier3, 2); + + // Create a thread. + pthread_create (&thread_1, NULL, thread1, NULL); + + // Wait for thread1 to start. + pseudo_barrier_wait(g_barrier1); + + // Create another thread. + pthread_create (&thread_2, NULL, thread2, NULL); // Set first breakpoint here + + // Wait for thread2 to start. + pseudo_barrier_wait(g_barrier2); + + // Wait for the first thread to finish + pthread_join(thread_1, NULL); + + // Synchronize with the remaining thread + pseudo_barrier_wait(g_barrier3); // Set third breakpoint here + + // Wait for the second thread to finish + pthread_join(thread_2, NULL); + + return 0; // Set fourth breakpoint here +} diff --git a/lldb/test/lldbtest.py b/lldb/test/lldbtest.py index ae811890018..353f4553fc9 100644 --- a/lldb/test/lldbtest.py +++ b/lldb/test/lldbtest.py @@ -1251,6 +1251,29 @@ class Base(unittest2.TestCase): if not module.buildDwarf(self, architecture, compiler, dictionary, clean): raise Exception("Don't know how to build binary with dwarf") + def getBuildFlags(self, use_cpp11=True, use_pthreads=True): + """ Returns a dictionary (which can be provided to build* functions above) which + contains OS-specific build flags. + """ + cflags = "" + if use_cpp11: + cflags += "-std=" + if "gcc" in self.getCompiler() and "4.6" in self.getCompilerVersion(): + cflags += "c++0x" + else: + cflags += "c++11" + if sys.platform.startswith("darwin"): + cflags += " -stdlib=libc++" + elif "clang" in self.getCompiler(): + cflags += " -stdlib=libstdc++" + + if use_pthreads: + ldflags = "-lpthread" + + return {'CFLAGS_EXTRAS' : cflags, + 'LD_EXTRAS' : ldflags, + } + def cleanup(self, dictionary=None): """Platform specific way to do cleanup after build.""" if lldb.skip_build_and_cleanup: |