xref: /llvm-project/lldb/test/API/debuginfod/SplitDWARF/TestDebuginfodDWP.py (revision e77ac42bccb8c26bbf4b74d8e92eb09e7fa1b218)
1"""
2Test support for the DebugInfoD network symbol acquisition protocol.
3"""
4import os
5import shutil
6import tempfile
7
8import lldb
9from lldbsuite.test.decorators import *
10import lldbsuite.test.lldbutil as lldbutil
11from lldbsuite.test.lldbtest import *
12
13
14"""
15Test support for the DebugInfoD network symbol acquisition protocol.
16This file is for split-dwarf (dwp) scenarios.
17
181 - A split binary target with it's corresponding DWP file
192 - A stripped, split binary target with an unstripped binary and a DWP file
203 - A stripped, split binary target with an --only-keep-debug symbols file and a DWP file
21"""
22
23
24class DebugInfodDWPTests(TestBase):
25    # No need to try every flavor of debug inf.
26    NO_DEBUG_INFO_TESTCASE = True
27
28    @skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
29    def test_normal_stripped(self):
30        """
31        Validate behavior with a stripped binary, no symbols or symbol locator.
32        """
33        self.config_test(["a.out"])
34        self.try_breakpoint(False)
35
36    @skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
37    def test_normal_stripped_split_with_dwp(self):
38        """
39        Validate behavior with symbols, but no symbol locator.
40        """
41        self.config_test(["a.out", "a.out.debug", "a.out.dwp"])
42        self.try_breakpoint(True)
43
44    @skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
45    def test_normal_stripped_only_dwp(self):
46        """
47        Validate behavior *with* dwp symbols only, but missing other symbols,
48        but no symbol locator. This shouldn't work: without the other symbols
49        DWO's appear mostly useless.
50        """
51        self.config_test(["a.out", "a.out.dwp"])
52        self.try_breakpoint(False)
53
54    @skipIfCurlSupportMissing
55    @skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
56    def test_debuginfod_dwp_from_service(self):
57        """
58        Test behavior with the unstripped binary, and DWP from the service.
59        """
60        self.config_test(["a.out.debug"], "a.out.dwp")
61        self.try_breakpoint(True)
62
63    @skipIfCurlSupportMissing
64    @skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
65    def test_debuginfod_both_symfiles_from_service(self):
66        """
67        Test behavior with a stripped binary, with the unstripped binary and
68        dwp symbols from Debuginfod.
69        """
70        self.config_test(["a.out"], "a.out.dwp", "a.out.unstripped")
71        self.try_breakpoint(True)
72
73    @skipIfCurlSupportMissing
74    @skipUnlessPlatform(["linux_freebsd_but_old_dwp_tools_on_build_bots_are_broken"])
75    def test_debuginfod_both_okd_symfiles_from_service(self):
76        """
77        Test behavior with both the only-keep-debug symbols and the dwp symbols
78        from Debuginfod.
79        """
80        self.config_test(["a.out"], "a.out.dwp", "a.out.debug")
81        self.try_breakpoint(True)
82
83    def try_breakpoint(self, should_have_loc):
84        """
85        This function creates a target from self.aout, sets a function-name
86        breakpoint, and checks to see if we have a file/line location,
87        as a way to validate that the symbols have been loaded.
88        should_have_loc specifies if we're testing that symbols have or
89        haven't been loaded.
90        """
91        target = self.dbg.CreateTarget(self.aout)
92        self.assertTrue(target and target.IsValid(), "Target is valid")
93
94        bp = target.BreakpointCreateByName("func")
95        self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid")
96        self.assertEqual(bp.GetNumLocations(), 1)
97
98        loc = bp.GetLocationAtIndex(0)
99        self.assertTrue(loc and loc.IsValid(), "Location is valid")
100        addr = loc.GetAddress()
101        self.assertTrue(addr and addr.IsValid(), "Loc address is valid")
102        line_entry = addr.GetLineEntry()
103        self.assertEqual(
104            should_have_loc,
105            line_entry != None and line_entry.IsValid(),
106            "Loc line entry is valid",
107        )
108        if should_have_loc:
109            self.assertEqual(line_entry.GetLine(), 4)
110            self.assertEqual(
111                line_entry.GetFileSpec().GetFilename(),
112                self.main_source_file.GetFilename(),
113            )
114        self.dbg.DeleteTarget(target)
115        shutil.rmtree(self.tmp_dir)
116
117    def config_test(self, local_files, debuginfo=None, executable=None):
118        """
119        Set up a test with local_files[] copied to a different location
120        so that we control which files are, or are not, found in the file system.
121        Also, create a stand-alone file-system 'hosted' debuginfod server with the
122        provided debuginfo and executable files (if they exist)
123
124        Make the filesystem look like:
125
126        /tmp/<tmpdir>/test/[local_files]
127
128        /tmp/<tmpdir>/cache (for lldb to use as a temp cache)
129
130        /tmp/<tmpdir>/buildid/<uuid>/executable -> <executable>
131        /tmp/<tmpdir>/buildid/<uuid>/debuginfo -> <debuginfo>
132        Returns the /tmp/<tmpdir> path
133        """
134
135        self.build()
136
137        uuid = self.getUUID("a.out")
138        if not uuid:
139            self.fail("Could not get UUID for a.out")
140            return
141        self.main_source_file = lldb.SBFileSpec("main.c")
142        self.tmp_dir = tempfile.mkdtemp()
143        self.test_dir = os.path.join(self.tmp_dir, "test")
144        os.makedirs(self.test_dir)
145
146        self.aout = ""
147        # Copy the files used by the test:
148        for f in local_files:
149            shutil.copy(self.getBuildArtifact(f), self.test_dir)
150            if self.aout == "":
151                self.aout = os.path.join(self.test_dir, f)
152
153        use_debuginfod = debuginfo != None or executable != None
154
155        # Populated the 'file://... mocked' Debuginfod server:
156        if use_debuginfod:
157            os.makedirs(os.path.join(self.tmp_dir, "cache"))
158            uuid_dir = os.path.join(self.tmp_dir, "buildid", uuid)
159            os.makedirs(uuid_dir)
160            if debuginfo:
161                shutil.copy(
162                    self.getBuildArtifact(debuginfo),
163                    os.path.join(uuid_dir, "debuginfo"),
164                )
165            if executable:
166                shutil.copy(
167                    self.getBuildArtifact(executable),
168                    os.path.join(uuid_dir, "executable"),
169                )
170        os.remove(self.getBuildArtifact("main.dwo"))
171        # Configure LLDB for the test:
172        self.runCmd(
173            "settings set symbols.enable-external-lookup %s"
174            % str(use_debuginfod).lower()
175        )
176        self.runCmd("settings clear plugin.symbol-locator.debuginfod.server-urls")
177        if use_debuginfod:
178            self.runCmd(
179                "settings set plugin.symbol-locator.debuginfod.cache-path %s/cache"
180                % self.tmp_dir
181            )
182            self.runCmd(
183                "settings insert-before plugin.symbol-locator.debuginfod.server-urls 0 file://%s"
184                % self.tmp_dir
185            )
186
187    def getUUID(self, filename):
188        try:
189            spec = lldb.SBModuleSpec()
190            spec.SetFileSpec(lldb.SBFileSpec(self.getBuildArtifact(filename)))
191            module = lldb.SBModule(spec)
192            uuid = module.GetUUIDString().replace("-", "").lower()
193            # Don't want lldb's fake 32 bit CRC's for this one
194            return uuid if len(uuid) > 8 else None
195        except:
196            return None
197