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