aboutsummaryrefslogtreecommitdiff
path: root/lldb/test/API/functionalities/target-new-solib-notifications/TestModuleLoadedNotifys.py
blob: 47af6909b728cfad35811660f233b8d21ac19df0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""
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))