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