[llvm] Add support for mach-o symbol stubs. (PR #225155)

via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 13:50:43 PDT 2026


================
@@ -66,12 +68,115 @@ static std::vector<uint8_t> getUUID(const object::ObjectFile &Obj) {
   return UUID;
 }
 
+/// Create function information entries for Mach-O symbol stubs.
+///
+/// Symbol stubs are small chunks of code, all with the same fixed size, that
+/// resolves a function pointer on first call and then jumps to the resolved
+/// function on subsequent calls for functions that live in another shared
+/// library. The stubs are stored in sections whose type is S_SYMBOL_STUBS and
+/// the size of a single stub is stored in the "reserved2" field of the section
+/// header. These stubs have no entries in the symbol table of their own, so
+/// they end up being attributed to whatever function precedes them unless they
+/// are synthesized here.
+///
+/// The name of the function a stub jumps to is found using the indirect symbol
+/// table from the LC_DYSYMTAB load command. The "reserved1" field of the
+/// section header contains the index of the indirect symbol table entry that
+/// describes the first stub in the section, and each subsequent stub is
+/// described by the entry that follows. Each indirect symbol table entry is an
+/// index into the symbol table where the matching undefined (N_UNDF) symbol
+/// supplies the name to use for the stub. Each name gets a "symbol stub for: "
+/// prefix prepended to it so that symbolication makes it clear that the address
+/// is the stub for a function and not the function itself.
+///
+/// \returns The number of function infos that were added to \a Gsym.
+static uint64_t addMachOSymbolStubs(const object::MachOObjectFile &MachO,
+                                    OutputAggregator &Out, GsymCreator &Gsym) {
+  const MachO::dysymtab_command Dysymtab = MachO.getDysymtabLoadCommand();
+  if (Dysymtab.nindirectsyms == 0)
+    return 0;
+  const uint32_t NumSyms = MachO.getSymtabLoadCommand().nsyms;
+  const bool Is64Bit = MachO.is64Bit();
+
+  size_t NumBefore = Gsym.getNumFunctionInfos();
+  for (const object::SectionRef &Sect : MachO.sections()) {
+    const object::DataRefImpl SectDRI = Sect.getRawDataRefImpl();
+    uint32_t SectFlags, IndirectSymIdxStart, StubByteSize;
+    if (Is64Bit) {
+      const MachO::section_64 S = MachO.getSection64(SectDRI);
+      SectFlags = S.flags;
+      IndirectSymIdxStart = S.reserved1;
+      StubByteSize = S.reserved2;
+    } else {
+      const MachO::section S = MachO.getSection(SectDRI);
+      SectFlags = S.flags;
+      IndirectSymIdxStart = S.reserved1;
+      StubByteSize = S.reserved2;
+    }
+    if ((SectFlags & MachO::SECTION_TYPE) != MachO::S_SYMBOL_STUBS)
+      continue;
+    if (StubByteSize == 0)
+      continue;
+
+    const uint64_t SectAddr = Sect.getAddress();
+    const uint64_t NumStubs = Sect.getSize() / StubByteSize;
+    for (uint64_t StubIdx = 0; StubIdx < NumStubs; ++StubIdx) {
+      const uint64_t StubAddr = SectAddr + StubIdx * StubByteSize;
+      if (!Gsym.IsValidTextAddress(StubAddr))
+        continue;
+      const uint64_t IndirectSymIdx = IndirectSymIdxStart + StubIdx;
+      if (IndirectSymIdx >= Dysymtab.nindirectsyms)
+        continue;
+      const uint32_t SymIdx =
+          MachO.getIndirectSymbolTableEntry(Dysymtab, IndirectSymIdx);
+      // Entries that are absolute or local don't refer to a symbol table entry.
+      if (SymIdx & (MachO::INDIRECT_SYMBOL_ABS | MachO::INDIRECT_SYMBOL_LOCAL))
+        continue;
+      if (SymIdx >= NumSyms)
+        continue;
+      const object::symbol_iterator SymIt = MachO.getSymbolByIndex(SymIdx);
+      const object::DataRefImpl SymDRI = SymIt->getRawDataRefImpl();
+      const uint8_t NType = Is64Bit ? MachO.getSymbol64TableEntry(SymDRI).n_type
+                                    : MachO.getSymbolTableEntry(SymDRI).n_type;
+      // Only undefined symbols name a stub, any other symbol type means the
+      // function itself is in this file and already has a symbol table entry.
+      if ((NType & MachO::N_TYPE) != MachO::N_UNDF)
+        continue;
+      Expected<StringRef> Name = SymIt->getName();
+      if (!Name) {
+        if (Out.GetOS())
+          logAllUnhandledErrors(Name.takeError(), *Out.GetOS(),
+                                "ObjectFileTransformer: ");
+        else
+          consumeError(Name.takeError());
+        continue;
+      }
+      // Remove the leading '_' character in any symbol names if there is one
+      // for mach-o files.
+      Name->consume_front("_");
+      if (Name->empty())
+        continue;
+      // Append a "symbol stub for: " prefix so it is clear when symbolicating
+      // that the address is the stub for the function and not the function
+      // itself. The string must be copied into the string table since it is
+      // created here and, unlike the symbol names, has no backing storage in
+      // the object file.
+      constexpr bool Copy = true;
+      const std::string StubName = "symbol stub for: " + Name->str();
----------------
alx32 wrote:

For a C++ target this stores `symbol stub for: _ZN3foo3barEv`. Will llvm-symbolizer still demangle that, given it hands the whole string to the demangler ? If not - do we want to demangle before emitting ?  

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


More information about the llvm-commits mailing list