forked from OSchip/llvm-project
397 lines
20 KiB
Python
397 lines
20 KiB
Python
"""
|
|
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 a 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 a 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 a 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 a 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))
|