[llvm] Reapply "[Object] Parsing and dumping of SFrame Frame Row Entries" (#152650) (PR #152695)

Pavel Labath via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 8 04:55:26 PDT 2025


https://github.com/labath created https://github.com/llvm/llvm-project/pull/152695

This reapplies #152650 with a build fix for clang-11 (need explicit template parameters for ArrayRef construction) and avoiding the default-in-a-switch-covering-enum warning. It also adds two new tests.

The original commit message was:

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).


>From dc08b6dcb676dc57a88eb91e7ca1a3a279314246 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Fri, 8 Aug 2025 09:54:23 +0200
Subject: [PATCH 1/3] Reapply "[Object] Parsing and dumping of SFrame Frame Row
 Entries" (#152650)

This reverts commit 7e8a251f751c47f31103db2975a34d1b7780d992.
---
 llvm/include/llvm/BinaryFormat/SFrame.h       |   1 +
 llvm/include/llvm/Object/SFrameParser.h       |  51 ++++
 llvm/lib/BinaryFormat/SFrame.cpp              |   8 +
 llvm/lib/Object/SFrameParser.cpp              | 132 +++++++-
 .../tools/llvm-readobj/ELF/sframe-fde.test    |  10 +-
 .../tools/llvm-readobj/ELF/sframe-fre.test    | 284 ++++++++++++++++++
 llvm/tools/llvm-readobj/ELFDumper.cpp         |  29 +-
 7 files changed, 508 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/sframe-fre.test

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..825b7af368211 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,119 @@ 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;
+}
+
+// The interpretation of offsets is ABI-specific. The implementation of this and
+// the following functions may need to be adjusted when adding support for a new
+// ABI.
+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:          Offset Size: B1 (0x0)
+#  CASE2-NEXT:          Base Register: FP (0x0)
+#  CASE2-NEXT:        }
+#  CASE2-NEXT:        Frame Row Entry {
+#  CASE2-NEXT:          Start Address: 0xDE001D
+#  CASE2-NEXT:          Return Address Signed: Yes
+#  CASE2-NEXT:          Offset Size: B1 (0x0)
+#  CASE2-NEXT:          Base Register: FP (0x0)
+#  CASE2-NEXT:          CFA Offset: 16
+#  CASE2-NEXT:        }
+#  CASE2-NEXT:        Frame Row Entry {
+#  CASE2-NEXT:          Start Address: 0xDE001E
+#  CASE2-NEXT:          Return Address Signed: No
+#  CASE2-NEXT:          Offset Size: B1 (0x0)
+#  CASE2-NEXT:          Base Register: FP (0x0)
+#  CASE2-NEXT:          CFA Offset: 16
+#  CASE2-NEXT:          RA Offset: 32
+#  CASE2-NEXT:        }
+#  CASE2-NEXT:        Frame Row Entry {
+#  CASE2-NEXT:          Start Address: 0xDE001F
+#  CASE2-NEXT:          Return Address Signed: Yes
+#  CASE2-NEXT:          Offset Size: B1 (0x0)
+#  CASE2-NEXT:          Base Register: FP (0x0)
+#  CASE2-NEXT:          CFA Offset: 16
+#  CASE2-NEXT:          RA Offset: 32
+#  CASE2-NEXT:          FP Offset: 48
+#  CASE2-NEXT:        }
+#  CASE2-NEXT:        Frame Row Entry {
+#  CASE2-NEXT:          Start Address: 0xDE0020
+#  CASE2-NEXT:          Return Address Signed: No
+#  CASE2-NEXT:          Offset Size: B1 (0x0)
+#  CASE2-NEXT:          Base Register: FP (0x0)
+#  CASE2-NEXT:          CFA Offset: 16
+#  CASE2-NEXT:          RA Offset: 32
+#  CASE2-NEXT:          FP Offset: 48
+#  CASE2-NEXT:          Extra Offsets: [64]
+#  CASE2-NEXT:        }
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 26a84fae5f7e0..ed29e0bf3f7cd 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -6497,7 +6497,8 @@ void ELFDumper<ELFT>::printSFrameFDEs(
         W,
         formatv("FuncDescEntry [{0}]", std::distance(FDEs.begin(), It)).str());
 
-    W.printHex("PC", Parser.getAbsoluteStartAddress(It));
+    uint64_t FDEStartAddress = Parser.getAbsoluteStartAddress(It);
+    W.printHex("PC", FDEStartAddress);
     W.printHex("Size", It->Size);
     W.printHex("Start FRE Offset", It->StartFREOff);
     W.printNumber("Num FREs", It->NumFREs);
@@ -6527,6 +6528,32 @@ void ELFDumper<ELFT>::printSFrameFDEs(
         It->RepSize);
 
     W.printHex("Padding2", It->Padding2);
+
+    ListScope FREListScope(W, "FREs");
+    Error Err = Error::success();
+    for (const typename SFrameParser<ELFT::Endianness>::FrameRowEntry &FRE :
+         Parser.fres(*It, Err)) {
+      DictScope FREScope(W, "Frame Row Entry");
+      W.printHex(
+          "Start Address",
+          (It->getFDEType() == sframe::FDEType::PCInc ? FDEStartAddress : 0) +
+              FRE.StartAddress);
+      W.printBoolean("Return Address Signed", FRE.Info.isReturnAddressSigned());
+      W.printEnum("Offset Size", FRE.Info.getOffsetSize(),
+                  sframe::getFREOffsets());
+      W.printEnum("Base Register", FRE.Info.getBaseRegister(),
+                  sframe::getBaseRegisters());
+      if (std::optional<int32_t> Off = Parser.getCFAOffset(FRE))
+        W.printNumber("CFA Offset", *Off);
+      if (std::optional<int32_t> Off = Parser.getRAOffset(FRE))
+        W.printNumber("RA Offset", *Off);
+      if (std::optional<int32_t> Off = Parser.getFPOffset(FRE))
+        W.printNumber("FP Offset", *Off);
+      if (ArrayRef<int32_t> Offs = Parser.getExtraOffsets(FRE); !Offs.empty())
+        W.printList("Extra Offsets", Offs);
+    }
+    if (Err)
+      reportWarning(std::move(Err), FileName);
   }
 }
 

>From bdac77dd917ea765a40e0dc4ff5baf518c7e4c4c Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Fri, 8 Aug 2025 09:54:37 +0200
Subject: [PATCH 2/3] build fixes

---
 llvm/lib/Object/SFrameParser.cpp | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Object/SFrameParser.cpp b/llvm/lib/Object/SFrameParser.cpp
index 825b7af368211..4e33b53674d85 100644
--- a/llvm/lib/Object/SFrameParser.cpp
+++ b/llvm/lib/Object/SFrameParser.cpp
@@ -146,9 +146,8 @@ static Error readFRE(ArrayRef<uint8_t> Data, uint64_t &Offset,
   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");
   }
+  return createError("unsupported/unknown offset size");
 }
 
 template <endianness E> Error SFrameParser<E>::FallibleFREIterator::inc() {
@@ -162,9 +161,8 @@ template <endianness E> Error SFrameParser<E>::FallibleFREIterator::inc() {
     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");
   }
+  return createError("invalid/unsupported FRE type");
 }
 
 template <endianness E>
@@ -220,7 +218,7 @@ SFrameParser<E>::getExtraOffsets(const FrameRowEntry &FRE) const {
   if (!usesFixedFPOffset())
     ++UsedOffsets;
   if (FRE.Offsets.size() > UsedOffsets)
-    return ArrayRef(FRE.Offsets).drop_front(UsedOffsets);
+    return ArrayRef<int32_t>(FRE.Offsets).drop_front(UsedOffsets);
   return {};
 }
 

>From 433566040e4f3847bc327f6ab0c4ea7a0f71c00a Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Fri, 8 Aug 2025 13:12:17 +0200
Subject: [PATCH 3/3] Add invalid encoding tests

---
 llvm/lib/Object/SFrameParser.cpp              |  7 ++++--
 .../tools/llvm-readobj/ELF/sframe-fre.test    | 25 ++++++++++++++++---
 2 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Object/SFrameParser.cpp b/llvm/lib/Object/SFrameParser.cpp
index 4e33b53674d85..847067a7ae989 100644
--- a/llvm/lib/Object/SFrameParser.cpp
+++ b/llvm/lib/Object/SFrameParser.cpp
@@ -147,7 +147,9 @@ static Error readFRE(ArrayRef<uint8_t> Data, uint64_t &Offset,
     return readArray<sframe::detail::packed<int32_t, E>>(
         Data, FRE.Info.getOffsetCount(), Offset, FRE.Offsets);
   }
-  return createError("unsupported/unknown offset size");
+  return createError(formatv("unsupported FRE offset size {0} at offset {1:x+}",
+                             static_cast<unsigned>(FRE.Info.getOffsetSize()),
+                             Offset));
 }
 
 template <endianness E> Error SFrameParser<E>::FallibleFREIterator::inc() {
@@ -162,7 +164,8 @@ template <endianness E> Error SFrameParser<E>::FallibleFREIterator::inc() {
   case sframe::FREType::Addr4:
     return readFRE<uint32_t, E>(Data, Offset, FRE);
   }
-  return createError("invalid/unsupported FRE type");
+  return createError(formatv("unsupported FRE type {0} at offset {1:x+}",
+                             static_cast<unsigned>(FREType), Offset));
 }
 
 template <endianness E>
diff --git a/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test b/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test
index 9711d04fe7974..3f1e7d667f47e 100644
--- a/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test
+++ b/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test
@@ -86,7 +86,7 @@ Sections:
       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
+      0x03, 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
@@ -106,6 +106,13 @@ Sections:
       0x03, 0x00, 0x00, 0x00,  # Number of FREs
       0x11, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
 
+      # FDE[2]:
+      0x00, 0x00, 0xbe, 0x00,  # Start Address
+      0xbe, 0x01, 0x00, 0x00,  # Size
+      0x08, 0x00, 0x00, 0x00,  # Start FRE Offset
+      0x01, 0x00, 0x00, 0x00,  # Number of FREs
+      0x03, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
+
       # FRE[0,0]: Zero offsets
       0x00, 0x00,              # Start Address, Info
 
@@ -129,6 +136,7 @@ Sections:
 ## - all offset sizes
 ## - various offset counts
 ## - hitting EOF after printing some number of FREs
+## - invalid FRE type
 # CASE1-LABEL:SFrame section '.sframe' {
 #       CASE1:    ABI: AMD64EndianLittle (0x3)
 #       CASE1:    CFA fixed RA offset: 64
@@ -179,7 +187,12 @@ Sections:
 #  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)
+#  CASE1-NEXT:{{.*}}: warning: '[[FILE]]': unexpected end of data at offset 0x6e while reading [0x6e, 0x71)
+#       CASE1:    FuncDescEntry [2] {
+#       CASE1:      Info {
+#  CASE1-NEXT:        FRE Type: 0x3
+#       CASE1:      FREs [
+#  CASE1-NEXT:{{.*}}: warning: '[[FILE]]': unsupported FRE type 3 at offset 0x60
 
 --- !ELF
 FileHeader:
@@ -204,7 +217,7 @@ Sections:
       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
+      0x00, 0x00, 0x00, 0x06,  # Number of FREs
       0x02, 0xde, 0xad, 0x00,  # Info, RepSize, Padding2
 
       # FRE[0]: Zero offsets
@@ -227,6 +240,10 @@ Sections:
       0x00, 0x00, 0x00, 0x04,  # Start Address
       0x08,                    # Info
       0x10, 0x20, 0x30, 0x40,  # Offset[0-3]
+
+      # FRE[4]: Invalid offset size
+      0x00, 0x00, 0x00, 0x05,  # Start Address
+      0x60,                    # Info
     ]
 ## Testing:
 ## - big-endian support
@@ -234,6 +251,7 @@ Sections:
 ## - four-byte address sizes
 ## - return address signing
 ## - various offset counts
+## - invalid offset size
 # CASE2-LABEL:SFrame section '.sframe' {
 #       CASE2:    ABI: AArch64EndianBig (0x1)
 #       CASE2:    FuncDescEntry [0] {
@@ -282,3 +300,4 @@ Sections:
 #  CASE2-NEXT:          FP Offset: 48
 #  CASE2-NEXT:          Extra Offsets: [64]
 #  CASE2-NEXT:        }
+#  CASE2-NEXT:{{.*}}: warning: '[[FILE]]': unsupported FRE offset size 3 at offset 0x58



More information about the llvm-commits mailing list