Skip to content

[lldb-server] Add type info for qMemoryinfo reponse#209235

Open
felipepiovezan wants to merge 3 commits into
llvm:mainfrom
felipepiovezan:felipe/qmeminfolinux
Open

[lldb-server] Add type info for qMemoryinfo reponse#209235
felipepiovezan wants to merge 3 commits into
llvm:mainfrom
felipepiovezan:felipe/qmeminfolinux

Conversation

@felipepiovezan

@felipepiovezan felipepiovezan commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Use the name of the memory region to report the known cases where an address points to stack/heap: "[stack]" or "[heap"].

According to 1, the "[stack]" name is the main thread's stack region:

  [stack]
                     The initial process's (also known as the main
                     thread's) stack.

For other threads, we can't know their stack region because this field got removed in newer kernels:

 [stack:tid] (from Linux 3.4 to Linux 4.4)
        A thread's stack (where the tid is a thread ID).  It
        corresponds to the /proc/pid/task/tid/ path.  This
        field was removed in Linux 4.5, since providing this
        information for a process with large numbers of
        threads is expensive.

So we can only set "isStack" to Yes in some cases, and to "No" when the name is "[heap]". If there is no name, or the name is something else, we keep the "don't know" answer.

Use the name of the memory region to report the known cases of where an
address points to stack/heap: "[stack]" or "[heap"].

According to [1], the "[stack]" name is only available for the main
thread. So we can only set "isStack" to Yes in some cases, and to "No"
when the name is "[heap]". If there is no name, or the name is something
else, we keep the "don't know" answer.

> [stack:tid] (from Linux 3.4 to Linux 4.4)
>        A thread's stack (where the tid is a thread ID).  It
>        corresponds to the /proc/pid/task/tid/ path.  This
>        field was removed in Linux 4.5, since providing this
>        information for a process with large numbers of
>        threads is expensive.

https://man7.org/linux/man-pages/man5/proc_pid_maps.5.html
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-lldb

Author: Felipe de Azevedo Piovezan (felipepiovezan)

Changes

Use the name of the memory region to report the known cases of where an address points to stack/heap: "[stack]" or "[heap"].

According to [1], the "[stack]" name is only available for the main thread. So we can only set "isStack" to Yes in some cases, and to "No" when the name is "[heap]". If there is no name, or the name is something else, we keep the "don't know" answer.

> [stack:tid] (from Linux 3.4 to Linux 4.4)
> A thread's stack (where the tid is a thread ID). It
> corresponds to the /proc/pid/task/tid/ path. This
> field was removed in Linux 4.5, since providing this
> information for a process with large numbers of
> threads is expensive.

https://man7.org/linux/man-pages/man5/proc_pid_maps.5.html


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

4 Files Affected:

  • (modified) lldb/include/lldb/Target/MemoryRegionInfo.h (+4-1)
  • (modified) lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp (+8-1)
  • (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp (+7)
  • (modified) lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp (+12-1)
diff --git a/lldb/include/lldb/Target/MemoryRegionInfo.h b/lldb/include/lldb/Target/MemoryRegionInfo.h
index 16faf6ed9d64b..e8a16a3ab9757 100644
--- a/lldb/include/lldb/Target/MemoryRegionInfo.h
+++ b/lldb/include/lldb/Target/MemoryRegionInfo.h
@@ -142,7 +142,10 @@ class MemoryRegionInfo {
 
   LazyBool IsStackMemory() const { return m_is_stack_memory; }
 
-  void SetIsStackMemory(LazyBool val) { m_is_stack_memory = val; }
+  MemoryRegionInfo &SetIsStackMemory(LazyBool val) {
+    m_is_stack_memory = val;
+    return *this;
+  }
 
   void SetPageSize(int pagesize) { m_pagesize = pagesize; }
 
diff --git a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
index d952b7dbdb816..bdb340332851a 100644
--- a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
+++ b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
@@ -114,8 +114,15 @@ ParseMemoryRegionInfoFromProcMapsLine(llvm::StringRef maps_line,
 
   line_extractor.SkipSpaces();
   const char *name = line_extractor.Peek();
-  if (name)
+  if (name) {
     region.SetName(name);
+    // /proc/maps labels only the main thread's stack; thread stacks are
+    // anonymous and the default MemoryRegionInfo::eDontKnow is kept.
+    if (llvm::StringRef(name) == "[stack]")
+      region.SetIsStackMemory(eLazyBoolYes);
+    else if (llvm::StringRef(name) == "[heap]")
+      region.SetIsStackMemory(eLazyBoolNo);
+  }
 
   return region;
 }
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 769705588fbdf..451855ef80e75 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -2971,6 +2971,13 @@ GDBRemoteCommunicationServerLLGS::Handle_qMemoryRegionInfo(
 
     if (std::optional<unsigned> protection_key = region_info.GetProtectionKey())
       response.Printf("protection-key:%" PRIu32 ";", *protection_key);
+
+    // Type: stack/heap classification, when known.
+    LazyBool is_stack = region_info.IsStackMemory();
+    if (is_stack == eLazyBoolYes)
+      response.PutCString("type:stack;");
+    else if (is_stack == eLazyBoolNo)
+      response.PutCString("type:heap;");
   }
 
   return SendPacketNoLock(response.GetString());
diff --git a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
index fcfd76cc67960..84aa206b4e50a 100644
--- a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
+++ b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
@@ -99,7 +99,18 @@ INSTANTIATE_TEST_SUITE_P(
                 MemoryRegionInfo(make_range(0x55a4512f7000, 0x55a451b68000),
                                  eLazyBoolYes, eLazyBoolYes, eLazyBoolNo,
                                  eLazyBoolNo, eLazyBoolYes,
-                                 ConstString("[heap]")),
+                                 ConstString("[heap]"))
+                    .SetIsStackMemory(eLazyBoolNo),
+            },
+            ""),
+        std::make_tuple(
+            "7ffcad8f7000-7ffcad918000 rw-p 00000000 00:00 0    [stack]",
+            MemoryRegionInfos{
+                MemoryRegionInfo(make_range(0x7ffcad8f7000, 0x7ffcad918000),
+                                 eLazyBoolYes, eLazyBoolYes, eLazyBoolNo,
+                                 eLazyBoolNo, eLazyBoolYes,
+                                 ConstString("[stack]"))
+                    .SetIsStackMemory(eLazyBoolYes),
             },
             ""),
         // Multiple entries

@felipepiovezan

felipepiovezan commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

As a bit of motivation, I'm trying to implement Linux support for some swift stuff, where CFAs are usually on the heap, and stumbled on this lack of memory information.

if (is_stack == eLazyBoolYes)
response.PutCString("type:stack;");
else if (is_stack == eLazyBoolNo)
response.PutCString("type:heap;");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't know linux VM but I don't think "if not stack, is heap" is a good assumption. We could be looking at shared memory or binary data or other VM types that are not heap allocations.

@felipepiovezan felipepiovezan Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But that's not the assumption we're making here, right? What we're doing is:

If stack => stack.
else If heap => not stack.
else => unknown.

@jasonmolenda jasonmolenda Jul 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, sorry I didn't see where IsStackMemory was initialized,

          for (llvm::StringRef entry : llvm::split(value, ',')) {
            if (entry == "stack")
              region_info.SetIsStackMemory(eLazyBoolYes);
            else if (entry == "heap")
              region_info.SetIsStackMemory(eLazyBoolNo);
          }

Ah, I see, using a LazyBool here isn't the clearest.
"Yes" == definitely-stack, "No" == definitely-heap, "Calculate" == could be anything incl maybe stack. Yeah this change is fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It used to be a different kind of value, whose options were "yes/no/don't know", but I believe @DavidSpickett unified them with LazyBool.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can do "don't know" with lazybool, it's an alias to calculate:

enum LazyBool {
  eLazyBoolCalculate = -1,
  eLazyBoolDontKnow = eLazyBoolCalculate,
  eLazyBoolNo = 0,
  eLazyBoolYes = 1
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

And yes we unified them because calculate/don't know was the only state that was different between the 2 types.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should put a comment as the implicit else:

// Else we do not know, so don't add any type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Or you could refactor it like:

if (is_stack != eLazyBoolDontKnow)
  response.Printf("type: %s", is_stack ? "stack" : "heap");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You can do "don't know" with lazybool, it's an alias to calculate:

TIL!

@DavidSpickett

Copy link
Copy Markdown
Contributor

What you quoted is confusing me because you've quoted a field removed in 4.4, and maybe you have a reason to support that even though it's a 10 year old kernel, but the code is actually parsing this one:

              [stack]
                     The initial process's (also known as the main
                     thread's) stack.

Which doesn't have the TID number in it.

Please make it clear which one you're looking for.

This information is in maps as well as smaps, but having read the code, we're using the same logic to parse the header of a smaps entry so it should be fine (and when you were testing it, it was likely reading from smaps anyway).

if (llvm::StringRef(name) == "[stack]")
region.SetIsStackMemory(eLazyBoolYes);
else if (llvm::StringRef(name) == "[heap]")
region.SetIsStackMemory(eLazyBoolNo);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a few cycles, but I'd put an explicit else here. That's a stronger signal than a comment.

@felipepiovezan

Copy link
Copy Markdown
Contributor Author

What you quoted is confusing me because you've quoted a field removed in 4.4, and maybe you have a reason to support that even though it's a 10 year old kernel, but the code is actually parsing this one:

              [stack]
                     The initial process's (also known as the main
                     thread's) stack.

Which doesn't have the TID number in it.

Please make it clear which one you're looking for.

This information is in maps as well as smaps, but having read the code, we're using the same logic to parse the header of a smaps entry so it should be fine (and when you were testing it, it was likely reading from smaps anyway).

Yup, good catch. I really meant just [stack] and was trying to say that this will only work for the main thread beacuse the other field was removed. Let me rephrase things.

@felipepiovezan felipepiovezan left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed review comments

if (is_stack == eLazyBoolYes)
response.PutCString("type:stack;");
else if (is_stack == eLazyBoolNo)
response.PutCString("type:heap;");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You can do "don't know" with lazybool, it's an alias to calculate:

TIL!

@felipepiovezan

Copy link
Copy Markdown
Contributor Author

(Updated the PR description, which will be used as the final commit message)

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

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

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 33365 tests passed
  • 900 tests skipped

✅ The build succeeded and all tests passed.

@DavidSpickett

Copy link
Copy Markdown
Contributor

got removed in older kernels:

In newer kernels you mean.

@felipepiovezan

felipepiovezan commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

got removed in older kernels:

In newer kernels you mean.

Oops, yes. Fixed

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.

3 participants