466 lines · python
1"""2Test that breakpoint by symbol name works correctly with dynamic libs.3"""4 5import os6import re7import lldb8from lldbsuite.test.decorators import *9from lldbsuite.test.lldbtest import *10from lldbsuite.test import lldbutil11 12 13class LoadUnloadTestCase(TestBase):14 NO_DEBUG_INFO_TESTCASE = True15 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_separator39 path = os.environ[self.dylibPath] + sep + path40 self.runCmd(41 "settings append target.env-vars '{}={}'".format(self.dylibPath, path)42 )43 self.default_path = path44 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." + ext73 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_dir79 )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 executable98 # dependencies are resolved relative to the debuggers PWD. Bug?99 @expectedFailureAll(oslist=["freebsd", "linux", "netbsd"])100 @skipIfRemote101 @skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently102 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_dir140 )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 executable155 # dependencies are resolved relative to the debuggers PWD. Bug?156 @expectedFailureAll(oslist=["freebsd", "linux", "netbsd"])157 @expectedFailureAndroid # wrong source file shows up for hidden library158 @skipIfWindows # Windows doesn't have dlopen and friends, dynamic libraries work differently159 @skipIfDarwinEmbedded160 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 sequences168 # 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 = wd185 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 old189 # library will be in the modules list.190 self.expect(191 "target modules list", substrs=[os.path.basename(old_dylib)], matching=True192 )193 194 lldbutil.run_break_set_by_file_and_line(195 self, "d.cpp", self.line_d_function, num_expected_locations=1196 )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 implemented218 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 implemented226 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=True242 )243 244 self.runCmd("run", RUN_SUCCEEDED)245 246 ctx = self.platformContext247 dylibName = ctx.shlib_prefix + "loadunload_a." + ctx.shlib_extension248 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 = localDylibPath254 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 break294 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 hit322 def test_load_unload(self):323 self.setSvr4Support(False)324 self.run_load_unload()325 326 @expectedFailureAll(oslist=["windows"]) # breakpoint not hit327 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/8508987358 # 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=True386 )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 because407 # 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=0423 )424 b_init_bp_num = lldbutil.run_break_set_by_symbol(425 self, "b_init", num_expected_locations=0426 )427 d_init_bp_num = lldbutil.run_break_set_by_symbol(428 self, "d_init", num_expected_locations=1429 )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