Skip to content

[Object] Parsing and dumping of SFrame Frame Row Entries #151301

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 1 commit into
base: main
Choose a base branch
from

Conversation

labath
Copy link
Collaborator

@labath labath commented Jul 30, 2025

The trickiest part here is that the FREs have a variable size, in two (or three?) dimensions:

  • the size of the StartAddress field. This determined by the FDE they are in, so it is uniform across all FREs in one FDE.
  • the number and sizes of offsets following the FRE. This can be different for each FRE.

While vending this information through a template API would be possible, I believe such an approach would be very unwieldy, and it would still require a sequential scan through the FRE list. This is why I'm implementing this by reading the data into a common data structure using the fallible iterator pattern.

For more information about the SFrame unwind format, see the specification and the related RFC.

The trickiest part here is that the FREs have a variable size, in two
(or three?) dimensions:
- the size of the StartAddress field. This determined by the FDE they
  are in, so it is uniform across all FREs in one FDE.
- the number and sizes of offsets following the FRE. This can be
  different for each FRE.

While vending this information through a template API would be possible,
I believe such an approach would be very unwieldy, and it would still
require a sequential scan through the FRE list. This is why I'm
implementing this by reading the data into a common data structure using
the fallible iterator pattern.

For more information about the SFrame unwind format, see the
[specification](https://sourceware.org/binutils/wiki/sframe) and the
related [RFC](https://discourse.llvm.org/t/rfc-adding-sframe-support-to-llvm/86900).
@llvmbot
Copy link
Member

llvmbot commented Jul 30, 2025

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

Author: Pavel Labath (labath)

Changes

The trickiest part here is that the FREs have a variable size, in two (or three?) dimensions:

  • the size of the StartAddress field. This determined by the FDE they are in, so it is uniform across all FREs in one FDE.
  • the number and sizes of offsets following the FRE. This can be different for each FRE.

While vending this information through a template API would be possible, I believe such an approach would be very unwieldy, and it would still require a sequential scan through the FRE list. This is why I'm implementing this by reading the data into a common data structure using the fallible iterator pattern.

For more information about the SFrame unwind format, see the specification and the related RFC.


Patch is 23.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/151301.diff

7 Files Affected:

  • (modified) llvm/include/llvm/BinaryFormat/SFrame.h (+1)
  • (modified) llvm/include/llvm/Object/SFrameParser.h (+51)
  • (modified) llvm/lib/BinaryFormat/SFrame.cpp (+8)
  • (modified) llvm/lib/Object/SFrameParser.cpp (+125-4)
  • (modified) llvm/test/tools/llvm-readobj/ELF/sframe-fde.test (+8-2)
  • (added) llvm/test/tools/llvm-readobj/ELF/sframe-fre.test (+284)
  • (modified) llvm/tools/llvm-readobj/ELFDumper.cpp (+28-1)
diff --git a/llvm/include/llvm/BinaryFormat/SFrame.h b/llvm/include/llvm/BinaryFormat/SFrame.h
index 0c6c4d176eaec..74e47ea8acca9 100644
--- a/llvm/include/llvm/BinaryFormat/SFrame.h
+++ b/llvm/include/llvm/BinaryFormat/SFrame.h
@@ -169,6 +169,7 @@ LLVM_ABI ArrayRef<EnumEntry<FREType>> getFRETypes();
 LLVM_ABI ArrayRef<EnumEntry<FDEType>> getFDETypes();
 LLVM_ABI ArrayRef<EnumEntry<AArch64PAuthKey>> getAArch64PAuthKeys();
 LLVM_ABI ArrayRef<EnumEntry<FREOffset>> getFREOffsets();
+LLVM_ABI ArrayRef<EnumEntry<BaseReg>> getBaseRegisters();
 
 } // namespace sframe
 } // namespace llvm
diff --git a/llvm/include/llvm/Object/SFrameParser.h b/llvm/include/llvm/Object/SFrameParser.h
index 245e7ba40d0b8..539bb1872cc99 100644
--- a/llvm/include/llvm/Object/SFrameParser.h
+++ b/llvm/include/llvm/Object/SFrameParser.h
@@ -10,6 +10,7 @@
 #define LLVM_OBJECT_SFRAME_H
 
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/fallible_iterator.h"
 #include "llvm/BinaryFormat/SFrame.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Error.h"
@@ -19,6 +20,8 @@ namespace llvm {
 namespace object {
 
 template <endianness E> class SFrameParser {
+  class FallibleFREIterator;
+
 public:
   static Expected<SFrameParser> create(ArrayRef<uint8_t> Contents,
                                        uint64_t SectionAddress);
@@ -42,6 +45,21 @@ template <endianness E> class SFrameParser {
   // objects returned by the `fdes()` function.
   uint64_t getAbsoluteStartAddress(typename FDERange::iterator FDE) const;
 
+  struct FrameRowEntry {
+    uint32_t StartAddress;
+    sframe::FREInfo<endianness::native> Info;
+    SmallVector<int32_t, 3> Offsets;
+  };
+
+  using fre_iterator = fallible_iterator<FallibleFREIterator>;
+  iterator_range<fre_iterator> fres(const sframe::FuncDescEntry<E> &FDE,
+                                    Error &Err) const;
+
+  std::optional<int32_t> getCFAOffset(const FrameRowEntry &FRE) const;
+  std::optional<int32_t> getRAOffset(const FrameRowEntry &FRE) const;
+  std::optional<int32_t> getFPOffset(const FrameRowEntry &FRE) const;
+  ArrayRef<int32_t> getExtraOffsets(const FrameRowEntry &FRE) const;
+
 private:
   ArrayRef<uint8_t> Data;
   uint64_t SectionAddress;
@@ -54,6 +72,39 @@ template <endianness E> class SFrameParser {
   uint64_t getFDEBase() const {
     return sizeof(Header) + Header.AuxHdrLen + Header.FDEOff;
   }
+
+  uint64_t getFREBase() const {
+    return getFDEBase() + Header.NumFDEs * sizeof(sframe::FuncDescEntry<E>);
+  }
+};
+
+template <endianness E> class SFrameParser<E>::FallibleFREIterator {
+public:
+  // NB: This iterator starts out in the before_begin() state. It must be
+  // ++'ed to reach the first element.
+  FallibleFREIterator(ArrayRef<uint8_t> Data, sframe::FREType FREType,
+                      uint32_t Idx, uint32_t Size, uint64_t Offset)
+      : Data(Data), FREType(FREType), Idx(Idx), Size(Size), Offset(Offset) {}
+
+  Error inc();
+  const FrameRowEntry &operator*() const { return FRE; }
+
+  friend bool operator==(const FallibleFREIterator &LHS,
+                         const FallibleFREIterator &RHS) {
+    assert(LHS.Data.data() == RHS.Data.data());
+    assert(LHS.Data.size() == RHS.Data.size());
+    assert(LHS.FREType == RHS.FREType);
+    assert(LHS.Size == RHS.Size);
+    return LHS.Idx == RHS.Idx;
+  }
+
+private:
+  ArrayRef<uint8_t> Data;
+  sframe::FREType FREType;
+  uint32_t Idx;
+  uint32_t Size;
+  uint64_t Offset;
+  FrameRowEntry FRE;
 };
 
 extern template class LLVM_TEMPLATE_ABI SFrameParser<endianness::big>;
diff --git a/llvm/lib/BinaryFormat/SFrame.cpp b/llvm/lib/BinaryFormat/SFrame.cpp
index f1765d7f3e852..8076a26f27f32 100644
--- a/llvm/lib/BinaryFormat/SFrame.cpp
+++ b/llvm/lib/BinaryFormat/SFrame.cpp
@@ -68,3 +68,11 @@ ArrayRef<EnumEntry<sframe::FREOffset>> sframe::getFREOffsets() {
   };
   return ArrayRef(FREOffsets);
 }
+
+ArrayRef<EnumEntry<sframe::BaseReg>> sframe::getBaseRegisters() {
+  static constexpr EnumEntry<sframe::BaseReg> BaseRegs[] = {
+      {"FP", sframe::BaseReg::FP},
+      {"SP", sframe::BaseReg::SP},
+  };
+  return ArrayRef(BaseRegs);
+}
diff --git a/llvm/lib/Object/SFrameParser.cpp b/llvm/lib/Object/SFrameParser.cpp
index 5863490634e32..73fdb643199ba 100644
--- a/llvm/lib/Object/SFrameParser.cpp
+++ b/llvm/lib/Object/SFrameParser.cpp
@@ -32,14 +32,25 @@ getDataSlice(ArrayRef<uint8_t> Data, uint64_t Offset, uint64_t Size) {
 }
 
 template <typename T>
-static Expected<const T &> getDataSliceAs(ArrayRef<uint8_t> Data,
-                                          uint64_t Offset) {
+static Expected<ArrayRef<T>>
+getDataSliceAsArrayOf(ArrayRef<uint8_t> Data, uint64_t Offset, uint64_t Count) {
   static_assert(std::is_trivial_v<T>);
-  Expected<ArrayRef<uint8_t>> Slice = getDataSlice(Data, Offset, sizeof(T));
+  Expected<ArrayRef<uint8_t>> Slice =
+      getDataSlice(Data, Offset, sizeof(T) * Count);
   if (!Slice)
     return Slice.takeError();
 
-  return *reinterpret_cast<const T *>(Slice->data());
+  return ArrayRef(reinterpret_cast<const T *>(Slice->data()), Count);
+}
+
+template <typename T>
+static Expected<const T &> getDataSliceAs(ArrayRef<uint8_t> Data,
+                                          uint64_t Offset) {
+  Expected<ArrayRef<T>> Array = getDataSliceAsArrayOf<T>(Data, Offset, 1);
+  if (!Array)
+    return Array.takeError();
+
+  return Array->front();
 }
 
 template <endianness E>
@@ -100,6 +111,116 @@ uint64_t SFrameParser<E>::getAbsoluteStartAddress(
   return Result;
 }
 
+template <typename EndianT>
+static Error readArray(ArrayRef<uint8_t> Data, uint64_t Count, uint64_t &Offset,
+                       SmallVectorImpl<int32_t> &Vec) {
+  Expected<ArrayRef<EndianT>> RawArray =
+      getDataSliceAsArrayOf<EndianT>(Data, Offset, Count);
+  if (!RawArray)
+    return RawArray.takeError();
+  Offset += Count * sizeof(EndianT);
+  Vec.resize(Count);
+  llvm::copy(*RawArray, Vec.begin());
+  return Error::success();
+}
+
+template <typename T, endianness E>
+static Error readFRE(ArrayRef<uint8_t> Data, uint64_t &Offset,
+                     typename SFrameParser<E>::FrameRowEntry &FRE) {
+  Expected<sframe::FrameRowEntry<T, E>> RawFRE =
+      getDataSliceAs<sframe::FrameRowEntry<T, E>>(Data, Offset);
+  if (!RawFRE)
+    return RawFRE.takeError();
+
+  Offset += sizeof(*RawFRE);
+  FRE.StartAddress = RawFRE->StartAddress;
+  FRE.Info.Info = RawFRE->Info.Info;
+
+  switch (FRE.Info.getOffsetSize()) {
+  case sframe::FREOffset::B1:
+    return readArray<sframe::detail::packed<int8_t, E>>(
+        Data, FRE.Info.getOffsetCount(), Offset, FRE.Offsets);
+  case sframe::FREOffset::B2:
+    return readArray<sframe::detail::packed<int16_t, E>>(
+        Data, FRE.Info.getOffsetCount(), Offset, FRE.Offsets);
+  case sframe::FREOffset::B4:
+    return readArray<sframe::detail::packed<int32_t, E>>(
+        Data, FRE.Info.getOffsetCount(), Offset, FRE.Offsets);
+  default:
+    return createError("unsupported/unknown offset size");
+  }
+}
+
+template <endianness E> Error SFrameParser<E>::FallibleFREIterator::inc() {
+  if (++Idx == Size)
+    return Error::success();
+
+  switch (FREType) {
+  case sframe::FREType::Addr1:
+    return readFRE<uint8_t, E>(Data, Offset, FRE);
+  case sframe::FREType::Addr2:
+    return readFRE<uint16_t, E>(Data, Offset, FRE);
+  case sframe::FREType::Addr4:
+    return readFRE<uint32_t, E>(Data, Offset, FRE);
+  default:
+    return createError("invalid/unsupported FRE type");
+  }
+}
+
+template <endianness E>
+iterator_range<typename SFrameParser<E>::fre_iterator>
+SFrameParser<E>::fres(const sframe::FuncDescEntry<E> &FDE, Error &Err) const {
+  uint64_t Offset = getFREBase() + FDE.StartFREOff;
+  fre_iterator BeforeBegin = make_fallible_itr(
+      FallibleFREIterator(Data, FDE.getFREType(), -1, FDE.NumFREs, Offset),
+      Err);
+  fre_iterator End = make_fallible_end(
+      FallibleFREIterator(Data, FDE.getFREType(), FDE.NumFREs, FDE.NumFREs,
+                          /*Offset=*/0));
+  return {++BeforeBegin, End};
+}
+
+static std::optional<int32_t> getOffset(ArrayRef<int32_t> Offsets, size_t Idx) {
+  if (Offsets.size() > Idx)
+    return Offsets[Idx];
+  return std::nullopt;
+}
+
+template <endianness E>
+std::optional<int32_t>
+SFrameParser<E>::getCFAOffset(const FrameRowEntry &FRE) const {
+  return getOffset(FRE.Offsets, 0);
+}
+
+template <endianness E>
+std::optional<int32_t>
+SFrameParser<E>::getRAOffset(const FrameRowEntry &FRE) const {
+  if (usesFixedRAOffset())
+    return Header.CFAFixedRAOffset;
+  return getOffset(FRE.Offsets, 1);
+}
+
+template <endianness E>
+std::optional<int32_t>
+SFrameParser<E>::getFPOffset(const FrameRowEntry &FRE) const {
+  if (usesFixedFPOffset())
+    return Header.CFAFixedFPOffset;
+  return getOffset(FRE.Offsets, usesFixedRAOffset() ? 1 : 2);
+}
+
+template <endianness E>
+ArrayRef<int32_t>
+SFrameParser<E>::getExtraOffsets(const FrameRowEntry &FRE) const {
+  size_t UsedOffsets = 1; // CFA
+  if (!usesFixedRAOffset())
+    ++UsedOffsets;
+  if (!usesFixedFPOffset())
+    ++UsedOffsets;
+  if (FRE.Offsets.size() > UsedOffsets)
+    return ArrayRef(FRE.Offsets).drop_front(UsedOffsets);
+  return {};
+}
+
 template class LLVM_EXPORT_TEMPLATE llvm::object::SFrameParser<endianness::big>;
 template class LLVM_EXPORT_TEMPLATE
     llvm::object::SFrameParser<endianness::little>;
diff --git a/llvm/test/tools/llvm-readobj/ELF/sframe-fde.test b/llvm/test/tools/llvm-readobj/ELF/sframe-fde.test
index dee40180c42e6..b9075a81eba4b 100644
--- a/llvm/test/tools/llvm-readobj/ELF/sframe-fde.test
+++ b/llvm/test/tools/llvm-readobj/ELF/sframe-fde.test
@@ -108,6 +108,8 @@ Sections:
 #  CASE1-NEXT:      }
 #  CASE1-NEXT:      Repetitive block size: 0xDE
 #  CASE1-NEXT:      Padding2: 0xAD
+#  CASE1-NEXT:      FREs [
+#  CASE1-NEXT:      ]
 #  CASE1-NEXT:    }
 #  CASE1-NEXT:  ]
 #  CASE1-NEXT:}
@@ -169,6 +171,8 @@ Sections:
 #  CASE1-NEXT:      }
 #  CASE1-NEXT:      Repetitive block size (unused): 0xDE
 #  CASE1-NEXT:      Padding2: 0xAD
+#  CASE1-NEXT:      FREs [
+#  CASE1-NEXT:      ]
 #  CASE1-NEXT:    }
 #  CASE1-NEXT:  ]
 #  CASE1-NEXT:}
@@ -196,7 +200,7 @@ Sections:
       0x00, 0xde, 0xad, 0x00,  # Start Address
       0x00, 0x00, 0x01, 0xbe,  # Size
       0x00, 0x00, 0x00, 0x10,  # Start FRE Offset
-      0x00, 0x00, 0x00, 0x10,  # Number of FREs
+      0x00, 0x00, 0x00, 0x00,  # Number of FREs
       0x02, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
     ]
 # CASE2-LABEL:SFrame section '.sframe' {
@@ -223,7 +227,7 @@ Sections:
 #  CASE2-NEXT:      PC: 0xDEAD1C
 #  CASE2-NEXT:      Size: 0x1BE
 #  CASE2-NEXT:      Start FRE Offset: 0x10
-#  CASE2-NEXT:      Num FREs: 16
+#  CASE2-NEXT:      Num FREs: 0
 #  CASE2-NEXT:      Info {
 #  CASE2-NEXT:        FRE Type: Addr4 (0x2)
 #  CASE2-NEXT:        FDE Type: PCInc (0x0)
@@ -232,6 +236,8 @@ Sections:
 #  CASE2-NEXT:      }
 #  CASE2-NEXT:      Repetitive block size (unused): 0xDE
 #  CASE2-NEXT:      Padding2: 0xAD00
+#  CASE2-NEXT:      FREs [
+#  CASE2-NEXT:      ]
 #  CASE2-NEXT:    }
 #  CASE2-NEXT:  ]
 #  CASE2-NEXT:}
diff --git a/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test b/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test
new file mode 100644
index 0000000000000..9711d04fe7974
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test
@@ -0,0 +1,284 @@
+# RUN: yaml2obj --docnum=1 %s -o %t.1
+# RUN: llvm-readobj --sframe=.sframe_eof_address --sframe=.sframe_eof_offset --sframe \
+# RUN:   %t.1 2>&1 | \
+# RUN:   FileCheck %s --strict-whitespace --match-full-lines \
+# RUN:   -DFILE=%t.1 --check-prefix=CASE1
+
+# RUN: yaml2obj --docnum=2 %s -o %t.2
+# RUN: llvm-readobj --sframe %t.2 2>&1 | \
+# RUN:   FileCheck %s --strict-whitespace --match-full-lines \
+# RUN:   -DFILE=%t.2 --check-prefix=CASE2
+
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_EXEC
+Sections:
+  - Name:  .sframe_eof_address
+    Type:  SHT_GNU_SFRAME
+    Flags: [ SHF_ALLOC ]
+    ContentArray: [
+      0xe2, 0xde, 0x02, 0x04,  # Preamble (magic, version, flags)
+      # Header:
+      0x03, 0x42, 0x47, 0x00,  # ABI, Fixed FP offset, Fixed RA Offset, AUX header length
+      0x01, 0x00, 0x00, 0x00,  # Number of FDEs
+      0x01, 0x00, 0x00, 0x00,  # Number of FREs
+      0x00, 0x10, 0x00, 0x00,  # FRE length
+      0x00, 0x00, 0x00, 0x00,  # FDE offset
+      0x00, 0x00, 0x00, 0x00,  # FRE offset
+
+      # FDE[0]:
+      0x00, 0xde, 0xad, 0x00,  # Start Address
+      0xbe, 0x01, 0x00, 0x00,  # Size
+      0x00, 0x00, 0x00, 0x00,  # Start FRE Offset
+      0x01, 0x00, 0x00, 0x00,  # Number of FREs
+      0x00, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
+
+      # FRE[0]:
+      0x00                     # Start Address, (missing) Info
+    ]
+# CASE1-LABEL:SFrame section '.sframe_eof_address' {
+#       CASE1:    FuncDescEntry [0] {
+#       CASE1:      Info {
+#  CASE1-NEXT:        FRE Type: Addr1 (0x0)
+#  CASE1-NEXT:        FDE Type: PCInc (0x0)
+#       CASE1:      FREs [
+#  CASE1-NEXT:{{.*}}: warning: '[[FILE]]': unexpected end of data at offset 0x31 while reading [0x30, 0x32)
+#  CASE1-NEXT:      ]
+
+  - Name:  .sframe_eof_offset
+    Type:  SHT_GNU_SFRAME
+    Flags: [ SHF_ALLOC ]
+    ContentArray: [
+      0xe2, 0xde, 0x02, 0x04,  # Preamble (magic, version, flags)
+      # Header:
+      0x03, 0x42, 0x47, 0x00,  # ABI, Fixed FP offset, Fixed RA Offset, AUX header length
+      0x01, 0x00, 0x00, 0x00,  # Number of FDEs
+      0x01, 0x00, 0x00, 0x00,  # Number of FREs
+      0x00, 0x10, 0x00, 0x00,  # FRE length
+      0x00, 0x00, 0x00, 0x00,  # FDE offset
+      0x00, 0x00, 0x00, 0x00,  # FRE offset
+
+      # FDE[0]:
+      0x00, 0xde, 0xad, 0x00,  # Start Address
+      0xbe, 0x01, 0x00, 0x00,  # Size
+      0x00, 0x00, 0x00, 0x00,  # Start FRE Offset
+      0x01, 0x00, 0x00, 0x00,  # Number of FREs
+      0x00, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
+
+      # FRE[0]:
+      0x00, 0x02               # Start Address, Info, (missing) Offsets
+    ]
+# CASE1-LABEL:SFrame section '.sframe_eof_offset' {
+#       CASE1:    FuncDescEntry [0] {
+#       CASE1:      Info {
+#  CASE1-NEXT:        FRE Type: Addr1 (0x0)
+#  CASE1-NEXT:        FDE Type: PCInc (0x0)
+#       CASE1:      FREs [
+#  CASE1-NEXT:{{.*}}: warning: '[[FILE]]': unexpected end of data at offset 0x32 while reading [0x32, 0x33)
+#  CASE1-NEXT:      ]
+
+  - Name:  .sframe
+    Type:  SHT_GNU_SFRAME
+    Flags: [ SHF_ALLOC ]
+    ContentArray: [
+      0xe2, 0xde, 0x02, 0x04,  # Preamble (magic, version, flags)
+      # Header:
+      0x03, 0x42, 0x40, 0x00,  # ABI, Fixed FP offset, Fixed RA Offset, AUX header length
+      0x02, 0x00, 0x00, 0x00,  # Number of FDEs
+      0x01, 0x00, 0x00, 0x00,  # Number of FREs
+      0x00, 0x10, 0x00, 0x00,  # FRE length
+      0x00, 0x00, 0x00, 0x00,  # FDE offset
+      0x00, 0x00, 0x00, 0x00,  # FRE offset
+
+      # FDE[0]:
+      0x00, 0x00, 0xde, 0x00,  # Start Address
+      0xbe, 0x01, 0x00, 0x00,  # Size
+      0x00, 0x00, 0x00, 0x00,  # Start FRE Offset
+      0x02, 0x00, 0x00, 0x00,  # Number of FREs
+      0x00, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
+
+      # FDE[1]:
+      0x00, 0x00, 0xad, 0x00,  # Start Address
+      0xbe, 0x01, 0x00, 0x00,  # Size
+      0x08, 0x00, 0x00, 0x00,  # Start FRE Offset
+      0x03, 0x00, 0x00, 0x00,  # Number of FREs
+      0x11, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
+
+      # FRE[0,0]: Zero offsets
+      0x00, 0x00,              # Start Address, Info
+
+      # FRE[0,1]: One four-byte offset, SP-based
+      0x01, 0x43,              # Start Address, Info
+      0x74, 0x00, 0x00, 0x00,  # Offset[0]
+
+      # FRE[1,0]: Two two-byte offsets, FP-based
+      0x00, 0x00, 0x24,        # Start Address, Info
+      0x10, 0x00, 0x20, 0x00,  # Offset[0], Offset[1]
+
+      # FRE[1,1]: Four one-byte offsets, FP-based
+      0x00, 0x01, 0x08,        # Start Address, Info
+      0x10, 0x20, 0x30, 0x40,  # Offset[0-3]
+    ]
+## Testing:
+## - little-endian support
+## - AMD64 ABI
+## - one and two byte address sizes
+## - PCInc and PCMask FDE types
+## - all offset sizes
+## - various offset counts
+## - hitting EOF after printing some number of FREs
+# CASE1-LABEL:SFrame section '.sframe' {
+#       CASE1:    ABI: AMD64EndianLittle (0x3)
+#       CASE1:    CFA fixed RA offset: 64
+#       CASE1:    FuncDescEntry [0] {
+#  CASE1-NEXT:      PC: 0xDE007F
+#       CASE1:      Info {
+#  CASE1-NEXT:        FRE Type: Addr1 (0x0)
+#  CASE1-NEXT:        FDE Type: PCInc (0x0)
+#       CASE1:      FREs [
+#  CASE1-NEXT:        Frame Row Entry {
+#  CASE1-NEXT:          Start Address: 0xDE007F
+#  CASE1-NEXT:          Return Address Signed: No
+#  CASE1-NEXT:          Offset Size: B1 (0x0)
+#  CASE1-NEXT:          Base Register: FP (0x0)
+#  CASE1-NEXT:          RA Offset: 64
+#  CASE1-NEXT:        }
+#  CASE1-NEXT:        Frame Row Entry {
+#  CASE1-NEXT:          Start Address: 0xDE0080
+#  CASE1-NEXT:          Return Address Signed: No
+#  CASE1-NEXT:          Offset Size: B4 (0x2)
+#  CASE1-NEXT:          Base Register: SP (0x1)
+#  CASE1-NEXT:          CFA Offset: 116
+#  CASE1-NEXT:          RA Offset: 64
+#  CASE1-NEXT:        }
+#  CASE1-NEXT:      ]
+#       CASE1:    FuncDescEntry [1] {
+#  CASE1-NEXT:      PC: 0xAD0093
+#       CASE1:      Info {
+#  CASE1-NEXT:        FRE Type: Addr2 (0x1)
+#  CASE1-NEXT:        FDE Type: PCMask (0x1)
+#       CASE1:      FREs [
+#  CASE1-NEXT:        Frame Row Entry {
+#  CASE1-NEXT:          Start Address: 0x0
+#  CASE1-NEXT:          Return Address Signed: No
+#  CASE1-NEXT:          Offset Size: B2 (0x1)
+#  CASE1-NEXT:          Base Register: FP (0x0)
+#  CASE1-NEXT:          CFA Offset: 16
+#  CASE1-NEXT:          RA Offset: 64
+#  CASE1-NEXT:          FP Offset: 32
+#  CASE1-NEXT:        }
+#  CASE1-NEXT:        Frame Row Entry {
+#  CASE1-NEXT:          Start Address: 0x100
+#  CASE1-NEXT:          Return Address Signed: No
+#  CASE1-NEXT:          Offset Size: B1 (0x0)
+#  CASE1-NEXT:          Base Register: FP (0x0)
+#  CASE1-NEXT:          CFA Offset: 16
+#  CASE1-NEXT:          RA Offset: 64
+#  CASE1-NEXT:          FP Offset: 32
+#  CASE1-NEXT:          Extra Offsets: [48, 64]
+#  CASE1-NEXT:        }
+#  CASE1-NEXT:{{.*}}: warning: '[[FILE]]': unexpected end of data at offset 0x5a while reading [0x5a, 0x5d)
+
+--- !ELF
+FileHeader:
+  Class: ELFCLASS64
+  Data:  ELFDATA2MSB
+  Type:  ET_EXEC
+Sections:
+  - Name:  .sframe
+    Type:  SHT_GNU_SFRAME
+    Flags: [ SHF_ALLOC ]
+    ContentArray: [
+      0xde, 0xe2, 0x02, 0x05,  # Preamble (magic, version, flags)
+      # Header:
+      0x01, 0x42, 0x47, 0x00,  # ABI, Fixed FP offset, Fixed RA Offset, AUX header length
+      0x00, 0x00, 0x00, 0x01,  # Number of FDEs
+      0x00, 0x00, 0x00, 0x10,  # Number of FREs
+      0x00, 0x00, 0x10, 0x00,  # FRE length
+      0x00, 0x00, 0x00, 0x00,  # FDE offset
+      0x00, 0x00, 0x01, 0x00,  # FRE offset
+
+      # FDE:
+      0x00, 0xde, 0x00, 0x00,  # Start Address
+      0x00, 0x00, 0x01, 0xbe,  # Size
+      0x00, 0x00, 0x00, 0x00,  # Start FRE Offset
+      0x00, 0x00, 0x00, 0x05,  # Number of FREs
+      0x02, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
+
+      # FRE[0]: Zero offsets
+      0x00, 0x00, 0x00, 0x00,  # Start Address
+      0x00,                    # Info
+
+      # FRE[1]: One offset
+      0x00, 0x00, 0x00, 0x01,  # Start Address
+      0x82, 0x10,              # Info, Offset[0]
+
+      # FRE[2]: Two offsets
+      0x00, 0x00, 0x00, 0x02,  # Start Address
+      0x04, 0x10, 0x20,        # Info, Offset[0-1]
+
+      # FRE[3]: Three offsets
+      0x00, 0x00, 0x00, 0x03,  # Start Address
+      0x86, 0x10, 0x20, 0x30,  # Info, Offset[0-2]
+
+      # FRE[4]: Four offsets
+      0x00, 0x00, 0x00, 0x04,  # Start Address
+      0x08,                    # Info
+      0x10, 0x20, 0x30, 0x40,  # Offset[0-3]
+    ]
+## Testing:
+## - big-endian support
+## - ARM64 ABI
+## - four-byte address sizes
+## - return address signing
+## - various offset counts
+# CASE2-LABEL:SFrame section '.sframe' {
+#       CASE2:    ABI: AArch64EndianBig (0x1)
+#       CASE2:    FuncDescEntry [0] {
+#  CASE2-NEXT:      PC: 0xDE001C
+#       CASE2:      Info {
+#  CASE2-NEXT:        FRE Type: Addr4 (0x2)
+#  CASE2-NEXT:        FDE Type: PCInc (0x0)
+#       CASE2:      FREs [
+#  CASE2-NEXT:        Frame Row Entry {
+#  CASE2-NEXT:          Start Address: 0xDE001C
+#  CASE2-NEXT:          Return Address Signed: No
+#  CASE2-NEXT:     ...
[truncated]

@labath
Copy link
Collaborator Author

labath commented Jul 30, 2025

@dwblaikie, @efriedma-quic, I am tagging you because you were involved in the DataExtractor-vs-reinterpret_cast discussion.

@jh7370, I am also going to be out for the rest of this week, so there's no rush. I appreciate all your reviews so far.

Comment on lines +72 to +78
ArrayRef<EnumEntry<sframe::BaseReg>> sframe::getBaseRegisters() {
static constexpr EnumEntry<sframe::BaseReg> BaseRegs[] = {
{"FP", sframe::BaseReg::FP},
{"SP", sframe::BaseReg::SP},
};
return ArrayRef(BaseRegs);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm probably missing something, but why not just have the static local exposed as a constant without the function wrapper?

@@ -100,6 +111,116 @@ uint64_t SFrameParser<E>::getAbsoluteStartAddress(
return Result;
}

template <typename EndianT>
static Error readArray(ArrayRef<uint8_t> Data, uint64_t Count, uint64_t &Offset,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this returning an Error rather than an Expected?

}

template <typename T, endianness E>
static Error readFRE(ArrayRef<uint8_t> Data, uint64_t &Offset,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question.

template <endianness E>
std::optional<int32_t>
SFrameParser<E>::getCFAOffset(const FrameRowEntry &FRE) const {
return getOffset(FRE.Offsets, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm a little concerned about this and the similar code in the following blocks, mostly because future ABIs/architectures won't necessarily have the same meanings for the different offsets. It's probably sufficient for now to add a comment or two highlighting that all supported ABIs use the specified offset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants