xref: /llvm-project/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpUUID.py (revision aa2784876a245f10811f65eb748c43574c17a173)
1"""
2Test basics of Minidump debugging.
3"""
4
5import lldb
6import os
7from lldbsuite.test.decorators import *
8from lldbsuite.test.lldbtest import *
9from lldbsuite.test import lldbutil
10
11
12class MiniDumpUUIDTestCase(TestBase):
13    NO_DEBUG_INFO_TESTCASE = True
14
15    def verify_module(self, module, verify_path, verify_uuid):
16        # Compare the filename and the directory separately. We are avoiding
17        # SBFileSpec.fullpath because it causes a slash/backslash confusion
18        # on Windows.  Similarly, we compare the directories using normcase
19        # because they may contain a Linux-style relative path from the
20        # minidump appended to a Windows-style root path from the host.
21        self.assertEqual(os.path.basename(verify_path), module.GetFileSpec().basename)
22        self.assertEqual(
23            os.path.normcase(os.path.dirname(verify_path)),
24            os.path.normcase(module.GetFileSpec().dirname or ""),
25        )
26        self.assertEqual(verify_uuid, module.GetUUIDString())
27
28    def get_minidump_modules(self, yaml_file, exe=None):
29        minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp")
30        self.yaml2obj(yaml_file, minidump_path)
31        self.target = self.dbg.CreateTarget(exe)
32        self.process = self.target.LoadCore(minidump_path)
33        return self.target.modules
34
35    def test_zero_uuid_modules(self):
36        """
37        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
38        but contains a PDB70 value whose age is zero and whose UUID values are
39        all zero. Prior to a fix all such modules would be duplicated to the
40        first one since the UUIDs claimed to be valid and all zeroes. Now we
41        ensure that the UUID is not valid for each module and that we have
42        each of the modules in the target after loading the core
43        """
44        modules = self.get_minidump_modules("linux-arm-zero-uuids.yaml")
45        self.assertEqual(2, len(modules))
46        self.verify_module(modules[0], "/file/does/not/exist/a", None)
47        self.verify_module(modules[1], "/file/does/not/exist/b", None)
48
49    def test_uuid_modules_no_age(self):
50        """
51        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
52        and contains a PDB70 value whose age is zero and whose UUID values are
53        valid. Ensure we decode the UUID and don't include the age field in the UUID.
54        """
55        modules = self.get_minidump_modules("linux-arm-uuids-no-age.yaml")
56        modules = self.target.modules
57        self.assertEqual(2, len(modules))
58        self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10")
59        self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0")
60
61    def test_uuid_modules_no_age_apple(self):
62        """
63        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
64        and contains a PDB70 value whose age is zero and whose UUID values are
65        valid. Ensure we decode the UUID and don't include the age field in the UUID.
66        Also ensure that the first uint32_t is byte swapped, along with the next
67        two uint16_t values. Breakpad incorrectly byte swaps these values when it
68        saves Darwin minidump files.
69        """
70        modules = self.get_minidump_modules("macos-arm-uuids-no-age.yaml")
71        modules = self.target.modules
72        self.assertEqual(2, len(modules))
73        self.verify_module(modules[0], "/tmp/a", "04030201-0605-0807-090A-0B0C0D0E0F10")
74        self.verify_module(modules[1], "/tmp/b", "281E140A-3C32-5046-5A64-6E78828C96A0")
75
76    def test_uuid_modules_with_age(self):
77        """
78        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
79        and contains a PDB70 value whose age is valid and whose UUID values are
80        valid. Ensure we decode the UUID and include the age field in the UUID.
81        """
82        modules = self.get_minidump_modules("linux-arm-uuids-with-age.yaml")
83        self.assertEqual(2, len(modules))
84        self.verify_module(
85            modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-10101010"
86        )
87        self.verify_module(
88            modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-20202020"
89        )
90
91    def test_uuid_modules_elf_build_id_16(self):
92        """
93        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
94        and contains an ELF build ID whose value is valid and is 16 bytes long.
95        """
96        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml")
97        self.assertEqual(2, len(modules))
98        self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10")
99        self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0")
100
101    def test_uuid_modules_elf_build_id_20(self):
102        """
103        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
104        and contains an ELF build ID whose value is valid and is 20 bytes long.
105        """
106        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml")
107        self.assertEqual(2, len(modules))
108        self.verify_module(
109            modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314"
110        )
111        self.verify_module(
112            modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8"
113        )
114
115    def test_uuid_modules_elf_build_id_zero(self):
116        """
117        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid,
118        and contains an ELF build ID whose value is all zero.
119        """
120        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-zero.yaml")
121        self.assertEqual(2, len(modules))
122        self.verify_module(modules[0], "/not/exist/a", None)
123        self.verify_module(modules[1], "/not/exist/b", None)
124
125    def test_uuid_modules_elf_build_id_same(self):
126        """
127        Test multiple modules having a MINIDUMP_MODULE.CvRecord that is
128        valid, and contains an ELF build ID whose value is the same. There
129        is an assert in the PlaceholderObjectFile that was firing when we
130        encountered this which was crashing the process that was checking
131        if PlaceholderObjectFile.m_base was the same as the address this
132        fake module was being loaded at. We need to ensure we don't crash
133        in such cases and that we add both modules even though they have
134        the same UUID.
135        """
136        modules = self.get_minidump_modules("linux-arm-same-uuids.yaml")
137        self.assertEqual(2, len(modules))
138        self.verify_module(
139            modules[0],
140            "/file/does/not/exist/a",
141            "11223344-1122-3344-1122-334411223344-11223344",
142        )
143        self.verify_module(
144            modules[1],
145            "/file/does/not/exist/b",
146            "11223344-1122-3344-1122-334411223344-11223344",
147        )
148
149    def test_partial_uuid_match(self):
150        """
151        Breakpad has been known to create minidump files using CvRecord in each
152        module whose signature is set to PDB70 where the UUID only contains the
153        first 16 bytes of a 20 byte ELF build ID. Code was added to
154        ProcessMinidump.cpp to deal with this and allows partial UUID matching.
155
156        This test verifies that if we have a minidump with a 16 byte UUID, that
157        we are able to associate a symbol file with a 20 byte UUID only if the
158        first 16 bytes match. In this case we will see the path from the file
159        we found in the test directory and the 20 byte UUID from the actual
160        file, not the 16 byte shortened UUID from the minidump.
161        """
162        so_path = self.getBuildArtifact("libuuidmatch.so")
163        self.yaml2obj("libuuidmatch.yaml", so_path)
164        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
165        self.dbg.HandleCommand(cmd)
166        modules = self.get_minidump_modules("linux-arm-partial-uuids-match.yaml")
167        self.assertEqual(1, len(modules))
168        self.verify_module(
169            modules[0], so_path, "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116"
170        )
171
172    def test_partial_uuid_mismatch(self):
173        """
174        Breakpad has been known to create minidump files using CvRecord in each
175        module whose signature is set to PDB70 where the UUID only contains the
176        first 16 bytes of a 20 byte ELF build ID. Code was added to
177        ProcessMinidump.cpp to deal with this and allows partial UUID matching.
178
179        This test verifies that if we have a minidump with a 16 byte UUID, that
180        we are not able to associate a symbol file with a 20 byte UUID only if
181        any of the first 16 bytes do not match. In this case we will see the UUID
182        from the minidump file and the path from the minidump file.
183        """
184        so_path = self.getBuildArtifact("libuuidmismatch.so")
185        self.yaml2obj("libuuidmismatch.yaml", so_path)
186        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
187        self.dbg.HandleCommand(cmd)
188        modules = self.get_minidump_modules("linux-arm-partial-uuids-mismatch.yaml")
189        self.assertEqual(1, len(modules))
190        self.verify_module(
191            modules[0],
192            "/invalid/path/on/current/system/libuuidmismatch.so",
193            "7295E17C-6668-9E05-CBB5-DEE5003865D5",
194        )
195
196    def test_breakpad_hash_match(self):
197        """
198        Breakpad creates minidump files using CvRecord in each module whose
199        signature is set to PDB70 where the UUID is a hash generated by
200        breakpad of the .text section. This is only done when the
201        executable has no ELF build ID.
202
203        This test verifies that if we have a minidump with a 16 byte UUID,
204        that we are able to associate a symbol file with no ELF build ID
205        and match it up by hashing the .text section.
206        """
207        so_path = self.getBuildArtifact("libbreakpad.so")
208        self.yaml2obj("libbreakpad.yaml", so_path)
209        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
210        self.dbg.HandleCommand(cmd)
211        modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml")
212        self.assertEqual(1, len(modules))
213        # LLDB makes up it own UUID as well when there is no build ID so we
214        # will check that this matches.
215        self.verify_module(modules[0], so_path, "D9C480E8")
216
217    def test_breakpad_hash_match_sysroot(self):
218        """
219        Check that we can match the breakpad .text section hash when the
220        module is located under a user-provided sysroot.
221        """
222        sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot")
223        # Create the directory under the sysroot where the minidump reports
224        # the module.
225        so_dir = os.path.join(
226            sysroot_path, "invalid", "path", "on", "current", "system"
227        )
228        so_path = os.path.join(so_dir, "libbreakpad.so")
229        lldbutil.mkdir_p(so_dir)
230        self.yaml2obj("libbreakpad.yaml", so_path)
231        self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path)
232        modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml")
233        self.assertEqual(1, len(modules))
234        # LLDB makes up its own UUID as well when there is no build ID so we
235        # will check that this matches.
236        self.verify_module(modules[0], so_path, "D9C480E8")
237
238    def test_breakpad_hash_match_sysroot_decoy(self):
239        """
240        Check that we can match the breakpad .text section hash when there is
241        a module with the right name but wrong contents under a user-provided
242        sysroot, and the right module is at the given search path..
243        """
244        sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot")
245        # Create the directory under the sysroot where the minidump reports
246        # the module.
247        decoy_dir = os.path.join(
248            sysroot_path, "invalid", "path", "on", "current", "system"
249        )
250        decoy_path = os.path.join(decoy_dir, "libbreakpad.so")
251        lldbutil.mkdir_p(decoy_dir)
252        self.yaml2obj("libbreakpad-decoy.yaml", decoy_path)
253        self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path)
254        so_dir = os.path.join(self.getBuildDir(), "searchpath_dir")
255        so_path = os.path.join(so_dir, "libbreakpad.so")
256        lldbutil.mkdir_p(so_dir)
257        self.yaml2obj("libbreakpad.yaml", so_path)
258        self.runCmd('settings set target.exec-search-paths "%s"' % so_dir)
259        modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml")
260        self.assertEqual(1, len(modules))
261        # LLDB makes up its own UUID as well when there is no build ID so we
262        # will check that this matches.
263        self.verify_module(modules[0], so_path, "D9C480E8")
264
265    def test_breakpad_overflow_hash_match(self):
266        """
267        This is a similar to test_breakpad_hash_match, but it verifies that
268        if the .text section does not end on a 16 byte boundary, then it
269        will overflow into the next section's data by up to 15 bytes. This
270        verifies that we are able to match what breakpad does as it will do
271        this.
272        """
273        so_path = self.getBuildArtifact("libbreakpad.so")
274        self.yaml2obj("libbreakpad-overflow.yaml", so_path)
275        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
276        self.dbg.HandleCommand(cmd)
277        modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml")
278        self.assertEqual(1, len(modules))
279        # LLDB makes up it own UUID as well when there is no build ID so we
280        # will check that this matches.
281        self.verify_module(modules[0], so_path, "48EB9FD7")
282
283    def test_breakpad_hash_match_exe_outside_sysroot(self):
284        """
285        Check that we can match the breakpad .text section hash when the
286        module is specified as the exe during launch, and a syroot is
287        provided, which does not contain the exe.
288        """
289        sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot")
290        lldbutil.mkdir_p(sysroot_path)
291        so_dir = os.path.join(self.getBuildDir(), "binary")
292        so_path = os.path.join(so_dir, "libbreakpad.so")
293        lldbutil.mkdir_p(so_dir)
294        self.yaml2obj("libbreakpad.yaml", so_path)
295        self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path)
296        modules = self.get_minidump_modules(
297            "linux-arm-breakpad-uuid-match.yaml", so_path
298        )
299        self.assertEqual(1, len(modules))
300        # LLDB makes up its own UUID as well when there is no build ID so we
301        # will check that this matches.
302        self.verify_module(modules[0], so_path, "D9C480E8")
303
304    def test_facebook_hash_match(self):
305        """
306        Breakpad creates minidump files using CvRecord in each module whose
307        signature is set to PDB70 where the UUID is a hash generated by
308        breakpad of the .text section and Facebook modified this hash to
309        avoid collisions. This is only done when the executable has no ELF
310        build ID.
311
312        This test verifies that if we have a minidump with a 16 byte UUID,
313        that we are able to associate a symbol file with no ELF build ID
314        and match it up by hashing the .text section like Facebook does.
315        """
316        so_path = self.getBuildArtifact("libbreakpad.so")
317        self.yaml2obj("libbreakpad.yaml", so_path)
318        cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path))
319        self.dbg.HandleCommand(cmd)
320        modules = self.get_minidump_modules("linux-arm-facebook-uuid-match.yaml")
321        self.assertEqual(1, len(modules))
322        # LLDB makes up it own UUID as well when there is no build ID so we
323        # will check that this matches.
324        self.verify_module(modules[0], so_path, "D9C480E8")
325
326    def test_relative_module_name(self):
327        old_cwd = os.getcwd()
328        self.addTearDownHook(lambda: os.chdir(old_cwd))
329        os.chdir(self.getBuildDir())
330        name = "file-with-a-name-unlikely-to-exist-in-the-current-directory.so"
331        open(name, "a").close()
332        modules = self.get_minidump_modules(
333            self.getSourcePath("relative_module_name.yaml")
334        )
335        self.assertEqual(1, len(modules))
336        self.verify_module(modules[0], name, None)
337
338    def test_add_module_build_id_16(self):
339        """
340        Test that adding module with 16 byte UUID returns the existing
341        module or fails.
342        """
343        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml")
344        self.assertEqual(2, len(modules))
345
346        # Add the existing modules.
347        self.assertEqual(
348            modules[0],
349            self.target.AddModule(
350                "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10"
351            ),
352        )
353        self.assertEqual(
354            modules[1],
355            self.target.AddModule(
356                "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0"
357            ),
358        )
359
360        # Adding modules with non-existing UUID should fail.
361        self.assertFalse(
362            self.target.AddModule(
363                "a", "", "12345678-1234-1234-1234-123456789ABC"
364            ).IsValid()
365        )
366        self.assertFalse(
367            self.target.AddModule(
368                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678"
369            ).IsValid()
370        )
371
372    def test_add_module_build_id_20(self):
373        """
374        Test that adding module with 20 byte UUID returns the existing
375        module or fails.
376        """
377        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml")
378
379        # Add the existing modules.
380        self.assertEqual(
381            modules[0],
382            self.target.AddModule(
383                "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314"
384            ),
385        )
386        self.assertEqual(
387            modules[1],
388            self.target.AddModule(
389                "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8"
390            ),
391        )
392
393        # Adding modules with non-existing UUID should fail.
394        self.assertFalse(
395            self.target.AddModule(
396                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10"
397            ).IsValid()
398        )
399        self.assertFalse(
400            self.target.AddModule(
401                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678"
402            ).IsValid()
403        )
404
405    def test_add_module_build_id_4(self):
406        """
407        Test that adding module with 4 byte UUID returns the existing
408        module or fails.
409        """
410        modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-4.yaml")
411
412        # Add the existing modules.
413        self.assertEqual(
414            modules[0], self.target.AddModule("/some/local/a.so", "", "01020304")
415        )
416        self.assertEqual(
417            modules[1], self.target.AddModule("/some/local/b.so", "", "0A141E28")
418        )
419
420        # Adding modules with non-existing UUID should fail.
421        self.assertFalse(
422            self.target.AddModule(
423                "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10"
424            ).IsValid()
425        )
426        self.assertFalse(self.target.AddModule("a", "", "01020305").IsValid())
427
428    def test_remove_placeholder_add_real_module(self):
429        """
430        Test that removing a placeholder module and adding back the real
431        module succeeds.
432        """
433        so_path = self.getBuildArtifact("libuuidmatch.so")
434        self.yaml2obj("libuuidmatch.yaml", so_path)
435        modules = self.get_minidump_modules("linux-arm-uuids-match.yaml")
436
437        uuid = "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116"
438        self.assertEqual(1, len(modules))
439        self.verify_module(modules[0], "/target/path/libuuidmatch.so", uuid)
440
441        self.target.RemoveModule(modules[0])
442        new_module = self.target.AddModule(so_path, "", uuid)
443
444        self.verify_module(new_module, so_path, uuid)
445        self.assertEqual(new_module, self.target.modules[0])
446        self.assertEqual(1, len(self.target.modules))
447