forked from OSchip/llvm-project
Add a test for debugserver handling threads suspended from within a program.
Mach allows you to suspend and resume other threads within a program, so debugserver has to be careful not to interfere with this when it goes to supend and resume threads while stepping over breakpoints and calling functions. Even trickier, if you call a function on a suspended thread, it has to resume the thread to get the expression to run, and then suspend it properly when done. This all works already, but there wasn't a test for it. Adding that here. This same test could be written for a unix that supports pthread_{suspend,resume}_np, but macOS doesn't support these calls, only the mach version. It doesn't look like a lot of Linux'es support this (AIX does apparently...) And IIUC Windows allows you to suspend and resume other threads, but the code for that would look pretty different than this main.c. So for simplicity's sake I wrote this test for Darwin-only.
This commit is contained in:
parent
b21c799952
commit
4570f2c7cf
|
@ -0,0 +1,4 @@
|
|||
C_SOURCES := main.c
|
||||
CFLAGS_EXTRAS := -std=c99
|
||||
|
||||
include Makefile.rules
|
|
@ -0,0 +1,108 @@
|
|||
"""
|
||||
Make sure that if threads are suspended outside of lldb, debugserver
|
||||
won't make them run, even if we call an expression on the thread.
|
||||
"""
|
||||
|
||||
import lldb
|
||||
from lldbsuite.test.decorators import *
|
||||
import lldbsuite.test.lldbutil as lldbutil
|
||||
from lldbsuite.test.lldbtest import *
|
||||
|
||||
class TestSuspendedThreadHandling(TestBase):
|
||||
|
||||
mydir = TestBase.compute_mydir(__file__)
|
||||
|
||||
NO_DEBUG_INFO_TESTCASE = True
|
||||
|
||||
@skipUnlessDarwin
|
||||
def test_suspended_threads(self):
|
||||
"""Test that debugserver doesn't disturb the suspend count of a thread
|
||||
that has been suspended from within a program, when navigating breakpoints
|
||||
on other threads, or calling functions both on the suspended thread and
|
||||
on other threads."""
|
||||
self.build()
|
||||
self.main_source_file = lldb.SBFileSpec("main.c")
|
||||
self.suspended_thread_test()
|
||||
|
||||
def setUp(self):
|
||||
# Call super's setUp().
|
||||
TestBase.setUp(self)
|
||||
# Set up your test case here. If your test doesn't need any set up then
|
||||
# remove this method from your TestCase class.
|
||||
|
||||
def try_an_expression(self, thread, correct_value, test_bp):
|
||||
frame = thread.frames[0]
|
||||
|
||||
value = frame.EvaluateExpression('function_to_call()')
|
||||
self.assertTrue(value.GetError().Success(), "Successfully called the function")
|
||||
self.assertEqual(value.GetValueAsSigned(), correct_value, "Got expected value for expression")
|
||||
|
||||
# Again, make sure we didn't let the suspend thread breakpoint run:
|
||||
self.assertEqual(test_bp.GetHitCount(), 0, "First expression allowed the suspend thread to run")
|
||||
|
||||
|
||||
def make_bkpt(self, pattern):
|
||||
bp = self.target.BreakpointCreateBySourceRegex(pattern, self.main_source_file)
|
||||
self.assertEqual(bp.GetNumLocations(), 1, "Locations for %s"%(pattern))
|
||||
return bp
|
||||
|
||||
def suspended_thread_test(self):
|
||||
(self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
|
||||
"Stop here to get things going", self.main_source_file)
|
||||
|
||||
# Make in the running thread, so the we will have to stop a number of times
|
||||
# while handling breakpoints. The first couple of times we hit it we will
|
||||
# run expressions as well. Make sure we don't let the suspended thread run
|
||||
# during those operations.
|
||||
rt_bp = self.make_bkpt("Break here to show we can handle breakpoints")
|
||||
|
||||
# Make a breakpoint that we will hit when the running thread exits:
|
||||
rt_exit_bp = self.make_bkpt("Break here after thread_join")
|
||||
|
||||
# Make a breakpoint in the suspended thread. We should not hit this till we
|
||||
# resume it after joining the running thread.
|
||||
st_bp = self.make_bkpt("We allowed the suspend thread to run")
|
||||
|
||||
# Make a breakpoint after pthread_join of the suspend thread to ensure
|
||||
# that we didn't keep the thread from exiting normally
|
||||
st_exit_bp = self.make_bkpt(" Break here to make sure the thread exited normally")
|
||||
|
||||
threads = lldbutil.continue_to_breakpoint(process, rt_bp)
|
||||
self.assertEqual(len(threads), 1, "Hit the running_func breakpoint")
|
||||
|
||||
# Make sure we didn't hit the suspend thread breakpoint:
|
||||
self.assertEqual(st_bp.GetHitCount(), 0, "Continue allowed the suspend thread to run")
|
||||
|
||||
# Now try an expression on the running thread:
|
||||
self.try_an_expression(threads[0], 0, st_bp)
|
||||
|
||||
# Continue, and check the same things:
|
||||
threads = lldbutil.continue_to_breakpoint(process, rt_bp)
|
||||
self.assertEqual(len(threads), 1, "We didn't hit running breakpoint")
|
||||
|
||||
# Try an expression on the suspended thread:
|
||||
thread = lldb.SBThread()
|
||||
for thread in process.threads:
|
||||
th_name = thread.GetName()
|
||||
if th_name == None:
|
||||
continue
|
||||
if "Look for me" in th_name:
|
||||
break
|
||||
self.assertTrue(thread.IsValid(), "We found the suspend thread.")
|
||||
self.try_an_expression(thread, 1, st_bp)
|
||||
|
||||
# Now set the running thread breakpoint to auto-continue and let it
|
||||
# run a bit to make sure we still don't let the suspend thread run.
|
||||
rt_bp.SetAutoContinue(True)
|
||||
threads = lldbutil.continue_to_breakpoint(process, rt_exit_bp)
|
||||
self.assertEqual(len(threads), 1)
|
||||
self.assertEqual(st_bp.GetHitCount(), 0, "Continue again let suspended thread run")
|
||||
|
||||
# Now continue and we SHOULD hit the suspend_func breakpoint:
|
||||
threads = lldbutil.continue_to_breakpoint(process, st_bp)
|
||||
self.assertEqual(len(threads), 1, "The thread resumed successfully")
|
||||
|
||||
# Finally, continue again and we should get out of the last pthread_join
|
||||
# and the process should be about to exit
|
||||
threads = lldbutil.continue_to_breakpoint(process, st_exit_bp)
|
||||
self.assertEqual(len(threads), 1, "pthread_join exited successfully")
|
|
@ -0,0 +1,58 @@
|
|||
#include <pthread.h>
|
||||
#include <mach/thread_act.h>
|
||||
#include <unistd.h>
|
||||
|
||||
pthread_mutex_t suspend_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_t signal_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_cond_t signal_cond = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
int g_running_count = 0;
|
||||
|
||||
int
|
||||
function_to_call() {
|
||||
return g_running_count;
|
||||
}
|
||||
|
||||
void *
|
||||
suspend_func (void *unused) {
|
||||
pthread_setname_np("Look for me");
|
||||
pthread_cond_signal(&signal_cond);
|
||||
pthread_mutex_lock(&suspend_mutex);
|
||||
|
||||
return NULL; // We allowed the suspend thread to run
|
||||
}
|
||||
|
||||
void *
|
||||
running_func (void *input) {
|
||||
while (g_running_count < 10) {
|
||||
usleep (100);
|
||||
g_running_count++; // Break here to show we can handle breakpoints
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
pthread_t suspend_thread; // Stop here to get things going
|
||||
|
||||
pthread_mutex_lock(&suspend_mutex);
|
||||
pthread_mutex_lock(&signal_mutex);
|
||||
pthread_create(&suspend_thread, NULL, suspend_func, NULL);
|
||||
|
||||
pthread_cond_wait(&signal_cond, &signal_mutex);
|
||||
|
||||
mach_port_t th_port = pthread_mach_thread_np(suspend_thread);
|
||||
thread_suspend(th_port);
|
||||
|
||||
pthread_mutex_unlock(&suspend_mutex);
|
||||
|
||||
pthread_t running_thread;
|
||||
pthread_create(&running_thread, NULL, running_func, NULL);
|
||||
|
||||
pthread_join(running_thread, NULL);
|
||||
thread_resume(th_port); // Break here after thread_join
|
||||
|
||||
pthread_join(suspend_thread, NULL);
|
||||
return 0; // Break here to make sure the thread exited normally
|
||||
}
|
Loading…
Reference in New Issue