forked from OSchip/llvm-project
Run all threads when extending a next range over a call.
If you don't do this you end up running arbitrary code with only one thread allowed to run, which can cause deadlocks. <rdar://problem/56422478> Differential Revision: https://reviews.llvm.org/D71440
This commit is contained in:
parent
53bcd1e141
commit
434905b97d
|
@ -287,6 +287,10 @@ public:
|
||||||
/// a function call (a branch that calls and returns to the next
|
/// a function call (a branch that calls and returns to the next
|
||||||
/// instruction). If false, find the instruction index of any
|
/// instruction). If false, find the instruction index of any
|
||||||
/// branch in the list.
|
/// branch in the list.
|
||||||
|
///
|
||||||
|
/// @param[out] found_calls
|
||||||
|
/// If non-null, this will be set to true if any calls were found in
|
||||||
|
/// extending the range.
|
||||||
///
|
///
|
||||||
/// @return
|
/// @return
|
||||||
/// The instruction index of the first branch that is at or past
|
/// The instruction index of the first branch that is at or past
|
||||||
|
@ -295,7 +299,8 @@ public:
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
uint32_t GetIndexOfNextBranchInstruction(uint32_t start,
|
uint32_t GetIndexOfNextBranchInstruction(uint32_t start,
|
||||||
Target &target,
|
Target &target,
|
||||||
bool ignore_calls) const;
|
bool ignore_calls,
|
||||||
|
bool *found_calls) const;
|
||||||
|
|
||||||
uint32_t GetIndexOfInstructionAtLoadAddress(lldb::addr_t load_addr,
|
uint32_t GetIndexOfInstructionAtLoadAddress(lldb::addr_t load_addr,
|
||||||
Target &target);
|
Target &target);
|
||||||
|
|
|
@ -76,6 +76,12 @@ protected:
|
||||||
lldb::BreakpointSP m_next_branch_bp_sp;
|
lldb::BreakpointSP m_next_branch_bp_sp;
|
||||||
bool m_use_fast_step;
|
bool m_use_fast_step;
|
||||||
bool m_given_ranges_only;
|
bool m_given_ranges_only;
|
||||||
|
bool m_found_calls = false; // When we set the next branch breakpoint for
|
||||||
|
// step over, we now extend them past call insns
|
||||||
|
// that directly return. But if we do that we
|
||||||
|
// need to run all threads, or we might cause
|
||||||
|
// deadlocks. This tells us whether we found
|
||||||
|
// any calls in setting the next branch breakpoint.
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<lldb::DisassemblerSP> m_instruction_ranges;
|
std::vector<lldb::DisassemblerSP> m_instruction_ranges;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
C_SOURCES := locking.c
|
CXX_SOURCES := locking.cpp
|
||||||
ENABLE_THREADS := YES
|
ENABLE_THREADS := YES
|
||||||
|
|
||||||
include Makefile.rules
|
include Makefile.rules
|
||||||
|
|
|
@ -16,9 +16,6 @@ class ExprDoesntDeadlockTestCase(TestBase):
|
||||||
mydir = TestBase.compute_mydir(__file__)
|
mydir = TestBase.compute_mydir(__file__)
|
||||||
|
|
||||||
@expectedFailureAll(oslist=['freebsd'], bugnumber='llvm.org/pr17946')
|
@expectedFailureAll(oslist=['freebsd'], bugnumber='llvm.org/pr17946')
|
||||||
@expectedFailureAll(
|
|
||||||
oslist=["windows"],
|
|
||||||
bugnumber="Windows doesn't have pthreads, test needs to be ported")
|
|
||||||
@add_test_categories(["basic_process"])
|
@add_test_categories(["basic_process"])
|
||||||
def test_with_run_command(self):
|
def test_with_run_command(self):
|
||||||
"""Test that expr will time out and allow other threads to run if it blocks."""
|
"""Test that expr will time out and allow other threads to run if it blocks."""
|
||||||
|
@ -32,7 +29,7 @@ class ExprDoesntDeadlockTestCase(TestBase):
|
||||||
# Now create a breakpoint at source line before call_me_to_get_lock
|
# Now create a breakpoint at source line before call_me_to_get_lock
|
||||||
# gets called.
|
# gets called.
|
||||||
|
|
||||||
main_file_spec = lldb.SBFileSpec("locking.c")
|
main_file_spec = lldb.SBFileSpec("locking.cpp")
|
||||||
breakpoint = target.BreakpointCreateBySourceRegex(
|
breakpoint = target.BreakpointCreateBySourceRegex(
|
||||||
'Break here', main_file_spec)
|
'Break here', main_file_spec)
|
||||||
if self.TraceOn():
|
if self.TraceOn():
|
||||||
|
@ -55,6 +52,6 @@ class ExprDoesntDeadlockTestCase(TestBase):
|
||||||
|
|
||||||
frame0 = thread.GetFrameAtIndex(0)
|
frame0 = thread.GetFrameAtIndex(0)
|
||||||
|
|
||||||
var = frame0.EvaluateExpression("call_me_to_get_lock()")
|
var = frame0.EvaluateExpression("call_me_to_get_lock(get_int())")
|
||||||
self.assertTrue(var.IsValid())
|
self.assertTrue(var.IsValid())
|
||||||
self.assertTrue(var.GetValueAsSigned(0) == 567)
|
self.assertEqual(var.GetValueAsSigned(0), 567)
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
#include <pthread.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
pthread_mutex_t contended_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
|
|
||||||
pthread_mutex_t control_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
pthread_cond_t control_condition;
|
|
||||||
|
|
||||||
pthread_mutex_t thread_started_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
pthread_cond_t thread_started_condition;
|
|
||||||
|
|
||||||
// This function runs in a thread. The locking dance is to make sure that
|
|
||||||
// by the time the main thread reaches the pthread_join below, this thread
|
|
||||||
// has for sure acquired the contended_mutex. So then the call_me_to_get_lock
|
|
||||||
// function will block trying to get the mutex, and only succeed once it
|
|
||||||
// signals this thread, then lets it run to wake up from the cond_wait and
|
|
||||||
// release the mutex.
|
|
||||||
|
|
||||||
void *
|
|
||||||
lock_acquirer_1 (void *input)
|
|
||||||
{
|
|
||||||
pthread_mutex_lock (&contended_mutex);
|
|
||||||
|
|
||||||
// Grab this mutex, that will ensure that the main thread
|
|
||||||
// is in its cond_wait for it (since that's when it drops the mutex.
|
|
||||||
|
|
||||||
pthread_mutex_lock (&thread_started_mutex);
|
|
||||||
pthread_mutex_unlock(&thread_started_mutex);
|
|
||||||
|
|
||||||
// Now signal the main thread that it can continue, we have the contended lock
|
|
||||||
// so the call to call_me_to_get_lock won't make any progress till this
|
|
||||||
// thread gets a chance to run.
|
|
||||||
|
|
||||||
pthread_mutex_lock (&control_mutex);
|
|
||||||
|
|
||||||
pthread_cond_signal (&thread_started_condition);
|
|
||||||
|
|
||||||
pthread_cond_wait (&control_condition, &control_mutex);
|
|
||||||
|
|
||||||
pthread_mutex_unlock (&contended_mutex);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
call_me_to_get_lock ()
|
|
||||||
{
|
|
||||||
pthread_cond_signal (&control_condition);
|
|
||||||
pthread_mutex_lock (&contended_mutex);
|
|
||||||
return 567;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main ()
|
|
||||||
{
|
|
||||||
pthread_t thread_1;
|
|
||||||
|
|
||||||
pthread_cond_init (&control_condition, NULL);
|
|
||||||
pthread_cond_init (&thread_started_condition, NULL);
|
|
||||||
|
|
||||||
pthread_mutex_lock (&thread_started_mutex);
|
|
||||||
|
|
||||||
pthread_create (&thread_1, NULL, lock_acquirer_1, NULL);
|
|
||||||
|
|
||||||
pthread_cond_wait (&thread_started_condition, &thread_started_mutex);
|
|
||||||
|
|
||||||
pthread_mutex_lock (&control_mutex);
|
|
||||||
pthread_mutex_unlock (&control_mutex);
|
|
||||||
|
|
||||||
// Break here. At this point the other thread will have the contended_mutex,
|
|
||||||
// and be sitting in its cond_wait for the control condition. So there is
|
|
||||||
// no way that our by-hand calling of call_me_to_get_lock will proceed
|
|
||||||
// without running the first thread at least somewhat.
|
|
||||||
|
|
||||||
call_me_to_get_lock();
|
|
||||||
pthread_join (thread_1, NULL);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
std::mutex contended_mutex;
|
||||||
|
|
||||||
|
std::mutex control_mutex;
|
||||||
|
std::condition_variable control_condition;
|
||||||
|
|
||||||
|
std::mutex thread_started_mutex;
|
||||||
|
std::condition_variable thread_started_condition;
|
||||||
|
|
||||||
|
// This function runs in a thread. The locking dance is to make sure that
|
||||||
|
// by the time the main thread reaches the pthread_join below, this thread
|
||||||
|
// has for sure acquired the contended_mutex. So then the call_me_to_get_lock
|
||||||
|
// function will block trying to get the mutex, and only succeed once it
|
||||||
|
// signals this thread, then lets it run to wake up from the cond_wait and
|
||||||
|
// release the mutex.
|
||||||
|
|
||||||
|
void
|
||||||
|
lock_acquirer_1 (void)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> contended_lock(contended_mutex);
|
||||||
|
|
||||||
|
// Grab this mutex, that will ensure that the main thread
|
||||||
|
// is in its cond_wait for it (since that's when it drops the mutex.
|
||||||
|
|
||||||
|
thread_started_mutex.lock();
|
||||||
|
thread_started_mutex.unlock();
|
||||||
|
|
||||||
|
// Now signal the main thread that it can continue, we have the contended lock
|
||||||
|
// so the call to call_me_to_get_lock won't make any progress till this
|
||||||
|
// thread gets a chance to run.
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> control_lock(control_mutex);
|
||||||
|
|
||||||
|
thread_started_condition.notify_all();
|
||||||
|
|
||||||
|
control_condition.wait(control_lock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
call_me_to_get_lock (int ret_val)
|
||||||
|
{
|
||||||
|
control_condition.notify_all();
|
||||||
|
contended_mutex.lock();
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
get_int() {
|
||||||
|
return 567;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> thread_started_lock(thread_started_mutex);
|
||||||
|
|
||||||
|
std::thread thread_1(lock_acquirer_1);
|
||||||
|
|
||||||
|
thread_started_condition.wait(thread_started_lock);
|
||||||
|
|
||||||
|
control_mutex.lock();
|
||||||
|
control_mutex.unlock();
|
||||||
|
|
||||||
|
// Break here. At this point the other thread will have the contended_mutex,
|
||||||
|
// and be sitting in its cond_wait for the control condition. So there is
|
||||||
|
// no way that our by-hand calling of call_me_to_get_lock will proceed
|
||||||
|
// without running the first thread at least somewhat.
|
||||||
|
|
||||||
|
int result = call_me_to_get_lock(get_int());
|
||||||
|
thread_1.join();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
CXX_SOURCES := locking.cpp
|
||||||
|
ENABLE_THREADS := YES
|
||||||
|
|
||||||
|
include Makefile.rules
|
|
@ -0,0 +1,30 @@
|
||||||
|
"""
|
||||||
|
Test that step over will let other threads run when necessary
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
|
||||||
|
import lldb
|
||||||
|
from lldbsuite.test.decorators import *
|
||||||
|
from lldbsuite.test.lldbtest import *
|
||||||
|
from lldbsuite.test import lldbutil
|
||||||
|
|
||||||
|
|
||||||
|
class StepOverDoesntDeadlockTestCase(TestBase):
|
||||||
|
|
||||||
|
mydir = TestBase.compute_mydir(__file__)
|
||||||
|
|
||||||
|
def test_step_over(self):
|
||||||
|
"""Test that when step over steps over a function it lets other threads run."""
|
||||||
|
self.build()
|
||||||
|
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
|
||||||
|
"without running the first thread at least somewhat",
|
||||||
|
lldb.SBFileSpec("locking.cpp"))
|
||||||
|
# This is just testing that the step over actually completes.
|
||||||
|
# If the test fails this step never return, so failure is really
|
||||||
|
# signaled by the test timing out.
|
||||||
|
|
||||||
|
thread.StepOver()
|
||||||
|
state = process.GetState()
|
||||||
|
self.assertEqual(state, lldb.eStateStopped)
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
std::mutex contended_mutex;
|
||||||
|
|
||||||
|
std::mutex control_mutex;
|
||||||
|
std::condition_variable control_condition;
|
||||||
|
|
||||||
|
std::mutex thread_started_mutex;
|
||||||
|
std::condition_variable thread_started_condition;
|
||||||
|
|
||||||
|
// This function runs in a thread. The locking dance is to make sure that
|
||||||
|
// by the time the main thread reaches the pthread_join below, this thread
|
||||||
|
// has for sure acquired the contended_mutex. So then the call_me_to_get_lock
|
||||||
|
// function will block trying to get the mutex, and only succeed once it
|
||||||
|
// signals this thread, then lets it run to wake up from the cond_wait and
|
||||||
|
// release the mutex.
|
||||||
|
|
||||||
|
void
|
||||||
|
lock_acquirer_1 (void)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> contended_lock(contended_mutex);
|
||||||
|
|
||||||
|
// Grab this mutex, that will ensure that the main thread
|
||||||
|
// is in its cond_wait for it (since that's when it drops the mutex.
|
||||||
|
|
||||||
|
thread_started_mutex.lock();
|
||||||
|
thread_started_mutex.unlock();
|
||||||
|
|
||||||
|
// Now signal the main thread that it can continue, we have the contended lock
|
||||||
|
// so the call to call_me_to_get_lock won't make any progress till this
|
||||||
|
// thread gets a chance to run.
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> control_lock(control_mutex);
|
||||||
|
|
||||||
|
thread_started_condition.notify_all();
|
||||||
|
|
||||||
|
control_condition.wait(control_lock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
call_me_to_get_lock (int ret_val)
|
||||||
|
{
|
||||||
|
control_condition.notify_all();
|
||||||
|
contended_mutex.lock();
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
get_int() {
|
||||||
|
return 567;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main ()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> thread_started_lock(thread_started_mutex);
|
||||||
|
|
||||||
|
std::thread thread_1(lock_acquirer_1);
|
||||||
|
|
||||||
|
thread_started_condition.wait(thread_started_lock);
|
||||||
|
|
||||||
|
control_mutex.lock();
|
||||||
|
control_mutex.unlock();
|
||||||
|
|
||||||
|
// Break here. At this point the other thread will have the contended_mutex,
|
||||||
|
// and be sitting in its cond_wait for the control condition. So there is
|
||||||
|
// no way that our by-hand calling of call_me_to_get_lock will proceed
|
||||||
|
// without running the first thread at least somewhat.
|
||||||
|
|
||||||
|
int result = call_me_to_get_lock(get_int());
|
||||||
|
thread_1.join();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
|
@ -1101,15 +1101,22 @@ void InstructionList::Append(lldb::InstructionSP &inst_sp) {
|
||||||
uint32_t
|
uint32_t
|
||||||
InstructionList::GetIndexOfNextBranchInstruction(uint32_t start,
|
InstructionList::GetIndexOfNextBranchInstruction(uint32_t start,
|
||||||
Target &target,
|
Target &target,
|
||||||
bool ignore_calls) const {
|
bool ignore_calls,
|
||||||
|
bool *found_calls) const {
|
||||||
size_t num_instructions = m_instructions.size();
|
size_t num_instructions = m_instructions.size();
|
||||||
|
|
||||||
uint32_t next_branch = UINT32_MAX;
|
uint32_t next_branch = UINT32_MAX;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
|
if (found_calls)
|
||||||
|
*found_calls = false;
|
||||||
for (i = start; i < num_instructions; i++) {
|
for (i = start; i < num_instructions; i++) {
|
||||||
if (m_instructions[i]->DoesBranch()) {
|
if (m_instructions[i]->DoesBranch()) {
|
||||||
if (ignore_calls && m_instructions[i]->IsCall())
|
if (ignore_calls && m_instructions[i]->IsCall()) {
|
||||||
|
if (found_calls)
|
||||||
|
*found_calls = true;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
next_branch = i;
|
next_branch = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5800,7 +5800,8 @@ Process::AdvanceAddressToNextBranchInstruction(Address default_stop_addr,
|
||||||
|
|
||||||
uint32_t branch_index =
|
uint32_t branch_index =
|
||||||
insn_list->GetIndexOfNextBranchInstruction(insn_offset, target,
|
insn_list->GetIndexOfNextBranchInstruction(insn_offset, target,
|
||||||
false /* ignore_calls*/);
|
false /* ignore_calls*/,
|
||||||
|
nullptr);
|
||||||
if (branch_index == UINT32_MAX) {
|
if (branch_index == UINT32_MAX) {
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,8 +238,18 @@ lldb::FrameComparison ThreadPlanStepRange::CompareCurrentFrameToStartFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ThreadPlanStepRange::StopOthers() {
|
bool ThreadPlanStepRange::StopOthers() {
|
||||||
return (m_stop_others == lldb::eOnlyThisThread ||
|
switch (m_stop_others) {
|
||||||
m_stop_others == lldb::eOnlyDuringStepping);
|
case lldb::eOnlyThisThread:
|
||||||
|
return true;
|
||||||
|
case lldb::eOnlyDuringStepping:
|
||||||
|
// If there is a call in the range of the next branch breakpoint,
|
||||||
|
// then we should always run all threads, since a call can execute
|
||||||
|
// arbitrary code which might for instance take a lock that's held
|
||||||
|
// by another thread.
|
||||||
|
return !m_found_calls;
|
||||||
|
case lldb::eAllThreads:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstructionList *ThreadPlanStepRange::GetInstructionsForAddress(
|
InstructionList *ThreadPlanStepRange::GetInstructionsForAddress(
|
||||||
|
@ -292,6 +302,7 @@ void ThreadPlanStepRange::ClearNextBranchBreakpoint() {
|
||||||
GetTarget().RemoveBreakpointByID(m_next_branch_bp_sp->GetID());
|
GetTarget().RemoveBreakpointByID(m_next_branch_bp_sp->GetID());
|
||||||
m_next_branch_bp_sp.reset();
|
m_next_branch_bp_sp.reset();
|
||||||
m_could_not_resolve_hw_bp = false;
|
m_could_not_resolve_hw_bp = false;
|
||||||
|
m_found_calls = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,6 +316,9 @@ bool ThreadPlanStepRange::SetNextBranchBreakpoint() {
|
||||||
if (!m_use_fast_step)
|
if (!m_use_fast_step)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// clear the m_found_calls, we'll rediscover it for this range.
|
||||||
|
m_found_calls = false;
|
||||||
|
|
||||||
lldb::addr_t cur_addr = GetThread().GetRegisterContext()->GetPC();
|
lldb::addr_t cur_addr = GetThread().GetRegisterContext()->GetPC();
|
||||||
// Find the current address in our address ranges, and fetch the disassembly
|
// Find the current address in our address ranges, and fetch the disassembly
|
||||||
// if we haven't already:
|
// if we haven't already:
|
||||||
|
@ -317,9 +331,11 @@ bool ThreadPlanStepRange::SetNextBranchBreakpoint() {
|
||||||
else {
|
else {
|
||||||
Target &target = GetThread().GetProcess()->GetTarget();
|
Target &target = GetThread().GetProcess()->GetTarget();
|
||||||
const bool ignore_calls = GetKind() == eKindStepOverRange;
|
const bool ignore_calls = GetKind() == eKindStepOverRange;
|
||||||
|
bool found_calls;
|
||||||
uint32_t branch_index =
|
uint32_t branch_index =
|
||||||
instructions->GetIndexOfNextBranchInstruction(pc_index, target,
|
instructions->GetIndexOfNextBranchInstruction(pc_index, target,
|
||||||
ignore_calls);
|
ignore_calls,
|
||||||
|
&m_found_calls);
|
||||||
|
|
||||||
Address run_to_address;
|
Address run_to_address;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue