Aj0SK created this revision.
Aj0SK added a reviewer: clayborg.
Herald added subscribers: pengfei, mgorny, emaste.
Aj0SK requested review of this revision.
Herald added subscribers: lldb-commits, MaskRay.
Herald added a project: LLDB.

This change adds save-core functionality into the ObjectFileELF that enables 
saving minidump of a stopped process. This change is mainly targeting Linux
running on x86_64 machines. Minidump should contain basic information needed
to examine state of threads, local variables and stack traces. Full support
for other platforms is not so far implemented. API tests are using LLDB's
MinidumpParser.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D108233

Files:
  lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt
  lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.cpp
  lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.h
  lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
  lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
  lldb/test/API/functionalities/process_save_core_minidump/Makefile
  
lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
  lldb/test/API/functionalities/process_save_core_minidump/main.cpp

Index: lldb/test/API/functionalities/process_save_core_minidump/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/process_save_core_minidump/main.cpp
@@ -0,0 +1,30 @@
+#include <cassert>
+#include <iostream>
+#include <thread>
+
+using namespace std;
+
+void g() { assert(false); }
+
+void f() { g(); }
+
+size_t h() {
+  size_t sum = 0;
+  for (size_t i = 0; i < 1000000; ++i)
+    for (size_t j = 0; j < 1000000; ++j)
+      if ((i * j) % 2 == 0) {
+        sum += 1;
+      }
+  return sum;
+}
+
+int main() {
+  thread t1(f);
+
+  size_t x = h();
+
+  t1.join();
+
+  cout << "X is " << x << "\n";
+  return 0;
+}
\ No newline at end of file
Index: lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
@@ -0,0 +1,78 @@
+"""
+Test saving a mini dump.
+"""
+
+
+import os
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class ProcessSaveCoreMinidumpTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @skipUnlessPlatform(['linux'])
+    def test_save_linux_mini_dump(self):
+        """Test that we can save a Linux mini dump."""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        core = self.getBuildArtifact("core.dmp")
+        try:
+            target = self.dbg.CreateTarget(exe)
+            process = target.LaunchSimple(
+                None, None, self.get_process_working_directory())
+            self.assertEqual(process.GetState(), lldb.eStateStopped)
+
+            # get neccessary data for the verification phase
+            process_info = process.GetProcessInfo()
+            expected_pid = process_info.GetProcessID() if process_info.IsValid() else -1
+            expected_number_of_modules = target.GetNumModules()
+            expected_modules = target.modules
+            expected_number_of_threads = process.GetNumThreads()
+            expected_threads = []
+
+            for thread_idx in range(process.GetNumThreads()):
+                thread = process.GetThreadAtIndex(thread_idx)
+                thread_id = thread.GetThreadID()
+                expected_threads.append(thread_id)
+
+            # save core and, kill process and verify corefile existence
+            self.assertTrue(process.SaveCore(core))
+            self.assertTrue(os.path.isfile(core))
+            self.assertTrue(process.Kill().Success())
+
+            # To verify, we'll launch with the mini dump
+            target = self.dbg.CreateTarget(None)
+            process = target.LoadCore(core)
+
+            # check if the core is in desired state
+            self.assertTrue(process, PROCESS_IS_VALID)
+            self.assertTrue(process.GetProcessInfo().IsValid())
+            self.assertEqual(process.GetProcessInfo().GetProcessID(), expected_pid)
+            self.assertTrue(target.GetTriple().find("linux") != -1)
+            self.assertTrue(target.GetNumModules(), expected_number_of_modules)
+            self.assertEqual(process.GetNumThreads(), expected_number_of_threads)
+
+            for module, expected in zip(target.modules, expected_modules):
+                self.assertTrue(module.IsValid())
+                module_file_name = module.GetFileSpec().GetFilename()
+                expected_file_name = expected.GetFileSpec().GetFilename()
+                # skip kernel virtual dynamic shared objects
+                if "vdso" in expected_file_name:
+                    continue
+                self.assertEqual(module_file_name, expected_file_name)
+                self.assertEqual(module.GetUUIDString(), expected.GetUUIDString())
+
+            for thread_idx in range(process.GetNumThreads()):
+                thread = process.GetThreadAtIndex(thread_idx)
+                self.assertTrue(thread.IsValid())
+                thread_id = thread.GetThreadID()
+                self.assertTrue(thread_id in expected_threads)
+        finally:
+            # Clean up the mini dump file.
+            self.assertTrue(self.dbg.DeleteTarget(target))
+            if (os.path.isfile(core)):
+                os.unlink(core)
Index: lldb/test/API/functionalities/process_save_core_minidump/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/process_save_core_minidump/Makefile
@@ -0,0 +1,6 @@
+CXX_SOURCES := main.cpp
+
+CFLAGS_EXTRAS := -lpthread
+
+include Makefile.rules
+
Index: lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
===================================================================
--- lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
+++ lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
@@ -74,6 +74,12 @@
       const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp,
       const lldb::ProcessSP &process_sp, lldb::addr_t header_addr);
 
+  // Saves dump in Minidump file format
+  static bool SaveCore(const lldb::ProcessSP &process_sp,
+                       const lldb_private::FileSpec &outfile,
+                       lldb::SaveCoreStyle &core_style,
+                       lldb_private::Status &error);
+
   static size_t GetModuleSpecifications(const lldb_private::FileSpec &file,
                                         lldb::DataBufferSP &data_sp,
                                         lldb::offset_t data_offset,
@@ -155,7 +161,6 @@
   void RelocateSection(lldb_private::Section *section) override;
 
 protected:
-
   std::vector<LoadableData>
   GetLoadableData(lldb_private::Target &target) override;
 
Index: lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
===================================================================
--- lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
+++ lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp
@@ -8,6 +8,8 @@
 
 #include "ObjectFileELF.h"
 
+#include "MinidumpFileBuilder.h"
+
 #include <algorithm>
 #include <cassert>
 #include <unordered_map>
@@ -22,6 +24,7 @@
 #include "lldb/Host/LZMA.h"
 #include "lldb/Symbol/DWARFCallFrameInfo.h"
 #include "lldb/Symbol/SymbolContext.h"
+#include "lldb/Target/Process.h"
 #include "lldb/Target/SectionLoadList.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/ArchSpec.h"
@@ -38,6 +41,7 @@
 #include "llvm/Object/Decompressor.h"
 #include "llvm/Support/ARMBuildAttributes.h"
 #include "llvm/Support/CRC.h"
+#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/MemoryBuffer.h"
@@ -326,9 +330,9 @@
 
 // Static methods.
 void ObjectFileELF::Initialize() {
-  PluginManager::RegisterPlugin(GetPluginNameStatic(),
-                                GetPluginDescriptionStatic(), CreateInstance,
-                                CreateMemoryInstance, GetModuleSpecifications);
+  PluginManager::RegisterPlugin(
+      GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
+      CreateMemoryInstance, GetModuleSpecifications, SaveCore);
 }
 
 void ObjectFileELF::Terminate() {
@@ -707,6 +711,61 @@
   return false;
 }
 
+bool ObjectFileELF::SaveCore(const lldb::ProcessSP &process_sp,
+                             const FileSpec &outfile,
+                             lldb::SaveCoreStyle &core_style, Status &error) {
+  if (!process_sp)
+    return false;
+
+  MinidumpFileBuilder builder;
+
+  Target &target = process_sp->GetTarget();
+
+  error = builder.AddSystemInfo(target.GetArchitecture().GetTriple());
+  if (error.Fail()) {
+    return false;
+  }
+
+  error = builder.AddModuleList(target);
+  if (error.Fail()) {
+    return false;
+  }
+
+  builder.AddMiscInfo(process_sp);
+
+  if (target.GetArchitecture().GetMachine() == llvm::Triple::ArchType::x86_64) {
+    error = builder.AddThreadList(process_sp);
+    if (error.Fail()) {
+      return false;
+    }
+
+    error = builder.AddException(process_sp);
+    if (error.Fail()) {
+      return false;
+    }
+
+    error = builder.AddMemoryList(process_sp);
+    if (error.Fail()) {
+      return false;
+    }
+  }
+
+  llvm::Expected<lldb::FileUP> maybe_core_file = FileSystem::Instance().Open(
+      outfile, File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate);
+  if (!maybe_core_file) {
+    error = maybe_core_file.takeError();
+    return false;
+  }
+  lldb::FileUP core_file = std::move(maybe_core_file.get());
+
+  error = builder.Dump(core_file);
+  if (error.Fail()) {
+    return false;
+  }
+
+  return true;
+}
+
 ByteOrder ObjectFileELF::GetByteOrder() const {
   if (m_header.e_ident[EI_DATA] == ELFDATA2MSB)
     return eByteOrderBig;
Index: lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.h
===================================================================
--- /dev/null
+++ lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.h
@@ -0,0 +1,91 @@
+//===-- MinidumpFileBuilder.h ---------------------------------- -*- C++
+//-*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Structure holding data neccessary for minidump file creation.
+///
+/// The class MinidumpFileWriter is used to hold the data that will eventually
+/// be dumped to the file.
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_MinidumpFileBuilder_h_
+#define liblldb_MinidumpFileBuilder_h_
+
+#include <cstddef>
+
+#include "lldb/Target/Target.h"
+#include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/Status.h"
+
+#include "llvm/Object/Minidump.h"
+
+// Write std::string to minidump in the UTF16 format(with null termination char)
+// with the size(without null termination char) preceding the UTF16 string.
+// Empty strings are also printed with zero length and just null termination
+// char.
+lldb_private::Status WriteString(const std::string &to_write,
+                                 lldb_private::DataBufferHeap *buffer);
+
+/// \class MinidumpFileBuilder
+/// Minidump writer for Linux
+///
+/// This class provides a Minidump writer that is able to
+/// snapshot the current process state. For the whole time, it stores all
+/// the data on heap.
+class MinidumpFileBuilder {
+public:
+  MinidumpFileBuilder() = default;
+
+  MinidumpFileBuilder(const MinidumpFileBuilder &) = delete;
+  MinidumpFileBuilder &operator=(const MinidumpFileBuilder &) = delete;
+
+  MinidumpFileBuilder(MinidumpFileBuilder &&other) = default;
+  MinidumpFileBuilder &operator=(MinidumpFileBuilder &&other) = default;
+
+  ~MinidumpFileBuilder() = default;
+
+  // Add SystemInfo stream, used for storing the most basic information
+  // about the system, platform etc...
+  lldb_private::Status AddSystemInfo(const llvm::Triple &target_triple);
+  // Add ModuleList stream, containing information about all loaded modules
+  // at the time of saving minidump.
+  lldb_private::Status AddModuleList(lldb_private::Target &target);
+  // Add ThreadList stream, containing information about all threads running
+  // at the moment of core saving. Contains information about thread
+  // contexts.
+  lldb_private::Status AddThreadList(const lldb::ProcessSP &process_sp);
+  // Add Exception stream, this contains information about the exception
+  // that stopped the process. In case no thread made exception it return
+  // failed status.
+  lldb_private::Status AddException(const lldb::ProcessSP &process_sp);
+  // Add MemoryList stream, containing dumps of important memory segments
+  lldb_private::Status AddMemoryList(const lldb::ProcessSP &process_sp);
+  // Add MiscInfo stream, mainly providing ProcessId
+  void AddMiscInfo(const lldb::ProcessSP &process_sp);
+  // Dump the prepared data into file. In case of the failure data are
+  // intact.
+  lldb_private::Status Dump(lldb::FileUP &core_file) const;
+  // Returns the current number of directories(streams) that have been so far
+  // created. This number of directories will be dumped when calling Dump()
+  size_t GetDirectoriesNum() const;
+
+private:
+  // Add directory of StreamType pointing to the current end of the prepared
+  // file with the specified size.
+  void AddDirectory(llvm::minidump::StreamType type, size_t stream_size);
+  size_t GetCurrentDataEndOffset() const;
+
+  // Stores directories to later put them at the end of minidump file
+  std::vector<llvm::minidump::Directory> m_directories;
+  // Main data buffer consisting of data without the minidump header and
+  // directories
+  lldb_private::DataBufferHeap m_data;
+};
+
+#endif // liblldb_MinidumpFileBuilder_h_
\ No newline at end of file
Index: lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.cpp
===================================================================
--- /dev/null
+++ lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.cpp
@@ -0,0 +1,672 @@
+//===-- MinidumpFileBuilder.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 "MinidumpFileBuilder.h"
+
+#include "Plugins/Process/minidump/RegisterContextMinidump_x86_64.h"
+
+#include "lldb/Core/Module.h"
+#include "lldb/Core/ModuleList.h"
+#include "lldb/Target/MemoryRegionInfo.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/StopInfo.h"
+#include "lldb/Target/ThreadList.h"
+#include "lldb/Utility/DataExtractor.h"
+#include "lldb/Utility/RegisterValue.h"
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/BinaryFormat/Minidump.h"
+#include "llvm/Support/ConvertUTF.h"
+#include "llvm/Support/Error.h"
+
+#include "Plugins/Process/minidump/MinidumpTypes.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace llvm::minidump;
+
+void MinidumpFileBuilder::AddDirectory(StreamType type, size_t stream_size) {
+  LocationDescriptor loc;
+  loc.DataSize = static_cast<llvm::support::ulittle32_t>(stream_size);
+  // Stream will begin at the current end of data section
+  loc.RVA = static_cast<llvm::support::ulittle32_t>(GetCurrentDataEndOffset());
+
+  Directory dir;
+  dir.Type = static_cast<llvm::support::little_t<StreamType>>(type);
+  dir.Location = loc;
+
+  m_directories.push_back(dir);
+}
+
+Status MinidumpFileBuilder::AddSystemInfo(const llvm::Triple &target_triple) {
+  AddDirectory(StreamType::SystemInfo, sizeof(llvm::minidump::SystemInfo));
+
+  llvm::minidump::ProcessorArchitecture arch;
+  switch (target_triple.getArch()) {
+  case llvm::Triple::ArchType::x86:
+    arch = ProcessorArchitecture::X86;
+    break;
+  case llvm::Triple::ArchType::mips64:
+  case llvm::Triple::ArchType::mips64el:
+  case llvm::Triple::ArchType::mips:
+  case llvm::Triple::ArchType::mipsel:
+    arch = ProcessorArchitecture::MIPS;
+    break;
+  case llvm::Triple::ArchType::ppc64:
+  case llvm::Triple::ArchType::ppc:
+  case llvm::Triple::ArchType::ppc64le:
+    arch = ProcessorArchitecture::PPC;
+    break;
+  case llvm::Triple::ArchType::arm:
+    arch = ProcessorArchitecture::ARM;
+    break;
+  default:
+    arch = ProcessorArchitecture::AMD64;
+    break;
+  };
+
+  llvm::minidump::SystemInfo sys_info;
+  sys_info.ProcessorArch =
+      static_cast<llvm::support::little_t<ProcessorArchitecture>>(arch);
+  // Global offset to beginning of a csd_string in a data section
+  sys_info.CSDVersionRVA = static_cast<llvm::support::ulittle32_t>(
+      GetCurrentDataEndOffset() + sizeof(llvm::minidump::SystemInfo));
+  sys_info.PlatformId = OSPlatform::Linux;
+  m_data.AppendData(&sys_info, sizeof(llvm::minidump::SystemInfo));
+
+  std::string csd_string = "";
+
+  Status error = WriteString(csd_string, &m_data);
+  if (error.Fail()) {
+    error.SetErrorString("Unable to convert the csd string to UTF16.");
+    return error;
+  }
+
+  return Status();
+}
+
+Status WriteString(const std::string &to_write,
+                   lldb_private::DataBufferHeap *buffer) {
+  Status error;
+  // let the StringRef eat also null termination char
+  llvm::StringRef to_write_ref(to_write.c_str(), to_write.size() + 1);
+  llvm::SmallVector<llvm::UTF16, 128> to_write_utf16;
+
+  bool converted = convertUTF8ToUTF16String(to_write_ref, to_write_utf16);
+  if (!converted) {
+    error.SetErrorStringWithFormat(
+        "Unable to convert the string to UTF16. Failed to convert %s",
+        to_write.c_str());
+    return error;
+  }
+
+  // size of the UTF16 string should be written without the null termination
+  // character that is stored in 2 bytes
+  llvm::support::ulittle32_t to_write_size(to_write_utf16.size_in_bytes() - 2);
+
+  buffer->AppendData(&to_write_size, sizeof(llvm::support::ulittle32_t));
+  buffer->AppendData(to_write_utf16.data(), to_write_utf16.size_in_bytes());
+
+  return error;
+}
+
+// ModuleList stream consists of a number of modules, followed by an array
+// of llvm::minidump::Module's structures. Every structure informs about a
+// single module. Additional data of variable length, such as module's names,
+// are stored just after the ModuleList stream. The llvm::minidump::Module
+// structures point to this helper data by global offset.
+Status MinidumpFileBuilder::AddModuleList(Target &target) {
+  constexpr size_t minidump_module_size = sizeof(llvm::minidump::Module);
+  Status error;
+
+  const ModuleList &modules = target.GetImages();
+  llvm::support::ulittle32_t modules_count =
+      static_cast<llvm::support::ulittle32_t>(modules.GetSize());
+
+  // This helps us with getting the correct global offset in minidump
+  // file later, when we will be setting up offsets from the
+  // the llvm::minidump::Module's structures into helper data
+  size_t size_before = GetCurrentDataEndOffset();
+
+  // This is the size of the main part of the ModuleList stream.
+  // It consists of a module number and corresponding number of
+  // structs describing individual modules
+  size_t module_stream_size =
+      sizeof(llvm::support::ulittle32_t) + modules_count * minidump_module_size;
+
+  // Adding directory describing this stream.
+  AddDirectory(StreamType::ModuleList, module_stream_size);
+
+  m_data.AppendData(&modules_count, sizeof(llvm::support::ulittle32_t));
+
+  // Temporary storage for the helper data (of variable length)
+  // as these cannot be dumped to m_data before dumping entire
+  // array of module structures.
+  DataBufferHeap helper_data;
+
+  for (size_t i = 0; i < modules_count; ++i) {
+    ModuleSP mod = modules.GetModuleAtIndex(i);
+    std::string module_name = mod->GetSpecificationDescription();
+
+    llvm::support::ulittle32_t signature =
+        static_cast<llvm::support::ulittle32_t>(
+            static_cast<uint32_t>(minidump::CvSignature::ElfBuildId));
+    auto uuid = mod->GetUUID().GetBytes();
+
+    VSFixedFileInfo info;
+    info.Signature = static_cast<llvm::support::ulittle32_t>(0u);
+    info.StructVersion = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileVersionHigh = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileVersionLow = static_cast<llvm::support::ulittle32_t>(0u);
+    info.ProductVersionHigh = static_cast<llvm::support::ulittle32_t>(0u);
+    info.ProductVersionLow = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileFlagsMask = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileFlags = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileOS = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileType = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileSubtype = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileDateHigh = static_cast<llvm::support::ulittle32_t>(0u);
+    info.FileDateLow = static_cast<llvm::support::ulittle32_t>(0u);
+
+    LocationDescriptor ld;
+    ld.DataSize = static_cast<llvm::support::ulittle32_t>(0u);
+    ld.RVA = static_cast<llvm::support::ulittle32_t>(0u);
+
+    // Setting up LocationDescriptor for uuid string. The global offset into
+    // minidump file is calculated.
+    LocationDescriptor ld_cv;
+    ld_cv.DataSize = static_cast<llvm::support::ulittle32_t>(
+        sizeof(llvm::support::ulittle32_t) + uuid.size());
+    ld_cv.RVA = static_cast<llvm::support::ulittle32_t>(
+        size_before + module_stream_size + helper_data.GetByteSize());
+
+    helper_data.AppendData(&signature, sizeof(llvm::support::ulittle32_t));
+    helper_data.AppendData(uuid.begin(), uuid.size());
+
+    llvm::minidump::Module m;
+    m.BaseOfImage = static_cast<llvm::support::ulittle64_t>(
+        mod->GetObjectFile()->GetBaseAddress().GetLoadAddress(&target));
+    m.SizeOfImage = static_cast<llvm::support::ulittle32_t>(
+        mod->GetObjectFile()->GetByteSize());
+    m.Checksum = static_cast<llvm::support::ulittle32_t>(0);
+    m.TimeDateStamp = static_cast<llvm::support::ulittle32_t>(std::time(0));
+    m.ModuleNameRVA = static_cast<llvm::support::ulittle32_t>(
+        size_before + module_stream_size + helper_data.GetByteSize());
+    m.VersionInfo = info;
+    m.CvRecord = ld_cv;
+    m.MiscRecord = ld;
+
+    error = WriteString(module_name, &helper_data);
+
+    if (error.Fail()) {
+      return error;
+    }
+
+    m_data.AppendData(&m, sizeof(llvm::minidump::Module));
+  }
+
+  m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize());
+  return error;
+}
+
+uint16_t read_register_u16_raw(RegisterContext *reg_ctx,
+                               const std::string &reg_name) {
+  const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
+  if (!reg_info) {
+    return 0;
+  }
+  lldb_private::RegisterValue reg_value;
+  bool success = reg_ctx->ReadRegister(reg_info, reg_value);
+  if (!success)
+    return 0;
+  return reg_value.GetAsUInt16();
+}
+
+uint32_t read_register_u32_raw(RegisterContext *reg_ctx,
+                               const std::string &reg_name) {
+  const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
+  if (!reg_info) {
+    return 0;
+  }
+  lldb_private::RegisterValue reg_value;
+  bool success = reg_ctx->ReadRegister(reg_info, reg_value);
+  if (!success)
+    return 0;
+  return reg_value.GetAsUInt32();
+}
+
+uint64_t read_register_u64_raw(RegisterContext *reg_ctx,
+                               const std::string &reg_name) {
+  const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
+  if (!reg_info) {
+    return 0;
+  }
+  lldb_private::RegisterValue reg_value;
+  bool success = reg_ctx->ReadRegister(reg_info, reg_value);
+  if (!success)
+    return 0;
+  return reg_value.GetAsUInt64();
+}
+
+llvm::support::ulittle16_t read_register_u16(RegisterContext *reg_ctx,
+                                             const std::string &reg_name) {
+  return static_cast<llvm::support::ulittle16_t>(
+      read_register_u16_raw(reg_ctx, reg_name));
+}
+
+llvm::support::ulittle32_t read_register_u32(RegisterContext *reg_ctx,
+                                             const std::string &reg_name) {
+  return static_cast<llvm::support::ulittle32_t>(
+      read_register_u32_raw(reg_ctx, reg_name));
+}
+
+llvm::support::ulittle64_t read_register_u64(RegisterContext *reg_ctx,
+                                             const std::string &reg_name) {
+  return static_cast<llvm::support::ulittle64_t>(
+      read_register_u64_raw(reg_ctx, reg_name));
+}
+
+lldb_private::minidump::MinidumpContext_x86_64
+GetThreadContext_64(RegisterContext *reg_ctx) {
+  lldb_private::minidump::MinidumpContext_x86_64 thread_context;
+  thread_context.context_flags = static_cast<uint32_t>(
+      lldb_private::minidump::MinidumpContext_x86_64_Flags::x86_64_Flag |
+      lldb_private::minidump::MinidumpContext_x86_64_Flags::Control |
+      lldb_private::minidump::MinidumpContext_x86_64_Flags::Segments |
+      lldb_private::minidump::MinidumpContext_x86_64_Flags::Integer);
+  thread_context.rax = read_register_u64(reg_ctx, "rax");
+  thread_context.rbx = read_register_u64(reg_ctx, "rbx");
+  thread_context.rcx = read_register_u64(reg_ctx, "rcx");
+  thread_context.rdx = read_register_u64(reg_ctx, "rdx");
+  thread_context.rdi = read_register_u64(reg_ctx, "rdi");
+  thread_context.rsi = read_register_u64(reg_ctx, "rsi");
+  thread_context.rbp = read_register_u64(reg_ctx, "rbp");
+  thread_context.rsp = read_register_u64(reg_ctx, "rsp");
+  thread_context.r8 = read_register_u64(reg_ctx, "r8");
+  thread_context.r9 = read_register_u64(reg_ctx, "r9");
+  thread_context.r10 = read_register_u64(reg_ctx, "r10");
+  thread_context.r11 = read_register_u64(reg_ctx, "r11");
+  thread_context.r12 = read_register_u64(reg_ctx, "r12");
+  thread_context.r13 = read_register_u64(reg_ctx, "r13");
+  thread_context.r14 = read_register_u64(reg_ctx, "r14");
+  thread_context.r15 = read_register_u64(reg_ctx, "r15");
+  thread_context.rip = read_register_u64(reg_ctx, "rip");
+  thread_context.eflags = read_register_u32(reg_ctx, "rflags");
+  thread_context.cs = read_register_u16(reg_ctx, "cs");
+  thread_context.fs = read_register_u16(reg_ctx, "fs");
+  thread_context.gs = read_register_u16(reg_ctx, "gs");
+  thread_context.ss = read_register_u16(reg_ctx, "ss");
+  thread_context.ds = read_register_u16(reg_ctx, "ds");
+  return thread_context;
+}
+
+// Function returns start and size of the memory region that contains
+// memory location pointed to by the current stack pointer.
+llvm::Expected<std::pair<addr_t, addr_t>>
+findStackHelper(const lldb::ProcessSP &process_sp, uint64_t rsp) {
+  MemoryRegionInfo range_info;
+  Status range_error = process_sp->GetMemoryRegionInfo(0, range_info);
+
+  if (range_error.Fail()) {
+    return llvm::createStringError(
+        std::errc::not_supported,
+        "process doesn't support getting memory region info");
+  }
+
+  while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) {
+    const addr_t addr = range_info.GetRange().GetRangeBase();
+    const addr_t size = range_info.GetRange().GetByteSize();
+
+    if (rsp >= addr && rsp <= addr + size) {
+      Status error;
+      return std::make_pair(addr, size);
+    }
+
+    if (size == 0)
+      break;
+
+    range_error = process_sp->GetMemoryRegionInfo(
+        range_info.GetRange().GetRangeEnd(), range_info);
+    if (range_error.Fail())
+      break;
+  }
+
+  return llvm::createStringError(
+      std::errc::not_supported,
+      "process doesn't support getting memory region info");
+}
+
+Status MinidumpFileBuilder::AddThreadList(const lldb::ProcessSP &process_sp) {
+  constexpr size_t minidump_thread_size = sizeof(llvm::minidump::Thread);
+  lldb_private::ThreadList thread_list = process_sp->GetThreadList();
+
+  // size of the entire thread stream consists of:
+  // number of threads and threads array
+  size_t thread_stream_size = sizeof(llvm::support::ulittle32_t) +
+                              thread_list.GetSize() * minidump_thread_size;
+  // save for the ability to set up RVA
+  size_t size_before = GetCurrentDataEndOffset();
+
+  AddDirectory(StreamType::ThreadList, thread_stream_size);
+
+  llvm::support::ulittle32_t thread_count =
+      static_cast<llvm::support::ulittle32_t>(thread_list.GetSize());
+  m_data.AppendData(&thread_count, sizeof(llvm::support::ulittle32_t));
+
+  DataBufferHeap helper_data;
+
+  const uint32_t num_threads = thread_list.GetSize();
+
+  for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
+    ThreadSP thread_sp(thread_list.GetThreadAtIndex(thread_idx));
+    RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
+    Status error;
+
+    if (!reg_ctx_sp) {
+      error.SetErrorString("Unable to get the register context.");
+      return error;
+    }
+    RegisterContext *reg_ctx = reg_ctx_sp.get();
+    auto thread_context = GetThreadContext_64(reg_ctx);
+    uint64_t rsp = read_register_u64_raw(reg_ctx, "rsp");
+    auto expected_address_range = findStackHelper(process_sp, rsp);
+
+    if (!expected_address_range) {
+      error.SetErrorString("Unable to get the stack address.");
+      return error;
+    }
+
+    std::pair<uint64_t, uint64_t> range = std::move(*expected_address_range);
+    uint64_t addr = range.first;
+    uint64_t size = range.second;
+
+    auto data_up = std::make_unique<DataBufferHeap>(size, 0);
+    process_sp->ReadMemory(addr, data_up->GetBytes(), size, error);
+
+    if (error.Fail()) {
+      return error;
+    }
+
+    LocationDescriptor stack_memory;
+    stack_memory.DataSize = static_cast<llvm::support::ulittle32_t>(size);
+    stack_memory.RVA = static_cast<llvm::support::ulittle32_t>(
+        size_before + thread_stream_size + helper_data.GetByteSize());
+
+    MemoryDescriptor stack;
+    stack.StartOfMemoryRange = static_cast<llvm::support::ulittle64_t>(addr);
+    stack.Memory = stack_memory;
+
+    helper_data.AppendData(data_up->GetBytes(), size);
+
+    LocationDescriptor thread_context_memory_locator;
+    thread_context_memory_locator.DataSize =
+        static_cast<llvm::support::ulittle32_t>(sizeof(thread_context));
+    thread_context_memory_locator.RVA = static_cast<llvm::support::ulittle32_t>(
+        size_before + thread_stream_size + helper_data.GetByteSize());
+
+    helper_data.AppendData(
+        &thread_context,
+        sizeof(lldb_private::minidump::MinidumpContext_x86_64));
+
+    llvm::minidump::Thread t;
+    t.ThreadId = static_cast<llvm::support::ulittle32_t>(thread_sp->GetID());
+    t.SuspendCount = static_cast<llvm::support::ulittle32_t>(
+        (thread_sp->GetState() == StateType::eStateSuspended) ? 1 : 0);
+    t.PriorityClass = static_cast<llvm::support::ulittle32_t>(0);
+    t.Priority = static_cast<llvm::support::ulittle32_t>(0);
+    t.EnvironmentBlock = static_cast<llvm::support::ulittle64_t>(0);
+    t.Stack = stack, t.Context = thread_context_memory_locator;
+
+    m_data.AppendData(&t, sizeof(llvm::minidump::Thread));
+  }
+
+  m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize());
+  return Status();
+}
+
+Status MinidumpFileBuilder::AddException(const lldb::ProcessSP &process_sp) {
+  lldb_private::ThreadList thread_list = process_sp->GetThreadList();
+
+  const uint32_t num_threads = thread_list.GetSize();
+  uint32_t stop_reason_thread_idx = 0;
+  for (stop_reason_thread_idx = 0; stop_reason_thread_idx < num_threads;
+       ++stop_reason_thread_idx) {
+    ThreadSP thread_sp(thread_list.GetThreadAtIndex(stop_reason_thread_idx));
+    StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
+
+    if (stop_info_sp && stop_info_sp->IsValid()) {
+      break;
+    }
+  }
+
+  if (stop_reason_thread_idx == num_threads) {
+    Status error;
+    error.SetErrorString("No stop reason thread found.");
+    return error;
+  }
+
+  constexpr size_t minidump_exception_size =
+      sizeof(llvm::minidump::ExceptionStream);
+  AddDirectory(StreamType::Exception, minidump_exception_size);
+  size_t size_before = GetCurrentDataEndOffset();
+
+  ThreadSP thread_sp(thread_list.GetThreadAtIndex(stop_reason_thread_idx));
+  RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
+  RegisterContext *reg_ctx = reg_ctx_sp.get();
+  auto thread_context = GetThreadContext_64(reg_ctx);
+  StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
+
+  DataBufferHeap helper_data;
+
+  LocationDescriptor thread_context_memory_locator;
+  thread_context_memory_locator.DataSize =
+      static_cast<llvm::support::ulittle32_t>(sizeof(thread_context));
+  thread_context_memory_locator.RVA = static_cast<llvm::support::ulittle32_t>(
+      size_before + minidump_exception_size + helper_data.GetByteSize());
+
+  helper_data.AppendData(
+      &thread_context, sizeof(lldb_private::minidump::MinidumpContext_x86_64));
+
+  Exception exp_record;
+  exp_record.ExceptionCode =
+      static_cast<llvm::support::ulittle32_t>(stop_info_sp->GetValue());
+  exp_record.ExceptionFlags = static_cast<llvm::support::ulittle32_t>(0);
+  exp_record.ExceptionRecord = static_cast<llvm::support::ulittle64_t>(0);
+  exp_record.ExceptionAddress = read_register_u64(reg_ctx, "rip");
+  exp_record.NumberParameters = static_cast<llvm::support::ulittle32_t>(0);
+  exp_record.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
+  // exp_record.ExceptionInformation;
+
+  ExceptionStream exp_stream;
+  exp_stream.ThreadId =
+      static_cast<llvm::support::ulittle32_t>(thread_sp->GetID());
+  exp_stream.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
+  exp_stream.ExceptionRecord = exp_record;
+  exp_stream.ThreadContext = thread_context_memory_locator;
+
+  m_data.AppendData(&exp_stream, minidump_exception_size);
+  m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize());
+  return Status();
+}
+
+lldb_private::Status
+MinidumpFileBuilder::AddMemoryList(const lldb::ProcessSP &process_sp) {
+  Status error;
+  MemoryRegionInfo range_info;
+  error = process_sp->GetMemoryRegionInfo(0, range_info);
+
+  if (error.Fail()) {
+    error.SetErrorString("Process doesn't support getting memory region info.");
+    return error;
+  }
+
+  // Get interesting addresses
+  std::vector<size_t> interesting_addresses;
+  auto thread_list = process_sp->GetThreadList();
+  for (size_t i = 0; i < thread_list.GetSize(); ++i) {
+    ThreadSP thread_sp(thread_list.GetThreadAtIndex(i));
+    RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
+    RegisterContext *reg_ctx = reg_ctx_sp.get();
+
+    interesting_addresses.push_back(read_register_u64(reg_ctx, "rsp"));
+    interesting_addresses.push_back(read_register_u64(reg_ctx, "rip"));
+  }
+
+  DataBufferHeap helper_data;
+  std::vector<MemoryDescriptor> mem_descriptors;
+
+  while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) {
+    const addr_t addr = range_info.GetRange().GetRangeBase();
+    const addr_t size = range_info.GetRange().GetByteSize();
+
+    bool is_interesting = false;
+    for (size_t interesting_address : interesting_addresses)
+      if (interesting_address >= addr && interesting_address < addr + size) {
+        is_interesting = true;
+        break;
+      }
+
+    if (is_interesting) {
+      LocationDescriptor memory_dump;
+      memory_dump.DataSize = static_cast<llvm::support::ulittle32_t>(size);
+      memory_dump.RVA =
+          static_cast<llvm::support::ulittle32_t>(GetCurrentDataEndOffset());
+
+      MemoryDescriptor memory_desc;
+      memory_desc.StartOfMemoryRange =
+          static_cast<llvm::support::ulittle64_t>(addr);
+      memory_desc.Memory = memory_dump;
+      mem_descriptors.push_back(memory_desc);
+
+      auto data_up = std::make_unique<DataBufferHeap>(size, 0);
+      process_sp->ReadMemory(addr, data_up->GetBytes(), size, error);
+
+      m_data.AppendData(data_up->GetBytes(), size);
+    }
+
+    if (size == 0)
+      break;
+
+    error = process_sp->GetMemoryRegionInfo(range_info.GetRange().GetRangeEnd(),
+                                            range_info);
+    if (error.Fail()) {
+      error = Status();
+      break;
+    }
+  }
+
+  AddDirectory(StreamType::MemoryList,
+               sizeof(llvm::support::ulittle32_t) +
+                   mem_descriptors.size() *
+                       sizeof(llvm::minidump::MemoryDescriptor));
+  llvm::support::ulittle32_t memory_ranges_num(mem_descriptors.size());
+
+  m_data.AppendData(&memory_ranges_num, sizeof(llvm::support::ulittle32_t));
+  for (auto memory_descriptor : mem_descriptors) {
+    m_data.AppendData(&memory_descriptor,
+                      sizeof(llvm::minidump::MemoryDescriptor));
+  }
+
+  return error;
+}
+
+void MinidumpFileBuilder::AddMiscInfo(const lldb::ProcessSP &process_sp) {
+  AddDirectory(StreamType::MiscInfo,
+               sizeof(lldb_private::minidump::MinidumpMiscInfo));
+
+  lldb_private::minidump::MinidumpMiscInfo misc_info;
+  misc_info.size = static_cast<llvm::support::ulittle32_t>(
+      sizeof(lldb_private::minidump::MinidumpMiscInfo));
+  // Default set flags1 to 0, in case that we will not be able to
+  // get any information
+  misc_info.flags1 = static_cast<llvm::support::ulittle32_t>(0);
+
+  lldb_private::ProcessInstanceInfo process_info;
+  process_sp->GetProcessInfo(process_info);
+  if (process_info.ProcessIDIsValid()) {
+    // Set flags1 to reflect that PID is filled in
+    misc_info.flags1 =
+        static_cast<llvm::support::ulittle32_t>(static_cast<uint32_t>(
+            lldb_private::minidump::MinidumpMiscInfoFlags::ProcessID));
+    misc_info.process_id =
+        static_cast<llvm::support::ulittle32_t>(process_info.GetProcessID());
+  }
+
+  m_data.AppendData(&misc_info,
+                    sizeof(lldb_private::minidump::MinidumpMiscInfo));
+}
+
+Status MinidumpFileBuilder::Dump(lldb::FileUP &core_file) const {
+  constexpr size_t header_size = sizeof(llvm::minidump::Header);
+  constexpr size_t directory_size = sizeof(llvm::minidump::Directory);
+
+  // write header
+  llvm::minidump::Header header;
+  header.Signature = static_cast<llvm::support::ulittle32_t>(
+      llvm::minidump::Header::MagicSignature);
+  header.Version = static_cast<llvm::support::ulittle32_t>(
+      llvm::minidump::Header::MagicVersion);
+  header.NumberOfStreams =
+      static_cast<llvm::support::ulittle32_t>(GetDirectoriesNum());
+  header.StreamDirectoryRVA =
+      static_cast<llvm::support::ulittle32_t>(GetCurrentDataEndOffset());
+  header.Checksum = static_cast<llvm::support::ulittle32_t>(
+      0u), // not used in most of the writers
+      header.TimeDateStamp =
+          static_cast<llvm::support::ulittle32_t>(std::time(0));
+  header.Flags =
+      static_cast<llvm::support::ulittle64_t>(0u); // minidump normal flag
+
+  Status error;
+  size_t bytes_written;
+
+  bytes_written = header_size;
+  error = core_file->Write(&header, bytes_written);
+  if (error.Fail() || bytes_written != header_size) {
+    if (bytes_written != header_size)
+      error.SetErrorStringWithFormat(
+          "Unable to write the header. (written %ld/%ld).", bytes_written,
+          header_size);
+    return error;
+  }
+
+  // write data
+  bytes_written = m_data.GetByteSize();
+  error = core_file->Write(m_data.GetBytes(), bytes_written);
+  if (error.Fail() || bytes_written != m_data.GetByteSize()) {
+    if (bytes_written != m_data.GetByteSize())
+      error.SetErrorStringWithFormat(
+          "Unable to write the data. (written %ld/%ld).", bytes_written,
+          m_data.GetByteSize());
+    return error;
+  }
+
+  // write directories
+  for (const Directory &dir : m_directories) {
+    bytes_written = directory_size;
+    error = core_file->Write(&dir, bytes_written);
+    if (error.Fail() || bytes_written != directory_size) {
+      if (bytes_written != directory_size)
+        error.SetErrorStringWithFormat(
+            "Unable to write the directory. (written %ld/%ld).", bytes_written,
+            directory_size);
+      return error;
+    }
+  }
+
+  return error;
+}
+
+size_t MinidumpFileBuilder::GetDirectoriesNum() const {
+  return m_directories.size();
+}
+
+size_t MinidumpFileBuilder::GetCurrentDataEndOffset() const {
+  return sizeof(llvm::minidump::Header) + m_data.GetByteSize();
+}
\ No newline at end of file
Index: lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt
===================================================================
--- lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt
+++ lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_lldb_library(lldbPluginObjectFileELF PLUGIN
   ELFHeader.cpp
   ObjectFileELF.cpp
+  MinidumpFileBuilder.cpp
 
   LINK_LIBS
     lldbCore
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to