404 lines · python
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 os13import io14import stat15 16import lldb17from lldbsuite.test.decorators import *18from lldbsuite.test.lldbtest import *19from lldbsuite.test import lldbutil20 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_text25 26 27def ansi_color_surround_regex(inner_regex_text):28 return "\033\\[3[0-7]m%s\033\\[0m" % inner_regex_text29 30 31class SourceManagerTestCase(TestBase):32 NO_DEBUG_INFO_TESTCASE = True33 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 = os.path.realpath(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-whitespace69 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 breakpoint73 # column to be --- is equal to the number of characters that get74 # stripped off the front when we lstrip it, plus one to specify75 # the character column after the initial whitespace.76 return len(stop_line) - len(stop_line.lstrip()) + 177 78 def do_display_source_python_api(79 self, use_color, needle_regex, highlight_source=False80 ):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 = None90 envp = None91 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 color99 # 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 = 2111 context_after = 2112 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 # 2124 # 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 ordered=False,133 patterns=["=>", "%d.*Hello world" % self.line, needle_regex],134 )135 136 # Boundary condition testings for SBStream(). LLDB should not crash!137 stream.Print(None)138 stream.RedirectToFile(None, True)139 140 @add_test_categories(["pyapi"])141 def test_display_source_python_dumb_terminal(self):142 """Test display of source using the SBSourceManager API, using a143 dumb terminal and thus no color support (the default)."""144 use_color = False145 self.do_display_source_python_api(use_color, r"\s+\^")146 147 @add_test_categories(["pyapi"])148 def test_display_source_python_ansi_terminal(self):149 """Test display of source using the SBSourceManager API, using a150 dumb terminal and thus no color support (the default)."""151 use_color = True152 underline_regex = ansi_underline_surround_regex(r"printf")153 self.do_display_source_python_api(use_color, underline_regex)154 155 @add_test_categories(["pyapi"])156 def test_display_source_python_ansi_terminal_syntax_highlighting(self):157 """Test display of source using the SBSourceManager API and check for158 the syntax highlighted output"""159 use_color = True160 syntax_highlighting = True161 162 # Just pick 'int' as something that should be colored.163 color_regex = ansi_color_surround_regex("int")164 self.do_display_source_python_api(use_color, color_regex, syntax_highlighting)165 166 # Same for 'char'.167 color_regex = ansi_color_surround_regex("char")168 self.do_display_source_python_api(use_color, color_regex, syntax_highlighting)169 170 # Test that we didn't color unrelated identifiers.171 self.do_display_source_python_api(use_color, r" main\(", syntax_highlighting)172 self.do_display_source_python_api(use_color, r"\);", syntax_highlighting)173 174 def test_move_and_then_display_source(self):175 """Test that target.source-map settings work by moving main.c to hidden/main.c."""176 self.build()177 exe = self.getBuildArtifact("a.out")178 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)179 180 # Move main.c to hidden/main.c.181 hidden = self.getBuildArtifact("hidden")182 lldbutil.mkdir_p(hidden)183 main_c_hidden = os.path.join(hidden, "main-copy.c")184 os.rename(self.file, main_c_hidden)185 186 # Set source remapping with invalid replace path and verify we get an187 # error188 self.expect(189 "settings set target.source-map /a/b/c/d/e /q/r/s/t/u",190 error=True,191 substrs=['''error: the replacement path doesn't exist: "/q/r/s/t/u"'''],192 )193 194 # 'make -C' has resolved current directory to its realpath form.195 builddir_real = os.path.realpath(self.getBuildDir())196 hidden_real = os.path.realpath(hidden)197 # Set target.source-map settings.198 self.runCmd(199 "settings set target.source-map %s %s" % (builddir_real, hidden_real)200 )201 # And verify that the settings work.202 self.expect(203 "settings show target.source-map", substrs=[builddir_real, hidden_real]204 )205 206 # Display main() and verify that the source mapping has been kicked in.207 self.expect(208 "source list -n main", SOURCE_DISPLAYED_CORRECTLY, substrs=["Hello world"]209 )210 211 @skipIf(oslist=["windows"], bugnumber="llvm.org/pr44431")212 def test_modify_source_file_while_debugging(self):213 """Modify a source file while debugging the executable."""214 self.build()215 exe = self.getBuildArtifact("a.out")216 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)217 218 lldbutil.run_break_set_by_file_and_line(219 self, "main-copy.c", self.line, num_expected_locations=1, loc_exact=True220 )221 222 self.runCmd("run", RUN_SUCCEEDED)223 224 # The stop reason of the thread should be breakpoint.225 self.expect(226 "thread list",227 STOPPED_DUE_TO_BREAKPOINT,228 substrs=[229 "stopped",230 "main-copy.c:%d" % self.line,231 "stop reason = breakpoint",232 ],233 )234 235 # Display some source code.236 self.expect(237 "source list -f main-copy.c -l %d" % self.line,238 SOURCE_DISPLAYED_CORRECTLY,239 substrs=["Hello world"],240 )241 242 # Do the same thing with a file & line spec:243 self.expect(244 "source list -y main-copy.c:%d" % self.line,245 SOURCE_DISPLAYED_CORRECTLY,246 substrs=["Hello world"],247 )248 249 # The '-b' option shows the line table locations from the debug information250 # that indicates valid places to set source level breakpoints.251 252 # The file to display is implicit in this case.253 self.runCmd("source list -l %d -c 3 -b" % self.line)254 output = self.res.GetOutput().splitlines()[0]255 256 # If the breakpoint set command succeeded, we should expect a positive number257 # of breakpoints for the current line, i.e., self.line.258 import re259 260 m = re.search(r"^\[(\d+)\].*// Set break point at this line.", output)261 if not m:262 self.fail("Fail to display source level breakpoints")263 self.assertGreater(int(m.group(1)), 0)264 265 # Modify content266 self.modify_content()267 268 # Display the source code again. We should not see the updated line.269 self.expect(270 "source list -f main-copy.c -l %d" % self.line,271 SOURCE_DISPLAYED_CORRECTLY,272 substrs=["Hello world"],273 )274 275 # clear the source cache.276 self.runCmd("source cache clear")277 278 # Display the source code again. Now we should see the updated line.279 self.expect(280 "source list -f main-copy.c -l %d" % self.line,281 SOURCE_DISPLAYED_CORRECTLY,282 substrs=["Hello lldb"],283 )284 285 def test_set_breakpoint_with_absolute_path(self):286 self.build()287 hidden = self.getBuildArtifact("hidden")288 lldbutil.mkdir_p(hidden)289 # 'make -C' has resolved current directory to its realpath form.290 builddir_real = os.path.realpath(self.getBuildDir())291 hidden_real = os.path.realpath(hidden)292 self.runCmd(293 "settings set target.source-map %s %s" % (builddir_real, hidden_real)294 )295 296 exe = self.getBuildArtifact("a.out")297 main = os.path.join(builddir_real, "hidden", "main-copy.c")298 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)299 300 lldbutil.run_break_set_by_file_and_line(301 self, main, self.line, num_expected_locations=1, loc_exact=False302 )303 304 self.runCmd("run", RUN_SUCCEEDED)305 306 # The stop reason of the thread should be breakpoint.307 self.expect(308 "thread list",309 STOPPED_DUE_TO_BREAKPOINT,310 substrs=[311 "stopped",312 "main-copy.c:%d" % self.line,313 "stop reason = breakpoint",314 ],315 )316 317 def test_artificial_source_location(self):318 src_file = "artificial_location.cpp"319 d = {"C_SOURCES": "", "CXX_SOURCES": src_file}320 self.build(dictionary=d)321 322 target = lldbutil.run_to_breakpoint_make_target(self)323 324 # Find the instruction with line=0 and put a breakpoint there.325 sc_list = target.FindFunctions("A::foo")326 self.assertEqual(len(sc_list), 1)327 insns = sc_list[0].function.GetInstructions(target)328 insn0 = next(filter(lambda insn: insn.addr.line_entry.line == 0, insns))329 bkpt = target.BreakpointCreateBySBAddress(insn0.addr)330 self.assertGreater(bkpt.GetNumLocations(), 0)331 332 lldbutil.run_to_breakpoint_do_run(self, target, bkpt)333 334 self.expect(335 "process status",336 substrs=[337 "stop reason = breakpoint",338 f"{src_file}:0",339 "static int foo();",340 "note: This address is not associated with a specific line "341 "of code. This may be due to compiler optimizations.",342 ],343 )344 345 def test_source_cache_dump_and_clear(self):346 self.build()347 exe = self.getBuildArtifact("a.out")348 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)349 lldbutil.run_break_set_by_file_and_line(350 self, self.file, self.line, num_expected_locations=1, loc_exact=True351 )352 self.runCmd("run", RUN_SUCCEEDED)353 354 # Make sure the main source file is in the source cache.355 self.expect(356 "source cache dump",357 substrs=["Modification time", "Lines", "Path", " 7", self.file],358 )359 360 # Clear the cache.361 self.expect("source cache clear")362 363 # Make sure the main source file is no longer in the source cache.364 self.expect("source cache dump", matching=False, substrs=[self.file])365 366 def test_source_cache_interactions(self):367 self.build()368 exe = self.getBuildArtifact("a.out")369 370 # Create a first target.371 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)372 lldbutil.run_break_set_by_symbol(self, "main", num_expected_locations=1)373 self.expect("run", RUN_SUCCEEDED, substrs=["Hello world"])374 375 # Create a second target.376 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)377 lldbutil.run_break_set_by_symbol(self, "main", num_expected_locations=1)378 self.expect("run", RUN_SUCCEEDED, substrs=["Hello world"])379 380 # Modify the source file content.381 self.modify_content()382 383 # Clear the source cache. This will wipe the debugger and the process384 # cache for the second process.385 self.runCmd("source cache clear")386 387 # Make sure we're seeing the new content from the clean process cache.388 self.expect(389 "next",390 SOURCE_DISPLAYED_CORRECTLY,391 substrs=["Hello lldb"],392 )393 394 # Switch back to the first target.395 self.runCmd("target select 0")396 397 # Make sure we're seeing the old content from the first target's398 # process cache.399 self.expect(400 "next",401 SOURCE_DISPLAYED_CORRECTLY,402 substrs=["Hello world"],403 )404