Skip to content

[lldb] Fix dynamic type resolutions for core files #138698

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 3 commits into from
May 7, 2025
Merged

Conversation

labath
Copy link
Collaborator

@labath labath commented May 6, 2025

We're reading from the object's vtable to determine the pointer to the full object. The vtable is normally in the "rodata" section of the executable, which is often not included in the core file because it's not supposed to change and the debugger can extrapolate its contents from the executable file. We weren't doing that.

This patch changes the read operation to use the target class (which falls back onto the executable module as expected) and adds the missing ReadSignedIntegerFromMemory API. The fix is tested by creating a core (minidump) file which deliberately omits the vtable pointer.

We're reading from the object's vtable to determine the pointer to the
full object. The vtable is normally in the "rodata" section of the
executable, which is often not included in the core file because it's
not supposed to change and the debugger can extrapolate its contents
from the executable file. We weren't doing that.

This patch changes the read operation to use the target class (which
falls back onto the executable module as expected) and adds the missing
ReadSignedIntegerFromMemory API. The fix is tested by creating a core
(minidump) file which deliberately omits the vtable pointer.
@labath labath requested a review from Michael137 May 6, 2025 14:42
@labath labath requested a review from JDevlieghere as a code owner May 6, 2025 14:42
@llvmbot llvmbot added the lldb label May 6, 2025
@llvmbot
Copy link
Member

llvmbot commented May 6, 2025

@llvm/pr-subscribers-lldb

Author: Pavel Labath (labath)

Changes

We're reading from the object's vtable to determine the pointer to the full object. The vtable is normally in the "rodata" section of the executable, which is often not included in the core file because it's not supposed to change and the debugger can extrapolate its contents from the executable file. We weren't doing that.

This patch changes the read operation to use the target class (which falls back onto the executable module as expected) and adds the missing ReadSignedIntegerFromMemory API. The fix is tested by creating a core (minidump) file which deliberately omits the vtable pointer.


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

4 Files Affected:

  • (modified) lldb/include/lldb/Target/Target.h (+5)
  • (modified) lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp (+1-1)
  • (modified) lldb/source/Target/Target.cpp (+11)
  • (modified) lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py (+50)
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 73f27dc934b46..0d4e11b65339e 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1158,6 +1158,11 @@ class Target : public std::enable_shared_from_this<Target>,
                                      Status &error,
                                      bool force_live_memory = false);
 
+  int64_t ReadSignedIntegerFromMemory(const Address &addr,
+                                      size_t integer_byte_size,
+                                      int64_t fail_value, Status &error,
+                                      bool force_live_memory = false);
+
   uint64_t ReadUnsignedIntegerFromMemory(const Address &addr,
                                          size_t integer_byte_size,
                                          uint64_t fail_value, Status &error,
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
index 8faf7135217ac..0d068ed5950d5 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
@@ -350,7 +350,7 @@ bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress(
   if (offset_to_top_location >= vtable_load_addr)
     return false;
   Status error;
-  const int64_t offset_to_top = m_process->ReadSignedIntegerFromMemory(
+  const int64_t offset_to_top = target.ReadSignedIntegerFromMemory(
       offset_to_top_location, addr_byte_size, INT64_MIN, error);
 
   if (offset_to_top == INT64_MIN)
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index e90e748191a7e..7f61f8689fb95 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -2270,6 +2270,17 @@ size_t Target::ReadScalarIntegerFromMemory(const Address &addr, uint32_t byte_si
   return 0;
 }
 
+int64_t Target::ReadSignedIntegerFromMemory(const Address &addr,
+                                            size_t integer_byte_size,
+                                            int64_t fail_value, Status &error,
+                                            bool force_live_memory) {
+  Scalar scalar;
+  if (ReadScalarIntegerFromMemory(addr, integer_byte_size, false, scalar, error,
+                                  force_live_memory))
+    return scalar.SLongLong(fail_value);
+  return fail_value;
+}
+
 uint64_t Target::ReadUnsignedIntegerFromMemory(const Address &addr,
                                                size_t integer_byte_size,
                                                uint64_t fail_value, Status &error,
diff --git a/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py b/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
index 634bd13d7c71a..b15b5f1dde377 100644
--- a/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
+++ b/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
@@ -279,3 +279,53 @@ def test_from_forward_decl(self):
             "frame var -d run-target --ptr-depth=1 --show-types a",
             substrs=["(B *) a", "m_b_value = 10"],
         )
+
+    @no_debug_info_test
+    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24663")
+    def test_from_core_file(self):
+        """Test fetching C++ dynamic values from core files. Specifically, test
+        that we can determine the dynamic type of the value if the core file
+        does not contain the type vtable."""
+        self.build()
+        lldbutil.run_to_name_breakpoint(self, "take_A")
+
+        # Get the address of our object and its vtable
+        a = self.frame().FindVariable("a")
+        self.assertSuccess(a.GetError())
+        vtable = a.GetVTable()
+        self.assertSuccess(vtable.GetError())
+        a = a.GetValueAsAddress()
+        vtable = vtable.GetValueAsAddress()
+
+        # Create a core file which will only contain the memory region
+        # containing `a`. The object is on the stack, so this will automatically
+        # include the stack of the main thread.
+        core = self.getBuildArtifact("a.dmp")
+        options = lldb.SBSaveCoreOptions()
+        options.SetPluginName("minidump")
+        options.SetStyle(lldb.eSaveCoreCustomOnly)
+        options.SetOutputFile(lldb.SBFileSpec(core))
+        region = lldb.SBMemoryRegionInfo()
+        self.assertSuccess(self.process().GetMemoryRegionInfo(a, region))
+        self.assertSuccess(options.AddMemoryRegionToSave(region))
+
+        # Save the core file and load it.
+        self.assertSuccess(self.process().SaveCore(options))
+        self.process().Kill()
+        error = lldb.SBError()
+        self.target().LoadCore(core, error)
+        self.assertSuccess(error)
+
+        # Sanity check -- the process should be able to read the object but not
+        # its vtable..
+        self.process().ReadPointerFromMemory(a, error)
+        self.assertSuccess(error)
+        self.process().ReadPointerFromMemory(vtable, error)
+        self.assertTrue(error.Fail())
+
+        # .. but we should still be able to see the dynamic type by reading the
+        # vtable from the executable file.
+        self.expect(
+            "frame var -d run-target --ptr-depth=1 --show-types a",
+            substrs=["(B *) a", "m_b_value = 10"],
+        )

Copy link

github-actions bot commented May 7, 2025

✅ With the latest revision this PR passed the Python code formatter.

@labath labath merged commit 21501d1 into llvm:main May 7, 2025
7 of 9 checks passed
@labath labath deleted the dynamic branch May 7, 2025 13:02
@labath
Copy link
Collaborator Author

labath commented May 7, 2025

@DavidSpickett, I am confused by the error on this arm builder: https://lab.llvm.org/buildbot/#/builders/18/builds/15619

It says arm is not supported, but I think that error message is coming from here, and that switch has a llvm::Triple::ArchType::arm case. Do you know how this could fire? Is it one of those armebv7.92+polka_dots triples that's throwing it off somehow?

@DavidSpickett
Copy link
Collaborator

There's two places that could make that error, I will check both locally.

@labath
Copy link
Collaborator Author

labath commented May 7, 2025

There are? Where's the other one?

@labath
Copy link
Collaborator Author

labath commented May 7, 2025

Oh, it's this one.

That makes more sense now. I'll skip the test on arm. Thanks for catching that.

@DavidSpickett
Copy link
Collaborator

Yep, called from AddThreadList:

#0 0xffffffffedfbe37c llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/home/david.spickett/build-llvm-arm/local/lib/python3.10/dist-packages/lldb/_lldb.cpython-310-arm-linux-gnueabihf.so+0xdbe37c)
#1 0xffffffffedfbca00 llvm::sys::RunSignalHandlers() (/home/david.spickett/build-llvm-arm/local/lib/python3.10/dist-packages/lldb/_lldb.cpython-310-arm-linux-gnueabihf.so+0xdbca00)
#2 0xffffffffedfbcb44 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
#3 0xfffffffff7c3d6f0 __default_rt_sa_restorer ./signal/../sysdeps/unix/sysv/linux/arm/sigrestorer.S:80:0
#4 0xffffffffedd87604 MinidumpFileBuilder::AddThreadList() (/home/david.spickett/build-llvm-arm/local/lib/python3.10/dist-packages/lldb/_lldb.cpython-310-arm-linux-gnueabihf.so+0xb87604)

@felipepiovezan
Copy link
Contributor

felipepiovezan commented May 7, 2025

Btw I think this is causing an asan failure in TestDynamicValue.py

https://green.lab.llvm.org/job/lldb-cmake-sanitized/1671/

2025-05-07T19:03:57.557Z] ==73219==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x0001070cf5b8 at pc 0x0001056051c0 bp 0x00016b0a3730 sp 0x00016b0a2ee0
[2025-05-07T19:03:57.557Z] READ of size 120 at 0x0001070cf5b8 thread T0
[2025-05-07T19:03:57.557Z]     #0 0x1056051bc in __asan_memcpy+0x394 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x511bc)
[2025-05-07T19:03:57.557Z]     #1 0x13656e5a4 in MinidumpFileBuilder::AddExceptions() MinidumpFileBuilder.cpp:714
[2025-05-07T19:03:57.557Z]     #2 0x136565688 in ObjectFileMinidump::SaveCore(std::__1::shared_ptr<lldb_private::Process> const&, lldb_private::SaveCoreOptions&, lldb_private::Status&) ObjectFileMinidump.cpp:132
[2025-05-07T19:03:57.557Z]     #3 0x1394034f4 in lldb_private::PluginManager::SaveCore(std::__1::shared_ptr<lldb_private::Process> const&, lldb_pri
...


[2025-05-07T19:03:57.558Z] Address 0x0001070cf5b8 is located in stack of thread T0 at offset 440 in frame
[2025-05-07T19:03:57.558Z]     #0 0x13656dfa4 in MinidumpFileBuilder::AddExceptions() MinidumpFileBuilder.cpp:685
[2025-05-07T19:03:57.558Z] 
[2025-05-07T19:03:57.558Z]   This frame has 10 object(s):
[2025-05-07T19:03:57.558Z]     [32, 40) 'ref.tmp.i.i'
[2025-05-07T19:03:57.558Z]     [64, 88) 'thread_list' (line 686)
[2025-05-07T19:03:57.558Z]     [128, 144) 'stop_info_sp' (line 690)
[2025-05-07T19:03:57.558Z]     [160, 192) 'ref.tmp' (line 698)
[2025-05-07T19:03:57.558Z]     [224, 240) 'reg_ctx_sp' (line 702)
[2025-05-07T19:03:57.558Z]     [256, 376) 'exp_record.sroa.10' (line 703)
[2025-05-07T19:03:57.558Z]     [416, 440) 'description' (line 711)
[2025-05-07T19:03:57.558Z]     [480, 648) 'exp_stream' (line 717) <== Memory access at offset 440 partially underflows this variable
[2025-05-07T19:03:57.558Z]     [720, 728) 'Iter' (line 722)
[2025-05-07T19:03:57.558Z]     [752, 760) 'ref.tmp151' (line 722)
[2025-05-07T19:03:57.558Z] HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
[2025-05-07T19:03:57.558Z]       (longjmp and C++ exceptions *are* supported)
[2025-05-07T19:03:57.558Z] SUMMARY: AddressSanitizer: stack-buffer-overflow (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x511bc) in __asan_memcpy+0x394
[2025-05-07T19:03:57.558Z] Shadow bytes around the buggy address:
[2025-05-07T19:03:57.558Z]   0x0001070cf300: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
[2025-05-07T19:03:57.558Z]   0x0001070cf380: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
[2025-05-07T19:03:57.558Z]   0x0001070cf400: f1 f1 f1 f1 f8 f2 f2 f2 00 00 00 f2 f2 f2 f2 f2
[2025-05-07T19:03:57.558Z]   0x0001070cf480: 00 00 f2 f2 f8 f8 f8 f8 f2 f2 f2 f2 00 00 f2 f2
[2025-05-07T19:03:57.558Z]   0x0001070cf500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f2
[2025-05-07T19:03:57.558Z] =>0x0001070cf580: f2 f2 f2 f2 00 00 00[f2]f2 f2 f2 f2 f8 f8 f8 f8
[2025-05-07T19:03:57.558Z]   0x0001070cf600: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
[2025-05-07T19:03:57.558Z]   0x0001070cf680: f8 f2 f2 f2 f2 f2 f2 f2 f2 f2 f8 f2 f2 f2 f8 f3
[2025-05-07T19:03:57.558Z]   0x0001070cf700: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
[2025-05-07T19:03:57.558Z]   0x0001070cf780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[2025-05-07T19:03:57.558Z]   0x0001070cf800: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
[2025-05-07T19:03:57.558Z] Shadow byte legend (one shadow byte represents 8 application bytes):

@labath
Copy link
Collaborator Author

labath commented May 7, 2025

Lol, I think its this:

// We have 120 bytes to work with and it's unlikely description will
// overflow, but we gotta check.
memcpy(&exp_record.ExceptionInformation, description.c_str(),
std::max(description.size(), Exception::MaxParameterBytes));

I'm done for this week, but could someone try if changing that to std::min fixes this?

felipepiovezan added a commit that referenced this pull request May 8, 2025
@felipepiovezan
Copy link
Contributor

Pushed your suggestion

Lol, I think its this:

// We have 120 bytes to work with and it's unlikely description will
// overflow, but we gotta check.
memcpy(&exp_record.ExceptionInformation, description.c_str(),
std::max(description.size(), Exception::MaxParameterBytes));

I'm done for this week, but could someone try if changing that to std::min fixes this?

llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request May 8, 2025
@labath
Copy link
Collaborator Author

labath commented May 13, 2025

Thanks.

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.

5 participants