[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