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