[Lldb-commits] [lldb] [lldb] Support PtrAuth in the expression evaluator (PR #186001)

Jonas Devlieghere via lldb-commits lldb-commits at lists.llvm.org
Fri Mar 13 10:56:42 PDT 2026


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

>From 49cb54dca1e791d291cd3f57025452d7c2473f0b Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Wed, 11 Mar 2026 19:33:22 -0700
Subject: [PATCH 1/4] [lldb] Support PtrAuth in the expression evaluator

---
 .../ExpressionParser/Clang/CMakeLists.txt     |   3 +-
 .../Clang/ClangExpressionParser.cpp           |  27 ++-
 .../Clang/ClangExpressionParser.h             |  10 +-
 .../Clang/ClangFunctionCaller.cpp             |   8 +-
 .../ExpressionParser/Clang/IRForTarget.cpp    |  14 +-
 .../ExpressionParser/Clang/IRForTarget.h      |   3 +
 .../Clang/InjectPointerSigningFixups.cpp      | 182 ++++++++++++++++++
 .../Clang/InjectPointerSigningFixups.h        |  27 +++
 .../expression/ptr-auth-fixups/Makefile       |   5 +
 .../ptr-auth-fixups/TestPtrAuthFixups.py      |  29 +++
 .../expression/ptr-auth-fixups/main.c         |   9 +
 11 files changed, 311 insertions(+), 6 deletions(-)
 create mode 100644 lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
 create mode 100644 lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.h
 create mode 100644 lldb/test/API/commands/expression/ptr-auth-fixups/Makefile
 create mode 100644 lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py
 create mode 100644 lldb/test/API/commands/expression/ptr-auth-fixups/main.c

diff --git a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
index fe56d5072ce3a..5a4a161e34d53 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
+++ b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
@@ -21,8 +21,9 @@ add_lldb_library(lldbPluginExpressionParserClang
   ClangUtilityFunction.cpp
   CppModuleConfiguration.cpp
   CxxModuleHandler.cpp
-  IRForTarget.cpp
   IRDynamicChecks.cpp
+  IRForTarget.cpp
+  InjectPointerSigningFixups.cpp
   NameSearchContext.cpp
 
   DEPENDS
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 176eddf65e911..18af934ddf712 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -728,7 +728,8 @@ static void SetupImportStdModuleLangOpts(CompilerInstance &compiler,
 ClangExpressionParser::ClangExpressionParser(
     ExecutionContextScope *exe_scope, Expression &expr,
     bool generate_debug_info, DiagnosticManager &diagnostic_manager,
-    std::vector<std::string> include_directories, std::string filename)
+    std::vector<std::string> include_directories, std::string filename,
+    bool force_disable_ptrauth_codegen)
     : ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(),
       m_pp_callbacks(nullptr),
       m_include_directories(std::move(include_directories)),
@@ -767,6 +768,16 @@ ClangExpressionParser::ClangExpressionParser(
   // appropriate for most situations.
   SetupTargetOpts(*m_compiler, *target_sp);
 
+  const llvm::Triple triple = target_sp->GetArchitecture().GetTriple();
+  const bool enable_ptrauth =
+      triple.isArm64e() && !force_disable_ptrauth_codegen;
+
+  if (enable_ptrauth) {
+    auto &lang_opts = m_compiler->getLangOpts();
+    lang_opts.PointerAuthIntrinsics = true;
+    lang_opts.PointerAuthCalls = true;
+  }
+
   // 3. Create and install the target on the compiler.
   m_compiler->createDiagnostics();
   // Limit the number of error diagnostics we emit.
@@ -794,6 +805,12 @@ ClangExpressionParser::ClangExpressionParser(
 
   // 4. Set language options.
   SetupLangOpts(*m_compiler, *exe_scope, expr, diagnostic_manager);
+  if (enable_ptrauth) {
+    auto &lang_opts = m_compiler->getLangOpts();
+    lang_opts.PointerAuthIntrinsics = true;
+    lang_opts.PointerAuthCalls = true;
+  }
+
   auto *clang_expr = dyn_cast<ClangUserExpression>(&m_expr);
   if (clang_expr && clang_expr->DidImportCxxModules()) {
     LLDB_LOG(log, "Adding lang options for importing C++ modules");
@@ -811,6 +828,12 @@ ClangExpressionParser::ClangExpressionParser(
   else
     m_compiler->getCodeGenOpts().setDebugInfo(codegenoptions::NoDebugInfo);
 
+  if (enable_ptrauth) {
+    PointerAuthOptions &ptrauth_opts = m_compiler->getCodeGenOpts().PointerAuth;
+    clang::CompilerInvocation::setDefaultPointerAuthOptions(
+        ptrauth_opts, m_compiler->getLangOpts(), triple);
+  }
+
   // Disable some warnings.
   SetupDefaultClangDiagnostics(*m_compiler);
 
@@ -1542,7 +1565,7 @@ lldb_private::Status ClangExpressionParser::DoPrepareForExecution(
     StreamString error_stream;
     IRForTarget ir_for_target(decl_map, m_expr.NeedsVariableResolution(),
                               *execution_unit_sp, error_stream,
-                              function_name.AsCString());
+                              execution_policy, function_name.AsCString());
 
     if (!ir_for_target.runOnModule(*execution_unit_sp->GetModule())) {
       err = Status(error_stream.GetString().str());
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
index 734ad51c9646e..8b15a30c07610 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
@@ -63,11 +63,19 @@ class ClangExpressionParser : public ExpressionParser {
   /// @param[in] filename
   ///     Name of the source file that should be used when rendering
   ///     diagnostics (i.e. errors, warnings or notes from Clang).
+  ///
+  /// \param[in] force_disable_ptrauth_codegen
+  ///     (rare) force pointer authentication code generation to be
+  ///     disabled for this expression.  Normally the decision of
+  ///     whether to generate ptrauth codegen or not is determined
+  ///     by the ArchSpec or ABI; this is for overriding the normal
+  ///     codegen.
   ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
                         bool generate_debug_info,
                         DiagnosticManager &diagnostic_manager,
                         std::vector<std::string> include_directories = {},
-                        std::string filename = "<clang expression>");
+                        std::string filename = "<clang expression>",
+                        bool force_disable_ptrauth_codegen = false);
 
   /// Destructor
   ~ClangExpressionParser() override;
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp
index d2db319afb7a0..76c24454ffbbf 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp
@@ -188,9 +188,15 @@ ClangFunctionCaller::CompileFunction(lldb::ThreadSP thread_to_use_sp,
 
   lldb::ProcessSP jit_process_sp(m_jit_process_wp.lock());
   if (jit_process_sp) {
+    // We will be passing in unauthenticated function addresses to the
+    // FunctionCaller code, so we need to force disable pointer auth
+    // codegen for this one code snippet.
+    const bool force_disable_ptrauth_codegen = true;
     const bool generate_debug_info = true;
     auto *clang_parser = new ClangExpressionParser(
-        jit_process_sp.get(), *this, generate_debug_info, diagnostic_manager);
+        jit_process_sp.get(), *this, generate_debug_info, diagnostic_manager,
+        std::vector<std::string>(), "<clang expression>",
+        force_disable_ptrauth_codegen);
     num_errors = clang_parser->Parse(diagnostic_manager);
     m_parser.reset(clang_parser);
   } else {
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
index a343766ce9c4f..2789f0a10b5ad 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "IRForTarget.h"
+#include "InjectPointerSigningFixups.h"
 
 #include "ClangExpressionDeclMap.h"
 #include "ClangUtil.h"
@@ -73,10 +74,11 @@ IRForTarget::IRForTarget(lldb_private::ClangExpressionDeclMap *decl_map,
                          bool resolve_vars,
                          lldb_private::IRExecutionUnit &execution_unit,
                          lldb_private::Stream &error_stream,
+                         lldb_private::ExecutionPolicy execution_policy,
                          const char *func_name)
     : m_resolve_vars(resolve_vars), m_func_name(func_name),
       m_decl_map(decl_map), m_error_stream(error_stream),
-      m_execution_unit(execution_unit),
+      m_execution_unit(execution_unit), m_policy(execution_policy),
       m_entry_instruction_finder(FindEntryInstruction) {}
 
 /* Handy utility functions used at several places in the code */
@@ -1758,6 +1760,16 @@ bool IRForTarget::runOnModule(Module &llvm_module) {
     }
   }
 
+  ////////////////////////////////////////////////////////////////////////
+  // Run function-level passes that only make sense on the main function
+  //
+  if (auto Err =
+          lldb_private::InjectPointerSigningFixupCode(*m_module, m_policy)) {
+    LLDB_LOGF(log, "InsertPointerSigningFixups() failed:\n\"%s\"",
+              toString(std::move(Err)).c_str());
+    return false;
+  }
+
   if (log && log->GetVerbose()) {
     std::string s;
     raw_string_ostream oss(s);
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.h b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.h
index 45027fcd6fa49..d710bdc950935 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.h
@@ -85,6 +85,7 @@ class IRForTarget {
   IRForTarget(lldb_private::ClangExpressionDeclMap *decl_map, bool resolve_vars,
               lldb_private::IRExecutionUnit &execution_unit,
               lldb_private::Stream &error_stream,
+              lldb_private::ExecutionPolicy execution_policy,
               const char *func_name = "$__lldb_expr");
 
   /// Run this IR transformer on a single module
@@ -352,6 +353,8 @@ class IRForTarget {
   /// ASTResultSynthesizer::SynthesizeBodyResult)
   bool m_result_is_pointer = false;
 
+  lldb_private::ExecutionPolicy m_policy;
+
   class FunctionValueCache {
   public:
     typedef std::function<llvm::Value *(llvm::Function *)> Maker;
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
new file mode 100644
index 0000000000000..fd28262a489eb
--- /dev/null
+++ b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
@@ -0,0 +1,182 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "InjectPointerSigningFixups.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/TargetParser/Triple.h"
+
+using namespace llvm;
+
+namespace {
+
+struct PtrAuthFixup {
+  GlobalVariable *GV;
+  ConstantPtrAuth *CPA;
+  SmallVector<unsigned, 4> Indices;
+};
+
+/// Recursively walk a constant looking for ConstantPtrAuth expressions.
+/// When found, record the global variable containing it and the index path
+/// to reach it within the initializer.
+void findPtrAuth(Constant *C, GlobalVariable &GV,
+                 SmallVector<unsigned, 4> &Indices,
+                 SmallVectorImpl<PtrAuthFixup> &Fixups) {
+  if (auto *CPA = dyn_cast<ConstantPtrAuth>(C)) {
+    Fixups.push_back({&GV, CPA, Indices});
+    return;
+  }
+  for (unsigned I = 0, E = C->getNumOperands(); I != E; ++I) {
+    if (auto *COp = dyn_cast<Constant>(C->getOperand(I))) {
+      Indices.push_back(I);
+      findPtrAuth(COp, GV, Indices, Fixups);
+      Indices.pop_back();
+    }
+  }
+}
+
+} // namespace
+
+namespace lldb_private {
+
+Error InjectPointerSigningFixupCode(llvm::Module &M,
+                                    ExecutionPolicy execution_policy) {
+  // If we cannot execute fixups, don't insert them.
+  if (execution_policy == eExecutionPolicyNever)
+    return Error::success();
+
+  llvm::Triple T(M.getTargetTriple());
+
+  // Bail out if we don't need pointer signing fixups.
+  if (!T.isArm64e())
+    return Error::success();
+
+  // Collect all ConstantPtrAuth expressions in global initializers.
+  SmallVector<PtrAuthFixup, 8> Fixups;
+  for (auto &G : M.globals()) {
+    if (!G.hasInitializer())
+      continue;
+    SmallVector<unsigned, 4> Indices;
+    findPtrAuth(G.getInitializer(), G, Indices, Fixups);
+  }
+
+  if (Fixups.empty())
+    return Error::success();
+
+  // Set up types and intrinsics.
+  auto &Ctx = M.getContext();
+  Type *Int32Ty = Type::getInt32Ty(Ctx);
+  Type *IntPtrTy = Type::getInt64Ty(Ctx);
+  Function *BlendIntrinsic =
+      Intrinsic::getOrInsertDeclaration(&M, Intrinsic::ptrauth_blend);
+  Function *SignIntrinsic =
+      Intrinsic::getOrInsertDeclaration(&M, Intrinsic::ptrauth_sign);
+
+  // Create the fixup function.
+  Function *FixupFn =
+      Function::Create(FunctionType::get(Type::getVoidTy(Ctx), false),
+                       GlobalValue::InternalLinkage, "arm64e", &M);
+  FixupFn->insert(FixupFn->end(), BasicBlock::Create(Ctx));
+  IRBuilder<> B(&FixupFn->back());
+
+  for (auto &Fixup : Fixups) {
+    GlobalVariable *GV = Fixup.GV;
+    ConstantPtrAuth *CPA = Fixup.CPA;
+
+    // Null pointers must remain zero.
+    if (isa<ConstantPointerNull>(CPA->getPointer())) {
+      CPA->replaceAllUsesWith(CPA->getPointer());
+      continue;
+    }
+
+    // Build a GEP to the location of the ConstantPtrAuth within the global.
+    Value *Loc;
+    if (Fixup.Indices.empty()) {
+      Loc = GV;
+    } else {
+      SmallVector<Value *, 4> GEPIndices;
+      GEPIndices.push_back(ConstantInt::get(Int32Ty, 0));
+      for (unsigned Idx : Fixup.Indices)
+        GEPIndices.push_back(ConstantInt::get(Int32Ty, Idx));
+      Loc = B.CreateGEP(GV->getValueType(), GV, GEPIndices);
+    }
+
+    Type *PtrTy = CPA->getType();
+
+    // Load the raw (unsigned) pointer.
+    Value *RawPtr = B.CreateLoad(PtrTy, Loc);
+
+    // Compute the discriminator, blending with the address if needed.
+    Value *Disc = CPA->getDiscriminator();
+    if (CPA->hasAddressDiscriminator())
+      Disc = B.CreateCall(BlendIntrinsic,
+                          {B.CreatePointerCast(Loc, IntPtrTy), Disc});
+
+    // Sign the pointer.
+    Value *SignedPtr =
+        B.CreateCall(SignIntrinsic, {B.CreatePointerCast(RawPtr, IntPtrTy),
+                                     CPA->getKey(), Disc});
+
+    // Store the signed pointer back.
+    B.CreateStore(B.CreateBitOrPointerCast(SignedPtr, PtrTy), Loc);
+
+    // Replace the ConstantPtrAuth in the initializer with the unsigned pointer.
+    CPA->replaceAllUsesWith(CPA->getPointer());
+  }
+
+  // Close off the fixup function.
+  B.CreateRetVoid();
+
+  // Update the global ctors list to call the pointer fixup function first.
+  auto *UInt8PtrTy = PointerType::getUnqual(Ctx);
+  StructType *CtorType =
+      StructType::get(Ctx, {Int32Ty, FixupFn->getType(), UInt8PtrTy});
+  Constant *PtrFixupCtor =
+      ConstantStruct::get(CtorType, {ConstantInt::get(Int32Ty, 0), FixupFn,
+                                     Constant::getNullValue(UInt8PtrTy)});
+
+  const char *LLVMGlobalCtorsName = "llvm.global_ctors";
+  GlobalVariable *OldCtorList = M.getNamedGlobal(LLVMGlobalCtorsName);
+  SmallVector<Constant *, 4> CtorListArgs;
+  CtorListArgs.push_back(PtrFixupCtor);
+
+  if (OldCtorList) {
+    // If the old ctors list has any uses then bail out: we do not know how to
+    // rewrite them.
+    if (OldCtorList->getNumUses() != 0) {
+      std::string ErrStr;
+      raw_string_ostream S(ErrStr);
+      S << "Global ctors variable has users, so can not be rewritten to "
+           "include pointer fixups: '"
+        << *OldCtorList << "'";
+      return make_error<StringError>(S.str(), inconvertibleErrorCode());
+    }
+
+    for (auto &Op : OldCtorList->getInitializer()->operands())
+      CtorListArgs.push_back(cast<Constant>(Op.get()));
+  }
+
+  ArrayType *CtorListType = ArrayType::get(CtorType, CtorListArgs.size());
+  Constant *CtorListInit = ConstantArray::get(CtorListType, CtorListArgs);
+
+  GlobalVariable *NewCtorList = new GlobalVariable(
+      M, CtorListType, false, GlobalValue::AppendingLinkage, CtorListInit);
+
+  if (OldCtorList) {
+    NewCtorList->takeName(OldCtorList);
+    OldCtorList->eraseFromParent();
+  } else
+    NewCtorList->setName(LLVMGlobalCtorsName);
+
+  return Error::success();
+}
+
+} // namespace lldb_private
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.h b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.h
new file mode 100644
index 0000000000000..291d286b0748c
--- /dev/null
+++ b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_INJECTPOINTERSIGNINGFIXUPS_H
+#define LLDB_SOURCE_PLUGINS_EXPRESSIONPARSER_CLANG_INJECTPOINTERSIGNINGFIXUPS_H
+
+#include "lldb/lldb-private-enumerations.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm {
+class Function;
+class Module;
+} // namespace llvm
+
+namespace lldb_private {
+
+llvm::Error InjectPointerSigningFixupCode(llvm::Module &M,
+                                          ExecutionPolicy execution_policy);
+
+} // namespace lldb_private
+
+#endif
diff --git a/lldb/test/API/commands/expression/ptr-auth-fixups/Makefile b/lldb/test/API/commands/expression/ptr-auth-fixups/Makefile
new file mode 100644
index 0000000000000..ac50baa81423e
--- /dev/null
+++ b/lldb/test/API/commands/expression/ptr-auth-fixups/Makefile
@@ -0,0 +1,5 @@
+C_SOURCES := main.c
+
+override ARCH := arm64e
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py b/lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py
new file mode 100644
index 0000000000000..a22030c1f5154
--- /dev/null
+++ b/lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py
@@ -0,0 +1,29 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestPtrAuthFixups(TestBase):
+    @skipUnlessDarwin
+    @skipUnlessArch("arm64")
+    def test_static_function_pointer(self):
+        """Test that a static function pointer initialized in an expression
+        gets correctly signed on arm64e via the pointer signing fixup pass."""
+        self.build()
+
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.c", False)
+        )
+
+        self.expect_expr(
+            "static int (*fp)(int, int) = &add; fp(5, 6);",
+            result_type="int",
+            result_value="11",
+        )
+
+        self.expect_expr(
+            "static int (*fp)(int, int) = &mul; fp(4, 5);",
+            result_type="int",
+            result_value="20",
+        )
diff --git a/lldb/test/API/commands/expression/ptr-auth-fixups/main.c b/lldb/test/API/commands/expression/ptr-auth-fixups/main.c
new file mode 100644
index 0000000000000..a7db8e5df83db
--- /dev/null
+++ b/lldb/test/API/commands/expression/ptr-auth-fixups/main.c
@@ -0,0 +1,9 @@
+#include <stdio.h>
+
+int add(int a, int b) { return a + b; }
+int mul(int a, int b) { return a * b; }
+
+int main(void) {
+  printf("%d %d\n", add(2, 3), mul(4, 5));
+  return 0; // break here
+}

>From 2ea75d994e7964695f9792057b514d951c2d0b7a Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Thu, 12 Mar 2026 09:58:50 -0700
Subject: [PATCH 2/4] Address David's feedback

---
 .../Clang/ClangExpressionParser.h             |  9 ++++---
 .../ExpressionParser/Clang/IRForTarget.cpp    | 22 ++++-------------
 .../Clang/InjectPointerSigningFixups.cpp      | 24 +++++++++----------
 .../ptr-auth-fixups/TestPtrAuthFixups.py      |  7 +++---
 4 files changed, 24 insertions(+), 38 deletions(-)

diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
index 8b15a30c07610..a50764a91e210 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
@@ -65,11 +65,10 @@ class ClangExpressionParser : public ExpressionParser {
   ///     diagnostics (i.e. errors, warnings or notes from Clang).
   ///
   /// \param[in] force_disable_ptrauth_codegen
-  ///     (rare) force pointer authentication code generation to be
-  ///     disabled for this expression.  Normally the decision of
-  ///     whether to generate ptrauth codegen or not is determined
-  ///     by the ArchSpec or ABI; this is for overriding the normal
-  ///     codegen.
+  ///     Force pointer authentication code generation to be disabled for this
+  ///     expression.  Normally the decision of whether to generate ptrauth
+  ///     codegen or not is determined by the ArchSpec or ABI; this is for
+  ///     overriding the normal codegen.
   ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
                         bool generate_debug_info,
                         DiagnosticManager &diagnostic_manager,
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
index 2789f0a10b5ad..373cc5e1df4b1 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
@@ -1649,10 +1649,7 @@ bool IRForTarget::runOnModule(Module &llvm_module) {
     }
   }
 
-  ////////////////////////////////////////////////////////////
-  // Replace $__lldb_expr_result with a persistent variable
-  //
-
+  // Replace $__lldb_expr_result with a persistent variable.
   if (main_function) {
     if (!CreateResultVariable(*main_function)) {
       LLDB_LOG(log, "CreateResultVariable() failed");
@@ -1701,10 +1698,7 @@ bool IRForTarget::runOnModule(Module &llvm_module) {
     }
   }
 
-  ///////////////////////////////////////////////////////////////////////////////
   // Fix all Objective-C constant strings to use NSStringWithCString:encoding:
-  //
-
   if (!RewriteObjCConstStrings()) {
     LLDB_LOG(log, "RewriteObjCConstStrings() failed");
 
@@ -1738,10 +1732,7 @@ bool IRForTarget::runOnModule(Module &llvm_module) {
     }
   }
 
-  ////////////////////////////////////////////////////////////////////////
-  // Run function-level passes that only make sense on the main function
-  //
-
+  // Run function-level passes that only make sense on the main function.
   if (main_function) {
     if (!ResolveExternals(*main_function)) {
       LLDB_LOG(log, "ResolveExternals() failed");
@@ -1760,13 +1751,10 @@ bool IRForTarget::runOnModule(Module &llvm_module) {
     }
   }
 
-  ////////////////////////////////////////////////////////////////////////
-  // Run function-level passes that only make sense on the main function
-  //
-  if (auto Err =
+  // Run architecture specific module-level passes.
+  if (llvm::Error error =
           lldb_private::InjectPointerSigningFixupCode(*m_module, m_policy)) {
-    LLDB_LOGF(log, "InsertPointerSigningFixups() failed:\n\"%s\"",
-              toString(std::move(Err)).c_str());
+    LLDB_LOG_ERROR(log, std::move(error), "InsertPointerSigningFixups() failed: {0}");
     return false;
   }
 
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
index fd28262a489eb..123b8274af8bc 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
@@ -17,19 +17,19 @@
 using namespace llvm;
 
 namespace {
-
 struct PtrAuthFixup {
   GlobalVariable *GV;
   ConstantPtrAuth *CPA;
-  SmallVector<unsigned, 4> Indices;
+  SmallVector<unsigned> Indices;
 };
+} // namespace
 
 /// Recursively walk a constant looking for ConstantPtrAuth expressions.
-/// When found, record the global variable containing it and the index path
-/// to reach it within the initializer.
-void findPtrAuth(Constant *C, GlobalVariable &GV,
-                 SmallVector<unsigned, 4> &Indices,
-                 SmallVectorImpl<PtrAuthFixup> &Fixups) {
+/// When found, record the global variable containing the ConstantPtrAuth and
+/// the index path to reach it within the initializer.
+static void findPtrAuth(Constant *C, GlobalVariable &GV,
+                        SmallVector<unsigned> &Indices,
+                        SmallVectorImpl<PtrAuthFixup> &Fixups) {
   if (auto *CPA = dyn_cast<ConstantPtrAuth>(C)) {
     Fixups.push_back({&GV, CPA, Indices});
     return;
@@ -43,8 +43,6 @@ void findPtrAuth(Constant *C, GlobalVariable &GV,
   }
 }
 
-} // namespace
-
 namespace lldb_private {
 
 Error InjectPointerSigningFixupCode(llvm::Module &M,
@@ -60,11 +58,11 @@ Error InjectPointerSigningFixupCode(llvm::Module &M,
     return Error::success();
 
   // Collect all ConstantPtrAuth expressions in global initializers.
-  SmallVector<PtrAuthFixup, 8> Fixups;
+  SmallVector<PtrAuthFixup> Fixups;
   for (auto &G : M.globals()) {
     if (!G.hasInitializer())
       continue;
-    SmallVector<unsigned, 4> Indices;
+    SmallVector<unsigned> Indices;
     findPtrAuth(G.getInitializer(), G, Indices, Fixups);
   }
 
@@ -102,7 +100,7 @@ Error InjectPointerSigningFixupCode(llvm::Module &M,
     if (Fixup.Indices.empty()) {
       Loc = GV;
     } else {
-      SmallVector<Value *, 4> GEPIndices;
+      SmallVector<Value *> GEPIndices;
       GEPIndices.push_back(ConstantInt::get(Int32Ty, 0));
       for (unsigned Idx : Fixup.Indices)
         GEPIndices.push_back(ConstantInt::get(Int32Ty, Idx));
@@ -145,7 +143,7 @@ Error InjectPointerSigningFixupCode(llvm::Module &M,
 
   const char *LLVMGlobalCtorsName = "llvm.global_ctors";
   GlobalVariable *OldCtorList = M.getNamedGlobal(LLVMGlobalCtorsName);
-  SmallVector<Constant *, 4> CtorListArgs;
+  SmallVector<Constant *> CtorListArgs;
   CtorListArgs.push_back(PtrFixupCtor);
 
   if (OldCtorList) {
diff --git a/lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py b/lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py
index a22030c1f5154..96b9b1ff33dec 100644
--- a/lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py
+++ b/lldb/test/API/commands/expression/ptr-auth-fixups/TestPtrAuthFixups.py
@@ -6,10 +6,11 @@
 
 class TestPtrAuthFixups(TestBase):
     @skipUnlessDarwin
-    @skipUnlessArch("arm64")
+    @skipUnlessArch("arm64e")
     def test_static_function_pointer(self):
-        """Test that a static function pointer initialized in an expression
-        gets correctly signed on arm64e via the pointer signing fixup pass."""
+        """On arm64e, function pointers are automatically signed (PAC).
+        Test that we can call a function through a function pointer from the
+        expression evaluator, which requires "fixing up" the pointer signing."""
         self.build()
 
         lldbutil.run_to_source_breakpoint(

>From 608ff615eca38af0040dcda7391017c933b242c1 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Thu, 12 Mar 2026 10:02:21 -0700
Subject: [PATCH 3/4] Reformat

---
 lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
index 373cc5e1df4b1..49f8f4fd5581e 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
@@ -1754,7 +1754,8 @@ bool IRForTarget::runOnModule(Module &llvm_module) {
   // Run architecture specific module-level passes.
   if (llvm::Error error =
           lldb_private::InjectPointerSigningFixupCode(*m_module, m_policy)) {
-    LLDB_LOG_ERROR(log, std::move(error), "InsertPointerSigningFixups() failed: {0}");
+    LLDB_LOG_ERROR(log, std::move(error),
+                   "InsertPointerSigningFixups() failed: {0}");
     return false;
   }
 

>From 39cfe159f1e73d44410003b5e4b15cbdbdf4c9aa Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 13 Mar 2026 10:56:25 -0700
Subject: [PATCH 4/4] Add comment requested by Michael

---
 .../Clang/InjectPointerSigningFixups.cpp      | 30 ++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
index 123b8274af8bc..11c44334519b3 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/InjectPointerSigningFixups.cpp
@@ -5,6 +5,34 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
+//
+// On arm64e, Clang emits ConstantPtrAuth expressions in global initializers
+// to represent signed pointers. These are normally resolved by the static
+// linker, but LLDB's JIT does not run the linker, so they must be resolved
+// manually. This pass replaces each ConstantPtrAuth in a global initializer
+// with the unsigned pointer and emits a constructor function that signs the
+// pointer at runtime using the ptrauth intrinsics.
+//
+// Example: given "static int (*fp)(int, int) = &mul;", Clang emits:
+//
+//   @fp = internal global ptr ptrauth (ptr @mul, i32 0)
+//
+// This pass transforms it into:
+//
+//   @fp = internal global ptr @mul
+//   @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }]
+//       [{ i32, ptr, ptr } { i32 0, ptr @ptrauth.sign, ptr null }]
+//
+//   define internal void @ptrauth.sign() {
+//     %1 = load ptr, ptr @fp, align 8
+//     %2 = ptrtoint ptr %1 to i64
+//     %3 = call i64 @llvm.ptrauth.sign(i64 %2, i32 0, i64 0)
+//     %4 = inttoptr i64 %3 to ptr
+//     store ptr %4, ptr @fp, align 8
+//     ret void
+//   }
+//
+//===----------------------------------------------------------------------===//
 
 #include "InjectPointerSigningFixups.h"
 #include "llvm/IR/Constants.h"
@@ -81,7 +109,7 @@ Error InjectPointerSigningFixupCode(llvm::Module &M,
   // Create the fixup function.
   Function *FixupFn =
       Function::Create(FunctionType::get(Type::getVoidTy(Ctx), false),
-                       GlobalValue::InternalLinkage, "arm64e", &M);
+                       GlobalValue::InternalLinkage, "ptrauth.sign", &M);
   FixupFn->insert(FixupFn->end(), BasicBlock::Create(Ctx));
   IRBuilder<> B(&FixupFn->back());
 



More information about the lldb-commits mailing list