1""" 2Test lldb core component: SourceManager. 3 4Test cases: 5 6o test_display_source_python: 7 Test display of source using the SBSourceManager API. 8o test_modify_source_file_while_debugging: 9 Test the caching mechanism of the source manager. 10""" 11 12import os 13import io 14import stat 15 16import lldb 17from lldbsuite.test.decorators import * 18from lldbsuite.test.lldbtest import * 19from lldbsuite.test import lldbutil 20 21 22def ansi_underline_surround_regex(inner_regex_text): 23 # return re.compile(r"\[4m%s\[0m" % inner_regex_text) 24 return "4.+\033\\[4m%s\033\\[0m" % inner_regex_text 25 26 27def ansi_color_surround_regex(inner_regex_text): 28 return "\033\\[3[0-7]m%s\033\\[0m" % inner_regex_text 29 30 31class SourceManagerTestCase(TestBase): 32 NO_DEBUG_INFO_TESTCASE = True 33 34 def setUp(self): 35 # Call super's setUp(). 36 TestBase.setUp(self) 37 # Find the line number to break inside main(). 38 self.file = self.getBuildArtifact("main-copy.c") 39 self.line = line_number("main.c", "// Set break point at this line.") 40 41 def modify_content(self): 42 # Read the main.c file content. 43 with io.open(self.file, "r", newline="\n") as f: 44 original_content = f.read() 45 if self.TraceOn(): 46 print("original content:", original_content) 47 48 # Modify the in-memory copy of the original source code. 49 new_content = original_content.replace("Hello world", "Hello lldb", 1) 50 51 # Modify the source code file. 52 # If the source was read only, the copy will also be read only. 53 # Run "chmod u+w" on it first so we can modify it. 54 statinfo = os.stat(self.file) 55 os.chmod(self.file, statinfo.st_mode | stat.S_IWUSR) 56 57 with io.open(self.file, "w", newline="\n") as f: 58 time.sleep(1) 59 f.write(new_content) 60 if self.TraceOn(): 61 print("new content:", new_content) 62 print( 63 "os.path.getmtime() after writing new content:", 64 os.path.getmtime(self.file), 65 ) 66 67 def get_expected_stop_column_number(self): 68 """Return the 1-based column number of the first non-whitespace 69 character in the breakpoint source line.""" 70 stop_line = get_line(self.file, self.line) 71 # The number of spaces that must be skipped to get to the first non- 72 # whitespace character --- where we expect the debugger breakpoint 73 # column to be --- is equal to the number of characters that get 74 # stripped off the front when we lstrip it, plus one to specify 75 # the character column after the initial whitespace. 76 return len(stop_line) - len(stop_line.lstrip()) + 1 77 78 def do_display_source_python_api( 79 self, use_color, needle_regex, highlight_source=False 80 ): 81 self.build() 82 exe = self.getBuildArtifact("a.out") 83 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 84 85 target = self.dbg.CreateTarget(exe) 86 self.assertTrue(target, VALID_TARGET) 87 88 # Launch the process, and do not stop at the entry point. 89 args = None 90 envp = None 91 process = target.LaunchSimple(args, envp, self.get_process_working_directory()) 92 self.assertIsNotNone(process) 93 94 # 95 # Exercise Python APIs to display source lines. 96 # 97 98 # Setup whether we should use ansi escape sequences, including color 99 # and styles such as underline. 100 self.dbg.SetUseColor(use_color) 101 # Disable syntax highlighting if needed. 102 103 self.runCmd("settings set highlight-source " + str(highlight_source).lower()) 104 105 filespec = lldb.SBFileSpec(self.file, False) 106 source_mgr = self.dbg.GetSourceManager() 107 # Use a string stream as the destination. 108 stream = lldb.SBStream() 109 column = self.get_expected_stop_column_number() 110 context_before = 2 111 context_after = 2 112 current_line_prefix = "=>" 113 source_mgr.DisplaySourceLinesWithLineNumbersAndColumn( 114 filespec, 115 self.line, 116 column, 117 context_before, 118 context_after, 119 current_line_prefix, 120 stream, 121 ) 122 123 # 2 124 # 3 int main(int argc, char const *argv[]) { 125 # => 4 printf("Hello world.\n"); // Set break point at this line. 126 # 5 return 0; 127 # 6 } 128 self.expect( 129 stream.GetData(), 130 "Source code displayed correctly:\n" + stream.GetData(), 131 exe=False, 132 patterns=["=>", "%d.*Hello world" % self.line, needle_regex], 133 ) 134 135 # Boundary condition testings for SBStream(). LLDB should not crash! 136 stream.Print(None) 137 stream.RedirectToFile(None, True) 138 139 @add_test_categories(["pyapi"]) 140 def test_display_source_python_dumb_terminal(self): 141 """Test display of source using the SBSourceManager API, using a 142 dumb terminal and thus no color support (the default).""" 143 use_color = False 144 self.do_display_source_python_api(use_color, r"\s+\^") 145 146 @add_test_categories(["pyapi"]) 147 def test_display_source_python_ansi_terminal(self): 148 """Test display of source using the SBSourceManager API, using a 149 dumb terminal and thus no color support (the default).""" 150 use_color = True 151 underline_regex = ansi_underline_surround_regex(r"printf") 152 self.do_display_source_python_api(use_color, underline_regex) 153 154 @add_test_categories(["pyapi"]) 155 def test_display_source_python_ansi_terminal_syntax_highlighting(self): 156 """Test display of source using the SBSourceManager API and check for 157 the syntax highlighted output""" 158 use_color = True 159 syntax_highlighting = True 160 161 # Just pick 'int' as something that should be colored. 162 color_regex = ansi_color_surround_regex("int") 163 self.do_display_source_python_api(use_color, color_regex, syntax_highlighting) 164 165 # Same for 'char'. 166 color_regex = ansi_color_surround_regex("char") 167 self.do_display_source_python_api(use_color, color_regex, syntax_highlighting) 168 169 # Test that we didn't color unrelated identifiers. 170 self.do_display_source_python_api(use_color, r" main\(", syntax_highlighting) 171 self.do_display_source_python_api(use_color, r"\);", syntax_highlighting) 172 173 def test_move_and_then_display_source(self): 174 """Test that target.source-map settings work by moving main.c to hidden/main.c.""" 175 self.build() 176 exe = self.getBuildArtifact("a.out") 177 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 178 179 # Move main.c to hidden/main.c. 180 hidden = self.getBuildArtifact("hidden") 181 lldbutil.mkdir_p(hidden) 182 main_c_hidden = os.path.join(hidden, "main-copy.c") 183 os.rename(self.file, main_c_hidden) 184 185 # Set source remapping with invalid replace path and verify we get an 186 # error 187 self.expect( 188 "settings set target.source-map /a/b/c/d/e /q/r/s/t/u", 189 error=True, 190 substrs=['''error: the replacement path doesn't exist: "/q/r/s/t/u"'''], 191 ) 192 193 # 'make -C' has resolved current directory to its realpath form. 194 builddir_real = os.path.realpath(self.getBuildDir()) 195 hidden_real = os.path.realpath(hidden) 196 # Set target.source-map settings. 197 self.runCmd( 198 "settings set target.source-map %s %s" % (builddir_real, hidden_real) 199 ) 200 # And verify that the settings work. 201 self.expect( 202 "settings show target.source-map", substrs=[builddir_real, hidden_real] 203 ) 204 205 # Display main() and verify that the source mapping has been kicked in. 206 self.expect( 207 "source list -n main", SOURCE_DISPLAYED_CORRECTLY, substrs=["Hello world"] 208 ) 209 210 @skipIf(oslist=["windows"], bugnumber="llvm.org/pr44431") 211 def test_modify_source_file_while_debugging(self): 212 """Modify a source file while debugging the executable.""" 213 self.build() 214 exe = self.getBuildArtifact("a.out") 215 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 216 217 lldbutil.run_break_set_by_file_and_line( 218 self, "main-copy.c", self.line, num_expected_locations=1, loc_exact=True 219 ) 220 221 self.runCmd("run", RUN_SUCCEEDED) 222 223 # The stop reason of the thread should be breakpoint. 224 self.expect( 225 "thread list", 226 STOPPED_DUE_TO_BREAKPOINT, 227 substrs=[ 228 "stopped", 229 "main-copy.c:%d" % self.line, 230 "stop reason = breakpoint", 231 ], 232 ) 233 234 # Display some source code. 235 self.expect( 236 "source list -f main-copy.c -l %d" % self.line, 237 SOURCE_DISPLAYED_CORRECTLY, 238 substrs=["Hello world"], 239 ) 240 241 # Do the same thing with a file & line spec: 242 self.expect( 243 "source list -y main-copy.c:%d" % self.line, 244 SOURCE_DISPLAYED_CORRECTLY, 245 substrs=["Hello world"], 246 ) 247 248 # The '-b' option shows the line table locations from the debug information 249 # that indicates valid places to set source level breakpoints. 250 251 # The file to display is implicit in this case. 252 self.runCmd("source list -l %d -c 3 -b" % self.line) 253 output = self.res.GetOutput().splitlines()[0] 254 255 # If the breakpoint set command succeeded, we should expect a positive number 256 # of breakpoints for the current line, i.e., self.line. 257 import re 258 259 m = re.search("^\[(\d+)\].*// Set break point at this line.", output) 260 if not m: 261 self.fail("Fail to display source level breakpoints") 262 self.assertGreater(int(m.group(1)), 0) 263 264 # Modify content 265 self.modify_content() 266 267 # Display the source code again. We should not see the updated line. 268 self.expect( 269 "source list -f main-copy.c -l %d" % self.line, 270 SOURCE_DISPLAYED_CORRECTLY, 271 substrs=["Hello world"], 272 ) 273 274 # clear the source cache. 275 self.runCmd("source cache clear") 276 277 # Display the source code again. Now we should see the updated line. 278 self.expect( 279 "source list -f main-copy.c -l %d" % self.line, 280 SOURCE_DISPLAYED_CORRECTLY, 281 substrs=["Hello lldb"], 282 ) 283 284 def test_set_breakpoint_with_absolute_path(self): 285 self.build() 286 hidden = self.getBuildArtifact("hidden") 287 lldbutil.mkdir_p(hidden) 288 # 'make -C' has resolved current directory to its realpath form. 289 builddir_real = os.path.realpath(self.getBuildDir()) 290 hidden_real = os.path.realpath(hidden) 291 self.runCmd( 292 "settings set target.source-map %s %s" % (builddir_real, hidden_real) 293 ) 294 295 exe = self.getBuildArtifact("a.out") 296 main = os.path.join(builddir_real, "hidden", "main-copy.c") 297 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 298 299 lldbutil.run_break_set_by_file_and_line( 300 self, main, self.line, num_expected_locations=1, loc_exact=False 301 ) 302 303 self.runCmd("run", RUN_SUCCEEDED) 304 305 # The stop reason of the thread should be breakpoint. 306 self.expect( 307 "thread list", 308 STOPPED_DUE_TO_BREAKPOINT, 309 substrs=[ 310 "stopped", 311 "main-copy.c:%d" % self.line, 312 "stop reason = breakpoint", 313 ], 314 ) 315 316 def test_artificial_source_location(self): 317 src_file = "artificial_location.cpp" 318 d = {"C_SOURCES": "", "CXX_SOURCES": src_file} 319 self.build(dictionary=d) 320 321 target = lldbutil.run_to_breakpoint_make_target(self) 322 323 # Find the instruction with line=0 and put a breakpoint there. 324 sc_list = target.FindFunctions("A::foo") 325 self.assertEqual(len(sc_list), 1) 326 insns = sc_list[0].function.GetInstructions(target) 327 insn0 = next(filter(lambda insn: insn.addr.line_entry.line == 0, insns)) 328 bkpt = target.BreakpointCreateBySBAddress(insn0.addr) 329 self.assertGreater(bkpt.GetNumLocations(), 0) 330 331 lldbutil.run_to_breakpoint_do_run(self, target, bkpt) 332 333 self.expect( 334 "process status", 335 substrs=[ 336 "stop reason = breakpoint", 337 f"{src_file}:0", 338 "static int foo();", 339 "note: This address is not associated with a specific line " 340 "of code. This may be due to compiler optimizations.", 341 ], 342 ) 343 344 def test_source_cache_dump_and_clear(self): 345 self.build() 346 exe = self.getBuildArtifact("a.out") 347 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 348 lldbutil.run_break_set_by_file_and_line( 349 self, self.file, self.line, num_expected_locations=1, loc_exact=True 350 ) 351 self.runCmd("run", RUN_SUCCEEDED) 352 353 # Make sure the main source file is in the source cache. 354 self.expect( 355 "source cache dump", 356 substrs=["Modification time", "Lines", "Path", " 7", self.file], 357 ) 358 359 # Clear the cache. 360 self.expect("source cache clear") 361 362 # Make sure the main source file is no longer in the source cache. 363 self.expect("source cache dump", matching=False, substrs=[self.file]) 364 365 def test_source_cache_interactions(self): 366 self.build() 367 exe = self.getBuildArtifact("a.out") 368 369 # Create a first target. 370 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 371 lldbutil.run_break_set_by_symbol(self, "main", num_expected_locations=1) 372 self.expect("run", RUN_SUCCEEDED, substrs=["Hello world"]) 373 374 # Create a second target. 375 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 376 lldbutil.run_break_set_by_symbol(self, "main", num_expected_locations=1) 377 self.expect("run", RUN_SUCCEEDED, substrs=["Hello world"]) 378 379 # Modify the source file content. 380 self.modify_content() 381 382 # Clear the source cache. This will wipe the debugger and the process 383 # cache for the second process. 384 self.runCmd("source cache clear") 385 386 # Make sure we're seeing the new content from the clean process cache. 387 self.expect( 388 "next", 389 SOURCE_DISPLAYED_CORRECTLY, 390 substrs=["Hello lldb"], 391 ) 392 393 # Switch back to the first target. 394 self.runCmd("target select 0") 395 396 # Make sure we're seeing the old content from the first target's 397 # process cache. 398 self.expect( 399 "next", 400 SOURCE_DISPLAYED_CORRECTLY, 401 substrs=["Hello world"], 402 ) 403