| """ | |
| Test how many times newly loaded binaries are notified; | |
| they should be delivered in batches instead of one-by-one. | |
| """ | |
| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| @skipIfTargetDoesNotSupportSharedLibraries() | |
| @skipUnlessPlatform(["linux"] + lldbplatformutil.getDarwinOSTriples()) | |
| class ModuleLoadedNotifysTestCase(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| # At least DynamicLoaderDarwin and DynamicLoaderPOSIXDYLD should batch up | |
| # notifications about newly added/removed libraries. Other DynamicLoaders may | |
| # not be written this way. | |
| def setUp(self): | |
| # Call super's setUp(). | |
| TestBase.setUp(self) | |
| # Find the line number to break inside main(). | |
| self.line = line_number("main.cpp", "// breakpoint") | |
| def setup_test(self, solibs): | |
| if lldb.remote_platform: | |
| path = lldb.remote_platform.GetWorkingDirectory() | |
| for f in solibs: | |
| lldbutil.install_to_target(self, self.getBuildArtifact(f)) | |
| else: | |
| path = self.getBuildDir() | |
| if self.dylibPath in os.environ: | |
| sep = self.platformContext.shlib_path_separator | |
| path = os.environ[self.dylibPath] + sep + path | |
| self.runCmd( | |
| "settings append target.env-vars '{}={}'".format(self.dylibPath, path) | |
| ) | |
| self.default_path = path | |
| def test_launch_notifications(self): | |
| """Test that lldb broadcasts newly loaded libraries in batches.""" | |
| expected_solibs = [ | |
| "lib_a." + self.platformContext.shlib_extension, | |
| "lib_b." + self.platformContext.shlib_extension, | |
| "lib_c." + self.platformContext.shlib_extension, | |
| "lib_d." + self.platformContext.shlib_extension, | |
| ] | |
| self.build() | |
| self.setup_test(expected_solibs) | |
| exe = self.getBuildArtifact("a.out") | |
| self.dbg.SetAsync(False) | |
| listener = self.dbg.GetListener() | |
| listener.StartListeningForEventClass( | |
| self.dbg, | |
| lldb.SBTarget.GetBroadcasterClassName(), | |
| lldb.SBTarget.eBroadcastBitModulesLoaded | |
| | lldb.SBTarget.eBroadcastBitModulesUnloaded, | |
| ) | |
| # Create a target by the debugger. | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| # break on main | |
| breakpoint = target.BreakpointCreateByName("main", "a.out") | |
| event = lldb.SBEvent() | |
| # CreateTarget() generated modules-loaded events; consume them & toss | |
| while listener.GetNextEvent(event): | |
| True | |
| error = lldb.SBError() | |
| flags = target.GetLaunchInfo().GetLaunchFlags() | |
| process = target.Launch( | |
| listener, | |
| None, # argv | |
| None, # envp | |
| None, # stdin_path | |
| None, # stdout_path | |
| None, # stderr_path | |
| None, # working directory | |
| flags, # launch flags | |
| False, # Stop at entry | |
| error, | |
| ) # error | |
| self.assertEqual(process.GetState(), lldb.eStateStopped, PROCESS_STOPPED) | |
| total_solibs_added = 0 | |
| total_solibs_removed = 0 | |
| total_modules_added_events = 0 | |
| total_modules_removed_events = 0 | |
| already_loaded_modules = [] | |
| max_solibs_per_event = 0 | |
| max_solib_chunk_per_event = [] | |
| while listener.GetNextEvent(event): | |
| if lldb.SBTarget.EventIsTargetEvent(event): | |
| if event.GetType() == lldb.SBTarget.eBroadcastBitModulesLoaded: | |
| solib_count = lldb.SBTarget.GetNumModulesFromEvent(event) | |
| total_modules_added_events += 1 | |
| total_solibs_added += solib_count | |
| added_files = [] | |
| for i in range(solib_count): | |
| module = lldb.SBTarget.GetModuleAtIndexFromEvent(i, event) | |
| # On macOS Ventura and later, dyld and the main binary | |
| # will be loaded again when dyld moves itself into the | |
| # shared cache. Use the basename so this also works | |
| # when reading dyld from the expanded shared cache. | |
| exe_basename = lldb.SBFileSpec(exe).basename | |
| if module.file.basename not in [ | |
| "dyld", | |
| "libsharedcache.dylib", | |
| exe_basename, | |
| ]: | |
| self.assertNotIn( | |
| module, | |
| already_loaded_modules, | |
| "{} is already loaded".format(module), | |
| ) | |
| already_loaded_modules.append(module) | |
| added_files.append(module.GetFileSpec().GetFilename()) | |
| if self.TraceOn(): | |
| # print all of the binaries that have been added | |
| print("Loaded files: %s" % (", ".join(added_files))) | |
| # We will check the latest biggest chunk of loaded solibs. | |
| # We expect all of our solibs in the last chunk of loaded modules. | |
| if solib_count >= max_solibs_per_event: | |
| max_solib_chunk_per_event = added_files.copy() | |
| max_solibs_per_event = solib_count | |
| if event.GetType() == lldb.SBTarget.eBroadcastBitModulesUnloaded: | |
| solib_count = lldb.SBTarget.GetNumModulesFromEvent(event) | |
| total_modules_removed_events += 1 | |
| total_solibs_removed += solib_count | |
| if self.TraceOn(): | |
| # print all of the binaries that have been removed | |
| removed_files = [] | |
| i = 0 | |
| while i < solib_count: | |
| module = lldb.SBTarget.GetModuleAtIndexFromEvent(i, event) | |
| removed_files.append(module.GetFileSpec().GetFilename()) | |
| i = i + 1 | |
| print("Unloaded files: %s" % (", ".join(removed_files))) | |
| # This is testing that we get back a small number of events with the loaded | |
| # binaries in batches. Check that we got back more than 1 solib per event. | |
| # In practice on Darwin today, we get back two events for a do-nothing c | |
| # program: a.out and dyld, and then all the rest of the system libraries. | |
| # On Linux we get events for ld.so, [vdso], the binary and then all libraries, | |
| # but the different configurations could load a different number of .so modules | |
| # per event. | |
| self.assertLessEqual(set(expected_solibs), set(max_solib_chunk_per_event)) |