1"""
2Test basics of Minidump debugging.
3"""
4
5from six import iteritems
6
7
8import lldb
9import os
10from lldbsuite.test.decorators import *
11from lldbsuite.test.lldbtest import *
12from lldbsuite.test import lldbutil
13
14
15class MiniDumpUUIDTestCase(TestBase):
16
17    mydir = TestBase.compute_mydir(__file__)
18
19    NO_DEBUG_INFO_TESTCASE = True
20
21    def verify_module(self, module, verify_path, verify_uuid):
22        # Compare the filename and the directory separately. We are avoiding
23        # SBFileSpec.fullpath because it causes a slash/backslash confusion
24        # on Windows.
25        self.assertEqual(
26            os.path.basename(verify_path), module.GetFileSpec().basename)
27        self.assertEqual(
28            os.path.dirname(verify_path), module.GetFileSpec().dirname or "")
29        self.assertEqual(verify_uuid, module.GetUUIDString())
30
31    def get_minidump_modules(self, yaml_file):
32        minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp")
33        self.yaml2obj(yaml_file, minidump_path)
34        self.target = self.dbg.CreateTarget(None)
35        self.process = self.target.LoadCore(minidump_path)
36        return self.target.modules
37
38    def test_zero_uuid_modules(self):
39        """
40            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
41            but contains a PDB70 value whose age is zero and whose UUID values are
42            all zero. Prior to a fix all such modules would be duplicated to the
43            first one since the UUIDs claimed to be valid and all zeroes. Now we
44            ensure that the UUID is not valid for each module and that we have
45            each of the modules in the target after loading the core
46        """
47        modules = self.get_minidump_modules("linux-arm-zero-uuids.yaml")
48        self.assertEqual(2, len(modules))
49        self.verify_module(modules[0], "/file/does/not/exist/a", None)
50        self.verify_module(modules[1], "/file/does/not/exist/b", None)
51
52    def test_uuid_modules_no_age(self):
53        """
54            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
55            and contains a PDB70 value whose age is zero and whose UUID values are
56            valid. Ensure we decode the UUID and don't include the age field in the UUID.
57        """
58        modules = self.get_minidump_modules("linux-arm-uuids-no-age.yaml")
59        modules = self.target.modules
60        self.assertEqual(2, len(modules))
61        self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10")
62        self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0")
63
64    def test_uuid_modules_no_age_apple(self):
65        """
66            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
67            and contains a PDB70 value whose age is zero and whose UUID values are
68            valid. Ensure we decode the UUID and don't include the age field in the UUID.
69            Also ensure that the first uint32_t is byte swapped, along with the next
70            two uint16_t values. Breakpad incorrectly byte swaps these values when it
71            saves Darwin minidump files.
72        """
73        modules = self.get_minidump_modules("macos-arm-uuids-no-age.yaml")
74        modules = self.target.modules
75        self.assertEqual(2, len(modules))
76        self.verify_module(modules[0], "/tmp/a", "04030201-0605-0807-090A-0B0C0D0E0F10")
77        self.verify_module(modules[1], "/tmp/b", "281E140A-3C32-5046-5A64-6E78828C96A0")
78
79    def test_uuid_modules_with_age(self):
80        """
81            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
82            and contains a PDB70 value whose age is valid and whose UUID values are
83            valid. Ensure we decode the UUID and include the age field in the UUID.
84        """
85        modules = self.get_minidump_modules("linux-arm-uuids-with-age.yaml")
86        self.assertEqual(2, len(modules))
87        self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-10101010")
88        self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-20202020")
89
90    def test_uuid_modules_elf_build_id_16(self):
91        """
92            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
93            and contains a ELF build ID whose value is valid and is 16 bytes long.
94        """
95        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml")
96        self.assertEqual(2, len(modules))
97        self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10")
98        self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0")
99
100    def test_uuid_modules_elf_build_id_20(self):
101        """
102            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
103            and contains a ELF build ID whose value is valid and is 20 bytes long.
104        """
105        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml")
106        self.assertEqual(2, len(modules))
107        self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314")
108        self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8")
109
110    def test_uuid_modules_elf_build_id_zero(self):
111        """
112            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
113            and contains a ELF build ID whose value is all zero.
114        """
115        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-zero.yaml")
116        self.assertEqual(2, len(modules))
117        self.verify_module(modules[0], "/not/exist/a", None)
118        self.verify_module(modules[1], "/not/exist/b", None)
119
120    def test_uuid_modules_elf_build_id_same(self):
121        """
122            Test multiple modules having a MINIDUMP_MODULE.CvRecord that is
123            valid, and contains a ELF build ID whose value is the same. There
124            is an assert in the PlaceholderObjectFile that was firing when we
125            encountered this which was crashing the process that was checking
126            if PlaceholderObjectFile.m_base was the same as the address this
127            fake module was being loaded at. We need to ensure we don't crash
128            in such cases and that we add both modules even though they have
129            the same UUID.
130        """
131        modules = self.get_minidump_modules("linux-arm-same-uuids.yaml")
132        self.assertEqual(2, len(modules))
133        self.verify_module(modules[0], "/file/does/not/exist/a",
134                           '11223344-1122-3344-1122-334411223344-11223344')
135        self.verify_module(modules[1], "/file/does/not/exist/b",
136                           '11223344-1122-3344-1122-334411223344-11223344')
137
138    def test_partial_uuid_match(self):
139        """
140            Breakpad has been known to create minidump files using CvRecord in each
141            module whose signature is set to PDB70 where the UUID only contains the
142            first 16 bytes of a 20 byte ELF build ID. Code was added to
143            ProcessMinidump.cpp to deal with this and allows partial UUID matching.
144
145            This test verifies that if we have a minidump with a 16 byte UUID, that
146            we are able to associate a symbol file with a 20 byte UUID only if the
147            first 16 bytes match. In this case we will see the path from the file
148            we found in the test directory and the 20 byte UUID from the actual
149            file, not the 16 byte shortened UUID from the minidump.
150        """
151        so_path = self.getBuildArtifact("libuuidmatch.so")
152        self.yaml2obj("libuuidmatch.yaml", so_path)
153        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
154        self.dbg.HandleCommand(cmd)
155        modules = self.get_minidump_modules("linux-arm-partial-uuids-match.yaml")
156        self.assertEqual(1, len(modules))
157        self.verify_module(modules[0], so_path,
158                           "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116")
159
160    def test_partial_uuid_mismatch(self):
161        """
162            Breakpad has been known to create minidump files using CvRecord in each
163            module whose signature is set to PDB70 where the UUID only contains the
164            first 16 bytes of a 20 byte ELF build ID. Code was added to
165            ProcessMinidump.cpp to deal with this and allows partial UUID matching.
166
167            This test verifies that if we have a minidump with a 16 byte UUID, that
168            we are not able to associate a symbol file with a 20 byte UUID only if
169            any of the first 16 bytes do not match. In this case we will see the UUID
170            from the minidump file and the path from the minidump file.
171        """
172        so_path = self.getBuildArtifact("libuuidmismatch.so")
173        self.yaml2obj("libuuidmismatch.yaml", so_path)
174        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
175        self.dbg.HandleCommand(cmd)
176        modules = self.get_minidump_modules("linux-arm-partial-uuids-mismatch.yaml")
177        self.assertEqual(1, len(modules))
178        self.verify_module(modules[0],
179                           "/invalid/path/on/current/system/libuuidmismatch.so",
180                           "7295E17C-6668-9E05-CBB5-DEE5003865D5")
181
182    def test_breakpad_hash_match(self):
183        """
184            Breakpad creates minidump files using CvRecord in each module whose
185            signature is set to PDB70 where the UUID is a hash generated by
186            breakpad of the .text section. This is only done when the
187            executable has no ELF build ID.
188
189            This test verifies that if we have a minidump with a 16 byte UUID,
190            that we are able to associate a symbol file with no ELF build ID
191            and match it up by hashing the .text section.
192        """
193        so_path = self.getBuildArtifact("libbreakpad.so")
194        self.yaml2obj("libbreakpad.yaml", so_path)
195        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
196        self.dbg.HandleCommand(cmd)
197        modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml")
198        self.assertEqual(1, len(modules))
199        # LLDB makes up it own UUID as well when there is no build ID so we
200        # will check that this matches.
201        self.verify_module(modules[0], so_path, "D9C480E8")
202
203    def test_breakpad_overflow_hash_match(self):
204        """
205            This is a similar to test_breakpad_hash_match, but it verifies that
206            if the .text section does not end on a 16 byte boundary, then it
207            will overflow into the next section's data by up to 15 bytes. This
208            verifies that we are able to match what breakpad does as it will do
209            this.
210        """
211        so_path = self.getBuildArtifact("libbreakpad.so")
212        self.yaml2obj("libbreakpad-overflow.yaml", so_path)
213        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
214        self.dbg.HandleCommand(cmd)
215        modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml")
216        self.assertEqual(1, len(modules))
217        # LLDB makes up it own UUID as well when there is no build ID so we
218        # will check that this matches.
219        self.verify_module(modules[0], so_path, "48EB9FD7")
220
221
222    def test_facebook_hash_match(self):
223        """
224            Breakpad creates minidump files using CvRecord in each module whose
225            signature is set to PDB70 where the UUID is a hash generated by
226            breakpad of the .text section and Facebook modified this hash to
227            avoid collisions. This is only done when the executable has no ELF
228            build ID.
229
230            This test verifies that if we have a minidump with a 16 byte UUID,
231            that we are able to associate a symbol file with no ELF build ID
232            and match it up by hashing the .text section like Facebook does.
233        """
234        so_path = self.getBuildArtifact("libbreakpad.so")
235        self.yaml2obj("libbreakpad.yaml", so_path)
236        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
237        self.dbg.HandleCommand(cmd)
238        modules = self.get_minidump_modules("linux-arm-facebook-uuid-match.yaml")
239        self.assertEqual(1, len(modules))
240        # LLDB makes up it own UUID as well when there is no build ID so we
241        # will check that this matches.
242        self.verify_module(modules[0], so_path, "D9C480E8")
243
244
245    def test_relative_module_name(self):
246        old_cwd = os.getcwd()
247        self.addTearDownHook(lambda: os.chdir(old_cwd))
248        os.chdir(self.getBuildDir())
249        name = "file-with-a-name-unlikely-to-exist-in-the-current-directory.so"
250        open(name, "a").close()
251        modules = self.get_minidump_modules(
252                self.getSourcePath("relative_module_name.yaml"))
253        self.assertEqual(1, len(modules))
254        self.verify_module(modules[0], name, None)
255
256    def test_add_module_build_id_16(self):
257        """
258            Test that adding module with 16 byte UUID returns the existing
259            module or fails.
260        """
261        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml")
262        self.assertEqual(2, len(modules))
263
264        # Add the existing modules.
265        self.assertEqual(modules[0], self.target.AddModule(
266            "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10"))
267        self.assertEqual(modules[1], self.target.AddModule(
268            "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0"))
269
270        # Adding modules with non-existing UUID should fail.
271        self.assertFalse(
272            self.target.AddModule(
273                "a", "", "12345678-1234-1234-1234-123456789ABC").IsValid())
274        self.assertFalse(
275            self.target.AddModule(
276                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678").IsValid())
277
278    def test_add_module_build_id_20(self):
279        """
280            Test that adding module with 20 byte UUID returns the existing
281            module or fails.
282        """
283        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml")
284
285        # Add the existing modules.
286        self.assertEqual(modules[0], self.target.AddModule(
287            "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314"))
288        self.assertEqual(modules[1], self.target.AddModule(
289            "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8"))
290
291        # Adding modules with non-existing UUID should fail.
292        self.assertFalse(
293            self.target.AddModule(
294                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10").IsValid())
295        self.assertFalse(
296            self.target.AddModule(
297                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678").IsValid())
298
299    def test_add_module_build_id_4(self):
300        """
301            Test that adding module with 4 byte UUID returns the existing
302            module or fails.
303        """
304        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-4.yaml")
305
306        # Add the existing modules.
307        self.assertEqual(modules[0], self.target.AddModule(
308            "/some/local/a.so", "", "01020304"))
309        self.assertEqual(modules[1], self.target.AddModule(
310            "/some/local/b.so", "", "0A141E28"))
311
312        # Adding modules with non-existing UUID should fail.
313        self.assertFalse(
314            self.target.AddModule(
315                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10").IsValid())
316        self.assertFalse(self.target.AddModule("a", "", "01020305").IsValid())
317
318    @skipIfReproducer # Modules are not orphaned and it finds the module with the same UUID from test_partial_uuid_match.
319    def test_remove_placeholder_add_real_module(self):
320        """
321            Test that removing a placeholder module and adding back the real
322            module succeeds.
323        """
324        so_path = self.getBuildArtifact("libuuidmatch.so")
325        self.yaml2obj("libuuidmatch.yaml", so_path)
326        modules = self.get_minidump_modules("linux-arm-uuids-match.yaml")
327
328        uuid = "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116";
329        self.assertEqual(1, len(modules))
330        self.verify_module(modules[0], "/target/path/libuuidmatch.so",uuid)
331
332        self.target.RemoveModule(modules[0])
333        new_module = self.target.AddModule(so_path, "", uuid)
334
335        self.verify_module(new_module, so_path, uuid)
336        self.assertEqual(new_module, self.target.modules[0])
337        self.assertEqual(1, len(self.target.modules))
338