1"""
2Test how many times newly loaded binaries are notified;
3they should be delivered in batches instead of one-by-one.
4"""
5
6from __future__ import print_function
7
8
9import lldb
10from lldbsuite.test.decorators import *
11from lldbsuite.test.lldbtest import *
12from lldbsuite.test import lldbutil
13
14class ModuleLoadedNotifysTestCase(TestBase):
15    NO_DEBUG_INFO_TESTCASE = True
16
17    # At least DynamicLoaderDarwin and DynamicLoaderPOSIXDYLD should batch up
18    # notifications about newly added/removed libraries.  Other DynamicLoaders may
19    # not be written this way.
20    @skipUnlessPlatform(["linux"]+lldbplatformutil.getDarwinOSTriples())
21
22    def setUp(self):
23        # Call super's setUp().
24        TestBase.setUp(self)
25        # Find the line number to break inside main().
26        self.line = line_number('main.cpp', '// breakpoint')
27
28    def test_launch_notifications(self):
29        """Test that lldb broadcasts newly loaded libraries in batches."""
30        self.build()
31        exe = self.getBuildArtifact("a.out")
32        self.dbg.SetAsync(False)
33
34        listener = self.dbg.GetListener()
35        listener.StartListeningForEventClass(
36            self.dbg,
37            lldb.SBTarget.GetBroadcasterClassName(),
38            lldb.SBTarget.eBroadcastBitModulesLoaded | lldb.SBTarget.eBroadcastBitModulesUnloaded)
39
40        # Create a target by the debugger.
41        target = self.dbg.CreateTarget(exe)
42        self.assertTrue(target, VALID_TARGET)
43
44        # break on main
45        breakpoint = target.BreakpointCreateByName('main', 'a.out')
46
47        event = lldb.SBEvent()
48        # CreateTarget() generated modules-loaded events; consume them & toss
49        while listener.GetNextEvent(event):
50            True
51
52        error = lldb.SBError()
53        flags = target.GetLaunchInfo().GetLaunchFlags()
54        process = target.Launch(listener,
55                                None,      # argv
56                                None,      # envp
57                                None,      # stdin_path
58                                None,      # stdout_path
59                                None,      # stderr_path
60                                None,      # working directory
61                                flags,     # launch flags
62                                False,     # Stop at entry
63                                error)     # error
64
65        self.assertEqual(
66            process.GetState(), lldb.eStateStopped,
67            PROCESS_STOPPED)
68
69        total_solibs_added = 0
70        total_solibs_removed = 0
71        total_modules_added_events = 0
72        total_modules_removed_events = 0
73        already_loaded_modules = []
74        while listener.GetNextEvent(event):
75            if lldb.SBTarget.EventIsTargetEvent(event):
76                if event.GetType() == lldb.SBTarget.eBroadcastBitModulesLoaded:
77                    solib_count = lldb.SBTarget.GetNumModulesFromEvent(event)
78                    total_modules_added_events += 1
79                    total_solibs_added += solib_count
80                    added_files = []
81                    for i in range (solib_count):
82                        module = lldb.SBTarget.GetModuleAtIndexFromEvent(i, event)
83                        # On macOS Ventura and later, dyld and the main binary
84                        # will be loaded again when dyld moves itself into the
85                        # shared cache.
86                        if module.file.fullpath not in ['/usr/lib/dyld', exe]:
87                            self.assertTrue(module not in already_loaded_modules, '{} is already loaded'.format(module))
88                        already_loaded_modules.append(module)
89                        if self.TraceOn():
90                            added_files.append(module.GetFileSpec().GetFilename())
91                    if self.TraceOn():
92                        # print all of the binaries that have been added
93                        print("Loaded files: %s" % (', '.join(added_files)))
94
95                if event.GetType() == lldb.SBTarget.eBroadcastBitModulesUnloaded:
96                    solib_count = lldb.SBTarget.GetNumModulesFromEvent(event)
97                    total_modules_removed_events += 1
98                    total_solibs_removed += solib_count
99                    if self.TraceOn():
100                        # print all of the binaries that have been removed
101                        removed_files = []
102                        i = 0
103                        while i < solib_count:
104                            module = lldb.SBTarget.GetModuleAtIndexFromEvent(i, event)
105                            removed_files.append(module.GetFileSpec().GetFilename())
106                            i = i + 1
107                        print("Unloaded files: %s" % (', '.join(removed_files)))
108
109
110        # This is testing that we get back a small number of events with the loaded
111        # binaries in batches.  Check that we got back more than 1 solib per event.
112        # In practice on Darwin today, we get back two events for a do-nothing c
113        # program: a.out and dyld, and then all the rest of the system libraries.
114        # On Linux we get events for ld.so, [vdso], the binary and then all libraries.
115
116        avg_solibs_added_per_event = round(float(total_solibs_added) / float(total_modules_added_events))
117        self.assertGreater(avg_solibs_added_per_event, 1)
118