Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.h =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.h +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.h @@ -34,6 +34,16 @@ namespace minidump { +// Describes a range of memory captured in the Minidump +struct Range { + lldb::addr_t start; // virtual address of the beginning of the range + // range_ref - absolute pointer to the first byte of the range and size + llvm::ArrayRef range_ref; + + Range(lldb::addr_t start, llvm::ArrayRef range_ref) + : start(start), range_ref(range_ref) {} +}; + class MinidumpParser { public: static llvm::Optional @@ -47,6 +57,8 @@ llvm::ArrayRef GetThreads(); + llvm::ArrayRef GetThreadContext(const MinidumpThread &td); + const MinidumpSystemInfo *GetSystemInfo(); ArchSpec GetArchitecture(); @@ -59,8 +71,20 @@ llvm::ArrayRef GetModuleList(); + // There are cases in which there is more than one record in the ModuleList + // for the same module name.(e.g. when the binary has non contiguous segments) + // So this function returns a filtered module list - if it finds records that + // have the same name, it keeps the copy with the lowest load address. + std::vector GetFilteredModuleList(); + const MinidumpExceptionStream *GetExceptionStream(); + llvm::Optional FindMemoryRange(lldb::addr_t addr); + + llvm::ArrayRef GetMemory(lldb::addr_t addr, size_t size); + + llvm::Optional GetMemoryRegionInfo(lldb::addr_t); + private: lldb::DataBufferSP m_data_sp; const MinidumpHeader *m_header; Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.cpp =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.cpp +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpParser.cpp @@ -11,8 +11,11 @@ #include "MinidumpParser.h" // Other libraries and framework includes +#include "lldb/Target/MemoryRegionInfo.h" + // C includes // C++ includes +#include using namespace lldb_private; using namespace minidump; @@ -100,6 +103,14 @@ return MinidumpThread::ParseThreadList(data); } +llvm::ArrayRef +MinidumpParser::GetThreadContext(const MinidumpThread &td) { + if (td.thread_context.rva + td.thread_context.data_size > GetData().size()) + return llvm::None; + + return GetData().slice(td.thread_context.rva, td.thread_context.data_size); +} + const MinidumpSystemInfo *MinidumpParser::GetSystemInfo() { llvm::ArrayRef data = GetStream(MinidumpStreamType::SystemInfo); @@ -216,6 +227,42 @@ return MinidumpModule::ParseModuleList(data); } +std::vector MinidumpParser::GetFilteredModuleList() { + llvm::ArrayRef modules = GetModuleList(); + // mapping module_name to pair(load_address, pointer to module struct in + // memory) + llvm::StringMap> lowest_addr; + + std::vector filtered_modules; + + llvm::Optional name; + std::string module_name; + + for (const auto &module : modules) { + name = GetMinidumpString(module.module_name_rva); + + if (!name) + continue; + + module_name = name.getValue(); + + auto iter = lowest_addr.end(); + bool exists; + std::tie(iter, exists) = lowest_addr.try_emplace( + module_name, std::make_pair(module.base_of_image, &module)); + + if (exists && module.base_of_image < iter->second.first) + iter->second = std::make_pair(module.base_of_image, &module); + } + + filtered_modules.reserve(lowest_addr.size()); + for (const auto &module : lowest_addr) { + filtered_modules.push_back(module.second.second); + } + + return filtered_modules; +} + const MinidumpExceptionStream *MinidumpParser::GetExceptionStream() { llvm::ArrayRef data = GetStream(MinidumpStreamType::Exception); @@ -224,3 +271,156 @@ return MinidumpExceptionStream::Parse(data); } + +llvm::Optional +MinidumpParser::FindMemoryRange(lldb::addr_t addr) { + llvm::ArrayRef data = GetStream(MinidumpStreamType::MemoryList); + llvm::ArrayRef data64 = GetStream(MinidumpStreamType::Memory64List); + + if (data.empty() && data64.empty()) + return llvm::None; + + if (!data.empty()) { + llvm::ArrayRef memory_list = + MinidumpMemoryDescriptor::ParseMemoryList(data); + + if (memory_list.empty()) + return llvm::None; + + for (const auto &memory_desc : memory_list) { + const MinidumpLocationDescriptor &loc_desc = memory_desc.memory; + const lldb::addr_t range_start = memory_desc.start_of_memory_range; + const size_t range_size = loc_desc.data_size; + + if (loc_desc.rva + loc_desc.data_size > GetData().size()) + return llvm::None; + + if (range_start <= addr && addr < range_start + range_size) { + return minidump::Range(range_start, + GetData().slice(loc_desc.rva, range_size)); + } + } + } + + // Some Minidumps have a Memory64ListStream that captures all the heap + // memory (full-memory Minidumps). We can't exactly use the same loop as + // above, because the Minidump uses slightly different data structures to + // describe those + + if (!data64.empty()) { + llvm::ArrayRef memory64_list; + uint64_t base_rva; + std::tie(memory64_list, base_rva) = + MinidumpMemoryDescriptor64::ParseMemory64List(data64); + + if (memory64_list.empty()) + return llvm::None; + + for (const auto &memory_desc64 : memory64_list) { + const lldb::addr_t range_start = memory_desc64.start_of_memory_range; + const size_t range_size = memory_desc64.data_size; + + if (base_rva + range_size > GetData().size()) + return llvm::None; + + if (range_start <= addr && addr < range_start + range_size) { + return minidump::Range(range_start, + GetData().slice(base_rva, range_size)); + } + base_rva += range_size; + } + } + + return llvm::None; +} + +llvm::ArrayRef MinidumpParser::GetMemory(lldb::addr_t addr, + size_t size) { + // I don't have a sense of how frequently this is called or how many memory + // ranges a Minidump typically has, so I'm not sure if searching for the + // appropriate range linearly each time is stupid. Perhaps we should build + // an index for faster lookups. + llvm::Optional range = FindMemoryRange(addr); + if (!range) + return {}; + + // There's at least some overlap between the beginning of the desired range + // (addr) and the current range. Figure out where the overlap begins and + // how much overlap there is. + + const size_t offset = addr - range->start; + + if (addr < range->start || offset >= range->range_ref.size()) + return {}; + + const size_t overlap = std::min(size, range->range_ref.size() - offset); + return range->range_ref.slice(offset, overlap); +} + +llvm::Optional +MinidumpParser::GetMemoryRegionInfo(lldb::addr_t load_addr) { + MemoryRegionInfo info; + llvm::ArrayRef data = GetStream(MinidumpStreamType::MemoryInfoList); + if (data.empty()) + return llvm::None; + + std::vector mem_info_list = + MinidumpMemoryInfo::ParseMemoryInfoList(data); + if (mem_info_list.empty()) + return llvm::None; + + const auto yes = MemoryRegionInfo::eYes; + const auto no = MemoryRegionInfo::eNo; + + const MinidumpMemoryInfo *next_entry = nullptr; + for (const auto &entry : mem_info_list) { + const auto head = entry->base_address; + const auto tail = head + entry->region_size; + + if (head <= load_addr && load_addr < tail) { + info.GetRange().SetRangeBase( + (entry->state != uint32_t(MinidumpMemoryInfoState::MemFree)) + ? head + : load_addr); + info.GetRange().SetRangeEnd(tail); + + const uint32_t PageNoAccess = + static_cast(MinidumpMemoryProtectionContants::PageNoAccess); + info.SetReadable((entry->protect & PageNoAccess) == 0 ? yes : no); + + const uint32_t PageWritable = + static_cast(MinidumpMemoryProtectionContants::PageWritable); + info.SetWritable((entry->protect & PageWritable) != 0 ? yes : no); + + const uint32_t PageExecutable = static_cast( + MinidumpMemoryProtectionContants::PageExecutable); + info.SetExecutable((entry->protect & PageExecutable) != 0 ? yes : no); + + const uint32_t MemFree = + static_cast(MinidumpMemoryInfoState::MemFree); + info.SetMapped((entry->state != MemFree) ? yes : no); + + return info; + } else if (head > load_addr && + (next_entry == nullptr || head < next_entry->base_address)) { + // In case there is no region containing load_addr keep track of the + // nearest region after load_addr so we can return the distance to it. + next_entry = entry; + } + } + + // No containing region found. Create an unmapped region that extends to the + // next region or LLDB_INVALID_ADDRESS + info.GetRange().SetRangeBase(load_addr); + info.GetRange().SetRangeEnd((next_entry != nullptr) ? next_entry->base_address + : LLDB_INVALID_ADDRESS); + info.SetReadable(no); + info.SetWritable(no); + info.SetExecutable(no); + info.SetMapped(no); + + // Note that the memory info list doesn't seem to contain ranges in kernel + // space, so if you're walking a stack that has kernel frames, the stack may + // appear truncated. + return info; +} Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.h =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.h +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.h @@ -207,10 +207,23 @@ struct MinidumpMemoryDescriptor { llvm::support::ulittle64_t start_of_memory_range; MinidumpLocationDescriptor memory; + + static llvm::ArrayRef + ParseMemoryList(llvm::ArrayRef &data); }; static_assert(sizeof(MinidumpMemoryDescriptor) == 16, "sizeof MinidumpMemoryDescriptor is not correct!"); +struct MinidumpMemoryDescriptor64 { + llvm::support::ulittle64_t start_of_memory_range; + llvm::support::ulittle64_t data_size; + + static std::pair, uint64_t> + ParseMemory64List(llvm::ArrayRef &data); +}; +static_assert(sizeof(MinidumpMemoryDescriptor64) == 16, + "sizeof MinidumpMemoryDescriptor64 is not correct!"); + // Reference: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680365.aspx struct MinidumpDirectory { @@ -221,6 +234,70 @@ "sizeof MinidumpDirectory is not correct!"); // Reference: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680385(v=vs.85).aspx +struct MinidumpMemoryInfoListHeader { + llvm::support::ulittle32_t size_of_header; + llvm::support::ulittle32_t size_of_entry; + llvm::support::ulittle64_t num_of_entries; +}; +static_assert(sizeof(MinidumpMemoryInfoListHeader) == 16, + "sizeof MinidumpMemoryInfoListHeader is not correct!"); + +// Reference: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms680386(v=vs.85).aspx +struct MinidumpMemoryInfo { + llvm::support::ulittle64_t base_address; + llvm::support::ulittle64_t allocation_base; + llvm::support::ulittle32_t allocation_protect; + llvm::support::ulittle32_t alignment1; + llvm::support::ulittle64_t region_size; + llvm::support::ulittle32_t state; + llvm::support::ulittle32_t protect; + llvm::support::ulittle32_t type; + llvm::support::ulittle32_t alignment2; + + static std::vector + ParseMemoryInfoList(llvm::ArrayRef &data); +}; +static_assert(sizeof(MinidumpMemoryInfo) == 48, + "sizeof MinidumpMemoryInfo is not correct!"); + +enum class MinidumpMemoryInfoState : uint32_t { + MemCommit = 0x1000, + MemFree = 0x10000, + MemReserve = 0x2000, + LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ MemFree) +}; + +enum class MinidumpMemoryInfoType : uint32_t { + MemImage = 0x1000000, + MemMapped = 0x40000, + MemPrivate = 0x20000, + LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ MemImage) +}; + +// Reference: +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786(v=vs.85).aspx +enum class MinidumpMemoryProtectionContants : uint32_t { + PageExecute = 0x10, + PageExecuteRead = 0x20, + PageExecuteReadWrite = 0x40, + PageExecuteWriteCopy = 0x80, + PageNoAccess = 0x01, + PageReadOnly = 0x02, + PageReadWrite = 0x04, + PageWriteCopy = 0x08, + PageTargetsInvalid = 0x40000000, + PageTargetsNoUpdate = 0x40000000, + + PageWritable = PageExecuteReadWrite | PageExecuteWriteCopy | PageReadWrite | + PageWriteCopy, + PageExecutable = PageExecute | PageExecuteRead | PageExecuteReadWrite | + PageExecuteWriteCopy, + LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ PageTargetsInvalid) +}; + +// Reference: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680517(v=vs.85).aspx struct MinidumpThread { llvm::support::ulittle32_t thread_id; Index: lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.cpp =================================================================== --- lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.cpp +++ lldb/trunk/source/Plugins/Process/minidump/MinidumpTypes.cpp @@ -176,3 +176,60 @@ return exception_stream; } + +llvm::ArrayRef +MinidumpMemoryDescriptor::ParseMemoryList(llvm::ArrayRef &data) { + const llvm::support::ulittle32_t *mem_ranges_count; + Error error = consumeObject(data, mem_ranges_count); + if (error.Fail() || + *mem_ranges_count * sizeof(MinidumpMemoryDescriptor) > data.size()) + return {}; + + return llvm::makeArrayRef( + reinterpret_cast(data.data()), + *mem_ranges_count); +} + +std::pair, uint64_t> +MinidumpMemoryDescriptor64::ParseMemory64List(llvm::ArrayRef &data) { + const llvm::support::ulittle64_t *mem_ranges_count; + Error error = consumeObject(data, mem_ranges_count); + if (error.Fail() || + *mem_ranges_count * sizeof(MinidumpMemoryDescriptor64) > data.size()) + return {}; + + const llvm::support::ulittle64_t *base_rva; + error = consumeObject(data, base_rva); + if (error.Fail()) + return {}; + + return std::make_pair( + llvm::makeArrayRef( + reinterpret_cast(data.data()), + *mem_ranges_count), + *base_rva); +} + +std::vector +MinidumpMemoryInfo::ParseMemoryInfoList(llvm::ArrayRef &data) { + const MinidumpMemoryInfoListHeader *header; + Error error = consumeObject(data, header); + if (error.Fail() || + header->size_of_header < sizeof(MinidumpMemoryInfoListHeader) || + header->size_of_entry < sizeof(MinidumpMemoryInfo)) + return {}; + + data = data.drop_front(header->size_of_header - + sizeof(MinidumpMemoryInfoListHeader)); + + if (header->size_of_entry * header->num_of_entries > data.size()) + return {}; + + std::vector result; + for (uint64_t i = 0; i < header->num_of_entries; ++i) { + result.push_back(reinterpret_cast( + data.data() + i * header->size_of_entry)); + } + + return result; +} Index: lldb/trunk/unittests/Process/minidump/CMakeLists.txt =================================================================== --- lldb/trunk/unittests/Process/minidump/CMakeLists.txt +++ lldb/trunk/unittests/Process/minidump/CMakeLists.txt @@ -4,6 +4,8 @@ set(test_inputs linux-x86_64.dmp - fizzbuzz_no_heap.dmp) + linux-x86_64_not_crashed.dmp + fizzbuzz_no_heap.dmp + fizzbuzz_wow64.dmp) add_unittest_inputs(LLDBMinidumpTests "${test_inputs}") Index: lldb/trunk/unittests/Process/minidump/MinidumpParserTest.cpp =================================================================== --- lldb/trunk/unittests/Process/minidump/MinidumpParserTest.cpp +++ lldb/trunk/unittests/Process/minidump/MinidumpParserTest.cpp @@ -19,6 +19,7 @@ #include "lldb/Core/ArchSpec.h" #include "lldb/Core/DataExtractor.h" #include "lldb/Host/FileSpec.h" +#include "lldb/Target/MemoryRegionInfo.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/Optional.h" @@ -68,7 +69,11 @@ ASSERT_EQ(1UL, thread_list.size()); const MinidumpThread thread = thread_list[0]; - ASSERT_EQ(16001UL, thread.thread_id); + + EXPECT_EQ(16001UL, thread.thread_id); + + llvm::ArrayRef context = parser->GetThreadContext(thread); + EXPECT_EQ(1232UL, context.size()); } TEST_F(MinidumpParserTest, GetThreadsTruncatedFile) { @@ -131,6 +136,28 @@ } } +TEST_F(MinidumpParserTest, GetFilteredModuleList) { + SetUpData("linux-x86_64_not_crashed.dmp"); + llvm::ArrayRef modules = parser->GetModuleList(); + std::vector filtered_modules = + parser->GetFilteredModuleList(); + EXPECT_EQ(10UL, modules.size()); + EXPECT_EQ(9UL, filtered_modules.size()); + // EXPECT_GT(modules.size(), filtered_modules.size()); + bool found = false; + for (size_t i = 0; i < filtered_modules.size(); ++i) { + llvm::Optional name = + parser->GetMinidumpString(filtered_modules[i]->module_name_rva); + ASSERT_TRUE(name.hasValue()); + if (name.getValue() == "/tmp/test/linux-x86_64_not_crashed") { + ASSERT_FALSE(found) << "There should be only one module with this name " + "in the filtered module list"; + found = true; + ASSERT_EQ(0x400000UL, filtered_modules[i]->base_of_image); + } + } +} + TEST_F(MinidumpParserTest, GetExceptionStream) { SetUpData("linux-x86_64.dmp"); const MinidumpExceptionStream *exception_stream = @@ -139,6 +166,81 @@ ASSERT_EQ(11UL, exception_stream->exception_record.exception_code); } +void check_mem_range_exists(std::unique_ptr &parser, + const uint64_t range_start, + const uint64_t range_size) { + llvm::Optional 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) { + SetUpData("linux-x86_64.dmp"); + // There are two memory ranges in the file (size is in bytes, decimal): + // 1) 0x401d46 256 + // 2) 0x7ffceb34a000 12288 + EXPECT_FALSE(parser->FindMemoryRange(0x00).hasValue()); + EXPECT_FALSE(parser->FindMemoryRange(0x2a).hasValue()); + + check_mem_range_exists(parser, 0x401d46, 256); + EXPECT_FALSE(parser->FindMemoryRange(0x401d46 + 256).hasValue()); + + check_mem_range_exists(parser, 0x7ffceb34a000, 12288); + EXPECT_FALSE(parser->FindMemoryRange(0x7ffceb34a000 + 12288).hasValue()); +} + +TEST_F(MinidumpParserTest, GetMemory) { + SetUpData("linux-x86_64.dmp"); + + EXPECT_EQ(128UL, parser->GetMemory(0x401d46, 128).size()); + EXPECT_EQ(256UL, parser->GetMemory(0x401d46, 512).size()); + + EXPECT_EQ(12288UL, parser->GetMemory(0x7ffceb34a000, 12288).size()); + EXPECT_EQ(1024UL, parser->GetMemory(0x7ffceb34a000, 1024).size()); + + EXPECT_TRUE(parser->GetMemory(0x500000, 512).empty()); +} + +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()); +} + +void check_region_info(std::unique_ptr &parser, + const uint64_t addr, MemoryRegionInfo::OptionalBool read, + MemoryRegionInfo::OptionalBool write, + MemoryRegionInfo::OptionalBool exec) { + auto range_info = parser->GetMemoryRegionInfo(addr); + ASSERT_TRUE(range_info.hasValue()); + EXPECT_EQ(read, range_info->GetReadable()); + EXPECT_EQ(write, range_info->GetWritable()); + EXPECT_EQ(exec, range_info->GetExecutable()); +} + +TEST_F(MinidumpParserTest, GetMemoryRegionInfo) { + SetUpData("fizzbuzz_wow64.dmp"); + + const auto yes = MemoryRegionInfo::eYes; + const auto no = MemoryRegionInfo::eNo; + + check_region_info(parser, 0x00000, no, no, no); + check_region_info(parser, 0x10000, yes, yes, no); + check_region_info(parser, 0x20000, yes, yes, no); + check_region_info(parser, 0x30000, yes, yes, no); + check_region_info(parser, 0x31000, no, no, no); + check_region_info(parser, 0x40000, yes, no, no); +} + // Windows Minidump tests // fizzbuzz_no_heap.dmp is copied from the WinMiniDump tests TEST_F(MinidumpParserTest, GetArchitectureWindows) {