dvlahovski updated this revision to Diff 74694. dvlahovski marked 7 inline comments as done. dvlahovski added a comment.
Resolved Pavel's remarks. Also added a unit test for the GetMemory function. https://reviews.llvm.org/D25569 Files: source/Plugins/Process/minidump/MinidumpParser.cpp source/Plugins/Process/minidump/MinidumpParser.h source/Plugins/Process/minidump/MinidumpTypes.cpp source/Plugins/Process/minidump/MinidumpTypes.h unittests/Process/minidump/CMakeLists.txt unittests/Process/minidump/Inputs/fizzbuzz_wow64.dmp unittests/Process/minidump/Inputs/linux-x86_64_not_crashed.dmp unittests/Process/minidump/MinidumpParserTest.cpp
Index: unittests/Process/minidump/MinidumpParserTest.cpp =================================================================== --- unittests/Process/minidump/MinidumpParserTest.cpp +++ 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<uint8_t> context = parser->GetThreadContext(thread); + EXPECT_EQ(1232UL, context.size()); } TEST_F(MinidumpParserTest, GetThreadsTruncatedFile) { @@ -131,14 +136,111 @@ } } +TEST_F(MinidumpParserTest, GetFilteredModuleList) { + SetUpData("linux-x86_64_not_crashed.dmp"); + llvm::ArrayRef<MinidumpModule> modules = parser->GetModuleList(); + std::vector<const MinidumpModule *> 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<std::string> 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 = parser->GetExceptionStream(); ASSERT_NE(nullptr, exception_stream); ASSERT_EQ(11UL, exception_stream->exception_record.exception_code); } +void check_mem_range_exists(std::unique_ptr<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) { + 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_EQ(0UL, parser->GetMemory(0x500000, 512).size()); +} + +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<MinidumpParser> &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) { Index: unittests/Process/minidump/CMakeLists.txt =================================================================== --- unittests/Process/minidump/CMakeLists.txt +++ 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: source/Plugins/Process/minidump/MinidumpTypes.h =================================================================== --- source/Plugins/Process/minidump/MinidumpTypes.h +++ 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<MinidumpMemoryDescriptor> + ParseMemoryList(llvm::ArrayRef<uint8_t> &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<llvm::ArrayRef<MinidumpMemoryDescriptor64>, uint64_t> + ParseMemory64List(llvm::ArrayRef<uint8_t> &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,67 @@ "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<const MinidumpMemoryInfo *> + ParseMemoryInfoList(llvm::ArrayRef<uint8_t> &data); +}; +static_assert(sizeof(MinidumpMemoryInfo) == 48, + "sizeof MinidumpMemoryInfo is not correct!"); + +enum class MinidumpMemoryInfoState : uint32_t { + MemCommit = 0x1000, + MemFree = 0x10000, + MemReserve = 0x2000, +}; + +enum class MinidumpMemoryInfoType : uint32_t { + MemImage = 0x1000000, + MemMapped = 0x40000, + MemPrivate = 0x20000, +}; + +// 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, +}; + +// Reference: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680517(v=vs.85).aspx struct MinidumpThread { llvm::support::ulittle32_t thread_id; Index: source/Plugins/Process/minidump/MinidumpTypes.cpp =================================================================== --- source/Plugins/Process/minidump/MinidumpTypes.cpp +++ source/Plugins/Process/minidump/MinidumpTypes.cpp @@ -176,3 +176,62 @@ return exception_stream; } + +llvm::ArrayRef<MinidumpMemoryDescriptor> +MinidumpMemoryDescriptor::ParseMemoryList(llvm::ArrayRef<uint8_t> &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<const MinidumpMemoryDescriptor *>(data.data()), + *mem_ranges_count); +} + +std::pair<llvm::ArrayRef<MinidumpMemoryDescriptor64>, uint64_t> +MinidumpMemoryDescriptor64::ParseMemory64List(llvm::ArrayRef<uint8_t> &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<const MinidumpMemoryDescriptor64 *>(data.data()), + *mem_ranges_count), + *base_rva); +} + +std::vector<const MinidumpMemoryInfo *> +MinidumpMemoryInfo::ParseMemoryInfoList(llvm::ArrayRef<uint8_t> &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 {}; + + if (header->size_of_header > sizeof(MinidumpMemoryInfoListHeader)) { + data = data.drop_front(header->size_of_header - + sizeof(MinidumpMemoryInfoListHeader)); + } + + if (header->size_of_entry * header->num_of_entries > data.size()) + return {}; + + std::vector<const MinidumpMemoryInfo *> result; + for (uint64_t i = 0; i < header->num_of_entries; ++i) { + result.push_back(reinterpret_cast<const MinidumpMemoryInfo *>( + data.data() + i * header->size_of_entry)); + } + + return result; +} Index: source/Plugins/Process/minidump/MinidumpParser.h =================================================================== --- source/Plugins/Process/minidump/MinidumpParser.h +++ 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<uint8_t> range_ref; + + Range(lldb::addr_t start, llvm::ArrayRef<uint8_t> range_ref) + : start(start), range_ref(range_ref) {} +}; + class MinidumpParser { public: static llvm::Optional<MinidumpParser> @@ -47,6 +57,8 @@ llvm::ArrayRef<MinidumpThread> GetThreads(); + llvm::ArrayRef<uint8_t> GetThreadContext(const MinidumpThread &td); + const MinidumpSystemInfo *GetSystemInfo(); ArchSpec GetArchitecture(); @@ -59,8 +71,20 @@ llvm::ArrayRef<MinidumpModule> 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<const MinidumpModule *> GetFilteredModuleList(); + const MinidumpExceptionStream *GetExceptionStream(); + llvm::Optional<Range> FindMemoryRange(lldb::addr_t addr); + + llvm::ArrayRef<uint8_t> GetMemory(lldb::addr_t addr, size_t size); + + llvm::Optional<MemoryRegionInfo> GetMemoryRegionInfo(lldb::addr_t); + private: lldb::DataBufferSP m_data_sp; const MinidumpHeader *m_header; Index: source/Plugins/Process/minidump/MinidumpParser.cpp =================================================================== --- source/Plugins/Process/minidump/MinidumpParser.cpp +++ source/Plugins/Process/minidump/MinidumpParser.cpp @@ -11,8 +11,12 @@ #include "MinidumpParser.h" // Other libraries and framework includes +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Utility/LLDBAssert.h" + // C includes // C++ includes +#include <map> using namespace lldb_private; using namespace minidump; @@ -100,6 +104,14 @@ return MinidumpThread::ParseThreadList(data); } +llvm::ArrayRef<uint8_t> +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<uint8_t> data = GetStream(MinidumpStreamType::SystemInfo); @@ -216,11 +228,200 @@ return MinidumpModule::ParseModuleList(data); } +std::vector<const MinidumpModule *> MinidumpParser::GetFilteredModuleList() { + llvm::ArrayRef<MinidumpModule> modules = GetModuleList(); + // mapping module_name to pair(load_address, pointer to module struct in + // memory) + std::map<std::string, std::pair<uint64_t, const MinidumpModule *>> + lowest_addr; + std::vector<const MinidumpModule *> filtered_modules; + + llvm::Optional<std::string> 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.emplace(std::make_pair( + 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<uint8_t> data = GetStream(MinidumpStreamType::Exception); if (data.size() == 0) return nullptr; return MinidumpExceptionStream::Parse(data); } + +llvm::Optional<minidump::Range> +MinidumpParser::FindMemoryRange(lldb::addr_t addr) { + llvm::ArrayRef<uint8_t> data = GetStream(MinidumpStreamType::MemoryList); + llvm::ArrayRef<uint8_t> data64 = GetStream(MinidumpStreamType::Memory64List); + + if (data.size() == 0 && data64.size() == 0) + return llvm::None; + + if (data.size() > 0) { + llvm::ArrayRef<MinidumpMemoryDescriptor> memory_list = + MinidumpMemoryDescriptor::ParseMemoryList(data); + + if (memory_list.size() == 0) + return llvm::None; + + for (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.size() > 0) { + llvm::ArrayRef<MinidumpMemoryDescriptor64> memory64_list; + uint64_t base_rva; + std::tie(memory64_list, base_rva) = + MinidumpMemoryDescriptor64::ParseMemory64List(data64); + + if (memory64_list.size() == 0) + return llvm::None; + + for (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<uint8_t> 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<minidump::Range> 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<MemoryRegionInfo> +MinidumpParser::GetMemoryRegionInfo(lldb::addr_t load_addr) { + MemoryRegionInfo info; + llvm::ArrayRef<uint8_t> data = GetStream(MinidumpStreamType::MemoryInfoList); + if (data.size() == 0) + return llvm::None; + + std::vector<const MinidumpMemoryInfo *> mem_info_list = + MinidumpMemoryInfo::ParseMemoryInfoList(data); + if (mem_info_list.size() == 0) + return llvm::None; + + const auto yes = MemoryRegionInfo::eYes; + const auto no = MemoryRegionInfo::eNo; + + const MinidumpMemoryInfo *next_entry = nullptr; + for (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<uint32_t>(MinidumpMemoryProtectionContants::PageNoAccess); + info.SetReadable((entry->protect & PageNoAccess) == 0 ? yes : no); + + const uint32_t PageWritable = + static_cast<uint32_t>(MinidumpMemoryProtectionContants::PageWritable); + info.SetWritable((entry->protect & PageWritable) != 0 ? yes : no); + + const uint32_t PageExecutable = static_cast<uint32_t>( + MinidumpMemoryProtectionContants::PageExecutable); + info.SetExecutable((entry->protect & PageExecutable) != 0 ? yes : no); + + const uint32_t MemFree = + static_cast<uint32_t>(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; +}
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits