Skip to content

Vanishing threads #1254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lldb/include/lldb/lldb-enumerations.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ enum ExpressionResults {
eExpressionHitBreakpoint,
eExpressionTimedOut,
eExpressionResultUnavailable,
eExpressionStoppedForDebug
eExpressionStoppedForDebug,
eExpressionThreadVanished
};

enum SearchDepth {
Expand Down
5 changes: 3 additions & 2 deletions lldb/source/Expression/FunctionCaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,9 @@ lldb::ExpressionResults FunctionCaller::ExecuteFunction(
if (return_value != lldb::eExpressionCompleted) {
LLDB_LOGF(log,
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
"completed abnormally ==",
m_name.c_str());
"completed abnormally: %s ==",
m_name.c_str(),
Process::ExecutionResultAsCString(return_value));
} else {
LLDB_LOGF(log,
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
Expand Down
12 changes: 12 additions & 0 deletions lldb/source/Expression/LLVMUserExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
return lldb::eExpressionSetupError;
}

// Store away the thread ID for error reporting, in case it exits
// during execution:
lldb::tid_t expr_thread_id = exe_ctx.GetThreadRef().GetID();

Address wrapper_address(m_jit_start_addr);

std::vector<lldb::addr_t> args;
Expand Down Expand Up @@ -223,6 +227,14 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
"Use \"thread return -x\" to return to the state before expression "
"evaluation.");
return execution_result;
} else if (execution_result == lldb::eExpressionThreadVanished) {
diagnostic_manager.Printf(
eDiagnosticSeverityError,
"Couldn't complete execution; the thread "
"on which the expression was being run: 0x%" PRIx64
" exited during its execution.",
expr_thread_id);
return execution_result;
} else if (execution_result != lldb::eExpressionCompleted) {
diagnostic_manager.Printf(
eDiagnosticSeverityError, "Couldn't execute function; result was %s",
Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Interpreter/CommandInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,11 @@ Status CommandInterpreter::PreprocessCommand(std::string &command) {
"expression '%s'",
expr_str.c_str());
break;
case eExpressionThreadVanished:
error.SetErrorStringWithFormat(
"expression thread vanished for the expression '%s'",
expr_str.c_str());
break;
}
}
}
Expand Down
72 changes: 45 additions & 27 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4661,13 +4661,27 @@ GetExpressionTimeout(const EvaluateExpressionOptions &options,
}

static llvm::Optional<ExpressionResults>
HandleStoppedEvent(Thread &thread, const ThreadPlanSP &thread_plan_sp,
HandleStoppedEvent(lldb::tid_t thread_id, const ThreadPlanSP &thread_plan_sp,
RestorePlanState &restorer, const EventSP &event_sp,
EventSP &event_to_broadcast_sp,
const EvaluateExpressionOptions &options, bool handle_interrupts) {
const EvaluateExpressionOptions &options,
bool handle_interrupts) {
Log *log = GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STEP | LIBLLDB_LOG_PROCESS);

ThreadPlanSP plan = thread.GetCompletedPlan();
ThreadSP thread_sp = thread_plan_sp->GetTarget()
.GetProcessSP()
->GetThreadList()
.FindThreadByID(thread_id);
if (!thread_sp) {
LLDB_LOG(log,
"The thread on which we were running the "
"expression: tid = {0}, exited while "
"the expression was running.",
thread_id);
return eExpressionThreadVanished;
}

ThreadPlanSP plan = thread_sp->GetCompletedPlan();
if (plan == thread_plan_sp && plan->PlanSucceeded()) {
LLDB_LOG(log, "execution completed successfully");

Expand All @@ -4677,7 +4691,7 @@ HandleStoppedEvent(Thread &thread, const ThreadPlanSP &thread_plan_sp,
return eExpressionCompleted;
}

StopInfoSP stop_info_sp = thread.GetStopInfo();
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint &&
stop_info_sp->ShouldNotify(event_sp.get())) {
LLDB_LOG(log, "stopped for breakpoint: {0}.", stop_info_sp->GetDescription());
Expand Down Expand Up @@ -4739,6 +4753,10 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
return eExpressionSetupError;
}

// Record the thread's id so we can tell when a thread we were using
// to run the expression exits during the expression evaluation.
lldb::tid_t expr_thread_id = thread->GetID();

// We need to change some of the thread plan attributes for the thread plan
// runner. This will restore them when we are done:

Expand Down Expand Up @@ -4883,7 +4901,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
LLDB_LOGF(log,
"Process::RunThreadPlan(): Resuming thread %u - 0x%4.4" PRIx64
" to run thread plan \"%s\".",
thread->GetIndexID(), thread->GetID(), s.GetData());
thread_idx_id, expr_thread_id, s.GetData());
}

bool got_event;
Expand Down Expand Up @@ -5083,33 +5101,23 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,

switch (stop_state) {
case lldb::eStateStopped: {
// We stopped, figure out what we are going to do now.
ThreadSP thread_sp =
GetThreadList().FindThreadByIndexID(thread_idx_id);
if (!thread_sp) {
// Ooh, our thread has vanished. Unlikely that this was
// successful execution...
LLDB_LOGF(log,
"Process::RunThreadPlan(): execution completed "
"but our thread (index-id=%u) has vanished.",
thread_idx_id);
return_value = eExpressionInterrupted;
} else if (Process::ProcessEventData::GetRestartedFromEvent(
event_sp.get())) {
if (Process::ProcessEventData::GetRestartedFromEvent(
event_sp.get())) {
// If we were restarted, we just need to go back up to fetch
// another event.
if (log) {
LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
"restart, so we'll continue waiting.");
}
LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
"restart, so we'll continue waiting.");
keep_going = true;
do_resume = false;
handle_running_event = true;
} else {
const bool handle_interrupts = true;
return_value = *HandleStoppedEvent(
*thread, thread_plan_sp, thread_plan_restorer, event_sp,
event_to_broadcast_sp, options, handle_interrupts);
expr_thread_id, thread_plan_sp, thread_plan_restorer,
event_sp, event_to_broadcast_sp, options,
handle_interrupts);
if (return_value == eExpressionThreadVanished)
keep_going = false;
}
} break;

Expand Down Expand Up @@ -5231,8 +5239,9 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
// job. Check that here:
const bool handle_interrupts = false;
if (auto result = HandleStoppedEvent(
*thread, thread_plan_sp, thread_plan_restorer, event_sp,
event_to_broadcast_sp, options, handle_interrupts)) {
expr_thread_id, thread_plan_sp, thread_plan_restorer,
event_sp, event_to_broadcast_sp, options,
handle_interrupts)) {
return_value = *result;
back_to_top = false;
break;
Expand Down Expand Up @@ -5304,6 +5313,13 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
m_public_state.SetValueNoLock(old_state);
}

// If our thread went away on us, we need to get out of here without
// doing any more work. We don't have to clean up the thread plan, that
// will have happened when the Thread was destroyed.
if (return_value == eExpressionThreadVanished) {
return return_value;
}

if (return_value != eExpressionCompleted && log) {
// Print a backtrace into the log so we can figure out where we are:
StreamString s;
Expand Down Expand Up @@ -5492,7 +5508,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
}

const char *Process::ExecutionResultAsCString(ExpressionResults result) {
const char *result_name;
const char *result_name = "<unknown>";

switch (result) {
case eExpressionCompleted:
Expand Down Expand Up @@ -5522,6 +5538,8 @@ const char *Process::ExecutionResultAsCString(ExpressionResults result) {
case eExpressionStoppedForDebug:
result_name = "eExpressionStoppedForDebug";
break;
case eExpressionThreadVanished:
result_name = "eExpressionThreadVanished";
}
return result_name;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
C_SOURCES := main.c
CFLAGS_EXTRAS := -std=c99

ENABLE_THREADS := YES

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
Make sure that we handle an expression on a thread, if
the thread exits while the expression is running.
"""

import lldb
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.lldbtest import *

class TestExitDuringExpression(TestBase):

mydir = TestBase.compute_mydir(__file__)

NO_DEBUG_INFO_TESTCASE = True

@skipIfWindows
def test_exit_before_one_thread_unwind(self):
"""Test the case where we exit within the one thread timeout"""
self.exiting_expression_test(True, True)

@skipIfWindows
def test_exit_before_one_thread_no_unwind(self):
"""Test the case where we exit within the one thread timeout"""
self.exiting_expression_test(True, False)

@skipIfWindows
def test_exit_after_one_thread_unwind(self):
"""Test the case where we exit within the one thread timeout"""
self.exiting_expression_test(False, True)

@skipIfWindows
def test_exit_after_one_thread_no_unwind(self):
"""Test the case where we exit within the one thread timeout"""
self.exiting_expression_test(False, False)

def setUp(self):
TestBase.setUp(self)
self.main_source_file = lldb.SBFileSpec("main.c")
self.build()

def exiting_expression_test(self, before_one_thread_timeout , unwind):
"""function_to_call sleeps for g_timeout microseconds, then calls pthread_exit.
This test calls function_to_call with an overall timeout of 500
microseconds, and a one_thread_timeout as passed in.
It also sets unwind_on_exit for the call to the unwind passed in.
This allows you to have the thread exit either before the one thread
timeout is passed. """

(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
"Break here and cause the thread to exit", self.main_source_file)

# We'll continue to this breakpoint after running our expression:
return_bkpt = target.BreakpointCreateBySourceRegex("Break here to make sure the thread exited", self.main_source_file)
frame = thread.frames[0]
tid = thread.GetThreadID()
# Find the timeout:
var_options = lldb.SBVariablesOptions()
var_options.SetIncludeArguments(False)
var_options.SetIncludeLocals(False)
var_options.SetIncludeStatics(True)

value_list = frame.GetVariables(var_options)
g_timeout = value_list.GetFirstValueByName("g_timeout")
self.assertTrue(g_timeout.IsValid(), "Found g_timeout")

error = lldb.SBError()
timeout_value = g_timeout.GetValueAsUnsigned(error)
self.assertTrue(error.Success(), "Couldn't get timeout value: %s"%(error.GetCString()))

one_thread_timeout = 0
if (before_one_thread_timeout):
one_thread_timeout = timeout_value * 2
else:
one_thread_timeout = int(timeout_value / 2)

options = lldb.SBExpressionOptions()
options.SetUnwindOnError(unwind)
options.SetOneThreadTimeoutInMicroSeconds(one_thread_timeout)
options.SetTimeoutInMicroSeconds(4 * timeout_value)

result = frame.EvaluateExpression("function_to_call()", options)

# Make sure the thread actually exited:
thread = process.GetThreadByID(tid)
self.assertFalse(thread.IsValid(), "The thread exited")

# Make sure the expression failed:
self.assertFalse(result.GetError().Success(), "Expression failed.")

# Make sure we can keep going:
threads = lldbutil.continue_to_breakpoint(process, return_bkpt)
if not threads:
self.fail("didn't get any threads back after continuing")

self.assertEqual(len(threads), 1, "One thread hit our breakpoint")
thread = threads[0]
frame = thread.frames[0]
# Now get the return value, if we successfully caused the thread to exit
# it should be 10, not 20.
ret_val = frame.FindVariable("ret_val")
self.assertTrue(ret_val.GetError().Success(), "Found ret_val")
ret_val_value = ret_val.GetValueAsSigned(error)
self.assertTrue(error.Success(), "Got ret_val's value")
self.assertEqual(ret_val_value, 10, "We put the right value in ret_val")

38 changes: 38 additions & 0 deletions lldb/test/API/functionalities/thread/exit_during_expression/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static unsigned int g_timeout = 100000;

int function_to_call() {

errno = 0;
while (1) {
int result = usleep(g_timeout);
if (errno != EINTR)
break;
}

pthread_exit((void *)10);

return 20; // Prevent warning
}

void *exiting_thread_func(void *unused) {
function_to_call(); // Break here and cause the thread to exit
return NULL;
}

int main() {
char *exit_ptr;
pthread_t exiting_thread;

pthread_create(&exiting_thread, NULL, exiting_thread_func, NULL);

pthread_join(exiting_thread, &exit_ptr);
int ret_val = (int)exit_ptr;
usleep(g_timeout * 4); // Make sure in the "run all threads" case
// that we don't run past our breakpoint.
return ret_val; // Break here to make sure the thread exited.
}