1""" 2Test that breakpoint by symbol name works correctly with dynamic libs. 3""" 4 5from __future__ import print_function 6 7 8import os 9import re 10import lldb 11from lldbsuite.test.decorators import * 12from lldbsuite.test.lldbtest import * 13from lldbsuite.test import lldbutil 14 15 16class LoadUnloadTestCase(TestBase): 17 18 NO_DEBUG_INFO_TESTCASE = True 19 20 def setUp(self): 21 # Call super's setUp(). 22 TestBase.setUp(self) 23 self.setup_test() 24 # Invoke the default build rule. 25 self.build() 26 # Find the line number to break for main.cpp. 27 self.line = line_number( 28 'main.cpp', 29 '// Set break point at this line for test_lldb_process_load_and_unload_commands().') 30 self.line_d_function = line_number( 31 'd.cpp', '// Find this line number within d_dunction().') 32 33 def setup_test(self): 34 lldbutil.mkdir_p(self.getBuildArtifact("hidden")) 35 if lldb.remote_platform: 36 path = lldb.remote_platform.GetWorkingDirectory() 37 else: 38 path = self.getBuildDir() 39 if self.dylibPath in os.environ: 40 sep = self.platformContext.shlib_path_separator 41 path = os.environ[self.dylibPath] + sep + path 42 self.runCmd("settings append target.env-vars '{}={}'".format(self.dylibPath, path)) 43 self.default_path = path 44 45 def copy_shlibs_to_remote(self, hidden_dir=False): 46 """ Copies the shared libs required by this test suite to remote. 47 Does nothing in case of non-remote platforms. 48 """ 49 if lldb.remote_platform: 50 ext = 'so' 51 if self.platformIsDarwin(): 52 ext = 'dylib' 53 54 shlibs = ['libloadunload_a.' + ext, 'libloadunload_b.' + ext, 55 'libloadunload_c.' + ext, 'libloadunload_d.' + ext] 56 wd = lldb.remote_platform.GetWorkingDirectory() 57 cwd = os.getcwd() 58 for f in shlibs: 59 err = lldb.remote_platform.Put( 60 lldb.SBFileSpec(self.getBuildArtifact(f)), 61 lldb.SBFileSpec(os.path.join(wd, f))) 62 if err.Fail(): 63 raise RuntimeError( 64 "Unable copy '%s' to '%s'.\n>>> %s" % 65 (f, wd, err.GetCString())) 66 if hidden_dir: 67 shlib = 'libloadunload_d.' + ext 68 hidden_dir = os.path.join(wd, 'hidden') 69 hidden_file = os.path.join(hidden_dir, shlib) 70 err = lldb.remote_platform.MakeDirectory(hidden_dir) 71 if err.Fail(): 72 raise RuntimeError( 73 "Unable to create a directory '%s'." % hidden_dir) 74 err = lldb.remote_platform.Put( 75 lldb.SBFileSpec(os.path.join('hidden', shlib)), 76 lldb.SBFileSpec(hidden_file)) 77 if err.Fail(): 78 raise RuntimeError( 79 "Unable copy 'libloadunload_d.so' to '%s'.\n>>> %s" % 80 (wd, err.GetCString())) 81 82 def setSvr4Support(self, enabled): 83 self.runCmd( 84 "settings set plugin.process.gdb-remote.use-libraries-svr4 {enabled}".format( 85 enabled="true" if enabled else "false" 86 ) 87 ) 88 89 # libloadunload_d.so does not appear in the image list because executable 90 # dependencies are resolved relative to the debuggers PWD. Bug? 91 @expectedFailureAll(oslist=["freebsd", "linux", "netbsd"]) 92 @skipIfRemote 93 @skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently 94 def test_modules_search_paths(self): 95 """Test target modules list after loading a different copy of the library libd.dylib, and verifies that it works with 'target modules search-paths add'.""" 96 if self.platformIsDarwin(): 97 dylibName = 'libloadunload_d.dylib' 98 else: 99 dylibName = 'libloadunload_d.so' 100 101 # The directory with the dynamic library we did not link to. 102 new_dir = os.path.join(self.getBuildDir(), "hidden") 103 104 old_dylib = os.path.join(self.getBuildDir(), dylibName) 105 new_dylib = os.path.join(new_dir, dylibName) 106 exe = self.getBuildArtifact("a.out") 107 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 108 109 self.expect("target modules list", 110 substrs=[old_dylib]) 111 # self.expect("target modules list -t 3", 112 # patterns = ["%s-[^-]*-[^-]*" % self.getArchitecture()]) 113 # Add an image search path substitution pair. 114 self.runCmd( 115 "target modules search-paths add %s %s" % 116 (self.getBuildDir(), new_dir)) 117 118 self.expect("target modules search-paths list", 119 substrs=[self.getBuildDir(), new_dir]) 120 121 self.expect( 122 "target modules search-paths query %s" % 123 self.getBuildDir(), 124 "Image search path successfully transformed", 125 substrs=[new_dir]) 126 127 # Obliterate traces of libd from the old location. 128 os.remove(old_dylib) 129 # Inform (DY)LD_LIBRARY_PATH of the new path, too. 130 env_cmd_string = "settings replace target.env-vars " + self.dylibPath + "=" + new_dir 131 if self.TraceOn(): 132 print("Set environment to: ", env_cmd_string) 133 self.runCmd(env_cmd_string) 134 self.runCmd("settings show target.env-vars") 135 136 self.runCmd("run") 137 138 self.expect( 139 "target modules list", 140 "LLDB successfully locates the relocated dynamic library", 141 substrs=[new_dylib]) 142 143 # libloadunload_d.so does not appear in the image list because executable 144 # dependencies are resolved relative to the debuggers PWD. Bug? 145 @expectedFailureAll(oslist=["freebsd", "linux", "netbsd"]) 146 @expectedFailureAndroid # wrong source file shows up for hidden library 147 @skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently 148 @skipIfDarwinEmbedded 149 def test_dyld_library_path(self): 150 """Test (DY)LD_LIBRARY_PATH after moving libd.dylib, which defines d_function, somewhere else.""" 151 self.copy_shlibs_to_remote(hidden_dir=True) 152 153 exe = self.getBuildArtifact("a.out") 154 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 155 156 # Shut off ANSI color usage so we don't get ANSI escape sequences 157 # mixed in with stop locations. 158 self.dbg.SetUseColor(False) 159 160 if self.platformIsDarwin(): 161 dylibName = 'libloadunload_d.dylib' 162 dsymName = 'libloadunload_d.dylib.dSYM' 163 else: 164 dylibName = 'libloadunload_d.so' 165 166 # The directory to relocate the dynamic library and its debugging info. 167 special_dir = "hidden" 168 if lldb.remote_platform: 169 wd = lldb.remote_platform.GetWorkingDirectory() 170 else: 171 wd = self.getBuildDir() 172 173 old_dir = wd 174 new_dir = os.path.join(wd, special_dir) 175 old_dylib = os.path.join(old_dir, dylibName) 176 177 # For now we don't track (DY)LD_LIBRARY_PATH, so the old 178 # library will be in the modules list. 179 self.expect("target modules list", 180 substrs=[os.path.basename(old_dylib)], 181 matching=True) 182 183 lldbutil.run_break_set_by_file_and_line( 184 self, "d.cpp", self.line_d_function, num_expected_locations=1) 185 # After run, make sure the non-hidden library is picked up. 186 self.expect("run", substrs=["return", "700"]) 187 188 self.runCmd("continue") 189 190 # Add the hidden directory first in the search path. 191 env_cmd_string = ("settings set target.env-vars %s=%s%s%s" % 192 (self.dylibPath, new_dir, 193 self.platformContext.shlib_path_separator, self.default_path)) 194 self.runCmd(env_cmd_string) 195 196 # This time, the hidden library should be picked up. 197 self.expect("run", substrs=["return", "12345"]) 198 199 @expectedFailureAll( 200 bugnumber="llvm.org/pr25805", 201 hostoslist=["windows"], 202 triple='.*-android') 203 @expectedFailureAll(oslist=["windows"]) # process load not implemented 204 def test_lldb_process_load_and_unload_commands(self): 205 self.setSvr4Support(False) 206 self.run_lldb_process_load_and_unload_commands() 207 208 @expectedFailureAll( 209 bugnumber="llvm.org/pr25805", 210 hostoslist=["windows"], 211 triple='.*-android') 212 @expectedFailureAll(oslist=["windows"]) # process load not implemented 213 def test_lldb_process_load_and_unload_commands_with_svr4(self): 214 self.setSvr4Support(True) 215 self.run_lldb_process_load_and_unload_commands() 216 217 def run_lldb_process_load_and_unload_commands(self): 218 """Test that lldb process load/unload command work correctly.""" 219 self.copy_shlibs_to_remote() 220 221 exe = self.getBuildArtifact("a.out") 222 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 223 224 # Break at main.cpp before the call to dlopen(). 225 # Use lldb's process load command to load the dylib, instead. 226 227 lldbutil.run_break_set_by_file_and_line( 228 self, "main.cpp", self.line, num_expected_locations=1, loc_exact=True) 229 230 self.runCmd("run", RUN_SUCCEEDED) 231 232 ctx = self.platformContext 233 dylibName = ctx.shlib_prefix + 'loadunload_a.' + ctx.shlib_extension 234 localDylibPath = self.getBuildArtifact(dylibName) 235 if lldb.remote_platform: 236 wd = lldb.remote_platform.GetWorkingDirectory() 237 remoteDylibPath = lldbutil.join_remote_paths(wd, dylibName) 238 else: 239 remoteDylibPath = localDylibPath 240 241 # First make sure that we get some kind of error if process load fails. 242 # We print some error even if the load fails, which isn't formalized. 243 # The only plugin at present (Posix) that supports this says "unknown reasons". 244 # If another plugin shows up, let's require it uses "unknown error" as well. 245 non_existant_shlib = "/NoSuchDir/NoSuchSubdir/ReallyNo/NotAFile" 246 self.expect("process load %s"%(non_existant_shlib), error=True, matching=False, 247 patterns=["unknown reasons"]) 248 249 250 # Make sure that a_function does not exist at this point. 251 self.expect( 252 "image lookup -n a_function", 253 "a_function should not exist yet", 254 error=True, 255 matching=False, 256 patterns=["1 match found"]) 257 258 # Use lldb 'process load' to load the dylib. 259 self.expect( 260 "process load %s --install=%s" % (localDylibPath, remoteDylibPath), 261 "%s loaded correctly" % dylibName, 262 patterns=[ 263 'Loading "%s".*ok' % re.escape(localDylibPath), 264 'Image [0-9]+ loaded']) 265 266 # Search for and match the "Image ([0-9]+) loaded" pattern. 267 output = self.res.GetOutput() 268 pattern = re.compile("Image ([0-9]+) loaded") 269 for l in output.split(os.linesep): 270 self.trace("l:", l) 271 match = pattern.search(l) 272 if match: 273 break 274 index = match.group(1) 275 276 # Now we should have an entry for a_function. 277 self.expect( 278 "image lookup -n a_function", 279 "a_function should now exist", 280 patterns=[ 281 "1 match found .*%s" % 282 dylibName]) 283 284 # Use lldb 'process unload' to unload the dylib. 285 self.expect( 286 "process unload %s" % 287 index, 288 "%s unloaded correctly" % 289 dylibName, 290 patterns=[ 291 "Unloading .* with index %s.*ok" % 292 index]) 293 294 self.runCmd("process continue") 295 296 @expectedFailureAll(oslist=["windows"]) # breakpoint not hit 297 def test_load_unload(self): 298 self.setSvr4Support(False) 299 self.run_load_unload() 300 301 @expectedFailureAll(oslist=["windows"]) # breakpoint not hit 302 def test_load_unload_with_svr4(self): 303 self.setSvr4Support(True) 304 self.run_load_unload() 305 306 def run_load_unload(self): 307 """Test breakpoint by name works correctly with dlopen'ing.""" 308 self.copy_shlibs_to_remote() 309 310 exe = self.getBuildArtifact("a.out") 311 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 312 313 # Break by function name a_function (not yet loaded). 314 lldbutil.run_break_set_by_symbol( 315 self, "a_function", num_expected_locations=0) 316 317 self.runCmd("run", RUN_SUCCEEDED) 318 319 # The stop reason of the thread should be breakpoint and at a_function. 320 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 321 substrs=['stopped', 322 'a_function', 323 'stop reason = breakpoint']) 324 325 # The breakpoint should have a hit count of 1. 326 lldbutil.check_breakpoint(self, bpno = 1, expected_hit_count = 1) 327 328 # Issue the 'continue' command. We should stop agaian at a_function. 329 # The stop reason of the thread should be breakpoint and at a_function. 330 self.runCmd("continue") 331 332 # rdar://problem/8508987 333 # The a_function breakpoint should be encountered twice. 334 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 335 substrs=['stopped', 336 'a_function', 337 'stop reason = breakpoint']) 338 339 # The breakpoint should have a hit count of 2. 340 lldbutil.check_breakpoint(self, bpno = 1, expected_hit_count = 2) 341 342 def test_step_over_load(self): 343 self.setSvr4Support(False) 344 self.run_step_over_load() 345 346 def test_step_over_load_with_svr4(self): 347 self.setSvr4Support(True) 348 self.run_step_over_load() 349 350 def run_step_over_load(self): 351 """Test stepping over code that loads a shared library works correctly.""" 352 self.copy_shlibs_to_remote() 353 354 exe = self.getBuildArtifact("a.out") 355 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 356 357 # Break by function name a_function (not yet loaded). 358 lldbutil.run_break_set_by_file_and_line( 359 self, "main.cpp", self.line, num_expected_locations=1, loc_exact=True) 360 361 self.runCmd("run", RUN_SUCCEEDED) 362 363 # The stop reason of the thread should be breakpoint and at a_function. 364 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 365 substrs=['stopped', 366 'stop reason = breakpoint']) 367 368 self.runCmd( 369 "thread step-over", 370 "Stepping over function that loads library") 371 372 # The stop reason should be step end. 373 self.expect("thread list", "step over succeeded.", 374 substrs=['stopped', 375 'stop reason = step over']) 376 377 # We can't find a breakpoint location for d_init before launching because 378 # executable dependencies are resolved relative to the debuggers PWD. Bug? 379 @expectedFailureAll(oslist=["freebsd", "linux", "netbsd"], triple=no_match('aarch64-.*-android')) 380 @expectedFailureAll(oslist=["windows"], archs=["aarch64"]) 381 def test_static_init_during_load(self): 382 """Test that we can set breakpoints correctly in static initializers""" 383 self.copy_shlibs_to_remote() 384 385 exe = self.getBuildArtifact("a.out") 386 self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) 387 388 a_init_bp_num = lldbutil.run_break_set_by_symbol( 389 self, "a_init", num_expected_locations=0) 390 b_init_bp_num = lldbutil.run_break_set_by_symbol( 391 self, "b_init", num_expected_locations=0) 392 d_init_bp_num = lldbutil.run_break_set_by_symbol( 393 self, "d_init", num_expected_locations=1) 394 395 self.runCmd("run", RUN_SUCCEEDED) 396 397 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 398 substrs=['stopped', 399 'd_init', 400 'stop reason = breakpoint %d' % d_init_bp_num]) 401 402 self.runCmd("continue") 403 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 404 substrs=['stopped', 405 'b_init', 406 'stop reason = breakpoint %d' % b_init_bp_num]) 407 self.expect("thread backtrace", 408 substrs=['b_init', 409 'dylib_open', 410 'main']) 411 412 self.runCmd("continue") 413 self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, 414 substrs=['stopped', 415 'a_init', 416 'stop reason = breakpoint %d' % a_init_bp_num]) 417 self.expect("thread backtrace", 418 substrs=['a_init', 419 'dylib_open', 420 'main']) 421