[clang] [lldb] [lldb] Support arm64e C++ vtable pointer signing (PR #187611)

Jonas Devlieghere via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 20 07:42:49 PDT 2026


https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/187611

>From 0fcddd5e4a441fe4e1bf76f003d61025099f3bf1 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Thu, 19 Mar 2026 16:42:30 -0700
Subject: [PATCH 1/3] [lldb] Support arm64e C++ vtable pointer signing

When targeting arm64e, vtable pointers are signed with a discriminator
that incorporates the object's address (PointerAuthVTPtrAddressDiscrimination)
and class type (PointerAuthVTPtrTypeDiscrimination).

I had to make a small change to clang, specifically in
getPointerAuthDeclDiscriminator(). Previously, that was computing the
discriminator based on getMangledName(). The latter returns the
AsmLabelAttr, which for functions imported by lldb, is prefixed with
`$__lldb_func`, causing a different discriminator to be generated.
---
 clang/lib/CodeGen/CGPointerAuth.cpp           | 18 ++++++-
 .../Clang/ClangExpressionParser.cpp           |  2 +
 .../expression/ptrauth-vtable/Makefile        |  8 ++++
 .../TestPtrAuthVTableExpressions.py           | 48 +++++++++++++++++++
 .../expression/ptrauth-vtable/main.cpp        | 27 +++++++++++
 5 files changed, 101 insertions(+), 2 deletions(-)
 create mode 100644 lldb/test/API/commands/expression/ptrauth-vtable/Makefile
 create mode 100644 lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py
 create mode 100644 lldb/test/API/commands/expression/ptrauth-vtable/main.cpp

diff --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp
index 84b5c86e69a57..a083d10e9dbec 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -11,6 +11,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "CGCXXABI.h"
 #include "CodeGenFunction.h"
 #include "CodeGenModule.h"
 #include "clang/CodeGen/CodeGenABITypes.h"
@@ -62,8 +63,21 @@ CodeGenModule::getPointerAuthDeclDiscriminator(GlobalDecl Declaration) {
   uint16_t &EntityHash = PtrAuthDiscriminatorHashes[Declaration];
 
   if (EntityHash == 0) {
-    StringRef Name = getMangledName(Declaration);
-    EntityHash = llvm::getPointerAuthStableSipHash(Name);
+    const auto *ND = cast<NamedDecl>(Declaration.getDecl());
+    // If the declaration has an AsmLabelAttr (e.g., LLDB expression evaluator
+    // attaches one to map imported decls to debuggee symbols), the asm label
+    // would be used as the mangled name, producing a wrong discriminator.
+    // Compute the real C++ mangled name instead so the discriminator matches
+    // what the original translation unit used.
+    if (ND->hasAttr<AsmLabelAttr>()) {
+      SmallString<256> Buffer;
+      llvm::raw_svector_ostream Out(Buffer);
+      getCXXABI().getMangleContext().mangleCXXName(Declaration, Out);
+      EntityHash = llvm::getPointerAuthStableSipHash(Out.str());
+    } else {
+      StringRef Name = getMangledName(Declaration);
+      EntityHash = llvm::getPointerAuthStableSipHash(Name);
+    }
   }
 
   return EntityHash;
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 0956406960b23..7955fffa26c5a 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -729,6 +729,8 @@ static void SetPointerAuthOptionsForArm64e(LangOptions &lang_opts) {
   lang_opts.PointerAuthIntrinsics = true;
   lang_opts.PointerAuthCalls = true;
   lang_opts.PointerAuthReturns = true;
+  lang_opts.PointerAuthVTPtrAddressDiscrimination = true;
+  lang_opts.PointerAuthVTPtrTypeDiscrimination = true;
 }
 
 ClangExpressionParser::ClangExpressionParser(
diff --git a/lldb/test/API/commands/expression/ptrauth-vtable/Makefile b/lldb/test/API/commands/expression/ptrauth-vtable/Makefile
new file mode 100644
index 0000000000000..3c6bc2dd007e1
--- /dev/null
+++ b/lldb/test/API/commands/expression/ptrauth-vtable/Makefile
@@ -0,0 +1,8 @@
+CXX_SOURCES := main.cpp
+
+override ARCH := arm64e
+
+# We need an arm64e stblib.
+USE_SYSTEM_STDLIB := 1
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py b/lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py
new file mode 100644
index 0000000000000..07a806dd53355
--- /dev/null
+++ b/lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py
@@ -0,0 +1,48 @@
+"""
+VTable pointers are signed with a discriminator that incorporates the object's
+address (PointerAuthVTPtrAddressDiscrimination) and class type (
+PointerAuthVTPtrTypeDiscrimination).
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestPtrAuthVTableExpressions(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessArm64eSupported
+    def test_virtual_call_on_debuggee_object(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp", False)
+        )
+
+        self.expect_expr("d.value()", result_type="int", result_value="20")
+        self.expect_expr("od.value()", result_type="int", result_value="30")
+
+    @skipUnlessArm64eSupported
+    def test_virtual_call_through_base_pointer(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp", False)
+        )
+
+        self.expect_expr(
+            "base_ptr->value()", result_type="int", result_value="20"
+        )
+
+    @skipUnlessArm64eSupported
+    def test_virtual_call_via_helper(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp", False)
+        )
+
+        self.expect_expr("call_value(&d)", result_type="int", result_value="20")
+        self.expect_expr("call_value(&od)", result_type="int", result_value="30")
+        self.expect_expr(
+            "call_value(base_ptr)", result_type="int", result_value="20"
+        )
diff --git a/lldb/test/API/commands/expression/ptrauth-vtable/main.cpp b/lldb/test/API/commands/expression/ptrauth-vtable/main.cpp
new file mode 100644
index 0000000000000..d9dec9b9a6a41
--- /dev/null
+++ b/lldb/test/API/commands/expression/ptrauth-vtable/main.cpp
@@ -0,0 +1,27 @@
+#include <cstdio>
+
+class Base {
+public:
+  virtual int value() { return 10; }
+  virtual ~Base() = default;
+};
+
+class Derived : public Base {
+public:
+  int value() override { return 20; }
+};
+
+class OtherDerived : public Base {
+public:
+  int value() override { return 30; }
+};
+
+int call_value(Base *obj) { return obj->value(); }
+
+int main() {
+  Derived d;
+  OtherDerived od;
+  Base *base_ptr = &d;
+  printf("%d %d %d\n", d.value(), od.value(), base_ptr->value());
+  return 0; // break here
+}

>From 0195449ada8535bb22d3fd1a1c09f976b54eeaf0 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Thu, 19 Mar 2026 17:12:57 -0700
Subject: [PATCH 2/3] Formatting

---
 .../ptrauth-vtable/TestPtrAuthVTableExpressions.py        | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py b/lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py
index 07a806dd53355..92a30b6e6548f 100644
--- a/lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py
+++ b/lldb/test/API/commands/expression/ptrauth-vtable/TestPtrAuthVTableExpressions.py
@@ -30,9 +30,7 @@ def test_virtual_call_through_base_pointer(self):
             self, "// break here", lldb.SBFileSpec("main.cpp", False)
         )
 
-        self.expect_expr(
-            "base_ptr->value()", result_type="int", result_value="20"
-        )
+        self.expect_expr("base_ptr->value()", result_type="int", result_value="20")
 
     @skipUnlessArm64eSupported
     def test_virtual_call_via_helper(self):
@@ -43,6 +41,4 @@ def test_virtual_call_via_helper(self):
 
         self.expect_expr("call_value(&d)", result_type="int", result_value="20")
         self.expect_expr("call_value(&od)", result_type="int", result_value="30")
-        self.expect_expr(
-            "call_value(base_ptr)", result_type="int", result_value="20"
-        )
+        self.expect_expr("call_value(base_ptr)", result_type="int", result_value="20")

>From 0b04168d62d42129899159a853e98f1380deac0e Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 20 Mar 2026 07:42:33 -0700
Subject: [PATCH 3/3] Implement Michael's suggestion of checking the prefix

---
 clang/lib/CodeGen/CGPointerAuth.cpp | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp
index a083d10e9dbec..5e0ef91f51ffb 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -64,12 +64,13 @@ CodeGenModule::getPointerAuthDeclDiscriminator(GlobalDecl Declaration) {
 
   if (EntityHash == 0) {
     const auto *ND = cast<NamedDecl>(Declaration.getDecl());
-    // If the declaration has an AsmLabelAttr (e.g., LLDB expression evaluator
-    // attaches one to map imported decls to debuggee symbols), the asm label
-    // would be used as the mangled name, producing a wrong discriminator.
-    // Compute the real C++ mangled name instead so the discriminator matches
-    // what the original translation unit used.
-    if (ND->hasAttr<AsmLabelAttr>()) {
+    constexpr static llvm::StringLiteral LLDBLabelPrefix = "$__lldb_func:";
+    if (ND->hasAttr<AsmLabelAttr>() &&
+        ND->getAttr<AsmLabelAttr>()->getLabel().starts_with(LLDBLabelPrefix)) {
+      // If the declaration comes from LLDB, the asm label has a prefix that
+      // would producing a different discriminator. Compute the real C++ mangled
+      // name instead so the discriminator matches what the original translation
+      // unit used.
       SmallString<256> Buffer;
       llvm::raw_svector_ostream Out(Buffer);
       getCXXABI().getMangleContext().mangleCXXName(Declaration, Out);



More information about the cfe-commits mailing list