| """ |
| Test basics of Minidump debugging. |
| """ |
| |
| import lldb |
| import os |
| from lldbsuite.test.decorators import * |
| from lldbsuite.test.lldbtest import * |
| from lldbsuite.test import lldbutil |
| |
| |
| class MiniDumpUUIDTestCase(TestBase): |
| NO_DEBUG_INFO_TESTCASE = True |
| |
| def verify_module(self, module, verify_path, verify_uuid): |
| # Compare the filename and the directory separately. We are avoiding |
| # SBFileSpec.fullpath because it causes a slash/backslash confusion |
| # on Windows. Similarly, we compare the directories using normcase |
| # because they may contain a Linux-style relative path from the |
| # minidump appended to a Windows-style root path from the host. |
| self.assertEqual(os.path.basename(verify_path), module.GetFileSpec().basename) |
| self.assertEqual( |
| os.path.normcase(os.path.dirname(verify_path)), |
| os.path.normcase(module.GetFileSpec().dirname or ""), |
| ) |
| self.assertEqual(verify_uuid, module.GetUUIDString()) |
| |
| def get_minidump_modules(self, yaml_file, exe=None): |
| minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp") |
| self.yaml2obj(yaml_file, minidump_path) |
| self.target = self.dbg.CreateTarget(exe) |
| self.process = self.target.LoadCore(minidump_path) |
| return self.target.modules |
| |
| def test_zero_uuid_modules(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, |
| but contains a PDB70 value whose age is zero and whose UUID values are |
| all zero. Prior to a fix all such modules would be duplicated to the |
| first one since the UUIDs claimed to be valid and all zeroes. Now we |
| ensure that the UUID is not valid for each module and that we have |
| each of the modules in the target after loading the core |
| """ |
| modules = self.get_minidump_modules("linux-arm-zero-uuids.yaml") |
| self.assertEqual(2, len(modules)) |
| self.verify_module(modules[0], "/file/does/not/exist/a", None) |
| self.verify_module(modules[1], "/file/does/not/exist/b", None) |
| |
| def test_uuid_modules_no_age(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, |
| and contains a PDB70 value whose age is zero and whose UUID values are |
| valid. Ensure we decode the UUID and don't include the age field in the UUID. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-no-age.yaml") |
| modules = self.target.modules |
| self.assertEqual(2, len(modules)) |
| self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10") |
| self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0") |
| |
| def test_uuid_modules_no_age_apple(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, |
| and contains a PDB70 value whose age is zero and whose UUID values are |
| valid. Ensure we decode the UUID and don't include the age field in the UUID. |
| Also ensure that the first uint32_t is byte swapped, along with the next |
| two uint16_t values. Breakpad incorrectly byte swaps these values when it |
| saves Darwin minidump files. |
| """ |
| modules = self.get_minidump_modules("macos-arm-uuids-no-age.yaml") |
| modules = self.target.modules |
| self.assertEqual(2, len(modules)) |
| self.verify_module(modules[0], "/tmp/a", "04030201-0605-0807-090A-0B0C0D0E0F10") |
| self.verify_module(modules[1], "/tmp/b", "281E140A-3C32-5046-5A64-6E78828C96A0") |
| |
| def test_uuid_modules_with_age(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, |
| and contains a PDB70 value whose age is valid and whose UUID values are |
| valid. Ensure we decode the UUID and include the age field in the UUID. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-with-age.yaml") |
| self.assertEqual(2, len(modules)) |
| self.verify_module( |
| modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-10101010" |
| ) |
| self.verify_module( |
| modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-20202020" |
| ) |
| |
| def test_uuid_modules_elf_build_id_16(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, |
| and contains an ELF build ID whose value is valid and is 16 bytes long. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml") |
| self.assertEqual(2, len(modules)) |
| self.verify_module(modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10") |
| self.verify_module(modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0") |
| |
| def test_uuid_modules_elf_build_id_20(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, |
| and contains an ELF build ID whose value is valid and is 20 bytes long. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml") |
| self.assertEqual(2, len(modules)) |
| self.verify_module( |
| modules[0], "/tmp/a", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314" |
| ) |
| self.verify_module( |
| modules[1], "/tmp/b", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8" |
| ) |
| |
| def test_uuid_modules_elf_build_id_zero(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is valid, |
| and contains an ELF build ID whose value is all zero. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-zero.yaml") |
| self.assertEqual(2, len(modules)) |
| self.verify_module(modules[0], "/not/exist/a", None) |
| self.verify_module(modules[1], "/not/exist/b", None) |
| |
| def test_uuid_modules_elf_build_id_same(self): |
| """ |
| Test multiple modules having a MINIDUMP_MODULE.CvRecord that is |
| valid, and contains an ELF build ID whose value is the same. There |
| is an assert in the PlaceholderObjectFile that was firing when we |
| encountered this which was crashing the process that was checking |
| if PlaceholderObjectFile.m_base was the same as the address this |
| fake module was being loaded at. We need to ensure we don't crash |
| in such cases and that we add both modules even though they have |
| the same UUID. |
| """ |
| modules = self.get_minidump_modules("linux-arm-same-uuids.yaml") |
| self.assertEqual(2, len(modules)) |
| self.verify_module( |
| modules[0], |
| "/file/does/not/exist/a", |
| "11223344-1122-3344-1122-334411223344-11223344", |
| ) |
| self.verify_module( |
| modules[1], |
| "/file/does/not/exist/b", |
| "11223344-1122-3344-1122-334411223344-11223344", |
| ) |
| |
| def test_partial_uuid_match(self): |
| """ |
| Breakpad has been known to create minidump files using CvRecord in each |
| module whose signature is set to PDB70 where the UUID only contains the |
| first 16 bytes of a 20 byte ELF build ID. Code was added to |
| ProcessMinidump.cpp to deal with this and allows partial UUID matching. |
| |
| This test verifies that if we have a minidump with a 16 byte UUID, that |
| we are able to associate a symbol file with a 20 byte UUID only if the |
| first 16 bytes match. In this case we will see the path from the file |
| we found in the test directory and the 20 byte UUID from the actual |
| file, not the 16 byte shortened UUID from the minidump. |
| """ |
| so_path = self.getBuildArtifact("libuuidmatch.so") |
| self.yaml2obj("libuuidmatch.yaml", so_path) |
| cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) |
| self.dbg.HandleCommand(cmd) |
| modules = self.get_minidump_modules("linux-arm-partial-uuids-match.yaml") |
| self.assertEqual(1, len(modules)) |
| self.verify_module( |
| modules[0], so_path, "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116" |
| ) |
| |
| def test_partial_uuid_mismatch(self): |
| """ |
| Breakpad has been known to create minidump files using CvRecord in each |
| module whose signature is set to PDB70 where the UUID only contains the |
| first 16 bytes of a 20 byte ELF build ID. Code was added to |
| ProcessMinidump.cpp to deal with this and allows partial UUID matching. |
| |
| This test verifies that if we have a minidump with a 16 byte UUID, that |
| we are not able to associate a symbol file with a 20 byte UUID only if |
| any of the first 16 bytes do not match. In this case we will see the UUID |
| from the minidump file and the path from the minidump file. |
| """ |
| so_path = self.getBuildArtifact("libuuidmismatch.so") |
| self.yaml2obj("libuuidmismatch.yaml", so_path) |
| cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) |
| self.dbg.HandleCommand(cmd) |
| modules = self.get_minidump_modules("linux-arm-partial-uuids-mismatch.yaml") |
| self.assertEqual(1, len(modules)) |
| self.verify_module( |
| modules[0], |
| "/invalid/path/on/current/system/libuuidmismatch.so", |
| "7295E17C-6668-9E05-CBB5-DEE5003865D5", |
| ) |
| |
| def test_breakpad_hash_match(self): |
| """ |
| Breakpad creates minidump files using CvRecord in each module whose |
| signature is set to PDB70 where the UUID is a hash generated by |
| breakpad of the .text section. This is only done when the |
| executable has no ELF build ID. |
| |
| This test verifies that if we have a minidump with a 16 byte UUID, |
| that we are able to associate a symbol file with no ELF build ID |
| and match it up by hashing the .text section. |
| """ |
| so_path = self.getBuildArtifact("libbreakpad.so") |
| self.yaml2obj("libbreakpad.yaml", so_path) |
| cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) |
| self.dbg.HandleCommand(cmd) |
| modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") |
| self.assertEqual(1, len(modules)) |
| # LLDB makes up it own UUID as well when there is no build ID so we |
| # will check that this matches. |
| self.verify_module(modules[0], so_path, "D9C480E8") |
| |
| def test_breakpad_hash_match_sysroot(self): |
| """ |
| Check that we can match the breakpad .text section hash when the |
| module is located under a user-provided sysroot. |
| """ |
| sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot") |
| # Create the directory under the sysroot where the minidump reports |
| # the module. |
| so_dir = os.path.join( |
| sysroot_path, "invalid", "path", "on", "current", "system" |
| ) |
| so_path = os.path.join(so_dir, "libbreakpad.so") |
| lldbutil.mkdir_p(so_dir) |
| self.yaml2obj("libbreakpad.yaml", so_path) |
| self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path) |
| modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") |
| self.assertEqual(1, len(modules)) |
| # LLDB makes up its own UUID as well when there is no build ID so we |
| # will check that this matches. |
| self.verify_module(modules[0], so_path, "D9C480E8") |
| |
| def test_breakpad_hash_match_sysroot_decoy(self): |
| """ |
| Check that we can match the breakpad .text section hash when there is |
| a module with the right name but wrong contents under a user-provided |
| sysroot, and the right module is at the given search path.. |
| """ |
| sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot") |
| # Create the directory under the sysroot where the minidump reports |
| # the module. |
| decoy_dir = os.path.join( |
| sysroot_path, "invalid", "path", "on", "current", "system" |
| ) |
| decoy_path = os.path.join(decoy_dir, "libbreakpad.so") |
| lldbutil.mkdir_p(decoy_dir) |
| self.yaml2obj("libbreakpad-decoy.yaml", decoy_path) |
| self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path) |
| so_dir = os.path.join(self.getBuildDir(), "searchpath_dir") |
| so_path = os.path.join(so_dir, "libbreakpad.so") |
| lldbutil.mkdir_p(so_dir) |
| self.yaml2obj("libbreakpad.yaml", so_path) |
| self.runCmd('settings set target.exec-search-paths "%s"' % so_dir) |
| modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") |
| self.assertEqual(1, len(modules)) |
| # LLDB makes up its own UUID as well when there is no build ID so we |
| # will check that this matches. |
| self.verify_module(modules[0], so_path, "D9C480E8") |
| |
| def test_breakpad_overflow_hash_match(self): |
| """ |
| This is a similar to test_breakpad_hash_match, but it verifies that |
| if the .text section does not end on a 16 byte boundary, then it |
| will overflow into the next section's data by up to 15 bytes. This |
| verifies that we are able to match what breakpad does as it will do |
| this. |
| """ |
| so_path = self.getBuildArtifact("libbreakpad.so") |
| self.yaml2obj("libbreakpad-overflow.yaml", so_path) |
| cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) |
| self.dbg.HandleCommand(cmd) |
| modules = self.get_minidump_modules("linux-arm-breakpad-uuid-match.yaml") |
| self.assertEqual(1, len(modules)) |
| # LLDB makes up it own UUID as well when there is no build ID so we |
| # will check that this matches. |
| self.verify_module(modules[0], so_path, "48EB9FD7") |
| |
| def test_breakpad_hash_match_exe_outside_sysroot(self): |
| """ |
| Check that we can match the breakpad .text section hash when the |
| module is specified as the exe during launch, and a syroot is |
| provided, which does not contain the exe. |
| """ |
| sysroot_path = os.path.join(self.getBuildDir(), "mock_sysroot") |
| lldbutil.mkdir_p(sysroot_path) |
| so_dir = os.path.join(self.getBuildDir(), "binary") |
| so_path = os.path.join(so_dir, "libbreakpad.so") |
| lldbutil.mkdir_p(so_dir) |
| self.yaml2obj("libbreakpad.yaml", so_path) |
| self.runCmd("platform select remote-linux --sysroot '%s'" % sysroot_path) |
| modules = self.get_minidump_modules( |
| "linux-arm-breakpad-uuid-match.yaml", so_path |
| ) |
| self.assertEqual(1, len(modules)) |
| # LLDB makes up its own UUID as well when there is no build ID so we |
| # will check that this matches. |
| self.verify_module(modules[0], so_path, "D9C480E8") |
| |
| def test_facebook_hash_match(self): |
| """ |
| Breakpad creates minidump files using CvRecord in each module whose |
| signature is set to PDB70 where the UUID is a hash generated by |
| breakpad of the .text section and Facebook modified this hash to |
| avoid collisions. This is only done when the executable has no ELF |
| build ID. |
| |
| This test verifies that if we have a minidump with a 16 byte UUID, |
| that we are able to associate a symbol file with no ELF build ID |
| and match it up by hashing the .text section like Facebook does. |
| """ |
| so_path = self.getBuildArtifact("libbreakpad.so") |
| self.yaml2obj("libbreakpad.yaml", so_path) |
| cmd = 'settings set target.exec-search-paths "%s"' % (os.path.dirname(so_path)) |
| self.dbg.HandleCommand(cmd) |
| modules = self.get_minidump_modules("linux-arm-facebook-uuid-match.yaml") |
| self.assertEqual(1, len(modules)) |
| # LLDB makes up it own UUID as well when there is no build ID so we |
| # will check that this matches. |
| self.verify_module(modules[0], so_path, "D9C480E8") |
| |
| def test_relative_module_name(self): |
| old_cwd = os.getcwd() |
| self.addTearDownHook(lambda: os.chdir(old_cwd)) |
| os.chdir(self.getBuildDir()) |
| name = "file-with-a-name-unlikely-to-exist-in-the-current-directory.so" |
| open(name, "a").close() |
| modules = self.get_minidump_modules( |
| self.getSourcePath("relative_module_name.yaml") |
| ) |
| self.assertEqual(1, len(modules)) |
| self.verify_module(modules[0], name, None) |
| |
| def test_add_module_build_id_16(self): |
| """ |
| Test that adding module with 16 byte UUID returns the existing |
| module or fails. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-16.yaml") |
| self.assertEqual(2, len(modules)) |
| |
| # Add the existing modules. |
| self.assertEqual( |
| modules[0], |
| self.target.AddModule( |
| "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10" |
| ), |
| ) |
| self.assertEqual( |
| modules[1], |
| self.target.AddModule( |
| "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0" |
| ), |
| ) |
| |
| # Adding modules with non-existing UUID should fail. |
| self.assertFalse( |
| self.target.AddModule( |
| "a", "", "12345678-1234-1234-1234-123456789ABC" |
| ).IsValid() |
| ) |
| self.assertFalse( |
| self.target.AddModule( |
| "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678" |
| ).IsValid() |
| ) |
| |
| def test_add_module_build_id_20(self): |
| """ |
| Test that adding module with 20 byte UUID returns the existing |
| module or fails. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-20.yaml") |
| |
| # Add the existing modules. |
| self.assertEqual( |
| modules[0], |
| self.target.AddModule( |
| "/some/local/a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-11121314" |
| ), |
| ) |
| self.assertEqual( |
| modules[1], |
| self.target.AddModule( |
| "/some/local/b", "", "0A141E28-323C-4650-5A64-6E78828C96A0-AAB4BEC8" |
| ), |
| ) |
| |
| # Adding modules with non-existing UUID should fail. |
| self.assertFalse( |
| self.target.AddModule( |
| "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10" |
| ).IsValid() |
| ) |
| self.assertFalse( |
| self.target.AddModule( |
| "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10-12345678" |
| ).IsValid() |
| ) |
| |
| def test_add_module_build_id_4(self): |
| """ |
| Test that adding module with 4 byte UUID returns the existing |
| module or fails. |
| """ |
| modules = self.get_minidump_modules("linux-arm-uuids-elf-build-id-4.yaml") |
| |
| # Add the existing modules. |
| self.assertEqual( |
| modules[0], self.target.AddModule("/some/local/a.so", "", "01020304") |
| ) |
| self.assertEqual( |
| modules[1], self.target.AddModule("/some/local/b.so", "", "0A141E28") |
| ) |
| |
| # Adding modules with non-existing UUID should fail. |
| self.assertFalse( |
| self.target.AddModule( |
| "a", "", "01020304-0506-0708-090A-0B0C0D0E0F10" |
| ).IsValid() |
| ) |
| self.assertFalse(self.target.AddModule("a", "", "01020305").IsValid()) |
| |
| def test_remove_placeholder_add_real_module(self): |
| """ |
| Test that removing a placeholder module and adding back the real |
| module succeeds. |
| """ |
| so_path = self.getBuildArtifact("libuuidmatch.so") |
| self.yaml2obj("libuuidmatch.yaml", so_path) |
| modules = self.get_minidump_modules("linux-arm-uuids-match.yaml") |
| |
| uuid = "7295E17C-6668-9E05-CBB5-DEE5003865D5-5267C116" |
| self.assertEqual(1, len(modules)) |
| self.verify_module(modules[0], "/target/path/libuuidmatch.so", uuid) |
| |
| self.target.RemoveModule(modules[0]) |
| new_module = self.target.AddModule(so_path, "", uuid) |
| |
| self.verify_module(new_module, so_path, uuid) |
| self.assertEqual(new_module, self.target.modules[0]) |
| self.assertEqual(1, len(self.target.modules)) |