[llvm] [ASan] Optionally emit DInfo for inserted sanitization calls (PR #191548)

via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 10 15:10:19 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Emil Tsalapatis (etsal)

<details>
<summary>Changes</summary>

Modify the ASan pass to also generate DITypes for the function calls it inserts in the IR code. BPF programs uses the DITypes of the calls to generate BTF information for them, which in turn is necessary during linking. The lack of DITypes currently breaks ASAN BPF programs and requires workarounds like inserting direct calls to the ASAN functions to force DInfo generation.

DInfo generation is optional and placed behind a flag. The code adds a table with all ASAN callback names and function signatures. When enabled, the code scans the instrumented code to find all inserted calls, then generates the related DInfo entry by consulting the table.

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


1 Files Affected:

- (modified) llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp (+165) 


``````````diff
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 8b5969ffb3ca0..d0162bc2795f7 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -451,6 +451,11 @@ static cl::list<unsigned> ClAddrSpaces(
       SrcAddrSpaces.insert(AddrSpace);
     }));
 
+static cl::opt<bool> ClEmitDebugInfo(
+    "asan-emit-debug-info",
+    cl::desc("Emit debug info for ASan runtime function declarations"),
+    cl::Hidden, cl::init(false));
+
 // Debug flags.
 
 static cl::opt<int> ClDebug("asan-debug", cl::desc("debug"), cl::Hidden,
@@ -1288,6 +1293,163 @@ struct FunctionStackPoisoner : public InstVisitor<FunctionStackPoisoner> {
                      Instruction *ThenTerm, Value *ValueIfFalse);
 };
 
+// Exhaustive list of types in present in the signatures of ASan calls.
+// Necessary because we cannot infer the source-level type of the
+// callback from its Function instance.
+enum class AsanArgType : uint8_t {
+  Void,
+  Int,
+  VoidPtr,
+  BytePtr,
+  WordPtr,
+};
+
+static constexpr unsigned kMaxAsanCallbackArgs = 3;
+
+/// Metadata for ASan runtime callbacks. Each entry lists either
+/// a complete callback name, or a prefix of a class of callbacks
+/// (e.g., __asan_poison_stack_memory vs __asan_load*). The entry
+/// lists the function signature for the name/prefix.
+struct AsanCallbackInfo {
+  const char *Name;
+  bool IsPrefix;
+  AsanArgType RetType;
+  AsanArgType ArgTypes[kMaxAsanCallbackArgs];
+};
+
+// Table of all ASan callbacks with per-argument type annotations.
+// Some prefixes are themselves prefixes of others (prefix A is the prefix of prefix B).
+// We place prefix B first to avoid having a function name matching B from matching A instead.
+static constexpr auto Void = AsanArgType::Void;
+static constexpr auto Int = AsanArgType::Int;
+static constexpr auto VoidPtr = AsanArgType::VoidPtr;
+static constexpr auto BytePtr = AsanArgType::BytePtr;
+static constexpr auto WordPtr = AsanArgType::WordPtr;
+static constexpr AsanCallbackInfo AsanCallbacks[] = {
+    {kAsanReportErrorTemplate, true, Void, {VoidPtr, Int}},
+    {kAsanStackMallocAlwaysNameTemplate, true, Int, {Int}},
+    {kAsanStackMallocNameTemplate, true, Int, {Int}},
+    {kAsanStackFreeNameTemplate, true, Void, {BytePtr, Int}},
+    {kAsanSetShadowPrefix, true, Void, {VoidPtr, Int}},
+    {kAsanRegisterGlobalsName, false, Void, {VoidPtr, Int}},
+    {kAsanUnregisterGlobalsName, false, Void, {VoidPtr, Int}},
+    {kAsanRegisterImageGlobalsName, false, Void, {WordPtr}},
+    {kAsanUnregisterImageGlobalsName, false, Void, {WordPtr}},
+    {kAsanRegisterElfGlobalsName, false, Void, {WordPtr, VoidPtr, VoidPtr}},
+    {kAsanUnregisterElfGlobalsName, false, Void, {WordPtr, VoidPtr, VoidPtr}},
+    {kAsanPoisonGlobalsName, false, Void, {BytePtr}},
+    {kAsanUnpoisonGlobalsName, false, Void, {}},
+    {kAsanHandleNoReturnName, false, Void, {}},
+    {kAsanPtrCmp, false, Void, {VoidPtr, VoidPtr}},
+    {kAsanPtrSub, false, Void, {VoidPtr, VoidPtr}},
+    {kAsanPoisonStackMemoryName, false, Void, {BytePtr, Int}},
+    {kAsanUnpoisonStackMemoryName, false, Void, {BytePtr, Int}},
+    {kAsanAllocaPoison, false, Void, {VoidPtr, Int}},
+    {kAsanAllocasUnpoison, false, Void, {VoidPtr, Int}},
+};
+
+/// Check if the function is an ASAN callback and get its entry if so.
+static const AsanCallbackInfo *lookupAsanCallback(StringRef Name) {
+  for (const auto &CB : AsanCallbacks) {
+    if (CB.IsPrefix ? Name.starts_with(CB.Name) : Name == CB.Name)
+      return &CB;
+  }
+  // ClMemoryAccessCallbackPrefix is a cl::opt (default "__asan_") and can be
+  // overridden, so it cannot go into the constexpr table above.
+  static const AsanCallbackInfo MemAccessCB = {
+      nullptr, true, Void, {VoidPtr, Int, Int}};
+  if (Name.starts_with(ClMemoryAccessCallbackPrefix))
+    return &MemAccessCB;
+  return nullptr;
+}
+
+/// Create the pointee DIType for a pointer-typed ASan callback argument.
+/// List is exhaustive for the current set of callbacks (void *, __u8 *, __uintptr).
+static DIType *solveAsanPointeeType(DIBuilder &DIB, AsanArgType ArgType,
+                                    unsigned PtrBitWidth) {
+  switch (ArgType) {
+  case Void:
+  case Int:
+    llvm_unreachable("not a pointer type");
+  case VoidPtr:
+    return nullptr;
+  case BytePtr:
+    return DIB.createBasicType("__u8", 8, dwarf::DW_ATE_unsigned,
+                               DINode::FlagArtificial);
+  case WordPtr:
+    return DIB.createBasicType("__uintptr", PtrBitWidth, dwarf::DW_ATE_unsigned,
+                               DINode::FlagArtificial);
+  }
+  llvm_unreachable("unhandled AsanArgType");
+}
+
+/// Resolve an LLVM IR type to a synthetic DIType for ASan runtime callbacks.
+static DIType *solveAsanDIType(DIBuilder &DIB, Type *Ty, const DataLayout &DL,
+                               AsanArgType ArgType) {
+  if (Ty->isVoidTy())
+    return nullptr;
+
+  unsigned BitWidth = cast<IntegerType>(Ty)->getBitWidth();
+  if (ArgType != Int) {
+    unsigned AlignInBits =
+        DL.getABITypeAlign(PointerType::getUnqual(Ty->getContext())).value() *
+        CHAR_BIT;
+    DIType *Pointee = solveAsanPointeeType(DIB, ArgType, BitWidth);
+    return DIB.createPointerType(Pointee, BitWidth, AlignInBits);
+  }
+  SmallString<16> Name;
+  raw_svector_ostream OS(Name);
+  OS << "__int_" << BitWidth;
+  return DIB.createBasicType(OS.str(), BitWidth, dwarf::DW_ATE_signed,
+                             DINode::FlagArtificial);
+}
+
+static void emitAsanDebugInfo(Module &M) {
+  // Any CU will do, but we need at least one to
+  // reuse its DIBuilder. We can do so because it
+  // has not been finalized at this point.
+  if (M.debug_compile_units().empty())
+    return;
+
+  DICompileUnit *CU = *M.debug_compile_units_begin();
+  DIFile *File = CU->getFile();
+  const DataLayout &DL = M.getDataLayout();
+
+  DIBuilder DIB(M, false);
+
+  for (Function &F : M) {
+    if (!F.isDeclaration())
+      continue;
+    if (F.getSubprogram())
+      continue;
+
+    const AsanCallbackInfo *CB = lookupAsanCallback(F.getName());
+    if (!CB)
+      continue;
+
+    FunctionType *FTy = F.getFunctionType();
+
+    SmallVector<Metadata *, kMaxAsanCallbackArgs + 1> ParamTypes;
+    ParamTypes.push_back(
+        solveAsanDIType(DIB, FTy->getReturnType(), DL, CB->RetType));
+    assert(FTy->getNumParams() <= kMaxAsanCallbackArgs &&
+           "ASan callback param count impossible for existing callbacks");
+    for (unsigned I = 0, E = FTy->getNumParams(); I < E; ++I)
+      ParamTypes.push_back(
+          solveAsanDIType(DIB, FTy->params()[I], DL, CB->ArgTypes[I]));
+
+    DISubroutineType *SubTy =
+        DIB.createSubroutineType(DIB.getOrCreateTypeArray(ParamTypes));
+
+    DISubprogram *SP = DIB.createFunction(
+        File, F.getName(), F.getName(), File, 0, SubTy,
+        0, DINode::FlagArtificial | DINode::FlagPrototyped,
+        DISubprogram::SPFlagZero);
+
+    F.setSubprogram(SP);
+  }
+}
+
 } // end anonymous namespace
 
 void AddressSanitizerPass::printPipeline(
@@ -1347,6 +1509,9 @@ PreservedAnalyses AddressSanitizerPass::run(Module &M,
   if (!Modified)
     return PreservedAnalyses::all();
 
+  if (ClEmitDebugInfo)
+    emitAsanDebugInfo(M);
+
   PreservedAnalyses PA = PreservedAnalyses::none();
   // GlobalsAA is considered stateless and does not get invalidated unless
   // explicitly invalidated; PreservedAnalyses::none() is not enough. Sanitizers

``````````

</details>


https://github.com/llvm/llvm-project/pull/191548


More information about the llvm-commits mailing list