Skip to content

Generate an .sframe section with a skeleton header #151223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Sterling-Augustine
Copy link
Contributor

This continues the sframe implementation discussed previously.

Of note, this also adds some target dependent functions to the object file. Additional fields will be needed later. It would be possible to do all of this inside the sframe implementation itself if it feels a little messy and specialized, but generally I think that target info goes with target info.

Another question is if we want a sentinel value for unimplemented sframe abi arches, or a std::optional. Both work.

This continues the sframe implementation discussed previously.

Of note, this also adds some target dependent functions to the object
file. Additional fields will be needed later. It would be possible to
do all of this inside the sframe implementation itself if it feels
a little messy and specialized, but generally I think that target info
goes with target info.
@Sterling-Augustine Sterling-Augustine requested review from aengelke, labath and uweigand and removed request for labath July 29, 2025 20:27
@llvmbot llvmbot added mc Machine (object) code llvm:binary-utilities labels Jul 29, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 29, 2025

@llvm/pr-subscribers-mc

@llvm/pr-subscribers-llvm-binary-utilities

Author: None (Sterling-Augustine)

Changes

This continues the sframe implementation discussed previously.

Of note, this also adds some target dependent functions to the object file. Additional fields will be needed later. It would be possible to do all of this inside the sframe implementation itself if it feels a little messy and specialized, but generally I think that target info goes with target info.

Another question is if we want a sentinel value for unimplemented sframe abi arches, or a std::optional. Both work.


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

8 Files Affected:

  • (modified) llvm/include/llvm/BinaryFormat/SFrameConstants.def (+1)
  • (modified) llvm/include/llvm/MC/MCObjectFileInfo.h (+9)
  • (added) llvm/include/llvm/MC/MCSFrame.h (+36)
  • (modified) llvm/lib/MC/CMakeLists.txt (+1)
  • (modified) llvm/lib/MC/MCObjectFileInfo.cpp (+19)
  • (modified) llvm/lib/MC/MCObjectStreamer.cpp (+6-1)
  • (added) llvm/lib/MC/MCSFrame.cpp (+95)
  • (added) llvm/test/MC/ELF/cfi-sframe.s (+26)
diff --git a/llvm/include/llvm/BinaryFormat/SFrameConstants.def b/llvm/include/llvm/BinaryFormat/SFrameConstants.def
index 643b15f438c86..2bd25eb46fd3e 100644
--- a/llvm/include/llvm/BinaryFormat/SFrameConstants.def
+++ b/llvm/include/llvm/BinaryFormat/SFrameConstants.def
@@ -30,6 +30,7 @@ HANDLE_SFRAME_FLAG(0x01, FDESorted)
 HANDLE_SFRAME_FLAG(0x02, FramePointer)
 HANDLE_SFRAME_FLAG(0x04, FDEFuncStartPCRel)
 
+HANDLE_SFRAME_ABI(0x00, Undefined)
 HANDLE_SFRAME_ABI(0x01, AArch64EndianBig)
 HANDLE_SFRAME_ABI(0x02, AArch64EndianLittle)
 HANDLE_SFRAME_ABI(0x03, AMD64EndianLittle)
diff --git a/llvm/include/llvm/MC/MCObjectFileInfo.h b/llvm/include/llvm/MC/MCObjectFileInfo.h
index 5ce58ae0a836f..cb5da89342532 100644
--- a/llvm/include/llvm/MC/MCObjectFileInfo.h
+++ b/llvm/include/llvm/MC/MCObjectFileInfo.h
@@ -13,6 +13,7 @@
 #ifndef LLVM_MC_MCOBJECTFILEINFO_H
 #define LLVM_MC_MCOBJECTFILEINFO_H
 
+#include "llvm/BinaryFormat/SFrame.h"
 #include "llvm/BinaryFormat/Swift.h"
 #include "llvm/MC/MCSection.h"
 #include "llvm/Support/Compiler.h"
@@ -50,6 +51,9 @@ class LLVM_ABI MCObjectFileInfo {
   /// Compact unwind encoding indicating that we should emit only an EH frame.
   unsigned CompactUnwindDwarfEHFrameOnly = 0;
 
+  /// SFrame ABI architecture byte
+  sframe::ABI SFrameABIArch = sframe::ABI::Undefined;
+
   /// Section directive for standard text.
   MCSection *TextSection = nullptr;
 
@@ -174,6 +178,9 @@ class LLVM_ABI MCObjectFileInfo {
   /// It is initialized on demand so it can be overwritten (with uniquing).
   MCSection *EHFrameSection = nullptr;
 
+  /// SFrame section.
+  MCSection *SFrameSection = nullptr;
+
   /// Section containing metadata on function stack sizes.
   MCSection *StackSizesSection = nullptr;
 
@@ -266,6 +273,7 @@ class LLVM_ABI MCObjectFileInfo {
     return CompactUnwindDwarfEHFrameOnly;
   }
 
+  sframe::ABI getSFrameABIArch() const { return SFrameABIArch; }
   virtual unsigned getTextSectionAlignment() const { return 4; }
   MCSection *getTextSection() const { return TextSection; }
   MCSection *getDataSection() const { return DataSection; }
@@ -445,6 +453,7 @@ class LLVM_ABI MCObjectFileInfo {
   MCSection *getTOCBaseSection() const { return TOCBaseSection; }
 
   MCSection *getEHFrameSection() const { return EHFrameSection; }
+  MCSection *getSFrameSection() const { return SFrameSection; }
 
   bool isPositionIndependent() const { return PositionIndependent; }
 
diff --git a/llvm/include/llvm/MC/MCSFrame.h b/llvm/include/llvm/MC/MCSFrame.h
new file mode 100644
index 0000000000000..9ae84d6045c74
--- /dev/null
+++ b/llvm/include/llvm/MC/MCSFrame.h
@@ -0,0 +1,36 @@
+//===- MCSFrame.h - Machine Code SFrame support -----------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the declaration of MCSFrameEmitter to support emitting
+// sframe unwinding info from .cfi_* directives. It relies on FDEs and CIEs
+// created for Dwarf frame info, but emits that info in a different format.
+//
+// See https://sourceware.org/binutils/docs-2.41/sframe-spec.html
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_MC_MCSFRAME_H
+#define LLVM_MC_MCSFRAME_H
+
+#include <cstdint>
+
+#include "llvm/ADT/SmallVector.h"
+
+namespace llvm {
+
+class MCObjectStreamer;
+
+class MCSFrameEmitter {
+public:
+  //
+  // Emits the sframe section.
+  //
+  static void Emit(MCObjectStreamer &streamer);
+};
+
+} // end namespace llvm
+#endif // LLVM_MC_MCSFRAME_H
diff --git a/llvm/lib/MC/CMakeLists.txt b/llvm/lib/MC/CMakeLists.txt
index 18a85b3fed08c..1e1d0a6d00601 100644
--- a/llvm/lib/MC/CMakeLists.txt
+++ b/llvm/lib/MC/CMakeLists.txt
@@ -45,6 +45,7 @@ add_llvm_component_library(LLVMMC
   MCSection.cpp
   MCSectionMachO.cpp
   MCStreamer.cpp
+  MCSFrame.cpp
   MCSPIRVStreamer.cpp
   MCSubtargetInfo.cpp
   MCSymbol.cpp
diff --git a/llvm/lib/MC/MCObjectFileInfo.cpp b/llvm/lib/MC/MCObjectFileInfo.cpp
index 0069d12dce396..464431b45646a 100644
--- a/llvm/lib/MC/MCObjectFileInfo.cpp
+++ b/llvm/lib/MC/MCObjectFileInfo.cpp
@@ -10,6 +10,7 @@
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/BinaryFormat/COFF.h"
 #include "llvm/BinaryFormat/ELF.h"
+#include "llvm/BinaryFormat/SFrame.h"
 #include "llvm/BinaryFormat/Wasm.h"
 #include "llvm/MC/MCAsmInfo.h"
 #include "llvm/MC/MCContext.h"
@@ -380,6 +381,20 @@ void MCObjectFileInfo::initELFMCObjectFileInfo(const Triple &T, bool Large) {
   unsigned EHSectionType = T.getArch() == Triple::x86_64
                                ? ELF::SHT_X86_64_UNWIND
                                : ELF::SHT_PROGBITS;
+  switch (T.getArch()) {
+    case Triple::x86_64:
+      SFrameABIArch = sframe::ABI::AMD64EndianLittle;
+      break;
+    case Triple::aarch64:
+      SFrameABIArch = sframe::ABI::AArch64EndianLittle;
+      break;
+    case Triple::aarch64_be:
+      SFrameABIArch = sframe::ABI::AArch64EndianBig;
+      break;
+    default:
+      SFrameABIArch = sframe::ABI::Undefined;
+      break;
+  }
 
   // Solaris requires different flags for .eh_frame to seemingly every other
   // platform.
@@ -537,6 +552,9 @@ void MCObjectFileInfo::initELFMCObjectFileInfo(const Triple &T, bool Large) {
   EHFrameSection =
       Ctx->getELFSection(".eh_frame", EHSectionType, EHSectionFlags);
 
+  SFrameSection =
+      Ctx->getELFSection(".sframe", ELF::SHT_GNU_SFRAME, ELF::SHF_ALLOC);
+
   StackSizesSection = Ctx->getELFSection(".stack_sizes", ELF::SHT_PROGBITS, 0);
 
   PseudoProbeSection = Ctx->getELFSection(".pseudo_probe", DebugSecType, 0);
@@ -1062,6 +1080,7 @@ void MCObjectFileInfo::initMCObjectFileInfo(MCContext &MCCtx, bool PIC,
   CompactUnwindDwarfEHFrameOnly = 0;
 
   EHFrameSection = nullptr;             // Created on demand.
+  SFrameSection = nullptr;              // Created on demand.
   CompactUnwindSection = nullptr;       // Used only by selected targets.
   DwarfAccelNamesSection = nullptr;     // Used only by selected targets.
   DwarfAccelObjCSection = nullptr;      // Used only by selected targets.
diff --git a/llvm/lib/MC/MCObjectStreamer.cpp b/llvm/lib/MC/MCObjectStreamer.cpp
index e82393a8477ea..666841ebffdb5 100644
--- a/llvm/lib/MC/MCObjectStreamer.cpp
+++ b/llvm/lib/MC/MCObjectStreamer.cpp
@@ -19,6 +19,7 @@
 #include "llvm/MC/MCObjectWriter.h"
 #include "llvm/MC/MCSection.h"
 #include "llvm/MC/MCSymbol.h"
+#include "llvm/MC/MCSFrame.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/SourceMgr.h"
 using namespace llvm;
@@ -30,7 +31,7 @@ MCObjectStreamer::MCObjectStreamer(MCContext &Context,
     : MCStreamer(Context),
       Assembler(std::make_unique<MCAssembler>(
           Context, std::move(TAB), std::move(Emitter), std::move(OW))),
-      EmitEHFrame(true), EmitDebugFrame(false) {
+      EmitEHFrame(true), EmitDebugFrame(false), EmitSFrame(false) {
   assert(Assembler->getBackendPtr() && Assembler->getEmitterPtr());
   IsObj = true;
   setAllowAutoPadding(Assembler->getBackend().allowAutoPadding());
@@ -185,6 +186,10 @@ void MCObjectStreamer::emitFrames(MCAsmBackend *MAB) {
 
   if (EmitDebugFrame)
     MCDwarfFrameEmitter::Emit(*this, MAB, false);
+
+  if (EmitSFrame || (getContext().getTargetOptions() &&
+                     getContext().getTargetOptions()->EmitSFrameUnwind))
+    MCSFrameEmitter::Emit(*this);
 }
 
 void MCObjectStreamer::visitUsedSymbol(const MCSymbol &Sym) {
diff --git a/llvm/lib/MC/MCSFrame.cpp b/llvm/lib/MC/MCSFrame.cpp
new file mode 100644
index 0000000000000..eae49ccd31eb0
--- /dev/null
+++ b/llvm/lib/MC/MCSFrame.cpp
@@ -0,0 +1,95 @@
+//===- lib/MC/MCSFrame.cpp - MCSFrame implementation ----------------------===//
+//
+// 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 "llvm/MC/MCSFrame.h"
+#include "llvm/BinaryFormat/SFrame.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCObjectFileInfo.h"
+#include "llvm/MC/MCObjectStreamer.h"
+#include "llvm/MC/MCSection.h"
+#include "llvm/MC/MCSymbol.h"
+#include "llvm/Support/EndianStream.h"
+
+using namespace llvm;
+using namespace sframe;
+
+namespace {
+
+// Emitting these field-by-field, instead of constructing the actual structures
+// lets Streamer do target endian-fixups for free.
+
+class SFrameEmitterImpl {
+  MCObjectStreamer &Streamer;
+  ABI SFrameABI;
+  MCSymbol *FDESubSectionStart;
+  MCSymbol *FRESubSectionStart;
+  MCSymbol *FRESubSectionEnd;
+
+
+public:
+  SFrameEmitterImpl(MCObjectStreamer &Streamer) : Streamer(Streamer) {
+    SFrameABI = Streamer.getContext().getObjectFileInfo()->getSFrameABIArch();
+    FDESubSectionStart = Streamer.getContext().createTempSymbol();
+    FRESubSectionStart = Streamer.getContext().createTempSymbol();
+    FRESubSectionEnd = Streamer.getContext().createTempSymbol();
+  }
+
+  void EmitPreamble() {
+    Streamer.emitInt16(Magic);
+    Streamer.emitInt8(static_cast<uint64_t>(Version::V2));
+    Streamer.emitInt8(0);
+  }
+
+  void EmitHeader() {
+    EmitPreamble();
+    // sfh_abi_arch
+    Streamer.emitInt8(static_cast<uint64_t>(SFrameABI));
+    // sfh_cfa_fixed_fp_offset
+    Streamer.emitInt8(0);
+    // sfh_cfa_fixed_ra_offset
+    Streamer.emitInt8(0);
+    // sfh_auxhdr_len
+    Streamer.emitInt8(0);
+    // shf_num_fdes
+    Streamer.emitInt32(0);
+    // shf_num_fres
+    Streamer.emitInt32(0);
+    // shf_fre_len
+    Streamer.emitAbsoluteSymbolDiff(FRESubSectionEnd, FRESubSectionStart,
+                                    sizeof(int32_t));
+    // shf_fdeoff. With no sfh_auxhdr, these immediately follow this header.
+    Streamer.emitInt32(0);
+    // shf_freoff
+    Streamer.emitAbsoluteSymbolDiff(FRESubSectionStart, FDESubSectionStart,
+                                    sizeof(uint32_t));
+  }
+
+  void EmitFDEs() { Streamer.emitLabel(FDESubSectionStart); }
+
+  void EmitFREs() {
+    Streamer.emitLabel(FRESubSectionStart);
+    Streamer.emitLabel(FRESubSectionEnd);
+  }
+};
+
+} // end anonymous namespace
+
+void MCSFrameEmitter::Emit(MCObjectStreamer &Streamer) {
+  MCContext &Context = Streamer.getContext();
+  SFrameEmitterImpl Emitter(Streamer);
+
+  MCSection *Section = Context.getObjectFileInfo()->getSFrameSection();
+  // Not strictly necessary, but gas always aligns to 8, so match that.
+  Section->ensureMinAlignment(Align(8));
+  Streamer.switchSection(Section);
+  MCSymbol *SectionStart = Context.createTempSymbol();
+  Streamer.emitLabel(SectionStart);
+  Emitter.EmitHeader();
+  Emitter.EmitFDEs();
+  Emitter.EmitFREs();
+}
diff --git a/llvm/test/MC/ELF/cfi-sframe.s b/llvm/test/MC/ELF/cfi-sframe.s
new file mode 100644
index 0000000000000..6aae37bc370e0
--- /dev/null
+++ b/llvm/test/MC/ELF/cfi-sframe.s
@@ -0,0 +1,26 @@
+// TODO: Add other architectures as they gain sframe support
+// REQUIRES: x86-registered-target
+// RUN: llvm-mc --assemble --filetype=obj --gsframe -triple x86_64 %s -o %t.o
+// RUN: llvm-readelf --sframe %t.o | FileCheck %s
+
+.cfi_sections .sframe
+	
+f1:
+        .cfi_startproc
+        nop
+        .cfi_endproc
+
+// CHECK: SFrame section '.sframe' {
+// CHECK-NEXT:  Header {
+// CHECK-NEXT:    Magic: 0xDEE2
+// CHECK-NEXT:    Version: V2 (0x2)
+// CHECK-NEXT:    Flags [ (0x0)
+// CHECK:    ABI: AMD64EndianLittle (0x3)
+// CHECK-NEXT:    CFA fixed FP offset (unused): 0
+// CHECK-NEXT:    CFA fixed RA offset: 0
+// CHECK-NEXT:    Auxiliary header length: 0
+// CHECK-NEXT:    Num FDEs: 0
+// CHECK-NEXT:    Num FREs: 0
+// CHECK-NEXT:    FRE subsection length: 0
+// CHECK-NEXT:    FDE subsection offset: 0
+// CHECK-NEXT:    FRE subsection offset: 0

Copy link

github-actions bot commented Jul 29, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question is if we want a sentinel value for unimplemented sframe abi arches, or a std::optional.

For me, that depends on whether the value is going to end up in the final object file. If yes, then it should definitely be an enumerator. If not, then I'd slightly prefer not putting it there, either via std::optional, using {} instead of ABI::Undefined, or by moving the logic into a helper function in MCSFrame (where the unimplemented cases could presumably be handled by llvm_unreachable).

I don't see anything here or in #149935 that would prevent the sframe section from being generated for an unsupported target. Is that intentional? In my experiments, the GNU toolchain ignored the --sframe argument on an unsupported target -- which isn't particularly helpful (I would expect at least a warning), but I guess it's better than emitting something that can't be used.


void EmitPreamble() {
Streamer.emitInt16(Magic);
Streamer.emitInt8(static_cast<uint64_t>(Version::V2));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use uint8_t as that's the backing type of the Version enum, even though emitInt8 takes a uint64_t. Casting to uint64_t looks very odd here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to uint8_t and std::optional version for SFrameABI.

Somewhat weirdly, llvm-mc doesn't do option-compatibility checking. For example, it is perfectly happy to try to run:

build/bin/llvm-mc -triple=aarch64 -x86-sse2avx llvm-project/llvm/test/MC/AsmParser/sse2avx-att.s

The test fails, of course, but not at the option level.

Eventually the clang asm driver will get an --gsframe option, and that is where the incompatibility will be reported.

Switch to std::optional for SFrameABI
Use uint8_t for casting to MCObjectStreamer
@Sterling-Augustine
Copy link
Contributor Author

Anyone else?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
llvm:binary-utilities mc Machine (object) code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants