forked from OSchip/llvm-project
Handle the case where a thread exits while we are running a function on it.
This commit is contained in:
parent
e36be90c82
commit
dbbed971e3
|
@ -269,7 +269,8 @@ enum ExpressionResults {
|
||||||
eExpressionHitBreakpoint,
|
eExpressionHitBreakpoint,
|
||||||
eExpressionTimedOut,
|
eExpressionTimedOut,
|
||||||
eExpressionResultUnavailable,
|
eExpressionResultUnavailable,
|
||||||
eExpressionStoppedForDebug
|
eExpressionStoppedForDebug,
|
||||||
|
eExpressionThreadVanished
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SearchDepth {
|
enum SearchDepth {
|
||||||
|
|
|
@ -364,8 +364,9 @@ lldb::ExpressionResults FunctionCaller::ExecuteFunction(
|
||||||
if (return_value != lldb::eExpressionCompleted) {
|
if (return_value != lldb::eExpressionCompleted) {
|
||||||
LLDB_LOGF(log,
|
LLDB_LOGF(log,
|
||||||
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
|
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
|
||||||
"completed abnormally ==",
|
"completed abnormally: %s ==",
|
||||||
m_name.c_str());
|
m_name.c_str(),
|
||||||
|
Process::ExecutionResultAsCString(return_value));
|
||||||
} else {
|
} else {
|
||||||
LLDB_LOGF(log,
|
LLDB_LOGF(log,
|
||||||
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
|
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
|
||||||
|
|
|
@ -134,6 +134,10 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
|
||||||
return lldb::eExpressionSetupError;
|
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);
|
Address wrapper_address(m_jit_start_addr);
|
||||||
|
|
||||||
std::vector<lldb::addr_t> args;
|
std::vector<lldb::addr_t> args;
|
||||||
|
@ -223,6 +227,14 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
|
||||||
"Use \"thread return -x\" to return to the state before expression "
|
"Use \"thread return -x\" to return to the state before expression "
|
||||||
"evaluation.");
|
"evaluation.");
|
||||||
return execution_result;
|
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) {
|
} else if (execution_result != lldb::eExpressionCompleted) {
|
||||||
diagnostic_manager.Printf(
|
diagnostic_manager.Printf(
|
||||||
eDiagnosticSeverityError, "Couldn't execute function; result was %s",
|
eDiagnosticSeverityError, "Couldn't execute function; result was %s",
|
||||||
|
|
|
@ -4652,13 +4652,27 @@ GetExpressionTimeout(const EvaluateExpressionOptions &options,
|
||||||
}
|
}
|
||||||
|
|
||||||
static llvm::Optional<ExpressionResults>
|
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,
|
RestorePlanState &restorer, const EventSP &event_sp,
|
||||||
EventSP &event_to_broadcast_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);
|
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()) {
|
if (plan == thread_plan_sp && plan->PlanSucceeded()) {
|
||||||
LLDB_LOG(log, "execution completed successfully");
|
LLDB_LOG(log, "execution completed successfully");
|
||||||
|
|
||||||
|
@ -4668,7 +4682,7 @@ HandleStoppedEvent(Thread &thread, const ThreadPlanSP &thread_plan_sp,
|
||||||
return eExpressionCompleted;
|
return eExpressionCompleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
StopInfoSP stop_info_sp = thread.GetStopInfo();
|
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
|
||||||
if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint &&
|
if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint &&
|
||||||
stop_info_sp->ShouldNotify(event_sp.get())) {
|
stop_info_sp->ShouldNotify(event_sp.get())) {
|
||||||
LLDB_LOG(log, "stopped for breakpoint: {0}.", stop_info_sp->GetDescription());
|
LLDB_LOG(log, "stopped for breakpoint: {0}.", stop_info_sp->GetDescription());
|
||||||
|
@ -4730,6 +4744,10 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
|
||||||
return eExpressionSetupError;
|
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
|
// We need to change some of the thread plan attributes for the thread plan
|
||||||
// runner. This will restore them when we are done:
|
// runner. This will restore them when we are done:
|
||||||
|
|
||||||
|
@ -4874,7 +4892,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
|
||||||
LLDB_LOGF(log,
|
LLDB_LOGF(log,
|
||||||
"Process::RunThreadPlan(): Resuming thread %u - 0x%4.4" PRIx64
|
"Process::RunThreadPlan(): Resuming thread %u - 0x%4.4" PRIx64
|
||||||
" to run thread plan \"%s\".",
|
" to run thread plan \"%s\".",
|
||||||
thread->GetIndexID(), thread->GetID(), s.GetData());
|
thread_idx_id, expr_thread_id, s.GetData());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool got_event;
|
bool got_event;
|
||||||
|
@ -5074,33 +5092,23 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
|
||||||
|
|
||||||
switch (stop_state) {
|
switch (stop_state) {
|
||||||
case lldb::eStateStopped: {
|
case lldb::eStateStopped: {
|
||||||
// We stopped, figure out what we are going to do now.
|
if (Process::ProcessEventData::GetRestartedFromEvent(
|
||||||
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())) {
|
event_sp.get())) {
|
||||||
// If we were restarted, we just need to go back up to fetch
|
// If we were restarted, we just need to go back up to fetch
|
||||||
// another event.
|
// another event.
|
||||||
if (log) {
|
|
||||||
LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
|
LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
|
||||||
"restart, so we'll continue waiting.");
|
"restart, so we'll continue waiting.");
|
||||||
}
|
|
||||||
keep_going = true;
|
keep_going = true;
|
||||||
do_resume = false;
|
do_resume = false;
|
||||||
handle_running_event = true;
|
handle_running_event = true;
|
||||||
} else {
|
} else {
|
||||||
const bool handle_interrupts = true;
|
const bool handle_interrupts = true;
|
||||||
return_value = *HandleStoppedEvent(
|
return_value = *HandleStoppedEvent(
|
||||||
*thread, thread_plan_sp, thread_plan_restorer, event_sp,
|
expr_thread_id, thread_plan_sp, thread_plan_restorer,
|
||||||
event_to_broadcast_sp, options, handle_interrupts);
|
event_sp, event_to_broadcast_sp, options,
|
||||||
|
handle_interrupts);
|
||||||
|
if (return_value == eExpressionThreadVanished)
|
||||||
|
keep_going = false;
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
@ -5222,8 +5230,9 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
|
||||||
// job. Check that here:
|
// job. Check that here:
|
||||||
const bool handle_interrupts = false;
|
const bool handle_interrupts = false;
|
||||||
if (auto result = HandleStoppedEvent(
|
if (auto result = HandleStoppedEvent(
|
||||||
*thread, thread_plan_sp, thread_plan_restorer, event_sp,
|
expr_thread_id, thread_plan_sp, thread_plan_restorer,
|
||||||
event_to_broadcast_sp, options, handle_interrupts)) {
|
event_sp, event_to_broadcast_sp, options,
|
||||||
|
handle_interrupts)) {
|
||||||
return_value = *result;
|
return_value = *result;
|
||||||
back_to_top = false;
|
back_to_top = false;
|
||||||
break;
|
break;
|
||||||
|
@ -5295,6 +5304,13 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
|
||||||
m_public_state.SetValueNoLock(old_state);
|
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) {
|
if (return_value != eExpressionCompleted && log) {
|
||||||
// Print a backtrace into the log so we can figure out where we are:
|
// Print a backtrace into the log so we can figure out where we are:
|
||||||
StreamString s;
|
StreamString s;
|
||||||
|
@ -5483,7 +5499,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *Process::ExecutionResultAsCString(ExpressionResults result) {
|
const char *Process::ExecutionResultAsCString(ExpressionResults result) {
|
||||||
const char *result_name;
|
const char *result_name = "<unknown>";
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case eExpressionCompleted:
|
case eExpressionCompleted:
|
||||||
|
@ -5513,6 +5529,8 @@ const char *Process::ExecutionResultAsCString(ExpressionResults result) {
|
||||||
case eExpressionStoppedForDebug:
|
case eExpressionStoppedForDebug:
|
||||||
result_name = "eExpressionStoppedForDebug";
|
result_name = "eExpressionStoppedForDebug";
|
||||||
break;
|
break;
|
||||||
|
case eExpressionThreadVanished:
|
||||||
|
result_name = "eExpressionThreadVanished";
|
||||||
}
|
}
|
||||||
return result_name;
|
return result_name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
C_SOURCES := main.c
|
||||||
|
CFLAGS_EXTRAS := -std=c99
|
||||||
|
|
||||||
|
include Makefile.rules
|
|
@ -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")
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static unsigned int g_timeout = 200;
|
||||||
|
|
||||||
|
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.
|
||||||
|
}
|
Loading…
Reference in New Issue