| """ |
| 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 |
| |
| |
| @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", 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)) |