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