llvm-project/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
Joseph Tremoulet 85dfcaadc5 [LLDB] MinidumpParser: Prefer executable module even at higher address
When a program maps one of its own modules for reading, and then
crashes, breakpad can emit two entries for that module in the
ModuleList.  We have logic to identify this case by checking permissions
on mapped memory regions and report just the module with an executable
region.  As currently written, though, the check is asymmetric -- the
entry with the executable region must be the second one encountered for
the preference to kick in.

This change makes the logic symmetric, so that the first-encountered
module will similarly be preferred if it has an executable region but
the second-encountered module does not.  This happens for example when
the module in question is the executable itself, which breakpad likes to
report first -- we need to ignore the other entry for that module when
we see it later, even though it may be mapped at a lower virtual
address.

Reviewed By: clayborg

Differential Revision: https://reviews.llvm.org/D94629
2021-01-14 13:17:57 -05:00

911 lines
37 KiB
C++

//===-- MinidumpTypesTest.cpp ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Plugins/Process/minidump/MinidumpParser.h"
#include "Plugins/Process/minidump/MinidumpTypes.h"
#include "Plugins/Process/minidump/RegisterContextMinidump_x86_32.h"
#include "Plugins/Process/minidump/RegisterContextMinidump_x86_64.h"
#include "TestingSupport/SubsystemRAII.h"
#include "TestingSupport/TestUtilities.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/FileSpec.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
// C includes
// C++ includes
#include <memory>
using namespace lldb_private;
using namespace minidump;
class MinidumpParserTest : public testing::Test {
public:
SubsystemRAII<FileSystem> subsystems;
void SetUpData(const char *minidump_filename) {
std::string filename = GetInputFilePath(minidump_filename);
auto BufferPtr = FileSystem::Instance().CreateDataBuffer(filename, -1, 0);
ASSERT_NE(BufferPtr, nullptr);
llvm::Expected<MinidumpParser> expected_parser =
MinidumpParser::Create(BufferPtr);
ASSERT_THAT_EXPECTED(expected_parser, llvm::Succeeded());
parser = std::move(*expected_parser);
ASSERT_GT(parser->GetData().size(), 0UL);
}
llvm::Error SetUpFromYaml(llvm::StringRef yaml) {
std::string data;
llvm::raw_string_ostream os(data);
llvm::yaml::Input YIn(yaml);
if (!llvm::yaml::convertYAML(YIn, os, [](const llvm::Twine &Msg) {}))
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"convertYAML() failed");
os.flush();
auto data_buffer_sp =
std::make_shared<DataBufferHeap>(data.data(), data.size());
auto expected_parser = MinidumpParser::Create(std::move(data_buffer_sp));
if (!expected_parser)
return expected_parser.takeError();
parser = std::move(*expected_parser);
return llvm::Error::success();
}
llvm::Optional<MinidumpParser> parser;
};
TEST_F(MinidumpParserTest, InvalidMinidump) {
std::string duplicate_streams;
llvm::raw_string_ostream os(duplicate_streams);
llvm::yaml::Input YIn(R"(
--- !minidump
Streams:
- Type: LinuxAuxv
Content: DEADBEEFBAADF00D
- Type: LinuxAuxv
Content: DEADBEEFBAADF00D
)");
ASSERT_TRUE(llvm::yaml::convertYAML(YIn, os, [](const llvm::Twine &Msg){}));
os.flush();
auto data_buffer_sp = std::make_shared<DataBufferHeap>(
duplicate_streams.data(), duplicate_streams.size());
ASSERT_THAT_EXPECTED(MinidumpParser::Create(data_buffer_sp), llvm::Failed());
}
TEST_F(MinidumpParserTest, GetThreadsAndGetThreadContext) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ThreadList
Threads:
- Thread Id: 0x00003E81
Stack:
Start of Memory Range: 0x00007FFCEB34A000
Content: C84D04BCE97F00
Context: 00000000000000
...
)"),
llvm::Succeeded());
llvm::ArrayRef<minidump::Thread> thread_list;
thread_list = parser->GetThreads();
ASSERT_EQ(1UL, thread_list.size());
const minidump::Thread &thread = thread_list[0];
EXPECT_EQ(0x3e81u, thread.ThreadId);
llvm::ArrayRef<uint8_t> context = parser->GetThreadContext(thread);
EXPECT_EQ(7u, context.size());
}
TEST_F(MinidumpParserTest, GetArchitecture) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: SystemInfo
Processor Arch: AMD64
Processor Level: 6
Processor Revision: 16130
Number of Processors: 1
Platform ID: Linux
CPU:
Vendor ID: GenuineIntel
Version Info: 0x00000000
Feature Info: 0x00000000
...
)"),
llvm::Succeeded());
ASSERT_EQ(llvm::Triple::ArchType::x86_64,
parser->GetArchitecture().GetMachine());
ASSERT_EQ(llvm::Triple::OSType::Linux,
parser->GetArchitecture().GetTriple().getOS());
}
TEST_F(MinidumpParserTest, GetMiscInfo_no_stream) {
// Test that GetMiscInfo returns nullptr when the minidump does not contain
// this stream.
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
...
)"),
llvm::Succeeded());
EXPECT_EQ(nullptr, parser->GetMiscInfo());
}
TEST_F(MinidumpParserTest, GetLinuxProcStatus) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: SystemInfo
Processor Arch: AMD64
Processor Level: 6
Processor Revision: 16130
Number of Processors: 1
Platform ID: Linux
CSD Version: 'Linux 3.13.0-91-generic'
CPU:
Vendor ID: GenuineIntel
Version Info: 0x00000000
Feature Info: 0x00000000
- Type: LinuxProcStatus
Text: |
Name: a.out
State: t (tracing stop)
Tgid: 16001
Ngid: 0
Pid: 16001
PPid: 13243
TracerPid: 16002
Uid: 404696 404696 404696 404696
Gid: 5762 5762 5762 5762
...
)"),
llvm::Succeeded());
llvm::Optional<LinuxProcStatus> proc_status = parser->GetLinuxProcStatus();
ASSERT_TRUE(proc_status.hasValue());
lldb::pid_t pid = proc_status->GetPid();
ASSERT_EQ(16001UL, pid);
}
TEST_F(MinidumpParserTest, GetPid) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: SystemInfo
Processor Arch: AMD64
Processor Level: 6
Processor Revision: 16130
Number of Processors: 1
Platform ID: Linux
CSD Version: 'Linux 3.13.0-91-generic'
CPU:
Vendor ID: GenuineIntel
Version Info: 0x00000000
Feature Info: 0x00000000
- Type: LinuxProcStatus
Text: |
Name: a.out
State: t (tracing stop)
Tgid: 16001
Ngid: 0
Pid: 16001
PPid: 13243
TracerPid: 16002
Uid: 404696 404696 404696 404696
Gid: 5762 5762 5762 5762
...
)"),
llvm::Succeeded());
llvm::Optional<lldb::pid_t> pid = parser->GetPid();
ASSERT_TRUE(pid.hasValue());
ASSERT_EQ(16001UL, pid.getValue());
}
TEST_F(MinidumpParserTest, GetFilteredModuleList) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ModuleList
Modules:
- Base of Image: 0x0000000000400000
Size of Image: 0x00001000
Module Name: '/tmp/test/linux-x86_64_not_crashed'
CodeView Record: 4C4570426CCF3F60FFA7CC4B86AE8FF44DB2576A68983611
- Base of Image: 0x0000000000600000
Size of Image: 0x00002000
Module Name: '/tmp/test/linux-x86_64_not_crashed'
CodeView Record: 4C4570426CCF3F60FFA7CC4B86AE8FF44DB2576A68983611
...
)"),
llvm::Succeeded());
llvm::ArrayRef<minidump::Module> modules = parser->GetModuleList();
std::vector<const minidump::Module *> filtered_modules =
parser->GetFilteredModuleList();
EXPECT_EQ(2u, modules.size());
ASSERT_EQ(1u, filtered_modules.size());
const minidump::Module &M = *filtered_modules[0];
EXPECT_THAT_EXPECTED(parser->GetMinidumpFile().getString(M.ModuleNameRVA),
llvm::HasValue("/tmp/test/linux-x86_64_not_crashed"));
}
TEST_F(MinidumpParserTest, GetExceptionStream) {
SetUpData("linux-x86_64.dmp");
const llvm::minidump::ExceptionStream *exception_stream =
parser->GetExceptionStream();
ASSERT_NE(nullptr, exception_stream);
ASSERT_EQ(11UL, exception_stream->ExceptionRecord.ExceptionCode);
}
void check_mem_range_exists(MinidumpParser &parser, const uint64_t range_start,
const uint64_t range_size) {
llvm::Optional<minidump::Range> range = parser.FindMemoryRange(range_start);
ASSERT_TRUE(range.hasValue()) << "There is no range containing this address";
EXPECT_EQ(range_start, range->start);
EXPECT_EQ(range_start + range_size, range->start + range->range_ref.size());
}
TEST_F(MinidumpParserTest, FindMemoryRange) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: MemoryList
Memory Ranges:
- Start of Memory Range: 0x00007FFCEB34A000
Content: C84D04BCE9
- Start of Memory Range: 0x0000000000401D46
Content: 5421
...
)"),
llvm::Succeeded());
EXPECT_EQ(llvm::None, parser->FindMemoryRange(0x00));
EXPECT_EQ(llvm::None, parser->FindMemoryRange(0x2a));
EXPECT_EQ((minidump::Range{0x401d46, llvm::ArrayRef<uint8_t>{0x54, 0x21}}),
parser->FindMemoryRange(0x401d46));
EXPECT_EQ(llvm::None, parser->FindMemoryRange(0x401d46 + 2));
EXPECT_EQ(
(minidump::Range{0x7ffceb34a000,
llvm::ArrayRef<uint8_t>{0xc8, 0x4d, 0x04, 0xbc, 0xe9}}),
parser->FindMemoryRange(0x7ffceb34a000 + 2));
EXPECT_EQ(llvm::None, parser->FindMemoryRange(0x7ffceb34a000 + 5));
}
TEST_F(MinidumpParserTest, GetMemory) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: MemoryList
Memory Ranges:
- Start of Memory Range: 0x00007FFCEB34A000
Content: C84D04BCE9
- Start of Memory Range: 0x0000000000401D46
Content: 5421
...
)"),
llvm::Succeeded());
EXPECT_EQ((llvm::ArrayRef<uint8_t>{0x54}), parser->GetMemory(0x401d46, 1));
EXPECT_EQ((llvm::ArrayRef<uint8_t>{0x54, 0x21}),
parser->GetMemory(0x401d46, 4));
EXPECT_EQ((llvm::ArrayRef<uint8_t>{0xc8, 0x4d, 0x04, 0xbc, 0xe9}),
parser->GetMemory(0x7ffceb34a000, 5));
EXPECT_EQ((llvm::ArrayRef<uint8_t>{0xc8, 0x4d, 0x04}),
parser->GetMemory(0x7ffceb34a000, 3));
EXPECT_EQ(llvm::ArrayRef<uint8_t>(), parser->GetMemory(0x500000, 512));
}
TEST_F(MinidumpParserTest, FindMemoryRangeWithFullMemoryMinidump) {
SetUpData("fizzbuzz_wow64.dmp");
// There are a lot of ranges in the file, just testing with some of them
EXPECT_FALSE(parser->FindMemoryRange(0x00).hasValue());
EXPECT_FALSE(parser->FindMemoryRange(0x2a).hasValue());
check_mem_range_exists(*parser, 0x10000, 65536); // first range
check_mem_range_exists(*parser, 0x40000, 4096);
EXPECT_FALSE(parser->FindMemoryRange(0x40000 + 4096).hasValue());
check_mem_range_exists(*parser, 0x77c12000, 8192);
check_mem_range_exists(*parser, 0x7ffe0000, 4096); // last range
EXPECT_FALSE(parser->FindMemoryRange(0x7ffe0000 + 4096).hasValue());
}
constexpr auto yes = MemoryRegionInfo::eYes;
constexpr auto no = MemoryRegionInfo::eNo;
constexpr auto unknown = MemoryRegionInfo::eDontKnow;
TEST_F(MinidumpParserTest, GetMemoryRegionInfo) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: MemoryInfoList
Memory Ranges:
- Base Address: 0x0000000000000000
Allocation Protect: [ ]
Region Size: 0x0000000000010000
State: [ MEM_FREE ]
Protect: [ PAGE_NO_ACCESS ]
Type: [ ]
- Base Address: 0x0000000000010000
Allocation Protect: [ PAGE_READ_WRITE ]
Region Size: 0x0000000000021000
State: [ MEM_COMMIT ]
Type: [ MEM_MAPPED ]
- Base Address: 0x0000000000040000
Allocation Protect: [ PAGE_EXECUTE_WRITE_COPY ]
Region Size: 0x0000000000001000
State: [ MEM_COMMIT ]
Protect: [ PAGE_READ_ONLY ]
Type: [ MEM_IMAGE ]
- Base Address: 0x000000007FFE0000
Allocation Protect: [ PAGE_READ_ONLY ]
Region Size: 0x0000000000001000
State: [ MEM_COMMIT ]
Type: [ MEM_PRIVATE ]
- Base Address: 0x000000007FFE1000
Allocation Base: 0x000000007FFE0000
Allocation Protect: [ PAGE_READ_ONLY ]
Region Size: 0x000000000000F000
State: [ MEM_RESERVE ]
Protect: [ PAGE_NO_ACCESS ]
Type: [ MEM_PRIVATE ]
...
)"),
llvm::Succeeded());
EXPECT_THAT(
parser->BuildMemoryRegions(),
testing::Pair(testing::ElementsAre(
MemoryRegionInfo({0x0, 0x10000}, no, no, no, no,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no, yes,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x40000, 0x1000}, yes, no, no, yes,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no, yes,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no, yes,
ConstString(), unknown, 0, unknown)),
true));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemoryList) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: MemoryList
Memory Ranges:
- Start of Memory Range: 0x0000000000001000
Content: '31313131313131313131313131313131'
- Start of Memory Range: 0x0000000000002000
Content: '3333333333333333333333333333333333333333333333333333333333333333'
...
)"),
llvm::Succeeded());
// Test we can get memory regions from the MINIDUMP_MEMORY_LIST stream when
// we don't have a MemoryInfoListStream.
EXPECT_THAT(
parser->BuildMemoryRegions(),
testing::Pair(
testing::ElementsAre(
MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, yes,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, yes,
ConstString(), unknown, 0, unknown)),
false));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemory64List) {
SetUpData("regions-memlist64.dmp");
// Test we can get memory regions from the MINIDUMP_MEMORY64_LIST stream when
// we don't have a MemoryInfoListStream.
EXPECT_THAT(
parser->BuildMemoryRegions(),
testing::Pair(
testing::ElementsAre(
MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, yes,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, yes,
ConstString(), unknown, 0, unknown)),
false));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMaps) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: LinuxMaps
Text: |
400d9000-400db000 r-xp 00000000 b3:04 227 /system/bin/app_process
400db000-400dc000 r--p 00001000 b3:04 227 /system/bin/app_process
400dc000-400dd000 rw-p 00000000 00:00 0
400ec000-400ed000 r--p 00000000 00:00 0
400ee000-400ef000 rw-p 00010000 b3:04 300 /system/bin/linker
400fc000-400fd000 rwxp 00001000 b3:04 1096 /system/lib/liblog.so
...
)"),
llvm::Succeeded());
// Test we can get memory regions from the linux /proc/<pid>/maps stream when
// we don't have a MemoryInfoListStream.
ConstString app_process("/system/bin/app_process");
ConstString linker("/system/bin/linker");
ConstString liblog("/system/lib/liblog.so");
EXPECT_THAT(parser->BuildMemoryRegions(),
testing::Pair(
testing::ElementsAre(
MemoryRegionInfo({0x400d9000, 0x2000}, yes, no, yes, yes,
app_process, unknown, 0, unknown),
MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, yes,
app_process, unknown, 0, unknown),
MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, yes,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, yes,
ConstString(), unknown, 0, unknown),
MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, yes,
linker, unknown, 0, unknown),
MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, yes,
liblog, unknown, 0, unknown)),
true));
}
TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMapsError) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: LinuxMaps
Text: |
400d9000-400db000 r?xp 00000000 b3:04 227
400fc000-400fd000 rwxp 00001000 b3:04 1096
...
)"),
llvm::Succeeded());
// Test that when a /proc/maps region fails to parse
// we handle the error and continue with the rest.
EXPECT_THAT(parser->BuildMemoryRegions(),
testing::Pair(testing::ElementsAre(MemoryRegionInfo(
{0x400fc000, 0x1000}, yes, yes, yes, yes,
ConstString(nullptr), unknown, 0, unknown)),
true));
}
// Windows Minidump tests
TEST_F(MinidumpParserTest, GetArchitectureWindows) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: SystemInfo
Processor Arch: X86
Processor Level: 6
Processor Revision: 15876
Number of Processors: 32
Product type: 1
Major Version: 6
Minor Version: 1
Build Number: 7601
Platform ID: Win32NT
CSD Version: Service Pack 1
Suite Mask: 0x0100
CPU:
Vendor ID: GenuineIntel
Version Info: 0x000306E4
Feature Info: 0xBFEBFBFF
AMD Extended Features: 0x771EEC80
...
)"),
llvm::Succeeded());
ASSERT_EQ(llvm::Triple::ArchType::x86,
parser->GetArchitecture().GetMachine());
ASSERT_EQ(llvm::Triple::OSType::Win32,
parser->GetArchitecture().GetTriple().getOS());
}
TEST_F(MinidumpParserTest, GetLinuxProcStatus_no_stream) {
// Test that GetLinuxProcStatus returns nullptr when the minidump does not
// contain this stream.
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
...
)"),
llvm::Succeeded());
EXPECT_EQ(llvm::None, parser->GetLinuxProcStatus());
}
TEST_F(MinidumpParserTest, GetMiscInfoWindows) {
SetUpData("fizzbuzz_no_heap.dmp");
const MinidumpMiscInfo *misc_info = parser->GetMiscInfo();
ASSERT_NE(nullptr, misc_info);
llvm::Optional<lldb::pid_t> pid = misc_info->GetPid();
ASSERT_TRUE(pid.hasValue());
ASSERT_EQ(4440UL, pid.getValue());
}
TEST_F(MinidumpParserTest, GetPidWindows) {
SetUpData("fizzbuzz_no_heap.dmp");
llvm::Optional<lldb::pid_t> pid = parser->GetPid();
ASSERT_TRUE(pid.hasValue());
ASSERT_EQ(4440UL, pid.getValue());
}
// wow64
TEST_F(MinidumpParserTest, GetPidWow64) {
SetUpData("fizzbuzz_wow64.dmp");
llvm::Optional<lldb::pid_t> pid = parser->GetPid();
ASSERT_TRUE(pid.hasValue());
ASSERT_EQ(7836UL, pid.getValue());
}
// Register tests
#define REG_VAL32(x) *(reinterpret_cast<uint32_t *>(x))
#define REG_VAL64(x) *(reinterpret_cast<uint64_t *>(x))
TEST_F(MinidumpParserTest, GetThreadContext_x86_32) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ThreadList
Threads:
- Thread Id: 0x00026804
Stack:
Start of Memory Range: 0x00000000FF9DD000
Content: 68D39DFF
Context: 0F0001000000000000000000000000000000000000000000000000007F03FFFF0000FFFFFFFFFFFF09DC62F72300000088E36CF72B00FFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063000000000000002B0000002B000000A88204085CD59DFF008077F7A3D49DFF01000000000000003CD59DFFA082040823000000820201002CD59DFF2B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
)"),
llvm::Succeeded());
llvm::ArrayRef<minidump::Thread> thread_list = parser->GetThreads();
const minidump::Thread &thread = thread_list[0];
llvm::ArrayRef<uint8_t> registers(parser->GetThreadContext(thread));
const MinidumpContext_x86_32 *context;
EXPECT_TRUE(consumeObject(registers, context).Success());
EXPECT_EQ(MinidumpContext_x86_32_Flags(uint32_t(context->context_flags)),
MinidumpContext_x86_32_Flags::x86_32_Flag |
MinidumpContext_x86_32_Flags::Full |
MinidumpContext_x86_32_Flags::FloatingPoint);
EXPECT_EQ(0x00000000u, context->eax);
EXPECT_EQ(0xf7778000u, context->ebx);
EXPECT_EQ(0x00000001u, context->ecx);
EXPECT_EQ(0xff9dd4a3u, context->edx);
EXPECT_EQ(0x080482a8u, context->edi);
EXPECT_EQ(0xff9dd55cu, context->esi);
EXPECT_EQ(0xff9dd53cu, context->ebp);
EXPECT_EQ(0xff9dd52cu, context->esp);
EXPECT_EQ(0x080482a0u, context->eip);
EXPECT_EQ(0x00010282u, context->eflags);
EXPECT_EQ(0x0023u, context->cs);
EXPECT_EQ(0x0000u, context->fs);
EXPECT_EQ(0x0063u, context->gs);
EXPECT_EQ(0x002bu, context->ss);
EXPECT_EQ(0x002bu, context->ds);
EXPECT_EQ(0x002bu, context->es);
}
TEST_F(MinidumpParserTest, GetThreadContext_x86_64) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ThreadList
Threads:
- Thread Id: 0x00003E81
Stack:
Start of Memory Range: 0x00007FFCEB34A000
Content: C84D04BCE97F00
Context: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B0010000000000033000000000000000000000006020100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000010A234EBFC7F000010A234EBFC7F00000000000000000000F09C34EBFC7F0000C0A91ABCE97F00000000000000000000A0163FBCE97F00004602000000000000921C40000000000030A434EBFC7F000000000000000000000000000000000000C61D4000000000007F0300000000000000000000000000000000000000000000801F0000FFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFF00FFFFFFFFFFFFFF00FFFFFFFF25252525252525252525252525252525000000000000000000000000000000000000000000000000000000000000000000FFFF00FFFFFFFFFFFFFF00FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
...
)"),
llvm::Succeeded());
llvm::ArrayRef<minidump::Thread> thread_list = parser->GetThreads();
const minidump::Thread &thread = thread_list[0];
llvm::ArrayRef<uint8_t> registers(parser->GetThreadContext(thread));
const MinidumpContext_x86_64 *context;
EXPECT_TRUE(consumeObject(registers, context).Success());
EXPECT_EQ(MinidumpContext_x86_64_Flags(uint32_t(context->context_flags)),
MinidumpContext_x86_64_Flags::x86_64_Flag |
MinidumpContext_x86_64_Flags::Control |
MinidumpContext_x86_64_Flags::FloatingPoint |
MinidumpContext_x86_64_Flags::Integer);
EXPECT_EQ(0x0000000000000000u, context->rax);
EXPECT_EQ(0x0000000000000000u, context->rbx);
EXPECT_EQ(0x0000000000000010u, context->rcx);
EXPECT_EQ(0x0000000000000000u, context->rdx);
EXPECT_EQ(0x00007ffceb349cf0u, context->rdi);
EXPECT_EQ(0x0000000000000000u, context->rsi);
EXPECT_EQ(0x00007ffceb34a210u, context->rbp);
EXPECT_EQ(0x00007ffceb34a210u, context->rsp);
EXPECT_EQ(0x00007fe9bc1aa9c0u, context->r8);
EXPECT_EQ(0x0000000000000000u, context->r9);
EXPECT_EQ(0x00007fe9bc3f16a0u, context->r10);
EXPECT_EQ(0x0000000000000246u, context->r11);
EXPECT_EQ(0x0000000000401c92u, context->r12);
EXPECT_EQ(0x00007ffceb34a430u, context->r13);
EXPECT_EQ(0x0000000000000000u, context->r14);
EXPECT_EQ(0x0000000000000000u, context->r15);
EXPECT_EQ(0x0000000000401dc6u, context->rip);
EXPECT_EQ(0x00010206u, context->eflags);
EXPECT_EQ(0x0033u, context->cs);
EXPECT_EQ(0x0000u, context->ss);
}
TEST_F(MinidumpParserTest, GetThreadContext_x86_32_wow64) {
SetUpData("fizzbuzz_wow64.dmp");
llvm::ArrayRef<minidump::Thread> thread_list = parser->GetThreads();
const minidump::Thread &thread = thread_list[0];
llvm::ArrayRef<uint8_t> registers(parser->GetThreadContextWow64(thread));
const MinidumpContext_x86_32 *context;
EXPECT_TRUE(consumeObject(registers, context).Success());
EXPECT_EQ(MinidumpContext_x86_32_Flags(uint32_t(context->context_flags)),
MinidumpContext_x86_32_Flags::x86_32_Flag |
MinidumpContext_x86_32_Flags::Full |
MinidumpContext_x86_32_Flags::FloatingPoint |
MinidumpContext_x86_32_Flags::ExtendedRegisters);
EXPECT_EQ(0x00000000u, context->eax);
EXPECT_EQ(0x0037f608u, context->ebx);
EXPECT_EQ(0x00e61578u, context->ecx);
EXPECT_EQ(0x00000008u, context->edx);
EXPECT_EQ(0x00000000u, context->edi);
EXPECT_EQ(0x00000002u, context->esi);
EXPECT_EQ(0x0037f654u, context->ebp);
EXPECT_EQ(0x0037f5b8u, context->esp);
EXPECT_EQ(0x77ce01fdu, context->eip);
EXPECT_EQ(0x00000246u, context->eflags);
EXPECT_EQ(0x0023u, context->cs);
EXPECT_EQ(0x0053u, context->fs);
EXPECT_EQ(0x002bu, context->gs);
EXPECT_EQ(0x002bu, context->ss);
EXPECT_EQ(0x002bu, context->ds);
EXPECT_EQ(0x002bu, context->es);
}
TEST_F(MinidumpParserTest, MinidumpDuplicateModuleMinAddress) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ModuleList
Modules:
- Base of Image: 0x0000000000002000
Size of Image: 0x00001000
Module Name: '/tmp/a'
CodeView Record: ''
- Base of Image: 0x0000000000001000
Size of Image: 0x00001000
Module Name: '/tmp/a'
CodeView Record: ''
...
)"),
llvm::Succeeded());
// If we have a module mentioned twice in the module list, the filtered
// module list should contain the instance with the lowest BaseOfImage.
std::vector<const minidump::Module *> filtered_modules =
parser->GetFilteredModuleList();
ASSERT_EQ(1u, filtered_modules.size());
EXPECT_EQ(0x0000000000001000u, filtered_modules[0]->BaseOfImage);
}
TEST_F(MinidumpParserTest, MinidumpDuplicateModuleMappedFirst) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ModuleList
Modules:
- Base of Image: 0x400d0000
Size of Image: 0x00002000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Base of Image: 0x400d3000
Size of Image: 0x00001000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Type: LinuxMaps
Text: |
400d0000-400d2000 r--p 00000000 b3:04 227 /usr/lib/libc.so
400d2000-400d3000 rw-p 00000000 00:00 0
400d3000-400d4000 r-xp 00010000 b3:04 227 /usr/lib/libc.so
400d4000-400d5000 rwxp 00001000 b3:04 227 /usr/lib/libc.so
...
)"),
llvm::Succeeded());
// If we have a module mentioned twice in the module list, and we have full
// linux maps for all of the memory regions, make sure we pick the one that
// has a consecutive region with a matching path that has executable
// permissions. If clients open an object file with mmap, breakpad can create
// multiple mappings for a library errnoneously and the lowest address isn't
// always the right address. In this case we check the consective memory
// regions whose path matches starting at the base of image address and make
// sure one of the regions is executable and prefer that one.
//
// This test will make sure that if the executable is second in the module
// list, that it will become the selected module in the filtered list.
std::vector<const minidump::Module *> filtered_modules =
parser->GetFilteredModuleList();
ASSERT_EQ(1u, filtered_modules.size());
EXPECT_EQ(0x400d3000u, filtered_modules[0]->BaseOfImage);
}
TEST_F(MinidumpParserTest, MinidumpDuplicateModuleMappedSecond) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ModuleList
Modules:
- Base of Image: 0x400d0000
Size of Image: 0x00002000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Base of Image: 0x400d3000
Size of Image: 0x00001000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Type: LinuxMaps
Text: |
400d0000-400d1000 r-xp 00010000 b3:04 227 /usr/lib/libc.so
400d1000-400d2000 rwxp 00001000 b3:04 227 /usr/lib/libc.so
400d2000-400d3000 rw-p 00000000 00:00 0
400d3000-400d5000 r--p 00000000 b3:04 227 /usr/lib/libc.so
...
)"),
llvm::Succeeded());
// If we have a module mentioned twice in the module list, and we have full
// linux maps for all of the memory regions, make sure we pick the one that
// has a consecutive region with a matching path that has executable
// permissions. If clients open an object file with mmap, breakpad can create
// multiple mappings for a library errnoneously and the lowest address isn't
// always the right address. In this case we check the consective memory
// regions whose path matches starting at the base of image address and make
// sure one of the regions is executable and prefer that one.
//
// This test will make sure that if the executable is first in the module
// list, that it will remain the correctly selected module in the filtered
// list.
std::vector<const minidump::Module *> filtered_modules =
parser->GetFilteredModuleList();
ASSERT_EQ(1u, filtered_modules.size());
EXPECT_EQ(0x400d0000u, filtered_modules[0]->BaseOfImage);
}
TEST_F(MinidumpParserTest, MinidumpDuplicateModuleMappedSecondHigh) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ModuleList
Modules:
- Base of Image: 0x400d3000
Size of Image: 0x00002000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Base of Image: 0x400d0000
Size of Image: 0x00001000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Type: LinuxMaps
Text: |
400d0000-400d2000 r--p 00000000 b3:04 227 /usr/lib/libc.so
400d2000-400d3000 rw-p 00000000 00:00 0
400d3000-400d4000 r-xp 00010000 b3:04 227 /usr/lib/libc.so
400d4000-400d5000 rwxp 00001000 b3:04 227 /usr/lib/libc.so
...
)"),
llvm::Succeeded());
// If we have a module mentioned twice in the module list, and we have full
// linux maps for all of the memory regions, make sure we pick the one that
// has a consecutive region with a matching path that has executable
// permissions. If clients open an object file with mmap, breakpad can create
// multiple mappings for a library errnoneously and the lowest address isn't
// always the right address. In this case we check the consective memory
// regions whose path matches starting at the base of image address and make
// sure one of the regions is executable and prefer that one.
//
// This test will make sure that if the executable is first in the module
// list, that it will remain the correctly selected module in the filtered
// list, even if the non-executable module was loaded at a lower base address.
std::vector<const minidump::Module *> filtered_modules =
parser->GetFilteredModuleList();
ASSERT_EQ(1u, filtered_modules.size());
EXPECT_EQ(0x400d3000u, filtered_modules[0]->BaseOfImage);
}
TEST_F(MinidumpParserTest, MinidumpDuplicateModuleSeparateCode) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ModuleList
Modules:
- Base of Image: 0x400d0000
Size of Image: 0x00002000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Base of Image: 0x400d5000
Size of Image: 0x00001000
Module Name: '/usr/lib/libc.so'
CodeView Record: ''
- Type: LinuxMaps
Text: |
400d0000-400d3000 r--p 00000000 b3:04 227 /usr/lib/libc.so
400d3000-400d5000 rw-p 00000000 00:00 0
400d5000-400d6000 r--p 00000000 b3:04 227 /usr/lib/libc.so
400d6000-400d7000 r-xp 00010000 b3:04 227 /usr/lib/libc.so
400d7000-400d8000 rwxp 00001000 b3:04 227 /usr/lib/libc.so
...
)"),
llvm::Succeeded());
// If we have a module mentioned twice in the module list, and we have full
// linux maps for all of the memory regions, make sure we pick the one that
// has a consecutive region with a matching path that has executable
// permissions. If clients open an object file with mmap, breakpad can create
// multiple mappings for a library errnoneously and the lowest address isn't
// always the right address. In this case we check the consective memory
// regions whose path matches starting at the base of image address and make
// sure one of the regions is executable and prefer that one.
//
// This test will make sure if binaries are compiled with "-z separate-code",
// where the first region for a binary won't be marked as executable, that
// it gets selected by detecting the second consecutive mapping at 0x400d7000
// when asked about the a module mamed "/usr/lib/libc.so" at 0x400d5000.
std::vector<const minidump::Module *> filtered_modules =
parser->GetFilteredModuleList();
ASSERT_EQ(1u, filtered_modules.size());
EXPECT_EQ(0x400d5000u, filtered_modules[0]->BaseOfImage);
}
TEST_F(MinidumpParserTest, MinidumpModuleOrder) {
ASSERT_THAT_ERROR(SetUpFromYaml(R"(
--- !minidump
Streams:
- Type: ModuleList
Modules:
- Base of Image: 0x0000000000002000
Size of Image: 0x00001000
Module Name: '/tmp/a'
CodeView Record: ''
- Base of Image: 0x0000000000001000
Size of Image: 0x00001000
Module Name: '/tmp/b'
CodeView Record: ''
...
)"),
llvm::Succeeded());
// Test module filtering does not affect the overall module order. Previous
// versions of the MinidumpParser::GetFilteredModuleList() function would sort
// all images by address and modify the order of the modules.
std::vector<const minidump::Module *> filtered_modules =
parser->GetFilteredModuleList();
ASSERT_EQ(2u, filtered_modules.size());
EXPECT_EQ(0x0000000000002000u, filtered_modules[0]->BaseOfImage);
EXPECT_THAT_EXPECTED(
parser->GetMinidumpFile().getString(filtered_modules[0]->ModuleNameRVA),
llvm::HasValue("/tmp/a"));
EXPECT_EQ(0x0000000000001000u, filtered_modules[1]->BaseOfImage);
EXPECT_THAT_EXPECTED(
parser->GetMinidumpFile().getString(filtered_modules[1]->ModuleNameRVA),
llvm::HasValue("/tmp/b"));
}