1"""
2Test how many times newly loaded binaries are notified;
3they should be delivered in batches instead of one-by-one.
4"""
5
6import lldb
7from lldbsuite.test.decorators import *
8from lldbsuite.test.lldbtest import *
9from lldbsuite.test import lldbutil
10
11
12@skipUnlessPlatform(["linux"] + lldbplatformutil.getDarwinOSTriples())
13class ModuleLoadedNotifysTestCase(TestBase):
14    NO_DEBUG_INFO_TESTCASE = True
15
16    # At least DynamicLoaderDarwin and DynamicLoaderPOSIXDYLD should batch up
17    # notifications about newly added/removed libraries.  Other DynamicLoaders may
18    # not be written this way.
19    def setUp(self):
20        # Call super's setUp().
21        TestBase.setUp(self)
22        # Find the line number to break inside main().
23        self.line = line_number("main.cpp", "// breakpoint")
24
25    def setup_test(self, solibs):
26        if lldb.remote_platform:
27            path = lldb.remote_platform.GetWorkingDirectory()
28            for f in solibs:
29                lldbutil.install_to_target(self, self.getBuildArtifact(f))
30        else:
31            path = self.getBuildDir()
32            if self.dylibPath in os.environ:
33                sep = self.platformContext.shlib_path_separator
34                path = os.environ[self.dylibPath] + sep + path
35        self.runCmd(
36            "settings append target.env-vars '{}={}'".format(self.dylibPath, path)
37        )
38        self.default_path = path
39
40    def test_launch_notifications(self):
41        """Test that lldb broadcasts newly loaded libraries in batches."""
42
43        expected_solibs = [
44            "lib_a." + self.platformContext.shlib_extension,
45            "lib_b." + self.platformContext.shlib_extension,
46            "lib_c." + self.platformContext.shlib_extension,
47            "lib_d." + self.platformContext.shlib_extension,
48        ]
49
50        self.build()
51        self.setup_test(expected_solibs)
52
53        exe = self.getBuildArtifact("a.out")
54        self.dbg.SetAsync(False)
55
56        listener = self.dbg.GetListener()
57        listener.StartListeningForEventClass(
58            self.dbg,
59            lldb.SBTarget.GetBroadcasterClassName(),
60            lldb.SBTarget.eBroadcastBitModulesLoaded
61            | lldb.SBTarget.eBroadcastBitModulesUnloaded,
62        )
63
64        # Create a target by the debugger.
65        target = self.dbg.CreateTarget(exe)
66        self.assertTrue(target, VALID_TARGET)
67
68        # break on main
69        breakpoint = target.BreakpointCreateByName("main", "a.out")
70
71        event = lldb.SBEvent()
72        # CreateTarget() generated modules-loaded events; consume them & toss
73        while listener.GetNextEvent(event):
74            True
75
76        error = lldb.SBError()
77        flags = target.GetLaunchInfo().GetLaunchFlags()
78        process = target.Launch(
79            listener,
80            None,  # argv
81            None,  # envp
82            None,  # stdin_path
83            None,  # stdout_path
84            None,  # stderr_path
85            None,  # working directory
86            flags,  # launch flags
87            False,  # Stop at entry
88            error,
89        )  # error
90
91        self.assertEqual(process.GetState(), lldb.eStateStopped, PROCESS_STOPPED)
92
93        total_solibs_added = 0
94        total_solibs_removed = 0
95        total_modules_added_events = 0
96        total_modules_removed_events = 0
97        already_loaded_modules = []
98        max_solibs_per_event = 0
99        max_solib_chunk_per_event = []
100        while listener.GetNextEvent(event):
101            if lldb.SBTarget.EventIsTargetEvent(event):
102                if event.GetType() == lldb.SBTarget.eBroadcastBitModulesLoaded:
103                    solib_count = lldb.SBTarget.GetNumModulesFromEvent(event)
104                    total_modules_added_events += 1
105                    total_solibs_added += solib_count
106                    added_files = []
107                    for i in range(solib_count):
108                        module = lldb.SBTarget.GetModuleAtIndexFromEvent(i, event)
109                        # On macOS Ventura and later, dyld and the main binary
110                        # will be loaded again when dyld moves itself into the
111                        # shared cache. Use the basename so this also works
112                        # when reading dyld from the expanded shared cache.
113                        exe_basename = lldb.SBFileSpec(exe).basename
114                        if module.file.basename not in ["dyld", exe_basename]:
115                            self.assertNotIn(
116                                module,
117                                already_loaded_modules,
118                                "{} is already loaded".format(module),
119                            )
120                        already_loaded_modules.append(module)
121                        added_files.append(module.GetFileSpec().GetFilename())
122                    if self.TraceOn():
123                        # print all of the binaries that have been added
124                        print("Loaded files: %s" % (", ".join(added_files)))
125
126                    # We will check the latest biggest chunk of loaded solibs.
127                    # We expect all of our solibs in the last chunk of loaded modules.
128                    if solib_count >= max_solibs_per_event:
129                        max_solib_chunk_per_event = added_files.copy()
130                        max_solibs_per_event = solib_count
131
132                if event.GetType() == lldb.SBTarget.eBroadcastBitModulesUnloaded:
133                    solib_count = lldb.SBTarget.GetNumModulesFromEvent(event)
134                    total_modules_removed_events += 1
135                    total_solibs_removed += solib_count
136                    if self.TraceOn():
137                        # print all of the binaries that have been removed
138                        removed_files = []
139                        i = 0
140                        while i < solib_count:
141                            module = lldb.SBTarget.GetModuleAtIndexFromEvent(i, event)
142                            removed_files.append(module.GetFileSpec().GetFilename())
143                            i = i + 1
144                        print("Unloaded files: %s" % (", ".join(removed_files)))
145
146        # This is testing that we get back a small number of events with the loaded
147        # binaries in batches.  Check that we got back more than 1 solib per event.
148        # In practice on Darwin today, we get back two events for a do-nothing c
149        # program: a.out and dyld, and then all the rest of the system libraries.
150        # On Linux we get events for ld.so, [vdso], the binary and then all libraries,
151        # but the different configurations could load a different number of .so modules
152        # per event.
153        self.assertLessEqual(set(expected_solibs), set(max_solib_chunk_per_event))
154