1"""Test stepping over and into inlined functions.""" 2 3import lldb 4from lldbsuite.test.decorators import * 5from lldbsuite.test.lldbtest import * 6from lldbsuite.test import lldbutil 7 8 9class TestInlineStepping(TestBase): 10 @add_test_categories(["pyapi"]) 11 @skipIf(oslist=["windows"], archs=["aarch64"]) # Flaky on buildbot 12 @expectedFailureAll( 13 compiler="icc", 14 bugnumber="# Not really a bug. ICC combines two inlined functions.", 15 ) 16 def test_with_python_api(self): 17 """Test stepping over and into inlined functions.""" 18 self.build() 19 self.inline_stepping() 20 21 @add_test_categories(["pyapi"]) 22 def test_step_over_with_python_api(self): 23 """Test stepping over and into inlined functions.""" 24 self.build() 25 self.inline_stepping_step_over() 26 27 @add_test_categories(["pyapi"]) 28 @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr32343") 29 def test_step_in_template_with_python_api(self): 30 """Test stepping in to templated functions.""" 31 self.build() 32 self.step_in_template() 33 34 @add_test_categories(["pyapi"]) 35 def test_virtual_inline_stepping(self): 36 """Test stepping through a virtual inlined call stack""" 37 self.build() 38 self.virtual_inline_stepping() 39 40 def setUp(self): 41 # Call super's setUp(). 42 TestBase.setUp(self) 43 # Find the line numbers that we will step to in main: 44 self.main_source = "calling.cpp" 45 self.source_lines = {} 46 functions = [ 47 "caller_ref_1", 48 "caller_ref_2", 49 "inline_ref_1", 50 "inline_ref_2", 51 "called_by_inline_ref", 52 "caller_trivial_1", 53 "caller_trivial_2", 54 "inline_trivial_1", 55 "inline_trivial_2", 56 "called_by_inline_trivial", 57 ] 58 for name in functions: 59 self.source_lines[name] = line_number( 60 self.main_source, "// In " + name + "." 61 ) 62 self.main_source_spec = lldb.SBFileSpec(self.main_source) 63 64 def do_step(self, step_type, destination_line_entry, test_stack_depth): 65 expected_stack_depth = self.thread.GetNumFrames() 66 if step_type == "into": 67 expected_stack_depth += 1 68 self.thread.StepInto() 69 elif step_type == "out": 70 expected_stack_depth -= 1 71 self.thread.StepOut() 72 elif step_type == "over": 73 self.thread.StepOver() 74 else: 75 self.fail("Unrecognized step type: " + step_type) 76 77 threads = lldbutil.get_stopped_threads( 78 self.process, lldb.eStopReasonPlanComplete 79 ) 80 if len(threads) != 1: 81 destination_description = lldb.SBStream() 82 destination_line_entry.GetDescription(destination_description) 83 self.fail( 84 "Failed to stop due to step " 85 + step_type 86 + " operation stepping to: " 87 + destination_description.GetData() 88 ) 89 90 self.thread = threads[0] 91 92 stop_line_entry = self.thread.GetFrameAtIndex(0).GetLineEntry() 93 self.assertTrue(stop_line_entry.IsValid(), "Stop line entry was not valid.") 94 95 # Don't use the line entry equal operator because we don't care about 96 # the column number. 97 stop_at_right_place = ( 98 stop_line_entry.GetFileSpec() == destination_line_entry.GetFileSpec() 99 and stop_line_entry.GetLine() == destination_line_entry.GetLine() 100 ) 101 if not stop_at_right_place: 102 destination_description = lldb.SBStream() 103 destination_line_entry.GetDescription(destination_description) 104 105 actual_description = lldb.SBStream() 106 stop_line_entry.GetDescription(actual_description) 107 108 self.fail( 109 "Step " 110 + step_type 111 + " stopped at wrong place: expected: " 112 + destination_description.GetData() 113 + " got: " 114 + actual_description.GetData() 115 + "." 116 ) 117 118 real_stack_depth = self.thread.GetNumFrames() 119 120 if test_stack_depth and real_stack_depth != expected_stack_depth: 121 destination_description = lldb.SBStream() 122 destination_line_entry.GetDescription(destination_description) 123 self.fail( 124 "Step %s to %s got wrong number of frames, should be: %d was: %d." 125 % ( 126 step_type, 127 destination_description.GetData(), 128 expected_stack_depth, 129 real_stack_depth, 130 ) 131 ) 132 133 def run_step_sequence(self, step_sequence): 134 """This function takes a list of duples instructing how to run the program. The first element in each duple is 135 a source pattern for the target location, and the second is the operation that will take you from the current 136 source location to the target location. It will then run all the steps in the sequence. 137 It will check that you arrived at the expected source location at each step, and that the stack depth changed 138 correctly for the operation in the sequence.""" 139 140 target_line_entry = lldb.SBLineEntry() 141 target_line_entry.SetFileSpec(self.main_source_spec) 142 143 test_stack_depth = True 144 # Work around for <rdar://problem/16363195>, the darwin unwinder seems flakey about whether it duplicates the first frame 145 # or not, which makes counting stack depth unreliable. 146 if self.platformIsDarwin(): 147 test_stack_depth = False 148 149 for step_pattern in step_sequence: 150 step_stop_line = line_number(self.main_source, step_pattern[0]) 151 target_line_entry.SetLine(step_stop_line) 152 self.do_step(step_pattern[1], target_line_entry, test_stack_depth) 153 154 def inline_stepping(self): 155 """Use Python APIs to test stepping over and hitting breakpoints.""" 156 exe = self.getBuildArtifact("a.out") 157 158 target = self.dbg.CreateTarget(exe) 159 self.assertTrue(target, VALID_TARGET) 160 161 break_1_in_main = target.BreakpointCreateBySourceRegex( 162 "// Stop here and step over to set up stepping over.", self.main_source_spec 163 ) 164 self.assertTrue(break_1_in_main, VALID_BREAKPOINT) 165 166 # Now launch the process, and do not stop at entry point. 167 self.process = target.LaunchSimple( 168 None, None, self.get_process_working_directory() 169 ) 170 171 self.assertTrue(self.process, PROCESS_IS_VALID) 172 173 # The stop reason of the thread should be breakpoint. 174 threads = lldbutil.get_threads_stopped_at_breakpoint( 175 self.process, break_1_in_main 176 ) 177 178 if len(threads) != 1: 179 self.fail("Failed to stop at first breakpoint in main.") 180 181 self.thread = threads[0] 182 183 # Step over the inline_value = 0 line to get us to inline_trivial_1 called from main. Doing it this way works 184 # around a bug in lldb where the breakpoint on the containing line of an inlined function with no return value 185 # gets set past the insertion line in the function. 186 # Then test stepping over a simple inlined function. Note, to test all the parts of the inlined stepping 187 # the calls inline_stepping_1 and inline_stepping_2 should line up at the same address, that way we will test 188 # the "virtual" stepping. 189 # FIXME: Put in a check to see if that is true and warn if it is not. 190 191 step_sequence = [ 192 ["// At inline_trivial_1 called from main.", "over"], 193 ["// At first call of caller_trivial_1 in main.", "over"], 194 ] 195 self.run_step_sequence(step_sequence) 196 197 # Now step from caller_ref_1 all the way into called_by_inline_trivial 198 199 step_sequence = [ 200 ["// In caller_trivial_1.", "into"], 201 ["// In caller_trivial_2.", "into"], 202 ["// In inline_trivial_1.", "into"], 203 ["// In inline_trivial_2.", "into"], 204 ["// At caller_by_inline_trivial in inline_trivial_2.", "over"], 205 ["// In called_by_inline_trivial.", "into"], 206 ] 207 self.run_step_sequence(step_sequence) 208 209 # Now run to the inline_trivial_1 just before the immediate step into 210 # inline_trivial_2: 211 212 break_2_in_main = target.BreakpointCreateBySourceRegex( 213 "// At second call of caller_trivial_1 in main.", self.main_source_spec 214 ) 215 self.assertTrue(break_2_in_main, VALID_BREAKPOINT) 216 217 threads = lldbutil.continue_to_breakpoint(self.process, break_2_in_main) 218 self.assertEqual( 219 len(threads), 220 1, 221 "Successfully ran to call site of second caller_trivial_1 call.", 222 ) 223 self.thread = threads[0] 224 225 step_sequence = [ 226 ["// In caller_trivial_1.", "into"], 227 ["// In caller_trivial_2.", "into"], 228 ["// In inline_trivial_1.", "into"], 229 ] 230 self.run_step_sequence(step_sequence) 231 232 # Then call some trivial function, and make sure we end up back where 233 # we were in the inlined call stack: 234 235 frame = self.thread.GetFrameAtIndex(0) 236 before_line_entry = frame.GetLineEntry() 237 value = frame.EvaluateExpression("function_to_call()") 238 after_line_entry = frame.GetLineEntry() 239 240 self.assertEqual( 241 before_line_entry.GetLine(), 242 after_line_entry.GetLine(), 243 "Line entry before and after function calls are the same.", 244 ) 245 246 # Now make sure stepping OVER in the middle of the stack works, and 247 # then check finish from the inlined frame: 248 249 step_sequence = [ 250 ["// At increment in inline_trivial_1.", "over"], 251 ["// At increment in caller_trivial_2.", "out"], 252 ] 253 self.run_step_sequence(step_sequence) 254 255 # Now run to the place in main just before the first call to 256 # caller_ref_1: 257 258 break_3_in_main = target.BreakpointCreateBySourceRegex( 259 "// At first call of caller_ref_1 in main.", self.main_source_spec 260 ) 261 self.assertTrue(break_3_in_main, VALID_BREAKPOINT) 262 263 threads = lldbutil.continue_to_breakpoint(self.process, break_3_in_main) 264 self.assertEqual( 265 len(threads), 1, "Successfully ran to call site of first caller_ref_1 call." 266 ) 267 self.thread = threads[0] 268 269 step_sequence = [ 270 ["// In caller_ref_1.", "into"], 271 ["// In caller_ref_2.", "into"], 272 ["// In inline_ref_1.", "into"], 273 ["// In inline_ref_2.", "into"], 274 ["// In called_by_inline_ref.", "into"], 275 ["// In inline_ref_2.", "out"], 276 ["// In inline_ref_1.", "out"], 277 ["// At increment in inline_ref_1.", "over"], 278 ["// In caller_ref_2.", "out"], 279 ["// At increment in caller_ref_2.", "over"], 280 ] 281 self.run_step_sequence(step_sequence) 282 283 def inline_stepping_step_over(self): 284 """Use Python APIs to test stepping over and hitting breakpoints.""" 285 exe = self.getBuildArtifact("a.out") 286 287 target = self.dbg.CreateTarget(exe) 288 self.assertTrue(target, VALID_TARGET) 289 290 break_1_in_main = target.BreakpointCreateBySourceRegex( 291 "// At second call of caller_ref_1 in main.", self.main_source_spec 292 ) 293 self.assertGreater(break_1_in_main.GetNumLocations(), 0, VALID_BREAKPOINT) 294 295 # Now launch the process, and do not stop at entry point. 296 self.process = target.LaunchSimple( 297 None, None, self.get_process_working_directory() 298 ) 299 300 self.assertTrue(self.process, PROCESS_IS_VALID) 301 302 # The stop reason of the thread should be breakpoint. 303 threads = lldbutil.get_threads_stopped_at_breakpoint( 304 self.process, break_1_in_main 305 ) 306 307 if len(threads) != 1: 308 self.fail("Failed to stop at first breakpoint in main.") 309 310 self.thread = threads[0] 311 312 step_sequence = [ 313 ["// In caller_ref_1.", "into"], 314 ["// In caller_ref_2.", "into"], 315 ["// At increment in caller_ref_2.", "over"], 316 ] 317 self.run_step_sequence(step_sequence) 318 319 # Now make sure that next to a virtual inlined call stack 320 # gets the call stack depth correct. 321 break_2_in_main = target.BreakpointCreateBySourceRegex( 322 "// Call max_value specialized", self.main_source_spec 323 ) 324 self.assertGreater(break_2_in_main.GetNumLocations(), 0, VALID_BREAKPOINT) 325 threads = lldbutil.continue_to_breakpoint(self.process, break_2_in_main) 326 self.assertEqual(len(threads), 1, "Hit our second breakpoint") 327 self.assertEqual(threads[0].id, self.thread.id, "Stopped at right thread") 328 self.thread.StepOver() 329 frame_0 = self.thread.frames[0] 330 line_entry = frame_0.line_entry 331 self.assertEqual( 332 line_entry.file.basename, self.main_source_spec.basename, "File matches" 333 ) 334 target_line = line_number("calling.cpp", "// At caller_trivial_inline_1") 335 self.assertEqual(line_entry.line, target_line, "Lines match as well.") 336 337 def step_in_template(self): 338 """Use Python APIs to test stepping in to templated functions.""" 339 exe = self.getBuildArtifact("a.out") 340 341 target = self.dbg.CreateTarget(exe) 342 self.assertTrue(target, VALID_TARGET) 343 344 break_1_in_main = target.BreakpointCreateBySourceRegex( 345 "// Call max_value template", self.main_source_spec 346 ) 347 self.assertTrue(break_1_in_main, VALID_BREAKPOINT) 348 349 break_2_in_main = target.BreakpointCreateBySourceRegex( 350 "// Call max_value specialized", self.main_source_spec 351 ) 352 self.assertTrue(break_2_in_main, VALID_BREAKPOINT) 353 354 # Now launch the process, and do not stop at entry point. 355 self.process = target.LaunchSimple( 356 None, None, self.get_process_working_directory() 357 ) 358 self.assertTrue(self.process, PROCESS_IS_VALID) 359 360 # The stop reason of the thread should be breakpoint. 361 threads = lldbutil.get_threads_stopped_at_breakpoint( 362 self.process, break_1_in_main 363 ) 364 365 if len(threads) != 1: 366 self.fail("Failed to stop at first breakpoint in main.") 367 368 self.thread = threads[0] 369 370 step_sequence = [["// In max_value template", "into"]] 371 self.run_step_sequence(step_sequence) 372 373 threads = lldbutil.continue_to_breakpoint(self.process, break_2_in_main) 374 self.assertEqual( 375 len(threads), 376 1, 377 "Successfully ran to call site of second caller_trivial_1 call.", 378 ) 379 self.thread = threads[0] 380 381 step_sequence = [["// In max_value specialized", "into"]] 382 self.run_step_sequence(step_sequence) 383 384 def run_to_call_site_and_step( 385 self, source_regex, func_name, start_pos, one_more_step_loc=None 386 ): 387 main_spec = lldb.SBFileSpec("calling.cpp") 388 # Set the breakpoint by file and line, not sourced regex because 389 # we want to make sure we can set breakpoints on call sites: 390 call_site_line_num = line_number(self.main_source, source_regex) 391 target, process, thread, bkpt = lldbutil.run_to_line_breakpoint( 392 self, main_spec, call_site_line_num 393 ) 394 395 # Make sure that the location is at the call site (run_to_line_breakpoint already asserted 396 # that there's one location.): 397 bkpt_loc = bkpt.location[0] 398 strm = lldb.SBStream() 399 result = bkpt_loc.GetDescription(strm, lldb.eDescriptionLevelFull) 400 401 self.assertTrue(result, "Got a location description") 402 desc = strm.GetData() 403 self.assertIn(f"calling.cpp:{call_site_line_num}", desc, "Right line listed") 404 # We don't get the function name right yet - so we omit it in printing. 405 # Turn on this test when that is working. 406 # self.assertIn(func_name, desc, "Right function listed") 407 408 pc = thread.frame[0].pc 409 for i in range(start_pos, 3): 410 thread.StepInto() 411 frame_0 = thread.frame[0] 412 413 trivial_line_num = line_number( 414 self.main_source, f"In caller_trivial_inline_{i}." 415 ) 416 self.assertEqual( 417 frame_0.line_entry.line, 418 trivial_line_num, 419 f"Stepped into the caller_trivial_inline_{i}", 420 ) 421 if pc != frame_0.pc: 422 # If we get here, we stepped to the expected line number, but 423 # the compiler on this system has decided to insert an instruction 424 # between the call site of an inlined function with no arguments, 425 # returning void, and its immediate call to another void inlined function 426 # with no arguments. We aren't going to be testing virtual inline 427 # stepping for this function... 428 break 429 430 if one_more_step_loc: 431 thread.StepInto() 432 frame_0 = thread.frame[0] 433 self.assertEqual( 434 frame_0.line_entry.line, 435 line_number(self.main_source, one_more_step_loc), 436 "Was able to step one more time", 437 ) 438 process.Kill() 439 target.Clear() 440 441 def virtual_inline_stepping(self): 442 """Use the Python API's to step through a virtual inlined stack""" 443 self.run_to_call_site_and_step("At caller_trivial_inline_1", "main", 1) 444 self.run_to_call_site_and_step( 445 "In caller_trivial_inline_1", "caller_trivial_inline_1", 2 446 ) 447 self.run_to_call_site_and_step( 448 "In caller_trivial_inline_2", "caller_trivial_inline_2", 3 449 ) 450 self.run_to_call_site_and_step( 451 "In caller_trivial_inline_3", 452 "caller_trivial_inline_3", 453 4, 454 "After caller_trivial_inline_3", 455 ) 456