Skip to content

Commit b8534de

Browse files
authored
Merge pull request #1254 from jimingham/vanishing-threads
Vanishing threads
2 parents 8c7b60e + 79faa63 commit b8534de

File tree

8 files changed

+217
-30
lines changed

8 files changed

+217
-30
lines changed

lldb/include/lldb/lldb-enumerations.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ enum ExpressionResults {
269269
eExpressionHitBreakpoint,
270270
eExpressionTimedOut,
271271
eExpressionResultUnavailable,
272-
eExpressionStoppedForDebug
272+
eExpressionStoppedForDebug,
273+
eExpressionThreadVanished
273274
};
274275

275276
enum SearchDepth {

lldb/source/Expression/FunctionCaller.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,9 @@ lldb::ExpressionResults FunctionCaller::ExecuteFunction(
364364
if (return_value != lldb::eExpressionCompleted) {
365365
LLDB_LOGF(log,
366366
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
367-
"completed abnormally ==",
368-
m_name.c_str());
367+
"completed abnormally: %s ==",
368+
m_name.c_str(),
369+
Process::ExecutionResultAsCString(return_value));
369370
} else {
370371
LLDB_LOGF(log,
371372
"== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "

lldb/source/Expression/LLVMUserExpression.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
134134
return lldb::eExpressionSetupError;
135135
}
136136

137+
// Store away the thread ID for error reporting, in case it exits
138+
// during execution:
139+
lldb::tid_t expr_thread_id = exe_ctx.GetThreadRef().GetID();
140+
137141
Address wrapper_address(m_jit_start_addr);
138142

139143
std::vector<lldb::addr_t> args;
@@ -223,6 +227,14 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
223227
"Use \"thread return -x\" to return to the state before expression "
224228
"evaluation.");
225229
return execution_result;
230+
} else if (execution_result == lldb::eExpressionThreadVanished) {
231+
diagnostic_manager.Printf(
232+
eDiagnosticSeverityError,
233+
"Couldn't complete execution; the thread "
234+
"on which the expression was being run: 0x%" PRIx64
235+
" exited during its execution.",
236+
expr_thread_id);
237+
return execution_result;
226238
} else if (execution_result != lldb::eExpressionCompleted) {
227239
diagnostic_manager.Printf(
228240
eDiagnosticSeverityError, "Couldn't execute function; result was %s",

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,11 @@ Status CommandInterpreter::PreprocessCommand(std::string &command) {
16011601
"expression '%s'",
16021602
expr_str.c_str());
16031603
break;
1604+
case eExpressionThreadVanished:
1605+
error.SetErrorStringWithFormat(
1606+
"expression thread vanished for the expression '%s'",
1607+
expr_str.c_str());
1608+
break;
16041609
}
16051610
}
16061611
}

lldb/source/Target/Process.cpp

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4661,13 +4661,27 @@ GetExpressionTimeout(const EvaluateExpressionOptions &options,
46614661
}
46624662

46634663
static llvm::Optional<ExpressionResults>
4664-
HandleStoppedEvent(Thread &thread, const ThreadPlanSP &thread_plan_sp,
4664+
HandleStoppedEvent(lldb::tid_t thread_id, const ThreadPlanSP &thread_plan_sp,
46654665
RestorePlanState &restorer, const EventSP &event_sp,
46664666
EventSP &event_to_broadcast_sp,
4667-
const EvaluateExpressionOptions &options, bool handle_interrupts) {
4667+
const EvaluateExpressionOptions &options,
4668+
bool handle_interrupts) {
46684669
Log *log = GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STEP | LIBLLDB_LOG_PROCESS);
46694670

4670-
ThreadPlanSP plan = thread.GetCompletedPlan();
4671+
ThreadSP thread_sp = thread_plan_sp->GetTarget()
4672+
.GetProcessSP()
4673+
->GetThreadList()
4674+
.FindThreadByID(thread_id);
4675+
if (!thread_sp) {
4676+
LLDB_LOG(log,
4677+
"The thread on which we were running the "
4678+
"expression: tid = {0}, exited while "
4679+
"the expression was running.",
4680+
thread_id);
4681+
return eExpressionThreadVanished;
4682+
}
4683+
4684+
ThreadPlanSP plan = thread_sp->GetCompletedPlan();
46714685
if (plan == thread_plan_sp && plan->PlanSucceeded()) {
46724686
LLDB_LOG(log, "execution completed successfully");
46734687

@@ -4677,7 +4691,7 @@ HandleStoppedEvent(Thread &thread, const ThreadPlanSP &thread_plan_sp,
46774691
return eExpressionCompleted;
46784692
}
46794693

4680-
StopInfoSP stop_info_sp = thread.GetStopInfo();
4694+
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
46814695
if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint &&
46824696
stop_info_sp->ShouldNotify(event_sp.get())) {
46834697
LLDB_LOG(log, "stopped for breakpoint: {0}.", stop_info_sp->GetDescription());
@@ -4739,6 +4753,10 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
47394753
return eExpressionSetupError;
47404754
}
47414755

4756+
// Record the thread's id so we can tell when a thread we were using
4757+
// to run the expression exits during the expression evaluation.
4758+
lldb::tid_t expr_thread_id = thread->GetID();
4759+
47424760
// We need to change some of the thread plan attributes for the thread plan
47434761
// runner. This will restore them when we are done:
47444762

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

48894907
bool got_event;
@@ -5083,33 +5101,23 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
50835101

50845102
switch (stop_state) {
50855103
case lldb::eStateStopped: {
5086-
// We stopped, figure out what we are going to do now.
5087-
ThreadSP thread_sp =
5088-
GetThreadList().FindThreadByIndexID(thread_idx_id);
5089-
if (!thread_sp) {
5090-
// Ooh, our thread has vanished. Unlikely that this was
5091-
// successful execution...
5092-
LLDB_LOGF(log,
5093-
"Process::RunThreadPlan(): execution completed "
5094-
"but our thread (index-id=%u) has vanished.",
5095-
thread_idx_id);
5096-
return_value = eExpressionInterrupted;
5097-
} else if (Process::ProcessEventData::GetRestartedFromEvent(
5098-
event_sp.get())) {
5104+
if (Process::ProcessEventData::GetRestartedFromEvent(
5105+
event_sp.get())) {
50995106
// If we were restarted, we just need to go back up to fetch
51005107
// another event.
5101-
if (log) {
5102-
LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
5103-
"restart, so we'll continue waiting.");
5104-
}
5108+
LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
5109+
"restart, so we'll continue waiting.");
51055110
keep_going = true;
51065111
do_resume = false;
51075112
handle_running_event = true;
51085113
} else {
51095114
const bool handle_interrupts = true;
51105115
return_value = *HandleStoppedEvent(
5111-
*thread, thread_plan_sp, thread_plan_restorer, event_sp,
5112-
event_to_broadcast_sp, options, handle_interrupts);
5116+
expr_thread_id, thread_plan_sp, thread_plan_restorer,
5117+
event_sp, event_to_broadcast_sp, options,
5118+
handle_interrupts);
5119+
if (return_value == eExpressionThreadVanished)
5120+
keep_going = false;
51135121
}
51145122
} break;
51155123

@@ -5231,8 +5239,9 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
52315239
// job. Check that here:
52325240
const bool handle_interrupts = false;
52335241
if (auto result = HandleStoppedEvent(
5234-
*thread, thread_plan_sp, thread_plan_restorer, event_sp,
5235-
event_to_broadcast_sp, options, handle_interrupts)) {
5242+
expr_thread_id, thread_plan_sp, thread_plan_restorer,
5243+
event_sp, event_to_broadcast_sp, options,
5244+
handle_interrupts)) {
52365245
return_value = *result;
52375246
back_to_top = false;
52385247
break;
@@ -5304,6 +5313,13 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
53045313
m_public_state.SetValueNoLock(old_state);
53055314
}
53065315

5316+
// If our thread went away on us, we need to get out of here without
5317+
// doing any more work. We don't have to clean up the thread plan, that
5318+
// will have happened when the Thread was destroyed.
5319+
if (return_value == eExpressionThreadVanished) {
5320+
return return_value;
5321+
}
5322+
53075323
if (return_value != eExpressionCompleted && log) {
53085324
// Print a backtrace into the log so we can figure out where we are:
53095325
StreamString s;
@@ -5492,7 +5508,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
54925508
}
54935509

54945510
const char *Process::ExecutionResultAsCString(ExpressionResults result) {
5495-
const char *result_name;
5511+
const char *result_name = "<unknown>";
54965512

54975513
switch (result) {
54985514
case eExpressionCompleted:
@@ -5522,6 +5538,8 @@ const char *Process::ExecutionResultAsCString(ExpressionResults result) {
55225538
case eExpressionStoppedForDebug:
55235539
result_name = "eExpressionStoppedForDebug";
55245540
break;
5541+
case eExpressionThreadVanished:
5542+
result_name = "eExpressionThreadVanished";
55255543
}
55265544
return result_name;
55275545
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
C_SOURCES := main.c
2+
CFLAGS_EXTRAS := -std=c99
3+
4+
ENABLE_THREADS := YES
5+
6+
include Makefile.rules
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
Make sure that we handle an expression on a thread, if
3+
the thread exits while the expression is running.
4+
"""
5+
6+
import lldb
7+
from lldbsuite.test.decorators import *
8+
import lldbsuite.test.lldbutil as lldbutil
9+
from lldbsuite.test.lldbtest import *
10+
11+
class TestExitDuringExpression(TestBase):
12+
13+
mydir = TestBase.compute_mydir(__file__)
14+
15+
NO_DEBUG_INFO_TESTCASE = True
16+
17+
@skipIfWindows
18+
def test_exit_before_one_thread_unwind(self):
19+
"""Test the case where we exit within the one thread timeout"""
20+
self.exiting_expression_test(True, True)
21+
22+
@skipIfWindows
23+
def test_exit_before_one_thread_no_unwind(self):
24+
"""Test the case where we exit within the one thread timeout"""
25+
self.exiting_expression_test(True, False)
26+
27+
@skipIfWindows
28+
def test_exit_after_one_thread_unwind(self):
29+
"""Test the case where we exit within the one thread timeout"""
30+
self.exiting_expression_test(False, True)
31+
32+
@skipIfWindows
33+
def test_exit_after_one_thread_no_unwind(self):
34+
"""Test the case where we exit within the one thread timeout"""
35+
self.exiting_expression_test(False, False)
36+
37+
def setUp(self):
38+
TestBase.setUp(self)
39+
self.main_source_file = lldb.SBFileSpec("main.c")
40+
self.build()
41+
42+
def exiting_expression_test(self, before_one_thread_timeout , unwind):
43+
"""function_to_call sleeps for g_timeout microseconds, then calls pthread_exit.
44+
This test calls function_to_call with an overall timeout of 500
45+
microseconds, and a one_thread_timeout as passed in.
46+
It also sets unwind_on_exit for the call to the unwind passed in.
47+
This allows you to have the thread exit either before the one thread
48+
timeout is passed. """
49+
50+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
51+
"Break here and cause the thread to exit", self.main_source_file)
52+
53+
# We'll continue to this breakpoint after running our expression:
54+
return_bkpt = target.BreakpointCreateBySourceRegex("Break here to make sure the thread exited", self.main_source_file)
55+
frame = thread.frames[0]
56+
tid = thread.GetThreadID()
57+
# Find the timeout:
58+
var_options = lldb.SBVariablesOptions()
59+
var_options.SetIncludeArguments(False)
60+
var_options.SetIncludeLocals(False)
61+
var_options.SetIncludeStatics(True)
62+
63+
value_list = frame.GetVariables(var_options)
64+
g_timeout = value_list.GetFirstValueByName("g_timeout")
65+
self.assertTrue(g_timeout.IsValid(), "Found g_timeout")
66+
67+
error = lldb.SBError()
68+
timeout_value = g_timeout.GetValueAsUnsigned(error)
69+
self.assertTrue(error.Success(), "Couldn't get timeout value: %s"%(error.GetCString()))
70+
71+
one_thread_timeout = 0
72+
if (before_one_thread_timeout):
73+
one_thread_timeout = timeout_value * 2
74+
else:
75+
one_thread_timeout = int(timeout_value / 2)
76+
77+
options = lldb.SBExpressionOptions()
78+
options.SetUnwindOnError(unwind)
79+
options.SetOneThreadTimeoutInMicroSeconds(one_thread_timeout)
80+
options.SetTimeoutInMicroSeconds(4 * timeout_value)
81+
82+
result = frame.EvaluateExpression("function_to_call()", options)
83+
84+
# Make sure the thread actually exited:
85+
thread = process.GetThreadByID(tid)
86+
self.assertFalse(thread.IsValid(), "The thread exited")
87+
88+
# Make sure the expression failed:
89+
self.assertFalse(result.GetError().Success(), "Expression failed.")
90+
91+
# Make sure we can keep going:
92+
threads = lldbutil.continue_to_breakpoint(process, return_bkpt)
93+
if not threads:
94+
self.fail("didn't get any threads back after continuing")
95+
96+
self.assertEqual(len(threads), 1, "One thread hit our breakpoint")
97+
thread = threads[0]
98+
frame = thread.frames[0]
99+
# Now get the return value, if we successfully caused the thread to exit
100+
# it should be 10, not 20.
101+
ret_val = frame.FindVariable("ret_val")
102+
self.assertTrue(ret_val.GetError().Success(), "Found ret_val")
103+
ret_val_value = ret_val.GetValueAsSigned(error)
104+
self.assertTrue(error.Success(), "Got ret_val's value")
105+
self.assertEqual(ret_val_value, 10, "We put the right value in ret_val")
106+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include <errno.h>
2+
#include <pthread.h>
3+
#include <stdio.h>
4+
#include <unistd.h>
5+
6+
static unsigned int g_timeout = 100000;
7+
8+
int function_to_call() {
9+
10+
errno = 0;
11+
while (1) {
12+
int result = usleep(g_timeout);
13+
if (errno != EINTR)
14+
break;
15+
}
16+
17+
pthread_exit((void *)10);
18+
19+
return 20; // Prevent warning
20+
}
21+
22+
void *exiting_thread_func(void *unused) {
23+
function_to_call(); // Break here and cause the thread to exit
24+
return NULL;
25+
}
26+
27+
int main() {
28+
char *exit_ptr;
29+
pthread_t exiting_thread;
30+
31+
pthread_create(&exiting_thread, NULL, exiting_thread_func, NULL);
32+
33+
pthread_join(exiting_thread, &exit_ptr);
34+
int ret_val = (int)exit_ptr;
35+
usleep(g_timeout * 4); // Make sure in the "run all threads" case
36+
// that we don't run past our breakpoint.
37+
return ret_val; // Break here to make sure the thread exited.
38+
}

0 commit comments

Comments
 (0)