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