Files
clang-p2996/lldb/test/API/functionalities/postmortem/minidump-new/TestMiniDumpNew.py
Jacob Lalonde 492683527e [LLDB][Minidump] Support minidumps where there are multiple exception streams (#97470)
Currently, LLDB assumes all minidumps will have unique sections. This is
intuitive because almost all of the minidump sections are themselves
lists. Exceptions including Signals are unique in that they are all
individual sections with their own directory.

This means LLDB fails to load minidumps with multiple exceptions due to
them not being unique. This behavior is erroneous and this PR introduces
support for an arbitrary number of exception streams. Additionally, stop
info was calculated only for a single thread before, and now we properly
support mapping exceptions to threads.

~~This PR is starting in DRAFT because implementing testing is still
required.~~
2024-09-09 21:07:12 -07:00

527 lines
23 KiB
Python

"""
Test basics of Minidump debugging.
"""
import shutil
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class MiniDumpNewTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
_linux_x86_64_pid = 29917
_linux_x86_64_not_crashed_pid = 29939
_linux_x86_64_not_crashed_pid_offset = 0xD967
def process_from_yaml(self, yaml_file):
minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp")
self.yaml2obj(yaml_file, minidump_path)
self.target = self.dbg.CreateTarget(None)
self.process = self.target.LoadCore(minidump_path)
return self.process
def check_state(self):
with open(os.devnull) as devnul:
# sanitize test output
self.dbg.SetOutputFileHandle(devnul, False)
self.dbg.SetErrorFileHandle(devnul, False)
self.assertTrue(self.process.is_stopped)
# Process.Continue
error = self.process.Continue()
self.assertFalse(error.Success())
self.assertTrue(self.process.is_stopped)
# Thread.StepOut
thread = self.process.GetSelectedThread()
thread.StepOut()
self.assertTrue(self.process.is_stopped)
# command line
self.dbg.HandleCommand("s")
self.assertTrue(self.process.is_stopped)
self.dbg.HandleCommand("c")
self.assertTrue(self.process.is_stopped)
# restore file handles
self.dbg.SetOutputFileHandle(None, False)
self.dbg.SetErrorFileHandle(None, False)
def test_loadcore_error_status(self):
"""Test the SBTarget.LoadCore(core, error) overload."""
minidump_path = self.getBuildArtifact("linux-x86_64.dmp")
self.yaml2obj("linux-x86_64.yaml", minidump_path)
self.target = self.dbg.CreateTarget(None)
error = lldb.SBError()
self.process = self.target.LoadCore(minidump_path, error)
self.assertTrue(self.process, PROCESS_IS_VALID)
self.assertSuccess(error)
def test_loadcore_error_status_failure(self):
"""Test the SBTarget.LoadCore(core, error) overload."""
self.target = self.dbg.CreateTarget(None)
error = lldb.SBError()
self.process = self.target.LoadCore("non-existent.dmp", error)
self.assertFalse(self.process, PROCESS_IS_VALID)
self.assertTrue(error.Fail())
def test_process_info_in_minidump(self):
"""Test that lldb can read the process information from the Minidump."""
self.process_from_yaml("linux-x86_64.yaml")
self.assertTrue(self.process, PROCESS_IS_VALID)
self.assertEqual(self.process.GetNumThreads(), 1)
self.assertEqual(self.process.GetProcessID(), self._linux_x86_64_pid)
self.check_state()
def test_memory_region_name(self):
self.process_from_yaml("regions-linux-map.yaml")
result = lldb.SBCommandReturnObject()
addr_region_name_pairs = [
("0x400d9000", "/system/bin/app_process"),
("0x400db000", "/system/bin/app_process"),
("0x400dd000", "/system/bin/linker"),
("0x400ed000", "/system/bin/linker"),
("0x400ee000", "/system/bin/linker"),
("0x400fb000", "/system/lib/liblog.so"),
("0x400fc000", "/system/lib/liblog.so"),
("0x400fd000", "/system/lib/liblog.so"),
("0x400ff000", "/system/lib/liblog.so"),
("0x40100000", "/system/lib/liblog.so"),
("0x40101000", "/system/lib/libc.so"),
("0x40122000", "/system/lib/libc.so"),
("0x40123000", "/system/lib/libc.so"),
("0x40167000", "/system/lib/libc.so"),
("0x40169000", "/system/lib/libc.so"),
]
ci = self.dbg.GetCommandInterpreter()
for addr, region_name in addr_region_name_pairs:
command = "memory region " + addr
ci.HandleCommand(command, result, False)
message = 'Ensure memory "%s" shows up in output for "%s"' % (
region_name,
command,
)
self.assertIn(region_name, result.GetOutput(), message)
def test_thread_info_in_minidump(self):
"""Test that lldb can read the thread information from the Minidump."""
self.process_from_yaml("linux-x86_64.yaml")
self.check_state()
# This process crashed due to a segmentation fault in its
# one and only thread.
self.assertEqual(self.process.GetNumThreads(), 1)
thread = self.process.GetThreadAtIndex(0)
self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal)
stop_description = thread.GetStopDescription(256)
self.assertIn("SIGSEGV", stop_description)
@skipIfLLVMTargetMissing("X86")
def test_stack_info_in_minidump(self):
"""Test that we can see a trivial stack in a breakpad-generated Minidump."""
# target create linux-x86_64 -c linux-x86_64.dmp
self.dbg.CreateTarget("linux-x86_64")
self.target = self.dbg.GetSelectedTarget()
self.process = self.target.LoadCore("linux-x86_64.dmp")
self.check_state()
self.assertEqual(self.process.GetNumThreads(), 1)
self.assertEqual(self.process.GetProcessID(), self._linux_x86_64_pid)
thread = self.process.GetThreadAtIndex(0)
# frame #0: linux-x86_64`crash()
# frame #1: linux-x86_64`_start
self.assertEqual(thread.GetNumFrames(), 2)
frame = thread.GetFrameAtIndex(0)
self.assertTrue(frame.IsValid())
self.assertTrue(frame.GetModule().IsValid())
pc = frame.GetPC()
eip = frame.FindRegister("pc")
self.assertTrue(eip.IsValid())
self.assertEqual(pc, eip.GetValueAsUnsigned())
def test_snapshot_minidump_dump_requested(self):
"""Test that if we load a snapshot minidump file (meaning the process
did not crash) with exception code "DUMP_REQUESTED" there is no stop reason."""
# target create -c linux-x86_64_not_crashed.dmp
self.dbg.CreateTarget(None)
self.target = self.dbg.GetSelectedTarget()
self.process = self.target.LoadCore("linux-x86_64_not_crashed.dmp")
self.check_state()
self.assertEqual(self.process.GetNumThreads(), 1)
thread = self.process.GetThreadAtIndex(0)
self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonNone)
stop_description = thread.GetStopDescription(256)
self.assertEqual(stop_description, "")
def test_snapshot_minidump_null_exn_code(self):
"""Test that if we load a snapshot minidump file (meaning the process
did not crash) with exception code zero there is no stop reason."""
self.process_from_yaml("linux-x86_64_null_signal.yaml")
self.check_state()
self.assertEqual(self.process.GetNumThreads(), 1)
thread = self.process.GetThreadAtIndex(0)
self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonNone)
stop_description = thread.GetStopDescription(256)
self.assertEqual(stop_description, "")
def check_register_unsigned(self, set, name, expected):
reg_value = set.GetChildMemberWithName(name)
self.assertTrue(
reg_value.IsValid(), 'Verify we have a register named "%s"' % (name)
)
self.assertEqual(
reg_value.GetValueAsUnsigned(),
expected,
'Verify "%s" == %i' % (name, expected),
)
def check_register_string_value(self, set, name, expected, format):
reg_value = set.GetChildMemberWithName(name)
self.assertTrue(
reg_value.IsValid(), 'Verify we have a register named "%s"' % (name)
)
if format is not None:
reg_value.SetFormat(format)
self.assertEqual(
reg_value.GetValue(),
expected,
'Verify "%s" has string value "%s"' % (name, expected),
)
def test_arm64_registers(self):
"""Test ARM64 registers from a breakpad created minidump."""
self.process_from_yaml("arm64-macos.yaml")
self.check_state()
self.assertEqual(self.process.GetNumThreads(), 1)
thread = self.process.GetThreadAtIndex(0)
self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonNone)
stop_description = thread.GetStopDescription(256)
self.assertEqual(stop_description, "")
registers = thread.GetFrameAtIndex(0).GetRegisters()
# Verify the GPR registers are all correct
# Verify x0 - x31 register values
gpr = registers.GetValueAtIndex(0)
for i in range(32):
v = i + 1 | i + 2 << 32 | i + 3 << 48
w = i + 1
self.check_register_unsigned(gpr, "x%i" % (i), v)
self.check_register_unsigned(gpr, "w%i" % (i), w)
# Verify arg1 - arg8 register values
for i in range(1, 9):
v = i | i + 1 << 32 | i + 2 << 48
self.check_register_unsigned(gpr, "arg%i" % (i), v)
i = 29
v = i + 1 | i + 2 << 32 | i + 3 << 48
self.check_register_unsigned(gpr, "fp", v)
i = 30
v = i + 1 | i + 2 << 32 | i + 3 << 48
self.check_register_unsigned(gpr, "lr", v)
i = 31
v = i + 1 | i + 2 << 32 | i + 3 << 48
self.check_register_unsigned(gpr, "sp", v)
self.check_register_unsigned(gpr, "pc", 0x1000)
self.check_register_unsigned(gpr, "cpsr", 0x11223344)
self.check_register_unsigned(gpr, "psr", 0x11223344)
# Verify the FPR registers are all correct
fpr = registers.GetValueAtIndex(1)
for i in range(32):
v = "0x"
d = "0x"
s = "0x"
h = "0x"
for j in range(i + 15, i - 1, -1):
v += "%2.2x" % (j)
for j in range(i + 7, i - 1, -1):
d += "%2.2x" % (j)
for j in range(i + 3, i - 1, -1):
s += "%2.2x" % (j)
for j in range(i + 1, i - 1, -1):
h += "%2.2x" % (j)
self.check_register_string_value(fpr, "v%i" % (i), v, lldb.eFormatHex)
self.check_register_string_value(fpr, "d%i" % (i), d, lldb.eFormatHex)
self.check_register_string_value(fpr, "s%i" % (i), s, lldb.eFormatHex)
self.check_register_string_value(fpr, "h%i" % (i), h, lldb.eFormatHex)
self.check_register_unsigned(gpr, "fpsr", 0x55667788)
self.check_register_unsigned(gpr, "fpcr", 0x99AABBCC)
def verify_arm_registers(self, apple=False):
"""
Verify values of all ARM registers from a breakpad created
minidump.
"""
if apple:
self.process_from_yaml("arm-macos.yaml")
else:
self.process_from_yaml("arm-linux.yaml")
self.check_state()
self.assertEqual(self.process.GetNumThreads(), 1)
thread = self.process.GetThreadAtIndex(0)
self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonNone)
stop_description = thread.GetStopDescription(256)
self.assertEqual(stop_description, "")
registers = thread.GetFrameAtIndex(0).GetRegisters()
# Verify the GPR registers are all correct
# Verify x0 - x31 register values
gpr = registers.GetValueAtIndex(0)
for i in range(1, 16):
self.check_register_unsigned(gpr, "r%i" % (i), i + 1)
# Verify arg1 - arg4 register values
for i in range(1, 5):
self.check_register_unsigned(gpr, "arg%i" % (i), i)
if apple:
self.check_register_unsigned(gpr, "fp", 0x08)
else:
self.check_register_unsigned(gpr, "fp", 0x0C)
self.check_register_unsigned(gpr, "lr", 0x0F)
self.check_register_unsigned(gpr, "sp", 0x0E)
self.check_register_unsigned(gpr, "pc", 0x10)
self.check_register_unsigned(gpr, "cpsr", 0x11223344)
# Verify the FPR registers are all correct
fpr = registers.GetValueAtIndex(1)
# Check d0 - d31
self.check_register_unsigned(gpr, "fpscr", 0x55667788AABBCCDD)
for i in range(32):
value = (i + 1) | (i + 1) << 8 | (i + 1) << 32 | (i + 1) << 48
self.check_register_unsigned(fpr, "d%i" % (i), value)
# Check s0 - s31
for i in range(32):
i_val = (i >> 1) + 1
if i & 1:
value = "%#8.8x" % (i_val | i_val << 16)
else:
value = "%#8.8x" % (i_val | i_val << 8)
self.check_register_string_value(fpr, "s%i" % (i), value, lldb.eFormatHex)
# Check q0 - q15
for i in range(15):
a = i * 2 + 1
b = a + 1
value = (
"0x00%2.2x00%2.2x0000%2.2x%2.2x" "00%2.2x00%2.2x0000%2.2x%2.2x"
) % (b, b, b, b, a, a, a, a)
self.check_register_string_value(fpr, "q%i" % (i), value, lldb.eFormatHex)
def test_linux_arm_registers(self):
"""Test Linux ARM registers from a breakpad created minidump.
The frame pointer is R11 for linux.
"""
self.verify_arm_registers(apple=False)
def test_apple_arm_registers(self):
"""Test Apple ARM registers from a breakpad created minidump.
The frame pointer is R7 for linux.
"""
self.verify_arm_registers(apple=True)
def do_test_deeper_stack(self, binary, core, pid):
target = self.dbg.CreateTarget(binary)
process = target.LoadCore(core)
thread = process.GetThreadAtIndex(0)
self.assertEqual(process.GetProcessID(), pid)
expected_stack = {1: "bar", 2: "foo", 3: "_start"}
self.assertGreaterEqual(thread.GetNumFrames(), len(expected_stack))
for index, name in expected_stack.items():
frame = thread.GetFrameAtIndex(index)
self.assertTrue(frame.IsValid())
function_name = frame.GetFunctionName()
self.assertIn(name, function_name)
@skipIfLLVMTargetMissing("X86")
def test_deeper_stack_in_minidump(self):
"""Test that we can examine a more interesting stack in a Minidump."""
# Launch with the Minidump, and inspect the stack.
# target create linux-x86_64_not_crashed -c linux-x86_64_not_crashed.dmp
self.do_test_deeper_stack(
"linux-x86_64_not_crashed",
"linux-x86_64_not_crashed.dmp",
self._linux_x86_64_not_crashed_pid,
)
def do_change_pid_in_minidump(self, core, newcore, offset, oldpid, newpid):
"""This assumes that the minidump is breakpad generated on Linux -
meaning that the PID in the file will be an ascii string part of
/proc/PID/status which is written in the file
"""
shutil.copyfile(core, newcore)
with open(newcore, "rb+") as f:
f.seek(offset)
currentpid = f.read(5).decode("utf-8")
self.assertEqual(currentpid, oldpid)
f.seek(offset)
if len(newpid) < len(oldpid):
newpid += " " * (len(oldpid) - len(newpid))
newpid += "\n"
f.write(newpid.encode("utf-8"))
@skipIfLLVMTargetMissing("X86")
def test_deeper_stack_in_minidump_with_same_pid_running(self):
"""Test that we read the information from the core correctly even if we
have a running process with the same PID"""
new_core = self.getBuildArtifact("linux-x86_64_not_crashed-pid.dmp")
self.do_change_pid_in_minidump(
"linux-x86_64_not_crashed.dmp",
new_core,
self._linux_x86_64_not_crashed_pid_offset,
str(self._linux_x86_64_not_crashed_pid),
str(os.getpid()),
)
self.do_test_deeper_stack("linux-x86_64_not_crashed", new_core, os.getpid())
@skipIfLLVMTargetMissing("X86")
def test_two_cores_same_pid(self):
"""Test that we handle the situation if we have two core files with the same PID"""
new_core = self.getBuildArtifact("linux-x86_64_not_crashed-pid.dmp")
self.do_change_pid_in_minidump(
"linux-x86_64_not_crashed.dmp",
new_core,
self._linux_x86_64_not_crashed_pid_offset,
str(self._linux_x86_64_not_crashed_pid),
str(self._linux_x86_64_pid),
)
self.do_test_deeper_stack(
"linux-x86_64_not_crashed", new_core, self._linux_x86_64_pid
)
self.test_stack_info_in_minidump()
@skipIfLLVMTargetMissing("X86")
def test_local_variables_in_minidump(self):
"""Test that we can examine local variables in a Minidump."""
# Launch with the Minidump, and inspect a local variable.
# target create linux-x86_64_not_crashed -c linux-x86_64_not_crashed.dmp
self.target = self.dbg.CreateTarget("linux-x86_64_not_crashed")
self.process = self.target.LoadCore("linux-x86_64_not_crashed.dmp")
self.check_state()
thread = self.process.GetThreadAtIndex(0)
frame = thread.GetFrameAtIndex(1)
value = frame.EvaluateExpression("x")
self.assertEqual(value.GetValueAsSigned(), 3)
def test_memory_regions_in_minidump(self):
"""Test memory regions from a Minidump"""
self.process_from_yaml("regions-linux-map.yaml")
self.check_state()
regions_count = 19
region_info_list = self.process.GetMemoryRegions()
self.assertEqual(region_info_list.GetSize(), regions_count)
def check_region(index, start, end, read, write, execute, mapped, name):
region_info = lldb.SBMemoryRegionInfo()
self.assertTrue(
self.process.GetMemoryRegionInfo(start, region_info).Success()
)
self.assertEqual(start, region_info.GetRegionBase())
self.assertEqual(end, region_info.GetRegionEnd())
self.assertEqual(read, region_info.IsReadable())
self.assertEqual(write, region_info.IsWritable())
self.assertEqual(execute, region_info.IsExecutable())
self.assertEqual(mapped, region_info.IsMapped())
self.assertEqual(name, region_info.GetName())
# Ensure we have the same regions as SBMemoryRegionInfoList contains.
if index >= 0 and index < regions_count:
region_info_from_list = lldb.SBMemoryRegionInfo()
self.assertTrue(
region_info_list.GetMemoryRegionAtIndex(
index, region_info_from_list
)
)
self.assertEqual(region_info_from_list, region_info)
a = "/system/bin/app_process"
b = "/system/bin/linker"
c = "/system/lib/liblog.so"
d = "/system/lib/libc.so"
n = None
max_int = 0xFFFFFFFFFFFFFFFF
# Test address before the first entry comes back with nothing mapped up
# to first valid region info
check_region(-1, 0x00000000, 0x400D9000, False, False, False, False, n)
check_region(0, 0x400D9000, 0x400DB000, True, False, True, True, a)
check_region(1, 0x400DB000, 0x400DC000, True, False, False, True, a)
check_region(2, 0x400DC000, 0x400DD000, True, True, False, True, n)
check_region(3, 0x400DD000, 0x400EC000, True, False, True, True, b)
check_region(4, 0x400EC000, 0x400ED000, True, False, False, True, n)
check_region(5, 0x400ED000, 0x400EE000, True, False, False, True, b)
check_region(6, 0x400EE000, 0x400EF000, True, True, False, True, b)
check_region(7, 0x400EF000, 0x400FB000, True, True, False, True, n)
check_region(8, 0x400FB000, 0x400FC000, True, False, True, True, c)
check_region(9, 0x400FC000, 0x400FD000, True, True, True, True, c)
check_region(10, 0x400FD000, 0x400FF000, True, False, True, True, c)
check_region(11, 0x400FF000, 0x40100000, True, False, False, True, c)
check_region(12, 0x40100000, 0x40101000, True, True, False, True, c)
check_region(13, 0x40101000, 0x40122000, True, False, True, True, d)
check_region(14, 0x40122000, 0x40123000, True, True, True, True, d)
check_region(15, 0x40123000, 0x40167000, True, False, True, True, d)
check_region(16, 0x40167000, 0x40169000, True, False, False, True, d)
check_region(17, 0x40169000, 0x4016B000, True, True, False, True, d)
check_region(18, 0x4016B000, 0x40176000, True, True, False, True, n)
check_region(-1, 0x40176000, max_int, False, False, False, False, n)
@skipIfLLVMTargetMissing("X86")
def test_minidump_sysroot(self):
"""Test that lldb can find a module referenced in an i386 linux minidump using the sysroot."""
# Copy linux-x86_64 executable to tmp_sysroot/temp/test/ (since it was compiled as
# /tmp/test/linux-x86_64)
tmp_sysroot = os.path.join(self.getBuildDir(), "lldb_i386_mock_sysroot")
executable = os.path.join(tmp_sysroot, "tmp", "test", "linux-x86_64")
exe_dir = os.path.dirname(executable)
lldbutil.mkdir_p(exe_dir)
shutil.copyfile("linux-x86_64", executable)
# Set sysroot and load core
self.runCmd("platform select remote-linux --sysroot '%s'" % tmp_sysroot)
self.process_from_yaml("linux-x86_64.yaml")
self.check_state()
# Check that we loaded the module from the sysroot
self.assertEqual(self.target.GetNumModules(), 1)
module = self.target.GetModuleAtIndex(0)
spec_dir_norm = os.path.normcase(module.GetFileSpec().GetDirectory())
exe_dir_norm = os.path.normcase(exe_dir)
self.assertEqual(spec_dir_norm, exe_dir_norm)
def test_minidump_memory64list(self):
"""Test that lldb can read from the memory64list in a minidump."""
self.process_from_yaml("linux-x86_64_mem64.yaml")
region_count = 3
region_info_list = self.process.GetMemoryRegions()
self.assertEqual(region_info_list.GetSize(), region_count)
region = lldb.SBMemoryRegionInfo()
self.assertTrue(region_info_list.GetMemoryRegionAtIndex(0, region))
self.assertEqual(region.GetRegionBase(), 0x7FFF12A84030)
self.assertTrue(region.GetRegionEnd(), 0x7FFF12A84030 + 0x2FD0)
self.assertTrue(region_info_list.GetMemoryRegionAtIndex(1, region))
self.assertEqual(region.GetRegionBase(), 0x00007FFF12A87000)
self.assertTrue(region.GetRegionEnd(), 0x00007FFF12A87000 + 0x00000018)
self.assertTrue(region_info_list.GetMemoryRegionAtIndex(2, region))
self.assertEqual(region.GetRegionBase(), 0x00007FFF12A87018)
self.assertTrue(region.GetRegionEnd(), 0x00007FFF12A87018 + 0x00000400)
def test_multiple_exceptions_or_signals(self):
"""Test that lldb can read the exception information from the Minidump."""
print("Starting to read multiple-sigsev.yaml")
self.process_from_yaml("multiple-sigsev.yaml")
print("Done reading multiple-sigsev.yaml")
self.check_state()
# This process crashed due to a segmentation fault in both it's threads.
self.assertEqual(self.process.GetNumThreads(), 2)
for i in range(2):
thread = self.process.GetThreadAtIndex(i)
self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal)
stop_description = thread.GetStopDescription(256)
self.assertIn("SIGSEGV", stop_description)