1""" 2Test the use of the global module cache in lldb 3""" 4 5import lldb 6 7from lldbsuite.test.decorators import * 8from lldbsuite.test.lldbtest import * 9from lldbsuite.test import lldbutil 10import os 11import shutil 12from pathlib import Path 13import time 14 15class GlobalModuleCacheTestCase(TestBase): 16 # NO_DEBUG_INFO_TESTCASE = True 17 18 def check_counter_var(self, thread, value): 19 frame = thread.frames[0] 20 var = frame.FindVariable("counter") 21 self.assertTrue(var.GetError().Success(), "Got counter variable") 22 self.assertEqual(var.GetValueAsUnsigned(), value, "This was one-print") 23 24 def copy_to_main(self, src, dst): 25 # We are relying on the source file being newer than the .o file from 26 # a previous build, so sleep a bit here to ensure that the touch is later. 27 time.sleep(2) 28 try: 29 # Make sure dst is writeable before trying to write to it. 30 subprocess.run( 31 ["chmod", "777", dst], 32 stdin=None, 33 capture_output=False, 34 encoding="utf-8", 35 ) 36 shutil.copy(src, dst) 37 except: 38 self.fail(f"Could not copy {src} to {dst}") 39 Path(dst).touch() 40 41 # The rerun tests indicate rerunning on Windows doesn't really work, so 42 # this one won't either. 43 @skipIfWindows 44 # On Arm and AArch64 Linux, this test attempts to pop a thread plan when 45 # we only have the base plan remaining. Skip it until we can figure out 46 # the bug this is exposing (https://github.com/llvm/llvm-project/issues/76057). 47 @skipIf(oslist=["linux"], archs=["arm", "aarch64"]) 48 def test_OneTargetOneDebugger(self): 49 self.do_test(True, True) 50 51 # This behaves as implemented but that behavior is not desirable. 52 # This test tests for the desired behavior as an expected fail. 53 @skipIfWindows 54 @expectedFailureAll 55 def test_TwoTargetsOneDebugger(self): 56 self.do_test(False, True) 57 58 @skipIfWindows 59 @expectedFailureAll 60 def test_OneTargetTwoDebuggers(self): 61 self.do_test(True, False) 62 63 def do_test(self, one_target, one_debugger): 64 # Make sure that if we have one target, and we run, then 65 # change the binary and rerun, the binary (and any .o files 66 # if using dwarf in .o file debugging) get removed from the 67 # shared module cache. They are no longer reachable. 68 debug_style = self.getDebugInfo() 69 70 # Before we do anything, clear the global module cache so we don't 71 # see objects from other runs: 72 lldb.SBDebugger.MemoryPressureDetected() 73 74 # Set up the paths for our two versions of main.c: 75 main_c_path = os.path.join(self.getBuildDir(), "main.c") 76 one_print_path = os.path.join(self.getSourceDir(), "one-print.c") 77 two_print_path = os.path.join(self.getSourceDir(), "two-print.c") 78 main_filespec = lldb.SBFileSpec(main_c_path) 79 80 # First copy the one-print.c to main.c in the build folder and 81 # build our a.out from there: 82 self.copy_to_main(one_print_path, main_c_path) 83 self.build(dictionary={"C_SOURCES": main_c_path, "EXE": "a.out"}) 84 85 (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( 86 self, "return counter;", main_filespec 87 ) 88 89 # Make sure we ran the version we intended here: 90 self.check_counter_var(thread, 1) 91 process.Kill() 92 93 # Now copy two-print.c over main.c, rebuild, and rerun: 94 # os.unlink(target.GetExecutable().fullpath) 95 self.copy_to_main(two_print_path, main_c_path) 96 97 self.build(dictionary={"C_SOURCES": main_c_path, "EXE": "a.out"}) 98 error = lldb.SBError() 99 if one_debugger: 100 if one_target: 101 (_, process, thread, _) = lldbutil.run_to_breakpoint_do_run( 102 self, target, bkpt 103 ) 104 else: 105 (target2, process2, thread, bkpt) = lldbutil.run_to_source_breakpoint( 106 self, "return counter;", main_filespec 107 ) 108 else: 109 if one_target: 110 new_debugger = lldb.SBDebugger().Create() 111 self.old_debugger = self.dbg 112 self.dbg = new_debugger 113 def cleanupDebugger(self): 114 lldb.SBDebugger.Destroy(self.dbg) 115 self.dbg = self.old_debugger 116 self.old_debugger = None 117 118 self.addTearDownHook(cleanupDebugger) 119 (target2, process2, thread, bkpt) = lldbutil.run_to_source_breakpoint( 120 self, "return counter;", main_filespec 121 ) 122 123 # In two-print.c counter will be 2: 124 self.check_counter_var(thread, 2) 125 126 # If we made two targets, destroy the first one, that should free up the 127 # unreachable Modules: 128 if not one_target: 129 target.Clear() 130 131 num_a_dot_out_entries = 1 132 # For dSYM's there will be two lines of output, one for the a.out and one 133 # for the dSYM. 134 if debug_style == "dsym": 135 num_a_dot_out_entries += 1 136 137 error = self.check_image_list_result(num_a_dot_out_entries, 1) 138 # Even if this fails, MemoryPressureDetected should fix this. 139 lldb.SBDebugger.MemoryPressureDetected() 140 error_after_mpd = self.check_image_list_result(num_a_dot_out_entries, 1) 141 fail_msg = "" 142 if error != "": 143 fail_msg = "Error before MPD: " + error 144 145 if error_after_mpd != "": 146 fail_msg = fail_msg + "\nError after MPD: " + error_after_mpd 147 if fail_msg != "": 148 self.fail(fail_msg) 149 150 def check_image_list_result(self, num_a_dot_out, num_main_dot_o): 151 # Check the global module list, there should only be one a.out, and if we are 152 # doing dwarf in .o file, there should only be one .o file. This returns 153 # an error string on error - rather than asserting, so you can stage this 154 # failing. 155 image_cmd_result = lldb.SBCommandReturnObject() 156 interp = self.dbg.GetCommandInterpreter() 157 interp.HandleCommand("image list -g", image_cmd_result) 158 if self.TraceOn(): 159 print(f"Expected: a.out: {num_a_dot_out} main.o: {num_main_dot_o}") 160 print(image_cmd_result) 161 162 image_list_str = image_cmd_result.GetOutput() 163 image_list = image_list_str.splitlines() 164 found_a_dot_out = 0 165 found_main_dot_o = 0 166 167 for line in image_list: 168 # FIXME: force this to be at the end of the string: 169 if "a.out" in line: 170 found_a_dot_out += 1 171 if "main.o" in line: 172 found_main_dot_o += 1 173 174 if num_a_dot_out != found_a_dot_out: 175 return f"Got {found_a_dot_out} number of a.out's, expected {num_a_dot_out}" 176 177 if found_main_dot_o > 0 and num_main_dot_o != found_main_dot_o: 178 return f"Got {found_main_dot_o} number of main.o's, expected {num_main_dot_o}" 179 180 return "" 181