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 12from __future__ import print_function 13 14import lldb 15from lldbsuite.test.decorators import * 16from lldbsuite.test.lldbtest import * 17from lldbsuite.test import lldbutil 18 19 20def ansi_underline_surround_regex(inner_regex_text): 21 # return re.compile(r"\[4m%s\[0m" % inner_regex_text) 22 return "4.+\033\\[4m%s\033\\[0m" % inner_regex_text 23 24def ansi_color_surround_regex(inner_regex_text): 25 return "\033\\[3[0-7]m%s\033\\[0m" % inner_regex_text 26 27class SourceManagerTestCase(TestBase): 28 29 NO_DEBUG_INFO_TESTCASE = True 30 31 def setUp(self): 32 # Call super's setUp(). 33 TestBase.setUp(self) 34 # Find the line number to break inside main(). 35 self.file = self.getBuildArtifact("main-copy.c") 36 self.line = line_number("main.c", '// Set break point at this line.') 37 38 def get_expected_stop_column_number(self): 39 """Return the 1-based column number of the first non-whitespace 40 character in the breakpoint source line.""" 41 stop_line = get_line(self.file, self.line) 42 # The number of spaces that must be skipped to get to the first non- 43 # whitespace character --- where we expect the debugger breakpoint 44 # column to be --- is equal to the number of characters that get 45 # stripped off the front when we lstrip it, plus one to specify 46 # the character column after the initial whitespace. 47 return len(stop_line) - len(stop_line.lstrip()) + 1 48 49 def do_display_source_python_api(self, use_color, needle_regex, highlight_source=False): 50 self.build() 51 exe = self.getBuildArtifact("a.out") 52 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 53 54 target = self.dbg.CreateTarget(exe) 55 self.assertTrue(target, VALID_TARGET) 56 57 # Launch the process, and do not stop at the entry point. 58 args = None 59 envp = None 60 process = target.LaunchSimple( 61 args, envp, self.get_process_working_directory()) 62 self.assertIsNotNone(process) 63 64 # 65 # Exercise Python APIs to display source lines. 66 # 67 68 # Setup whether we should use ansi escape sequences, including color 69 # and styles such as underline. 70 self.dbg.SetUseColor(use_color) 71 # Disable syntax highlighting if needed. 72 73 self.runCmd("settings set highlight-source " + str(highlight_source).lower()) 74 75 filespec = lldb.SBFileSpec(self.file, False) 76 source_mgr = self.dbg.GetSourceManager() 77 # Use a string stream as the destination. 78 stream = lldb.SBStream() 79 column = self.get_expected_stop_column_number() 80 context_before = 2 81 context_after = 2 82 current_line_prefix = "=>" 83 source_mgr.DisplaySourceLinesWithLineNumbersAndColumn( 84 filespec, self.line, column, context_before, context_after, 85 current_line_prefix, stream) 86 87 # 2 88 # 3 int main(int argc, char const *argv[]) { 89 # => 4 printf("Hello world.\n"); // Set break point at this line. 90 # 5 return 0; 91 # 6 } 92 self.expect(stream.GetData(), "Source code displayed correctly:\n" + stream.GetData(), 93 exe=False, 94 patterns=['=>', '%d.*Hello world' % self.line, 95 needle_regex]) 96 97 # Boundary condition testings for SBStream(). LLDB should not crash! 98 stream.Print(None) 99 stream.RedirectToFile(None, True) 100 101 @add_test_categories(['pyapi']) 102 def test_display_source_python_dumb_terminal(self): 103 """Test display of source using the SBSourceManager API, using a 104 dumb terminal and thus no color support (the default).""" 105 use_color = False 106 self.do_display_source_python_api(use_color, r"\s+\^") 107 108 @add_test_categories(['pyapi']) 109 def test_display_source_python_ansi_terminal(self): 110 """Test display of source using the SBSourceManager API, using a 111 dumb terminal and thus no color support (the default).""" 112 use_color = True 113 underline_regex = ansi_underline_surround_regex(r"printf") 114 self.do_display_source_python_api(use_color, underline_regex) 115 116 @add_test_categories(['pyapi']) 117 def test_display_source_python_ansi_terminal_syntax_highlighting(self): 118 """Test display of source using the SBSourceManager API and check for 119 the syntax highlighted output""" 120 use_color = True 121 syntax_highlighting = True; 122 123 # Just pick 'int' as something that should be colored. 124 color_regex = ansi_color_surround_regex("int") 125 self.do_display_source_python_api(use_color, color_regex, syntax_highlighting) 126 127 # Same for 'char'. 128 color_regex = ansi_color_surround_regex("char") 129 self.do_display_source_python_api(use_color, color_regex, syntax_highlighting) 130 131 # Test that we didn't color unrelated identifiers. 132 self.do_display_source_python_api(use_color, r" main\(", syntax_highlighting) 133 self.do_display_source_python_api(use_color, r"\);", syntax_highlighting) 134 135 def test_move_and_then_display_source(self): 136 """Test that target.source-map settings work by moving main.c to hidden/main.c.""" 137 self.build() 138 exe = self.getBuildArtifact("a.out") 139 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 140 141 # Move main.c to hidden/main.c. 142 hidden = self.getBuildArtifact("hidden") 143 lldbutil.mkdir_p(hidden) 144 main_c_hidden = os.path.join(hidden, "main-copy.c") 145 os.rename(self.file, main_c_hidden) 146 147 # Set source remapping with invalid replace path and verify we get an 148 # error 149 self.expect( 150 "settings set target.source-map /a/b/c/d/e /q/r/s/t/u", 151 error=True, 152 substrs=['''error: the replacement path doesn't exist: "/q/r/s/t/u"''']) 153 154 # 'make -C' has resolved current directory to its realpath form. 155 builddir_real = os.path.realpath(self.getBuildDir()) 156 hidden_real = os.path.realpath(hidden) 157 # Set target.source-map settings. 158 self.runCmd("settings set target.source-map %s %s" % 159 (builddir_real, hidden_real)) 160 # And verify that the settings work. 161 self.expect("settings show target.source-map", 162 substrs=[builddir_real, hidden_real]) 163 164 # Display main() and verify that the source mapping has been kicked in. 165 self.expect("source list -n main", SOURCE_DISPLAYED_CORRECTLY, 166 substrs=['Hello world']) 167 168 @skipIf(oslist=["windows"], bugnumber="llvm.org/pr44431") 169 def test_modify_source_file_while_debugging(self): 170 """Modify a source file while debugging the executable.""" 171 self.build() 172 exe = self.getBuildArtifact("a.out") 173 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 174 175 lldbutil.run_break_set_by_file_and_line( 176 self, "main-copy.c", self.line, num_expected_locations=1, loc_exact=True) 177 178 self.runCmd("run", RUN_SUCCEEDED) 179 180 # The stop reason of the thread should be breakpoint. 181 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 182 substrs=['stopped', 183 'main-copy.c:%d' % self.line, 184 'stop reason = breakpoint']) 185 186 # Display some source code. 187 self.expect( 188 "source list -f main-copy.c -l %d" % 189 self.line, 190 SOURCE_DISPLAYED_CORRECTLY, 191 substrs=['Hello world']) 192 193 # Do the same thing with a file & line spec: 194 self.expect( 195 "source list -y main-copy.c:%d" % 196 self.line, 197 SOURCE_DISPLAYED_CORRECTLY, 198 substrs=['Hello world']) 199 200 # The '-b' option shows the line table locations from the debug information 201 # that indicates valid places to set source level breakpoints. 202 203 # The file to display is implicit in this case. 204 self.runCmd("source list -l %d -c 3 -b" % self.line) 205 output = self.res.GetOutput().splitlines()[0] 206 207 # If the breakpoint set command succeeded, we should expect a positive number 208 # of breakpoints for the current line, i.e., self.line. 209 import re 210 m = re.search('^\[(\d+)\].*// Set break point at this line.', output) 211 if not m: 212 self.fail("Fail to display source level breakpoints") 213 self.assertTrue(int(m.group(1)) > 0) 214 215 # Read the main.c file content. 216 with io.open(self.file, 'r', newline='\n') as f: 217 original_content = f.read() 218 if self.TraceOn(): 219 print("original content:", original_content) 220 221 # Modify the in-memory copy of the original source code. 222 new_content = original_content.replace('Hello world', 'Hello lldb', 1) 223 224 # Modify the source code file. 225 with io.open(self.file, 'w', newline='\n') as f: 226 time.sleep(1) 227 f.write(new_content) 228 if self.TraceOn(): 229 print("new content:", new_content) 230 print( 231 "os.path.getmtime() after writing new content:", 232 os.path.getmtime(self.file)) 233 234 # Display the source code again. We should see the updated line. 235 self.expect( 236 "source list -f main-copy.c -l %d" % 237 self.line, 238 SOURCE_DISPLAYED_CORRECTLY, 239 substrs=['Hello lldb']) 240 241 def test_set_breakpoint_with_absolute_path(self): 242 self.build() 243 hidden = self.getBuildArtifact("hidden") 244 lldbutil.mkdir_p(hidden) 245 # 'make -C' has resolved current directory to its realpath form. 246 builddir_real = os.path.realpath(self.getBuildDir()) 247 hidden_real = os.path.realpath(hidden) 248 self.runCmd("settings set target.source-map %s %s" % 249 (builddir_real, hidden_real)) 250 251 exe = self.getBuildArtifact("a.out") 252 main = os.path.join(builddir_real, "hidden", "main-copy.c") 253 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 254 255 lldbutil.run_break_set_by_file_and_line( 256 self, main, self.line, num_expected_locations=1, loc_exact=False) 257 258 self.runCmd("run", RUN_SUCCEEDED) 259 260 # The stop reason of the thread should be breakpoint. 261 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 262 substrs=['stopped', 263 'main-copy.c:%d' % self.line, 264 'stop reason = breakpoint']) 265 266 def test_artificial_source_location(self): 267 src_file = 'artificial_location.c' 268 d = {'C_SOURCES': src_file } 269 self.build(dictionary=d) 270 271 lldbutil.run_to_source_breakpoint( 272 self, 'main', 273 lldb.SBFileSpec(src_file, False)) 274 275 self.expect("run", RUN_SUCCEEDED, 276 substrs=['stop reason = breakpoint', '%s:%d' % (src_file,0), 277 'Note: this address is compiler-generated code in ' 278 'function', 'that has no source code associated ' 279 'with it.']) 280 281