Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[LLDB][Minidump] Have Minidumps save off and properly read TLS data #109477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 10, 2024

Conversation

Jlalond
Copy link
Contributor

@Jlalond Jlalond commented Sep 20, 2024

This patch adds the support to Process.cpp to automatically save off TLS sections, either via loading the memory region for the module, or via reading fs_base via generic register. Then when Minidumps are loaded, we now specify we want the dynamic loader to be the POSIXDYLD so we can leverage the same TLS accessor code as ProcessELFCore. Being able to access TLS Data is an important step for LLDB generated minidumps to have feature parity with ELF Core dumps.

@llvmbot
Copy link
Member

llvmbot commented Sep 20, 2024

@llvm/pr-subscribers-lldb

Author: Jacob Lalonde (Jlalond)

Changes

This patch adds the support to Process.cpp to automatically save off TLS sections, either via loading the memory region for the module, or via reading fs_base via generic register. Then when Minidumps are loaded, we now specify we want the dynamic loader to be the POSIXDYLD so we can leverage the same TLS accessor code as ProcessELFCore. Being able to access TLS Data is an important step for LLDB generated minidumps to have feature parity with ELF Core dumps.


Full diff: https://github.com/llvm/llvm-project/pull/109477.diff

7 Files Affected:

  • (modified) lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp (+3-2)
  • (modified) lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp (+19)
  • (modified) lldb/source/Plugins/Process/minidump/ProcessMinidump.h (+4-3)
  • (modified) lldb/source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp (+16-4)
  • (modified) lldb/source/Target/Process.cpp (+78-3)
  • (modified) lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py (+74)
  • (modified) lldb/test/API/functionalities/process_save_core_minidump/main.cpp (+1)
diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
index 51e4b3e6728f23..38e9a61817ab55 100644
--- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
+++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
@@ -115,7 +115,8 @@ void DynamicLoaderPOSIXDYLD::DidAttach() {
       // don't rebase if the module already has a load address
       Target &target = m_process->GetTarget();
       Address addr = obj->GetImageInfoAddress(&target);
-      if (addr.GetLoadAddress(&target) != LLDB_INVALID_ADDRESS)
+      addr_t load_addr = addr.GetLoadAddress(&target);
+      if (load_addr != LLDB_INVALID_ADDRESS)
         rebase_exec = false;
     }
   } else {
@@ -124,7 +125,7 @@ void DynamicLoaderPOSIXDYLD::DidAttach() {
   }
 
   // if the target executable should be re-based
-  if (rebase_exec) {
+  if (rebase_exec || IsCoreFile()) {
     ModuleList module_list;
 
     module_list.Append(executable_sp);
diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
index 32ffba763c08e3..2a8f9fe9b9d93d 100644
--- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
+++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
@@ -21,6 +21,7 @@
 #include "lldb/Interpreter/CommandReturnObject.h"
 #include "lldb/Interpreter/OptionArgParser.h"
 #include "lldb/Interpreter/OptionGroupBoolean.h"
+#include "lldb/Target/DynamicLoader.h"
 #include "lldb/Target/JITLoaderList.h"
 #include "lldb/Target/MemoryRegionInfo.h"
 #include "lldb/Target/SectionLoadList.h"
@@ -34,6 +35,7 @@
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Threading.h"
 
+#include "Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.h"
 #include "Plugins/ObjectFile/Placeholder/ObjectFilePlaceholder.h"
 #include "Plugins/Process/Utility/StopInfoMachException.h"
 
@@ -333,6 +335,23 @@ ArchSpec ProcessMinidump::GetArchitecture() {
   return ArchSpec(triple);
 }
 
+DynamicLoader *ProcessMinidump::GetDynamicLoader() {
+  if (m_dyld_up.get() == nullptr)
+    m_dyld_up.reset(DynamicLoader::FindPlugin(
+        this, DynamicLoaderPOSIXDYLD::GetPluginNameStatic()));
+  return m_dyld_up.get();
+}
+
+DataExtractor ProcessMinidump::GetAuxvData() {
+  std::optional<llvm::ArrayRef<uint8_t>> auxv =
+      m_minidump_parser->GetStream(StreamType::LinuxAuxv);
+  if (!auxv)
+    return DataExtractor();
+
+  return DataExtractor(auxv->data(), auxv->size(), ByteOrder::eByteOrderLittle,
+                       GetAddressByteSize(), GetAddressByteSize());
+}
+
 void ProcessMinidump::BuildMemoryRegions() {
   if (m_memory_regions)
     return;
diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h
index f2ea0a2b61d14e..332e2637cc7ee7 100644
--- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h
+++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h
@@ -53,11 +53,12 @@ class ProcessMinidump : public PostMortemProcess {
 
   Status DoLoadCore() override;
 
-  DynamicLoader *GetDynamicLoader() override { return nullptr; }
+  // Returns AUXV structure found in the core file
+  lldb_private::DataExtractor GetAuxvData() override;
 
-  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+  DynamicLoader *GetDynamicLoader() override;
 
-  SystemRuntime *GetSystemRuntime() override { return nullptr; }
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
 
   Status DoDestroy() override;
 
diff --git a/lldb/source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp b/lldb/source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp
index e879c493156593..f305d1b7031d82 100644
--- a/lldb/source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp
+++ b/lldb/source/Plugins/Process/minidump/RegisterContextMinidump_x86_64.cpp
@@ -44,6 +44,17 @@ static void writeRegister(const void *reg_src, uint8_t *context,
   memcpy(reg_dest.data(), reg_src, reg_dest.size());
 }
 
+// TODO: Fix the registers in this file!
+// writeRegister checks x86_64 registers without base registers. This causes
+// an overlap in the register enum values. So we were truncating fs_base.
+// We should standardize to the x86_64_with_base registers.
+static void writeBaseRegister(const void *reg_src, uint8_t *context,
+                              const RegisterInfo &reg) {
+  auto bytes = reg.mutable_data(context);
+  llvm::MutableArrayRef<uint8_t> reg_dest = bytes.take_front(8);
+  memcpy(reg_dest.data(), reg_src, reg_dest.size());
+}
+
 lldb::DataBufferSP lldb_private::minidump::ConvertMinidumpContext_x86_64(
     llvm::ArrayRef<uint8_t> source_data,
     RegisterInfoInterface *target_reg_interface) {
@@ -105,11 +116,12 @@ lldb::DataBufferSP lldb_private::minidump::ConvertMinidumpContext_x86_64(
     writeRegister(&context->r15, result_base, reg_info[lldb_r15_x86_64]);
   }
 
+  // See comment on base regsiter
   if ((context_flags & LLDBSpecificFlag) == LLDBSpecificFlag) {
-    writeRegister(&context->fs_base, result_base,
-                  reg_info[x86_64_with_base::lldb_fs_base]);
-    writeRegister(&context->gs_base, result_base,
-                  reg_info[x86_64_with_base::lldb_gs_base]);
+    writeBaseRegister(&context->fs_base, result_base,
+                      reg_info[x86_64_with_base::lldb_fs_base]);
+    writeBaseRegister(&context->gs_base, result_base,
+                      reg_info[x86_64_with_base::lldb_gs_base]);
   }
 
   // TODO parse the floating point registers
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index aca08972811470..8ddb2be409f855 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6528,6 +6528,74 @@ static void AddRegion(const MemoryRegionInfo &region, bool try_dirty_pages,
                 CreateCoreFileMemoryRange(region));
 }
 
+static void AddRegisterSections(Process &process, ThreadSP &thread_sp,
+                                CoreFileMemoryRanges &ranges,
+                                lldb::addr_t range_end) {
+  lldb::RegisterContextSP reg_ctx = thread_sp->GetRegisterContext();
+  if (!reg_ctx)
+    return;
+
+  const RegisterInfo *reg_info = reg_ctx->GetRegisterInfo(
+      lldb::RegisterKind::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_TP);
+  if (!reg_info)
+    return;
+
+  lldb_private::RegisterValue reg_value;
+  bool success = reg_ctx->ReadRegister(reg_info, reg_value);
+  if (!success)
+    return;
+
+  const uint64_t fail_value = UINT64_MAX;
+  bool readSuccess = false;
+  const lldb::addr_t reg_value_addr =
+      reg_value.GetAsUInt64(fail_value, &readSuccess);
+  if (!readSuccess || reg_value_addr == fail_value)
+    return;
+
+  MemoryRegionInfo register_region;
+  Status err = process.GetMemoryRegionInfo(reg_value_addr, register_region);
+  if (err.Fail())
+    return;
+
+  // We already saved off this truncated stack range.
+  if (register_region.GetRange().GetRangeEnd() == range_end)
+    return;
+
+  // We don't need to worry about duplication because the CoreFileMemoryRanges
+  // will unique them
+  AddRegion(register_region, true, ranges);
+}
+
+static void AddModuleSections(Process &process, CoreFileMemoryRanges &ranges,
+                              std::set<addr_t> &stack_ends) {
+  ModuleList &module_list = process.GetTarget().GetImages();
+  Target *target = &process.GetTarget();
+  for (size_t idx = 0; idx < module_list.GetSize(); idx++) {
+    ModuleSP module_sp = module_list.GetModuleAtIndex(idx);
+    if (!module_sp)
+      continue;
+
+    ObjectFile *obj = module_sp->GetObjectFile();
+    if (!obj)
+      continue;
+    Address addr = obj->GetImageInfoAddress(target);
+    addr_t load_addr = addr.GetLoadAddress(target);
+    if (load_addr == LLDB_INVALID_ADDRESS)
+      continue;
+
+    MemoryRegionInfo tls_storage_region;
+    Status err = process.GetMemoryRegionInfo(load_addr, tls_storage_region);
+    if (err.Fail())
+      continue;
+
+    // We already saved off this truncated stack range.
+    if (stack_ends.count(tls_storage_region.GetRange().GetRangeEnd()) > 0)
+      continue;
+
+    AddRegion(tls_storage_region, true, ranges);
+  }
+}
+
 static void SaveOffRegionsWithStackPointers(Process &process,
                                             const SaveCoreOptions &core_options,
                                             const MemoryRegionInfos &regions,
@@ -6559,11 +6627,14 @@ static void SaveOffRegionsWithStackPointers(Process &process,
       // off in other calls
       sp_region.GetRange().SetRangeBase(stack_head);
       sp_region.GetRange().SetByteSize(stack_size);
-      stack_ends.insert(sp_region.GetRange().GetRangeEnd());
+      const addr_t range_end = sp_region.GetRange().GetRangeEnd();
+      stack_ends.insert(range_end);
       // This will return true if the threadlist the user specified is empty,
       // or contains the thread id from thread_sp.
-      if (core_options.ShouldThreadBeSaved(thread_sp->GetID()))
+      if (core_options.ShouldThreadBeSaved(thread_sp->GetID())) {
         AddRegion(sp_region, try_dirty_pages, ranges);
+        AddRegisterSections(process, thread_sp, ranges, range_end);
+      }
     }
   }
 }
@@ -6672,9 +6743,13 @@ Status Process::CalculateCoreFileSaveRanges(const SaveCoreOptions &options,
   std::set<addr_t> stack_ends;
   // For fully custom set ups, we don't want to even look at threads if there
   // are no threads specified.
-  if (core_style != lldb::eSaveCoreCustomOnly || options.HasSpecifiedThreads())
+  if (core_style != lldb::eSaveCoreCustomOnly ||
+      options.HasSpecifiedThreads()) {
     SaveOffRegionsWithStackPointers(*this, options, regions, ranges,
                                     stack_ends);
+    // Save off the load sections for the TLS data.
+    AddModuleSections(*this, ranges, stack_ends);
+  }
 
   switch (core_style) {
   case eSaveCoreUnspecified:
diff --git a/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py b/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
index ccdb6653cf16f8..80ebc884d3dbf5 100644
--- a/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
+++ b/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py
@@ -522,3 +522,77 @@ def minidump_deleted_on_save_failure(self):
 
         finally:
             self.assertTrue(self.dbg.DeleteTarget(target))
+
+    @skipUnlessPlatform(["linux"])
+    @skipUnlessArch("x86_64")
+    def minidump_saves_fs_base_region(self):
+        """Test that verifies the minidump file saves region for fs_base"""
+
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        try:
+            target = self.dbg.CreateTarget(exe)
+            process = target.LaunchSimple(
+                None, None, self.get_process_working_directory()
+            )
+            self.assertState(process.GetState(), lldb.eStateStopped)
+            thread = process.GetThreadAtIndex(0)
+            custom_file = self.getBuildArtifact("core.reg_region.dmp")
+            options = lldb.SBSaveCoreOptions()
+            options.SetOutputFile(lldb.SBFileSpec(custom_file))
+            options.SetPluginName("minidump")
+            options.SetStyle(lldb.eSaveCoreCustomOnly)
+            options.AddThread(thread)
+            error = process.SaveCore(options)
+            self.assertTrue(error.Success())
+
+            registers = thread.GetFrameAtIndex(0).GetRegisters()
+            fs_base = registers.GetFirstValueByName("fs_base").GetValueAsUnsigned()
+            self.assertTrue(fs_base != 0)
+            core_target = self.dbg.CreateTarget(None)
+            core_proc = core_target.LoadCore(one_region_file)
+            core_region_list = core_proc.GetMemoryRegions()
+            live_region_list = process.GetMemoryRegions()
+            live_region = lldb.SBMemoryRegionInfo()
+            live_region_list.GetMemoryRegionForAddress(fs_base, live_region)
+            core_region = lldb.SBMemoryRegionInfo()
+            error = core_region_list.GetMemoryRegionForAddress(fs_base, core_region)
+            self.assertTrue(error.Success())
+            self.assertEqual(live_region, core_region)
+
+        finally:
+            self.assertTrue(self.dbg.DeleteTarget(target))
+            self.assertTrue(self.dbg.DeleteTarget(core_target))
+            if os.path.isfile(custom_file):
+                os.unlink(custom_file)
+
+    @skipUnlessPlatform(["linux"])
+    @skipUnlessArch("x86_64")
+    def minidump_saves_fs_base_region(self):
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        try:
+            target = self.dbg.CreateTarget(exe)
+            process = target.LaunchSimple(
+                None, None, self.get_process_working_directory()
+            )
+            self.assertState(process.GetState(), lldb.eStateStopped)
+            thread = process.GetThreadAtIndex(0)
+            tls_file = self.getBuildArtifact("core.tls.dmp")
+            options = lldb.SBSaveCoreOptions()
+            options.SetOutputFile(lldb.SBFileSpec(tls_file))
+            options.SetPluginName("minidump")
+            options.SetStyle(lldb.eSaveCoreCustomOnly)
+            options.AddThread(thread)
+            error = process.SaveCore(options)
+            self.assertTrue(error.Success())
+            core_target = self.dbg.CreateTarget(None)
+            core_proc = core_target.LoadCore(tls_file)
+            frame = core_proc.GetThreadAtIndex(0).GetFrameAtIndex(0)
+            tls_val = frame.FindValue("lf")
+            self.assertEqual(tls_val.GetValueAsUnsigned(), 42)
+
+        except:
+            self.assertTrue(self.dbg.DeleteTarget(target))
+            if os.path.isfile(tls_file):
+                os.unlink(tls_file)
diff --git a/lldb/test/API/functionalities/process_save_core_minidump/main.cpp b/lldb/test/API/functionalities/process_save_core_minidump/main.cpp
index fa34a371f20647..15daa68e9a648c 100644
--- a/lldb/test/API/functionalities/process_save_core_minidump/main.cpp
+++ b/lldb/test/API/functionalities/process_save_core_minidump/main.cpp
@@ -1,6 +1,7 @@
 #include <cassert>
 #include <iostream>
 #include <thread>
+thread_local size_t lf = 42;
 
 void g() { assert(false); }
 

lldb/source/Target/Process.cpp Outdated Show resolved Hide resolved
lldb/source/Target/Process.cpp Outdated Show resolved Hide resolved
lldb/source/Target/Process.cpp Outdated Show resolved Hide resolved
lldb/source/Target/Process.cpp Outdated Show resolved Hide resolved
lldb/source/Target/Process.cpp Outdated Show resolved Hide resolved
lldb/source/Target/Process.cpp Outdated Show resolved Hide resolved
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be best to separate the loading and saving parts into separate PRs, as the two are functionality separate. (and I have -- separate -- questions about each part).

For loading, I'd like to discuss the back door you're adding DidAttach function. The thing I don't like there is that this is already a sort of a back door added in http://reviews.llvm.org/D9471 to support "processes which can load their own libraries". Here we have a process "that loads its own libraries" (that's how it was able to get away without a dynamic loader so far), but which does want the loader plugin to do some loading as well (What exactly is the thing that's missing due to this check? Is it that m_loaded_modules does not get populated?).

Instead of poking holes in that check, I'd like to see if we can reduce the scope of the original patch, as it seems to have been added in support of a very specific use case. So specific that I can't even think of what it was -- the patch is light on specifics, and I don't know of any gdb server that qxfer:libraries (as opposed to qxfer:libraries-svr4) and the "posix" dynamic loader plugin.

If we can't come up with a good solution with reasonable effort, then I'd be willing to even reduce its scope to zero (effectively revert that part of the patch), as we should not be carrying (untested?) code which we don't understand, particularly when it causes problems (I doubt the author of that patch tested TLS, but your PR shows that it does not work this way, and I also remember having problems with these lines of code in the past).

For the second part of the patch (saving minidumps) I'd like to better understand what it is that you're saving with this code. In particular, I'm confused as to how we can save TLS by iterating over the modules (alone). For the most general (general dynamic tls model) case, I think you'd need a (threadxmodule) nested loop since dlopened shared libraries may have their TLS allocated in a random piece of memory by malloc (TLS is very complicated). Instead of trying to piece it together generically (the thread register is just the beginning) it may be better to ask the dynamic linker plugin to provide the list of memory regions that (may) contain the TLS data.

lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp Outdated Show resolved Hide resolved
lldb/source/Target/Process.cpp Outdated Show resolved Hide resolved
@Jlalond
Copy link
Contributor Author

Jlalond commented Sep 23, 2024

@labath I really like the idea to add the TLS saving to the dynamic loader, I'll add that,

As for the dynamic loader, I don't like how I've modified it. When a minidump is loaded, the link map isn't properly relocated, so TLS data is searched for relative to module lowest in memory.

So for my example, we were looking for TLS data in 5555ef... when the TLS section in the live process was in the relocated module at 777f... . Forcing the POSIX DYLD to rebase them worked, but it's at best a hack.

This codepath results in none of the modules being loaded, or rebased, and I don't why this should apply without checking all modules.

@labath
Copy link
Collaborator

labath commented Sep 24, 2024

This codepath results in none of the modules being loaded, or rebased, and I don't why this should apply without checking all modules.

Yeah, I don't like that code either, which is why I'm leaning more and more towards deleting it. Judging by the patch which introduced it, I believe this check is basically a proxy for "have the modules been loaded by the process class", and the intention more-or-less was to make the dynamic loader plugin a no-op in this case. What the other of the patch probably did not realize (just like I did not realize it when I was implementing module loading in ProcessMinidump) is that this makes thread local data access impossible (in fairness, it's quite possible that, at that point, thread-local access was not implemented yet).

@Jlalond
Copy link
Contributor Author

Jlalond commented Sep 24, 2024

This codepath results in none of the modules being loaded, or rebased, and I don't why this should apply without checking all modules.

Yeah, I don't like that code either, which is why I'm leaning more and more towards deleting it. Judging by the patch which introduced it, I believe this check is basically a proxy for "have the modules been loaded by the process class", and the intention more-or-less was to make the dynamic loader plugin a no-op in this case. What the other of the patch probably did not realize (just like I did not realize it when I was implementing module loading in ProcessMinidump) is that this makes thread local data access impossible (in fairness, it's quite possible that, at that point, thread-local access was not implemented yet).

Great context.

In my mind, I think every core file format will have to handle loading it's own modules that are from the core file. We will still want the dynamic loader plugin to do it's own book keeping. I haven't tested it, but I think we should allow the DYLD to do it's own book keeping but just not handle loading/unloading modules owned by the CoreProcess. This will likely require some extension of Process, but I think this a good evolution of the patch you linked

@labath
Copy link
Collaborator

labath commented Sep 25, 2024

Yeah, the thing which makes the core files different from live processes is that core files (at least minidumps, the situation is a bit fuzzier for elf cores) often have a better idea of where the files are located. OTOH, the dynamic loader plugin is a (I'm deliberately not using "the" because I could think of other places as well) for the thread-local storage lookup code, so we do the two to work together.

Regarding the "allow the DYLD to do it's own book keeping but just not handle loading/unloading modules owned by the CoreProcess" idea, how exactly would that differ from what your change in DynamicLoaderPOSIXDYLD::DidAttach does? In that the dynamic loader would not attempt to relocate the module if it has already been loaded by the process plugin, where as right now it does? I think that would make sense, though I also think that the current behavior (attempting to relocate it also does) -- my core question is: how likely is it that the dynamic loader will find the right thread-local for a module which was loaded by the process class, if the two classes can't even agree on its load address?

@Jlalond
Copy link
Contributor Author

Jlalond commented Sep 25, 2024

I think that would make sense, though I also think that the current behavior (attempting to relocate it also does) -- my core question is: how likely is it that the dynamic loader will find the right thread-local for a module which was loaded by the process class, if the two classes can't even agree on its load address?

I'm mentally struggling with this as there is a distinction I think between loading in a core file and actually loading of a module. ProcessMinidump handles loading modules defined in the module list, but the DYLD will need to relocate them. I want to support this workflow but not force it for all Cores (Which I am doing). I wonder if we can force have the process force relocation which is what I'm doing with the IsCoreFile() check in DidAttach

@labath
Copy link
Collaborator

labath commented Sep 25, 2024

The way I see it, there should be no distinction between "loading in a core file" and "actually loading" a module. "Loading of a module", in the sense that I'm using it here, consists of two steps:

  • adding the module to the target's list of modules (usually via target.GetOrCreateModule)
  • setting the load address of the module (loading in the narrow sense) -- done by calling module.SetLoadAddress

Both of these steps are necessary for a module to work properly, but it shouldn't matter who's doing the loading (except that probably thread local sections will not work for a module which was not loaded by the dynamic loader).

I think there's nothing wrong with giving the dynamic loader a chance to load any additional modules (for all processes even, not just core files). Normally, it should compute the same address (if any) that was produced by the process class, but if it happens to compute a different one, so be it (it probably means one of them has a bug).

Your mention of a relocation makes it sound like this is already happening though (process computing one load address and then the DYLD "relocating" it). Is that true? If so, then I think we ought to figure out what's causing the difference (and which one is correct).

@Jlalond
Copy link
Contributor Author

Jlalond commented Oct 2, 2024

On Pavel's suggestion, I broke the DYLD changes into their own patch #110885

lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp Outdated Show resolved Hide resolved
@@ -1,6 +1,7 @@
#include <cassert>
#include <iostream>
#include <thread>
thread_local size_t lf = 42;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be sure thread-locals work properly, I'd suggest also creating a shared library (which is dlopened at runtime) and creating a non-main thread, which will be used to access the value of the variable.

Copy link

github-actions bot commented Oct 3, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Jlalond added a commit that referenced this pull request Oct 7, 2024
…s a load address (#110885)

This is a part of #109477 that I'm making into it's own patch. Here we
remove logic from the DYLD that prevents it's logic from running if the
main executable already has a load address. Instead we let the DYLD
fully determine what should be loaded and what shouldn't.
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me what's up with the idea of moving the saving code to the dynamic loader plugin. Is that something you plan to work on?

The main thing that bothers me is the AddLinkMapSections function. Although it superficially generic, it uses the GetImageInfoAddress, which is only implemented by ObjectFileELF, and only called from the elf-core and posix-dyld plugins (i.e. code which already knows its dealing with elf object files), so I doubt it will be useful elsewhere. Even the name "link map" is very elf-specific, as there's nothing quite like that in other platforms/object file formats.

@@ -83,7 +83,11 @@ ModuleSP DynamicLoader::GetTargetExecutable() {
ModuleSpec module_spec(executable->GetFileSpec(),
executable->GetArchitecture());
auto module_sp = std::make_shared<Module>(module_spec);

// If we're a coredump and we already have a main executable, we don't
// need to reload the module list that target already has
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that? What is this trying to achieve/prevent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Testcase in MinidumpUUID, the one module which has a matching filename that isn't an actual match needs this check in order to work.

The situation is the DYLD will itself create a stub depending on some conditions, but this is after the process has already made a stub. So we need some way for the CoreProcess to supercede the dynamic loader clearing all loaded modules again, and so I added this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see. I'm not entirely happy by this, but it sort of makes sense, so I'm willing to look the other way. :P

lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp Outdated Show resolved Hide resolved
lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp Outdated Show resolved Hide resolved
@@ -6528,6 +6528,75 @@ static void AddRegion(const MemoryRegionInfo &region, bool try_dirty_pages,
CreateCoreFileMemoryRange(region));
}

static void AddSegmentRegisterSections(Process &process, ThreadSP &thread_sp,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name is very x86 specific (other architectures don't have "segment registers"). If this is to be generic code, then I guess what you're really trying to do is to save memory regions which are pointed to by the "tp" register.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the DYLD, do you think the x86 specificity is a problem? We know the DYLD handles it with fs/gs_base. So do you still think this is a concern?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly less of a concern, but it still isn't right as this is still used for architectures without segment registers. It's also completely avoidable -- you could just call this "AddThreadLocalMemoryRegions" or something like that..

@Jlalond
Copy link
Contributor Author

Jlalond commented Oct 8, 2024

The main thing that bothers me is the AddLinkMapSections function. Although it superficially generic, it uses the GetImageInfoAddress, which is only implemented by ObjectFileELF, and only called from the elf-core and posix-dyld plugins (i.e. code which already knows its dealing with elf object files), so I doubt it will be useful elsewhere. Even the name "link map" is very elf-specific, as there's nothing quite like that in other platforms/object file formats.

I completely forgot the recommendation to let the Dynamic loader handle this. I will convert this today.

lldb/include/lldb/Target/DynamicLoader.h Outdated Show resolved Hide resolved
lldb/include/lldb/Target/DynamicLoader.h Outdated Show resolved Hide resolved
@@ -83,7 +83,11 @@ ModuleSP DynamicLoader::GetTargetExecutable() {
ModuleSpec module_spec(executable->GetFileSpec(),
executable->GetArchitecture());
auto module_sp = std::make_shared<Module>(module_spec);

// If we're a coredump and we already have a main executable, we don't
// need to reload the module list that target already has
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see. I'm not entirely happy by this, but it sort of makes sense, so I'm willing to look the other way. :P

@@ -6528,6 +6528,75 @@ static void AddRegion(const MemoryRegionInfo &region, bool try_dirty_pages,
CreateCoreFileMemoryRange(region));
}

static void AddSegmentRegisterSections(Process &process, ThreadSP &thread_sp,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly less of a concern, but it still isn't right as this is still used for architectures without segment registers. It's also completely avoidable -- you could just call this "AddThreadLocalMemoryRegions" or something like that..

@Jlalond Jlalond merged commit e9c8f75 into llvm:main Oct 10, 2024
7 checks passed
DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
…lvm#109477)

This patch adds the support to `Process.cpp` to automatically save off
TLS sections, either via loading the memory region for the module, or
via reading `fs_base` via generic register. Then when Minidumps are
loaded, we now specify we want the dynamic loader to be the `POSIXDYLD`
so we can leverage the same TLS accessor code as `ProcessELFCore`. Being
able to access TLS Data is an important step for LLDB generated
minidumps to have feature parity with ELF Core dumps.
@nico
Copy link
Contributor

nico commented Dec 11, 2024

This broke minidumps in lldb: #119598

@DavidSpickett
Copy link
Collaborator

Hi @Jlalond I am looking at commits that warrant a release note for LLDB 20, and I think this and the rest of your Minidump changes might qualify. If you want to do that yourself you can send a PR adding a bullet point to https://github.com/llvm/llvm-project/blob/main/llvm/docs/ReleaseNotes.md#changes-to-lldb, describing the change.

There are a bunch (I listed some in https://discourse.llvm.org/t/lldb-20-request-for-release-notes/84034/1) so I assume you are working towards an end goal, so if you could summarise them that'd be great but if you don't think it's there yet, we can leave it for next release.

You also worked on SBSaveCore (#100443), same question there. Enough to warrant a release note?

(I don't use this stuff myself, so I won't attempt to write it up, I'll just get it wrong :) )

@Jlalond
Copy link
Contributor Author

Jlalond commented Jan 13, 2025

Hey @DavidSpickett, sure I'm drafting that up now. What time frame should we include commits from? I ask because one of my original patches added 64b support, and paging. I assume we could include anyway as I don't see it in the last release notes. I'll also make a discourse account so this is easier in the future :)

@DavidSpickett
Copy link
Collaborator

From the Discourse thread:

I also went through all the commits since the llvmorg-20-init tag until today, and noted anything I thought might warrant a release note.

If there was unfinished work in 19 that is now finished by later PRs that's fair to include too.

I assume we could include anyway as I don't see it in the last release notes.

Yeah sounds fine, I figured you'd want to group it all together so if it tells the story better, include it.

@Jlalond Jlalond deleted the tls-minidump branch March 7, 2025 19:20
labath added a commit to labath/llvm-project that referenced this pull request May 7, 2025
Minidump files contain explicit information about load addresses of
modules, so it can load them itself. This works on other platforms, but
fails on darwin because DynamicLoaderDarwin nukes the loaded module list
on initialization (which happens after the core file plugin has done its
work).

This used to work until llvm#109477, which enabled the dynamic loader
plugins for minidump files in order to get them to provide access to
TLS.

Clearing the load list makes sense, but I think we could do it earlier
in the process, so that both Process and DynamicLoader plugins get a
chance to load modules. This patch does that by calling the function
early in the launch/attach/load core flows.

This fixes TestDynamicValue.py:test_from_core_file on darwin.
labath added a commit to labath/llvm-project that referenced this pull request May 7, 2025
Minidump files contain explicit information about load addresses of
modules, so it can load them itself. This works on other platforms, but
fails on darwin because DynamicLoaderDarwin nukes the loaded module list
on initialization (which happens after the core file plugin has done its
work).

This used to work until llvm#109477, which enabled the dynamic loader
plugins for minidump files in order to get them to provide access to
TLS.

Clearing the load list makes sense, but I think we could do it earlier
in the process, so that both Process and DynamicLoader plugins get a
chance to load modules. This patch does that by calling the function
early in the launch/attach/load core flows.

This fixes TestDynamicValue.py:test_from_core_file on darwin.
labath added a commit that referenced this pull request May 14, 2025
Minidump files contain explicit information about load addresses of
modules, so it can load them itself. This works on other platforms, but
fails on darwin because DynamicLoaderDarwin nukes the loaded module list
on initialization (which happens after the core file plugin has done its
work).

This used to work until #109477, which enabled the dynamic loader
plugins for minidump files in order to get them to provide access to
TLS.

Clearing the load list makes sense, but I think we could do it earlier
in the process, so that both Process and DynamicLoader plugins get a
chance to load modules. This patch does that by calling the function
early in the launch/attach/load core flows.

This fixes TestDynamicValue.py:test_from_core_file on darwin.
labath added a commit to labath/llvm-project that referenced this pull request May 16, 2025
Minidump files contain explicit information about load addresses of
modules, so it can load them itself. This works on other platforms, but
fails on darwin because DynamicLoaderDarwin nukes the loaded module list
on initialization (which happens after the core file plugin has done its
work).

This used to work until llvm#109477, which enabled the dynamic loader
plugins for minidump files in order to get them to provide access to
TLS.

Clearing the load list makes sense, but I think we could do it earlier
in the process, so that both Process and DynamicLoader plugins get a
chance to load modules. This patch does that by calling the function
early in the launch/attach/load core flows.

This fixes TestDynamicValue.py:test_from_core_file on darwin.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.