| """ | |
| Test SBThread APIs. | |
| """ | |
| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| from lldbsuite.test.lldbutil import get_stopped_thread, get_caller_symbol | |
| class ThreadAPITestCase(TestBase): | |
| SHARED_BUILD_TESTCASE = False | |
| def test_get_process(self): | |
| """Test Python SBThread.GetProcess() API.""" | |
| self.build() | |
| self.get_process() | |
| def test_get_stop_description(self): | |
| """Test Python SBThread.GetStopDescription() API.""" | |
| self.build() | |
| self.get_stop_description() | |
| def test_run_to_address(self): | |
| """Test Python SBThread.RunToAddress() API.""" | |
| # We build a different executable than the default build() does. | |
| d = {"CXX_SOURCES": "main2.cpp", "EXE": self.exe_name} | |
| self.build(dictionary=d) | |
| self.setTearDownCleanup(dictionary=d) | |
| self.run_to_address(self.exe_name) | |
| @skipIfAsan # The output looks different under ASAN. | |
| @expectedFailureAll(oslist=["linux"], archs=["arm$"], bugnumber="llvm.org/pr45892") | |
| @expectedFailureAll(oslist=["windows"]) | |
| def test_step_out_of_malloc_into_function_b(self): | |
| """Test Python SBThread.StepOut() API to step out of a malloc call where the call site is at function b().""" | |
| # We build a different executable than the default build() does. | |
| d = {"CXX_SOURCES": "main2.cpp", "EXE": self.exe_name} | |
| self.build(dictionary=d) | |
| self.setTearDownCleanup(dictionary=d) | |
| self.step_out_of_malloc_into_function_b(self.exe_name) | |
| def test_step_over_3_times(self): | |
| """Test Python SBThread.StepOver() API.""" | |
| # We build a different executable than the default build() does. | |
| d = {"CXX_SOURCES": "main2.cpp", "EXE": self.exe_name} | |
| self.build(dictionary=d) | |
| self.setTearDownCleanup(dictionary=d) | |
| self.step_over_3_times(self.exe_name) | |
| def test_negative_indexing(self): | |
| """Test SBThread.frame with negative indexes.""" | |
| self.build() | |
| self.validate_negative_indexing() | |
| def test_StepInstruction(self): | |
| """Test that StepInstruction preserves the plan stack.""" | |
| self.build() | |
| self.step_instruction_in_called_function() | |
| def setUp(self): | |
| # Call super's setUp(). | |
| TestBase.setUp(self) | |
| # Find the line number within main.cpp to break inside main(). | |
| self.break_line = line_number( | |
| "main.cpp", "// Set break point at this line and check variable 'my_char'." | |
| ) | |
| # Find the line numbers within main2.cpp for | |
| # step_out_of_malloc_into_function_b() and step_over_3_times(). | |
| self.step_out_of_malloc = line_number( | |
| "main2.cpp", "// thread step-out of malloc into function b." | |
| ) | |
| self.after_3_step_overs = line_number( | |
| "main2.cpp", "// we should reach here after 3 step-over's." | |
| ) | |
| # We'll use the test method name as the exe_name for executable | |
| # compiled from main2.cpp. | |
| self.exe_name = self.testMethodName | |
| def get_process(self): | |
| """Test Python SBThread.GetProcess() API.""" | |
| exe = self.getBuildArtifact("a.out") | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| breakpoint = target.BreakpointCreateByLocation("main.cpp", self.break_line) | |
| self.assertTrue(breakpoint, VALID_BREAKPOINT) | |
| self.runCmd("breakpoint list") | |
| # Launch the process, and do not stop at the entry point. | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) | |
| self.assertTrue( | |
| thread.IsValid(), "There should be a thread stopped due to breakpoint" | |
| ) | |
| self.runCmd("process status") | |
| proc_of_thread = thread.GetProcess() | |
| self.trace("proc_of_thread:", proc_of_thread) | |
| self.assertEqual(proc_of_thread.GetProcessID(), process.GetProcessID()) | |
| def get_stop_description(self): | |
| """Test Python SBThread.GetStopDescription() API.""" | |
| exe = self.getBuildArtifact("a.out") | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| breakpoint = target.BreakpointCreateByLocation("main.cpp", self.break_line) | |
| self.assertTrue(breakpoint, VALID_BREAKPOINT) | |
| # self.runCmd("breakpoint list") | |
| # Launch the process, and do not stop at the entry point. | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) | |
| self.assertTrue( | |
| thread.IsValid(), "There should be a thread stopped due to breakpoint" | |
| ) | |
| # Get the stop reason. GetStopDescription expects that we pass in the size of the description | |
| # we expect plus an additional byte for the null terminator. | |
| # Test with a buffer that is exactly as large as the expected stop reason. | |
| self.assertEqual( | |
| "breakpoint 1.1", thread.GetStopDescription(len("breakpoint 1.1") + 1) | |
| ) | |
| # Test some smaller buffer sizes. | |
| self.assertEqual("breakpoint", thread.GetStopDescription(len("breakpoint") + 1)) | |
| self.assertEqual("break", thread.GetStopDescription(len("break") + 1)) | |
| self.assertEqual("b", thread.GetStopDescription(len("b") + 1)) | |
| # Test that we can pass in a much larger size and still get the right output. | |
| self.assertEqual( | |
| "breakpoint 1.1", thread.GetStopDescription(len("breakpoint 1.1") + 100) | |
| ) | |
| # Test the stream variation | |
| stream = lldb.SBStream() | |
| self.assertTrue(thread.GetStopDescription(stream)) | |
| self.assertEqual("breakpoint 1.1", stream.GetData()) | |
| def step_out_of_malloc_into_function_b(self, exe_name): | |
| """Test Python SBThread.StepOut() API to step out of a malloc call where the call site is at function b().""" | |
| exe = self.getBuildArtifact(exe_name) | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| breakpoint = target.BreakpointCreateByName("malloc") | |
| self.assertTrue(breakpoint, VALID_BREAKPOINT) | |
| # Launch the process, and do not stop at the entry point. | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| while True: | |
| thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) | |
| self.assertTrue( | |
| thread.IsValid(), "There should be a thread stopped due to breakpoint" | |
| ) | |
| caller_symbol = get_caller_symbol(thread) | |
| if not caller_symbol: | |
| self.fail("Test failed: could not locate the caller symbol of malloc") | |
| # Our top frame may be an inlined function in malloc() (e.g., on | |
| # FreeBSD). Apply a simple heuristic of stepping out until we find | |
| # a non-malloc caller | |
| while caller_symbol.startswith("malloc"): | |
| thread.StepOut() | |
| self.assertTrue( | |
| thread.IsValid(), "Thread valid after stepping to outer malloc" | |
| ) | |
| caller_symbol = get_caller_symbol(thread) | |
| if caller_symbol == "b(int)": | |
| break | |
| process.Continue() | |
| # On Linux malloc calls itself in some case. Remove the breakpoint because we don't want | |
| # to hit it during step-out. | |
| target.BreakpointDelete(breakpoint.GetID()) | |
| thread.StepOut() | |
| self.runCmd("thread backtrace") | |
| self.assertEqual( | |
| thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), | |
| self.step_out_of_malloc, | |
| "step out of malloc into function b is successful", | |
| ) | |
| def step_over_3_times(self, exe_name): | |
| """Test Python SBThread.StepOver() API.""" | |
| exe = self.getBuildArtifact(exe_name) | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| breakpoint = target.BreakpointCreateByLocation( | |
| "main2.cpp", self.step_out_of_malloc | |
| ) | |
| self.assertTrue(breakpoint, VALID_BREAKPOINT) | |
| self.runCmd("breakpoint list") | |
| # Launch the process, and do not stop at the entry point. | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| self.assertTrue(process, PROCESS_IS_VALID) | |
| # Frame #0 should be on self.step_out_of_malloc. | |
| self.assertState(process.GetState(), lldb.eStateStopped) | |
| thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) | |
| self.assertTrue( | |
| thread.IsValid(), | |
| "There should be a thread stopped due to breakpoint condition", | |
| ) | |
| self.runCmd("thread backtrace") | |
| frame0 = thread.GetFrameAtIndex(0) | |
| lineEntry = frame0.GetLineEntry() | |
| self.assertEqual(lineEntry.GetLine(), self.step_out_of_malloc) | |
| thread.StepOver() | |
| thread.StepOver() | |
| thread.StepOver() | |
| self.runCmd("thread backtrace") | |
| # Verify that we are stopped at the correct source line number in | |
| # main2.cpp. | |
| frame0 = thread.GetFrameAtIndex(0) | |
| lineEntry = frame0.GetLineEntry() | |
| self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonPlanComplete) | |
| # Expected failure with clang as the compiler. | |
| # rdar://problem/9223880 | |
| # | |
| # Which has been fixed on the lldb by compensating for inaccurate line | |
| # table information with r140416. | |
| self.assertEqual(lineEntry.GetLine(), self.after_3_step_overs) | |
| def run_to_address(self, exe_name): | |
| """Test Python SBThread.RunToAddress() API.""" | |
| exe = self.getBuildArtifact(exe_name) | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| breakpoint = target.BreakpointCreateByLocation( | |
| "main2.cpp", self.step_out_of_malloc | |
| ) | |
| self.assertTrue(breakpoint, VALID_BREAKPOINT) | |
| self.runCmd("breakpoint list") | |
| # Launch the process, and do not stop at the entry point. | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| self.assertTrue(process, PROCESS_IS_VALID) | |
| # Frame #0 should be on self.step_out_of_malloc. | |
| self.assertState(process.GetState(), lldb.eStateStopped) | |
| thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) | |
| self.assertTrue( | |
| thread.IsValid(), | |
| "There should be a thread stopped due to breakpoint condition", | |
| ) | |
| self.runCmd("thread backtrace") | |
| frame0 = thread.GetFrameAtIndex(0) | |
| lineEntry = frame0.GetLineEntry() | |
| self.assertEqual(lineEntry.GetLine(), self.step_out_of_malloc) | |
| # Get the start/end addresses for this line entry. | |
| start_addr = lineEntry.GetStartAddress().GetLoadAddress(target) | |
| end_addr = lineEntry.GetEndAddress().GetLoadAddress(target) | |
| if self.TraceOn(): | |
| print("start addr:", hex(start_addr)) | |
| print("end addr:", hex(end_addr)) | |
| # Disable the breakpoint. | |
| self.assertTrue(target.DisableAllBreakpoints()) | |
| self.runCmd("breakpoint list") | |
| thread.StepOver() | |
| thread.StepOver() | |
| thread.StepOver() | |
| self.runCmd("thread backtrace") | |
| # Now ask SBThread to run to the address 'start_addr' we got earlier, which | |
| # corresponds to self.step_out_of_malloc line entry's start address. | |
| thread.RunToAddress(start_addr) | |
| self.runCmd("process status") | |
| # self.runCmd("thread backtrace") | |
| def validate_negative_indexing(self): | |
| exe = self.getBuildArtifact("a.out") | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| breakpoint = target.BreakpointCreateByLocation("main.cpp", self.break_line) | |
| self.assertTrue(breakpoint, VALID_BREAKPOINT) | |
| self.runCmd("breakpoint list") | |
| # Launch the process, and do not stop at the entry point. | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) | |
| self.assertTrue( | |
| thread.IsValid(), "There should be a thread stopped due to breakpoint" | |
| ) | |
| self.runCmd("process status") | |
| pos_range = range(thread.num_frames) | |
| neg_range = range(thread.num_frames, 0, -1) | |
| for pos, neg in zip(pos_range, neg_range): | |
| self.assertEqual(thread.frame[pos].idx, thread.frame[-neg].idx) | |
| def step_instruction_in_called_function(self): | |
| main_file_spec = lldb.SBFileSpec("main.cpp") | |
| target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( | |
| self, "Set break point at this line", main_file_spec | |
| ) | |
| options = lldb.SBExpressionOptions() | |
| options.SetIgnoreBreakpoints(False) | |
| call_me_bkpt = target.BreakpointCreateBySourceRegex( | |
| "Set a breakpoint in call_me", main_file_spec | |
| ) | |
| self.assertGreater( | |
| call_me_bkpt.GetNumLocations(), 0, "Got at least one location in call_me" | |
| ) | |
| # On Windows this may be the full name "void __cdecl call_me(bool)", | |
| # elsewhere it's just "call_me(bool)". | |
| expected_name = r".*call_me\(bool\)$" | |
| # Now run the expression, this will fail because we stopped at a breakpoint: | |
| self.runCmd("expr -i 0 -- call_me(true)", check=False) | |
| # Now we should be stopped in call_me: | |
| self.assertRegex( | |
| thread.frames[0].name, expected_name, "Stopped in call_me(bool)" | |
| ) | |
| # Now do a various API steps. These should not cause the expression context to get unshipped: | |
| thread.StepInstruction(False) | |
| self.assertRegex( | |
| thread.frames[0].name, | |
| expected_name, | |
| "Still in call_me(bool) after StepInstruction", | |
| ) | |
| thread.StepInstruction(True) | |
| self.assertRegex( | |
| thread.frames[0].name, | |
| expected_name, | |
| "Still in call_me(bool) after NextInstruction", | |
| ) | |
| thread.StepInto() | |
| self.assertRegex( | |
| thread.frames[0].name, | |
| expected_name, | |
| "Still in call_me(bool) after StepInto", | |
| ) | |
| thread.StepOver(False) | |
| self.assertRegex( | |
| thread.frames[0].name, | |
| expected_name, | |
| "Still in call_me(bool) after StepOver", | |
| ) |