Improve LLDB's handling of non-local minidumps
authorLeonard Mosescu <mosescu@google.com>
Wed, 18 Apr 2018 23:10:46 +0000 (23:10 +0000)
committerLeonard Mosescu <mosescu@google.com>
Wed, 18 Apr 2018 23:10:46 +0000 (23:10 +0000)
Normally, LLDB is creating a high-fidelity representation of a live
process, including a list of modules and sections, with the
associated memory address ranges. In order to build the module and
section map LLDB tries to locate the local module image (object file)
and will parse it.

This does not work for postmortem debugging scenarios where the crash
dump (minidump in this case) was captured on a different machine.

Fortunately the minidump format encodes enough information about
each module's memory range to allow us to create placeholder modules.
This enables most LLDB functionality involving address-to-module
translations.

Also, we may want to completly disable the search for matching
local object files if we load minidumps unless we can prove that the
local image matches the one from the crash origin.
(not part of this change, see: llvm.org/pr35193)

Example: Identify the module from a stack frame PC:

Before:
  thread #1, stop reason = Exception 0xc0000005 encountered at address 0x164d14
    frame #0: 0x00164d14
    frame #1: 0x00167c79
    frame #2: 0x00167e6d
    frame #3: 0x7510336a
    frame #4: 0x77759882
    frame #5: 0x77759855

After:
  thread #1, stop reason = Exception 0xc0000005 encountered at address 0x164d14
    frame #0: 0x00164d14 C:\Users\amccarth\Documents\Visual Studio 2013\Projects\fizzbuzz\Debug\fizzbuzz.exe
    frame #1: 0x00167c79 C:\Users\amccarth\Documents\Visual Studio 2013\Projects\fizzbuzz\Debug\fizzbuzz.exe
    frame #2: 0x00167e6d C:\Users\amccarth\Documents\Visual Studio 2013\Projects\fizzbuzz\Debug\fizzbuzz.exe
    frame #3: 0x7510336a C:\Windows\SysWOW64\kernel32.dll
    frame #4: 0x77759882 C:\Windows\SysWOW64\ntdll.dll
    frame #5: 0x77759855 C:\Windows\SysWOW64\ntdll.dll

Example: target modules list

Before:
error: the target has no associated executable images

After:
[ 0] C:\Windows\System32\MSVCP120D.dll
[ 1] C:\Windows\SysWOW64\kernel32.dll
[ 2] C:\Users\amccarth\Documents\Visual Studio 2013\Projects\fizzbuzz\Debug\fizzbuzz.exe
[ 3] C:\Windows\System32\MSVCR120D.dll
[ 4] C:\Windows\SysWOW64\KERNELBASE.dll
[ 5] C:\Windows\SysWOW64\ntdll.dll

NOTE: the minidump format also includes the debug info GUID, so we can
fill-in the module UUID from it, but this part was excluded from this change
to keep the changes simple (the LLDB UUID is hardcoded to be either 16 or
20 bytes, while the CodeView GUIDs are normally 24 bytes)

Differential Revision: https://reviews.llvm.org/D45700

llvm-svn: 330302

lldb/packages/Python/lldbsuite/test/functionalities/postmortem/minidump-new/TestMiniDumpNew.py
lldb/packages/Python/lldbsuite/test/functionalities/postmortem/minidump/TestMiniDump.py
lldb/source/Commands/CommandObjectTarget.cpp
lldb/source/Core/Module.cpp
lldb/source/Core/Section.cpp
lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
lldb/source/Plugins/Process/minidump/ProcessMinidump.h

index 18c4c348aa41e1fd0397eff5ec0ec3396679ec3f..4cf4a9052d4477920a94fa00a5e8e713fe8ade0a 100644 (file)
@@ -70,6 +70,17 @@ class MiniDumpNewTestCase(TestBase):
         self.assertEqual(self.process.GetProcessID(), self._linux_x86_64_pid)
         self.check_state()
 
+    def test_modules_in_mini_dump(self):
+        """Test that lldb can read the list of modules from the minidump."""
+        # target create -c linux-x86_64.dmp
+        self.dbg.CreateTarget(None)
+        self.target = self.dbg.GetSelectedTarget()
+        self.process = self.target.LoadCore("linux-x86_64.dmp")
+        self.assertTrue(self.process, PROCESS_IS_VALID)
+        self.assertEqual(self.target.GetNumModules(), 9)
+        for module in self.target.modules:
+            self.assertTrue(module.IsValid())
+
     def test_thread_info_in_minidump(self):
         """Test that lldb can read the thread information from the Minidump."""
         # target create -c linux-x86_64.dmp
@@ -100,6 +111,7 @@ class MiniDumpNewTestCase(TestBase):
         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())
index 49ff0319746fe46cbb521cdb1733cccd0874ed17..cc677e6dcde4a2ba94c580d4de31f27ce664a3c1 100644 (file)
@@ -41,6 +41,26 @@ class MiniDumpTestCase(TestBase):
         stop_description = thread.GetStopDescription(256)
         self.assertTrue("0xc0000005" in stop_description)
 
+    def test_modules_in_mini_dump(self):
+        """Test that lldb can read the list of modules from the minidump."""
+        # target create -c fizzbuzz_no_heap.dmp
+        self.dbg.CreateTarget("")
+        self.target = self.dbg.GetSelectedTarget()
+        self.process = self.target.LoadCore("fizzbuzz_no_heap.dmp")
+        self.assertTrue(self.process, PROCESS_IS_VALID)
+        expected_modules = [
+            r"C:\Windows\System32\MSVCP120D.dll",
+            r"C:\Windows\SysWOW64\kernel32.dll",
+            r"C:\Users\amccarth\Documents\Visual Studio 2013\Projects\fizzbuzz\Debug\fizzbuzz.exe",
+            r"C:\Windows\System32\MSVCR120D.dll",
+            r"C:\Windows\SysWOW64\KERNELBASE.dll",
+            r"C:\Windows\SysWOW64\ntdll.dll",
+        ]
+        self.assertEqual(self.target.GetNumModules(), len(expected_modules))
+        for module, expected in zip(self.target.modules, expected_modules):
+            self.assertTrue(module.IsValid())
+            self.assertEqual(module.file.fullpath, expected)
+
     @expectedFailureAll(bugnumber="llvm.org/pr35193", hostoslist=["windows"])
     def test_stack_info_in_mini_dump(self):
         """Test that we can see a trivial stack in a VS-generate mini dump."""
@@ -58,6 +78,7 @@ class MiniDumpTestCase(TestBase):
             frame = thread.GetFrameAtIndex(i)
             self.assertTrue(frame.IsValid())
             self.assertEqual(frame.GetPC(), pc_list[i])
+            self.assertTrue(frame.GetModule().IsValid())
 
     @skipUnlessWindows # Minidump saving works only on windows
     def test_deeper_stack_in_mini_dump(self):
index 69eeae137dad4b94974757772ab77e70eea20749..12d322bd69e3e4a2eea25af0a2fe973b2a03b3f6 100644 (file)
@@ -1388,7 +1388,8 @@ static size_t DumpModuleObjfileHeaders(Stream &strm, ModuleList &module_list) {
           strm.EOL();
         }
         ObjectFile *objfile = module->GetObjectFile();
-        objfile->Dump(&strm);
+        if (objfile)
+          objfile->Dump(&strm);
       }
     }
     strm.IndentLess();
index 35be2949618c3767e0b09dc4c9a18286d17fa211..a86a642ae8acaca517e8a541788d809b73f202d2 100644 (file)
@@ -1278,7 +1278,7 @@ ObjectFile *Module::GetObjectFile() {
 }
 
 SectionList *Module::GetSectionList() {
-  // Populate m_unified_sections_ap with sections from objfile.
+  // Populate m_sections_ap with sections from objfile.
   if (!m_sections_ap) {
     ObjectFile *obj_file = GetObjectFile();
     if (obj_file != nullptr)
@@ -1297,7 +1297,6 @@ void Module::SectionFileAddressesChanged() {
 }
 
 SectionList *Module::GetUnifiedSectionList() {
-  // Populate m_unified_sections_ap with sections from objfile.
   if (!m_sections_ap)
     m_sections_ap = llvm::make_unique<SectionList>();
   return m_sections_ap.get();
index c9faf9f891571a498dc66cbbd728445b81457146..635b32186739d8ac6ab1ffe95c036b8724da9eff 100644 (file)
@@ -326,10 +326,11 @@ void Section::DumpName(Stream *s) const {
     // The top most section prints the module basename
     const char *name = NULL;
     ModuleSP module_sp(GetModule());
-    const FileSpec &file_spec = m_obj_file->GetFileSpec();
 
-    if (m_obj_file)
+    if (m_obj_file) {
+      const FileSpec &file_spec = m_obj_file->GetFileSpec();
       name = file_spec.GetFilename().AsCString();
+    }
     if ((!name || !name[0]) && module_sp)
       name = module_sp->GetFileSpec().GetFilename().AsCString();
     if (name && name[0])
index d4d65c044eabeec48292b2621885ef157212f9f1..6bfe0e759e9d5f8c3614a084f756a4f09920cec5 100644 (file)
@@ -19,6 +19,7 @@
 #include "lldb/Core/State.h"
 #include "lldb/Target/DynamicLoader.h"
 #include "lldb/Target/MemoryRegionInfo.h"
+#include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Target/UnixSignals.h"
 #include "lldb/Utility/DataBufferLLVM.h"
 // C includes
 // C++ includes
 
+using namespace lldb;
 using namespace lldb_private;
 using namespace minidump;
 
+//------------------------------------------------------------------
+/// A placeholder module used for minidumps, where the original
+/// object files may not be available (so we can't parse the object
+/// files to extract the set of sections/segments)
+///
+/// This placeholder module has a single synthetic section (.module_image)
+/// which represents the module memory range covering the whole module.
+//------------------------------------------------------------------
+class PlaceholderModule : public Module {
+public:
+  PlaceholderModule(const FileSpec &file_spec, const ArchSpec &arch) :
+    Module(file_spec, arch) {}
+
+  // Creates a synthetic module section covering the whole module image
+  // (and sets the section load address as well)
+  void CreateImageSection(const MinidumpModule *module, Target& target) {
+    const ConstString section_name(".module_image");
+    lldb::SectionSP section_sp(new Section(
+        shared_from_this(),     // Module to which this section belongs.
+        nullptr,                // ObjectFile
+        0,                      // Section ID.
+        section_name,           // Section name.
+        eSectionTypeContainer,  // Section type.
+        module->base_of_image,  // VM address.
+        module->size_of_image,  // VM size in bytes of this section.
+        0,                      // Offset of this section in the file.
+        module->size_of_image,  // Size of the section as found in the file.
+        12,                     // Alignment of the section (log2)
+        0,                      // Flags for this section.
+        1));                    // Number of host bytes per target byte
+    section_sp->SetPermissions(ePermissionsExecutable | ePermissionsReadable);
+    GetSectionList()->AddSection(section_sp);
+    target.GetSectionLoadList().SetSectionLoadAddress(
+        section_sp, module->base_of_image);
+  }
+
+  ObjectFile *GetObjectFile() override { return nullptr; }
+
+  SectionList *GetSectionList() override {
+    return Module::GetUnifiedSectionList();
+  }
+};
+
 ConstString ProcessMinidump::GetPluginNameStatic() {
   static ConstString g_name("minidump");
   return g_name;
@@ -281,7 +326,18 @@ void ProcessMinidump::ReadModuleList() {
     Status error;
     lldb::ModuleSP module_sp = GetTarget().GetSharedModule(module_spec, &error);
     if (!module_sp || error.Fail()) {
-      continue;
+      // We failed to locate a matching local object file. Fortunately,
+      // the minidump format encodes enough information about each module's
+      // memory range to allow us to create placeholder modules.
+      //
+      // This enables most LLDB functionality involving address-to-module
+      // translations (ex. identifing the module for a stack frame PC) and
+      // modules/sections commands (ex. target modules list, ...)
+      auto placeholder_module =
+          std::make_shared<PlaceholderModule>(file_spec, GetArchitecture());
+      placeholder_module->CreateImageSection(module, GetTarget());
+      module_sp = placeholder_module;
+      GetTarget().GetImages().Append(module_sp);
     }
 
     if (log) {
index 4b91d1ba396a4dee5aa4d942a2fd7537ad6c42f9..d65ada9009a7e225e3c4982c788088388fba1d3d 100644 (file)
@@ -61,6 +61,8 @@ public:
 
   uint32_t GetPluginVersion() override;
 
+  SystemRuntime *GetSystemRuntime() override { return nullptr; }
+
   Status DoDestroy() override;
 
   void RefreshStateAfterStop() override;