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