[llvm] [llvm-readobj][sframe] Add support for s390x (PR #155418)

Pavel Labath via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 26 07:13:06 PDT 2025


https://github.com/labath updated https://github.com/llvm/llvm-project/pull/155418

>From a3e4d11bb8f071d689b9a0c246d6c6ae8fd99c39 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Tue, 26 Aug 2025 16:02:35 +0200
Subject: [PATCH] [llvm-readobj][sframe] Add support for s390x

s390x support was recently added to binutils. This patch implements the
necessary (parsing) functionality in llvm.

The only difference is in the interpretation of the offset fields, which
is more complex for s390x. The CFA value uses a displacement and
a scaling factor to enable the usage of one-byte offsets as s390x frames
have at least 160 bytes. The FP and RA offsets also use a custom
encoding to be able to represent values which are saved to a (different)
register (only for leaf functions). This required a change in the parser
interface and the dump format.
---
 llvm/include/llvm/BinaryFormat/SFrame.h       |  18 +++
 .../llvm/BinaryFormat/SFrameConstants.def     |   1 +
 llvm/include/llvm/Object/SFrameParser.h       |  13 +-
 llvm/lib/Object/SFrameParser.cpp              |  56 +++++++--
 .../tools/llvm-readobj/ELF/sframe-fre.test    |  55 +++++++--
 .../tools/llvm-readobj/ELF/sframe-s390x.test  | 111 ++++++++++++++++++
 llvm/tools/llvm-readobj/ELFDumper.cpp         |  21 +++-
 7 files changed, 249 insertions(+), 26 deletions(-)
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/sframe-s390x.test

diff --git a/llvm/include/llvm/BinaryFormat/SFrame.h b/llvm/include/llvm/BinaryFormat/SFrame.h
index 095db18b9c254..f7e14f05d0d9d 100644
--- a/llvm/include/llvm/BinaryFormat/SFrame.h
+++ b/llvm/include/llvm/BinaryFormat/SFrame.h
@@ -31,6 +31,24 @@ LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
 
 constexpr uint16_t Magic = 0xdee2;
 
+constexpr int32_t FRERAOffsetInvalid = 0;
+
+constexpr int32_t S390xCFAOffsetAdjustment = -160;
+constexpr int32_t S390xCFAOffsetAlignmentFactor = 8;
+constexpr int32_t v2S390xCFAOffsetEncode(int32_t Offset) {
+  return (Offset + S390xCFAOffsetAdjustment) / S390xCFAOffsetAlignmentFactor;
+}
+constexpr int32_t v2S390xCFAOffsetDecode(int32_t Offset) {
+  return (Offset * S390xCFAOffsetAlignmentFactor) - S390xCFAOffsetAdjustment;
+}
+constexpr bool v2S390xOffsetIsRegnum(int32_t Offset) { return Offset & 1; }
+constexpr int32_t v2S390xOffsetEncodeRegnum(int32_t Regnum) {
+  return (Regnum << 1) | 1;
+}
+constexpr int32_t v2S390xOffsetDecodeRegnum(int32_t Offset) {
+  return Offset >> 1;
+}
+
 enum class Version : uint8_t {
 #define HANDLE_SFRAME_VERSION(CODE, NAME) NAME = CODE,
 #include "llvm/BinaryFormat/SFrameConstants.def"
diff --git a/llvm/include/llvm/BinaryFormat/SFrameConstants.def b/llvm/include/llvm/BinaryFormat/SFrameConstants.def
index fddd440e41f32..0cdd2a76bf728 100644
--- a/llvm/include/llvm/BinaryFormat/SFrameConstants.def
+++ b/llvm/include/llvm/BinaryFormat/SFrameConstants.def
@@ -52,6 +52,7 @@ HANDLE_SFRAME_FLAG(0x04, FDEFuncStartPCRel)
 HANDLE_SFRAME_ABI(0x01, AArch64EndianBig)
 HANDLE_SFRAME_ABI(0x02, AArch64EndianLittle)
 HANDLE_SFRAME_ABI(0x03, AMD64EndianLittle)
+HANDLE_SFRAME_ABI(0x04, S390xEndianBig)
 
 HANDLE_SFRAME_FRE_TYPE(0x00, Addr1)
 HANDLE_SFRAME_FRE_TYPE(0x01, Addr2)
diff --git a/llvm/include/llvm/Object/SFrameParser.h b/llvm/include/llvm/Object/SFrameParser.h
index 3ce5d70142a9f..ac5235efdad37 100644
--- a/llvm/include/llvm/Object/SFrameParser.h
+++ b/llvm/include/llvm/Object/SFrameParser.h
@@ -59,9 +59,16 @@ template <endianness E> class SFrameParser {
   iterator_range<fre_iterator> fres(const sframe::FuncDescEntry<E> &FDE,
                                     Error &Err) const;
 
+  struct RegisterLocation {
+    enum ValueKind { Register, StackSlot };
+
+    enum ValueKind Kind;
+    int32_t Value;
+  };
+
   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;
+  std::optional<RegisterLocation> getRAOffset(const FrameRowEntry &FRE) const;
+  std::optional<RegisterLocation> getFPOffset(const FrameRowEntry &FRE) const;
   ArrayRef<int32_t> getExtraOffsets(const FrameRowEntry &FRE) const;
 
 private:
@@ -80,6 +87,8 @@ template <endianness E> class SFrameParser {
   uint64_t getFREBase() const {
     return getFDEBase() + Header.NumFDEs * sizeof(sframe::FuncDescEntry<E>);
   }
+
+  RegisterLocation getRegisterLocation(int32_t Offset) const;
 };
 
 template <endianness E> class SFrameParser<E>::FallibleFREIterator {
diff --git a/llvm/lib/Object/SFrameParser.cpp b/llvm/lib/Object/SFrameParser.cpp
index 759b579230d9d..efec2635a7f04 100644
--- a/llvm/lib/Object/SFrameParser.cpp
+++ b/llvm/lib/Object/SFrameParser.cpp
@@ -196,23 +196,61 @@ static std::optional<int32_t> getOffset(ArrayRef<int32_t> Offsets, size_t Idx) {
 template <endianness E>
 std::optional<int32_t>
 SFrameParser<E>::getCFAOffset(const FrameRowEntry &FRE) const {
-  return getOffset(FRE.Offsets, 0);
+  std::optional<int32_t> Offset = getOffset(FRE.Offsets, 0);
+  switch (Header.ABIArch) {
+  case sframe::ABI::AArch64EndianBig:
+  case sframe::ABI::AArch64EndianLittle:
+  case sframe::ABI::AMD64EndianLittle:
+    return Offset;
+
+  case sframe::ABI::S390xEndianBig:
+    if (!Offset)
+      return std::nullopt;
+    return sframe::v2S390xCFAOffsetDecode(*Offset);
+  }
+  llvm_unreachable("Unhandled ABI!");
 }
 
 template <endianness E>
-std::optional<int32_t>
+typename SFrameParser<E>::RegisterLocation
+SFrameParser<E>::getRegisterLocation(int32_t Offset) const {
+  switch (Header.ABIArch) {
+  case sframe::ABI::S390xEndianBig:
+    if (sframe::v2S390xOffsetIsRegnum(Offset))
+      return RegisterLocation{RegisterLocation::Register,
+                              sframe::v2S390xOffsetDecodeRegnum(Offset)};
+    [[fallthrough]];
+  case sframe::ABI::AArch64EndianBig:
+  case sframe::ABI::AArch64EndianLittle:
+  case sframe::ABI::AMD64EndianLittle:
+    return RegisterLocation{RegisterLocation::StackSlot, Offset};
+  }
+  llvm_unreachable("Unhandled ABI!");
+}
+
+template <endianness E>
+std::optional<typename SFrameParser<E>::RegisterLocation>
 SFrameParser<E>::getRAOffset(const FrameRowEntry &FRE) const {
-  if (usesFixedRAOffset())
-    return Header.CFAFixedRAOffset;
-  return getOffset(FRE.Offsets, 1);
+  std::optional<int32_t> Offset =
+      usesFixedRAOffset() ? Header.CFAFixedRAOffset : getOffset(FRE.Offsets, 1);
+  if (!Offset)
+    return std::nullopt;
+  if (Header.ABIArch == sframe::ABI::S390xEndianBig &&
+      *Offset == sframe::FRERAOffsetInvalid)
+    return std::nullopt;
+  return getRegisterLocation(*Offset);
 }
 
 template <endianness E>
-std::optional<int32_t>
+std::optional<typename SFrameParser<E>::RegisterLocation>
 SFrameParser<E>::getFPOffset(const FrameRowEntry &FRE) const {
-  if (usesFixedFPOffset())
-    return Header.CFAFixedFPOffset;
-  return getOffset(FRE.Offsets, usesFixedRAOffset() ? 1 : 2);
+  size_t Idx = usesFixedRAOffset() ? 1 : 2;
+  std::optional<int32_t> Offset = usesFixedFPOffset()
+                                      ? Header.CFAFixedFPOffset
+                                      : getOffset(FRE.Offsets, Idx);
+  if (!Offset)
+    return std::nullopt;
+  return getRegisterLocation(*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 3f1e7d667f47e..023fdc77cc31b 100644
--- a/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test
+++ b/llvm/test/tools/llvm-readobj/ELF/sframe-fre.test
@@ -151,7 +151,10 @@ Sections:
 #  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:          RA Offset {
+#  CASE1-NEXT:            Kind: Stack Slot
+#  CASE1-NEXT:            Value: 64
+#  CASE1-NEXT:          }
 #  CASE1-NEXT:        }
 #  CASE1-NEXT:        Frame Row Entry {
 #  CASE1-NEXT:          Start Address: 0xDE0080
@@ -159,7 +162,10 @@ Sections:
 #  CASE1-NEXT:          Offset Size: B4 (0x2)
 #  CASE1-NEXT:          Base Register: SP (0x1)
 #  CASE1-NEXT:          CFA Offset: 116
-#  CASE1-NEXT:          RA Offset: 64
+#  CASE1-NEXT:          RA Offset {
+#  CASE1-NEXT:            Kind: Stack Slot
+#  CASE1-NEXT:            Value: 64
+#  CASE1-NEXT:          }
 #  CASE1-NEXT:        }
 #  CASE1-NEXT:      ]
 #       CASE1:    FuncDescEntry [1] {
@@ -174,8 +180,14 @@ Sections:
 #  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:          RA Offset {
+#  CASE1-NEXT:            Kind: Stack Slot
+#  CASE1-NEXT:            Value: 64
+#  CASE1-NEXT:          }
+#  CASE1-NEXT:          FP Offset {
+#  CASE1-NEXT:            Kind: Stack Slot
+#  CASE1-NEXT:            Value: 32
+#  CASE1-NEXT:          }
 #  CASE1-NEXT:        }
 #  CASE1-NEXT:        Frame Row Entry {
 #  CASE1-NEXT:          Start Address: 0x100
@@ -183,8 +195,14 @@ Sections:
 #  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:          RA Offset {
+#  CASE1-NEXT:            Kind: Stack Slot
+#  CASE1-NEXT:            Value: 64
+#  CASE1-NEXT:          }
+#  CASE1-NEXT:          FP Offset {
+#  CASE1-NEXT:            Kind: Stack Slot
+#  CASE1-NEXT:            Value: 32
+#  CASE1-NEXT:          }
 #  CASE1-NEXT:          Extra Offsets: [48, 64]
 #  CASE1-NEXT:        }
 #  CASE1-NEXT:{{.*}}: warning: '[[FILE]]': unexpected end of data at offset 0x6e while reading [0x6e, 0x71)
@@ -279,7 +297,10 @@ Sections:
 #  CASE2-NEXT:          Offset Size: B1 (0x0)
 #  CASE2-NEXT:          Base Register: FP (0x0)
 #  CASE2-NEXT:          CFA Offset: 16
-#  CASE2-NEXT:          RA Offset: 32
+#  CASE2-NEXT:          RA Offset {
+#  CASE2-NEXT:            Kind: Stack Slot
+#  CASE2-NEXT:            Value: 32
+#  CASE2-NEXT:          }
 #  CASE2-NEXT:        }
 #  CASE2-NEXT:        Frame Row Entry {
 #  CASE2-NEXT:          Start Address: 0xDE001F
@@ -287,8 +308,14 @@ Sections:
 #  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:          RA Offset {
+#  CASE2-NEXT:            Kind: Stack Slot
+#  CASE2-NEXT:            Value: 32
+#  CASE2-NEXT:          }
+#  CASE2-NEXT:          FP Offset {
+#  CASE2-NEXT:            Kind: Stack Slot
+#  CASE2-NEXT:            Value: 48
+#  CASE2-NEXT:          }
 #  CASE2-NEXT:        }
 #  CASE2-NEXT:        Frame Row Entry {
 #  CASE2-NEXT:          Start Address: 0xDE0020
@@ -296,8 +323,14 @@ Sections:
 #  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:          RA Offset {
+#  CASE2-NEXT:            Kind: Stack Slot
+#  CASE2-NEXT:            Value: 32
+#  CASE2-NEXT:          }
+#  CASE2-NEXT:          FP Offset {
+#  CASE2-NEXT:            Kind: Stack Slot
+#  CASE2-NEXT:            Value: 48
+#  CASE2-NEXT:          }
 #  CASE2-NEXT:          Extra Offsets: [64]
 #  CASE2-NEXT:        }
 #  CASE2-NEXT:{{.*}}: warning: '[[FILE]]': unsupported FRE offset size 3 at offset 0x58
diff --git a/llvm/test/tools/llvm-readobj/ELF/sframe-s390x.test b/llvm/test/tools/llvm-readobj/ELF/sframe-s390x.test
new file mode 100644
index 0000000000000..96fceca2bef14
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/sframe-s390x.test
@@ -0,0 +1,111 @@
+# RUN: yaml2obj %s -o %t
+# RUN: llvm-readobj --sframe=.sframe_eof_address --sframe %t 2>&1 | \
+# RUN:   FileCheck %s --strict-whitespace --match-full-lines -DFILE=%t
+
+--- !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:
+      0x04, 0x00, 0x00, 0x00,  # ABI, Fixed FP offset, Fixed RA Offset, AUX header length
+      0x00, 0x00, 0x00, 0x01,  # Number of FDEs
+      0x00, 0x00, 0x00, 0x04,  # Number of FREs
+      0x00, 0x00, 0x00, 0x00,  # FRE length
+      0x00, 0x00, 0x00, 0x00,  # FDE offset
+      0x00, 0x00, 0x00, 0x00,  # FRE offset
+
+      # FDE:
+      0x00, 0xde, 0x00, 0x00,  # Start Address
+      0x00, 0x00, 0x00, 0xbe,  # Size
+      0x00, 0x00, 0x00, 0x00,  # Start FRE Offset
+      0x00, 0x00, 0x00, 0x05,  # Number of FREs
+      0x02, 0x00, 0x00, 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
+      0x02, 0x10,              # Info, Offset[0]
+
+      # FRE[2]: Two offsets
+      0x00, 0x00, 0x00, 0x02,  # Start Address
+      0x04, 0x10, 0x21,        # Info, Offset[0-1]
+
+      # FRE[3]: Three offsets, second offset empty
+      0x00, 0x00, 0x00, 0x03,  # Start Address
+      0x07, 0x10, 0x00, 0x30,  # Info, Offset[0-2]
+
+      # FRE[4]: Four offsets
+      0x00, 0x00, 0x00, 0x04,  # Start Address
+      0x08,                    # Info
+      0x10, 0x20, 0x31, 0x40,  # Offset[0-3]
+
+    ]
+# CHECK-LABEL:SFrame section '.sframe' {
+#       CHECK:    ABI: S390xEndianBig (0x4)
+#       CHECK:    FuncDescEntry [0] {
+#  CHECK-NEXT:      PC: 0xDE001C
+#       CHECK:      Info {
+#  CHECK-NEXT:        FRE Type: Addr4 (0x2)
+#  CHECK-NEXT:        FDE Type: PCInc (0x0)
+#       CHECK:      FREs [
+#  CHECK-NEXT:        Frame Row Entry {
+#  CHECK-NEXT:          Start Address: 0xDE001C
+#  CHECK-NEXT:          Return Address Signed: No
+#  CHECK-NEXT:          Offset Size: B1 (0x0)
+#  CHECK-NEXT:          Base Register: FP (0x0)
+#  CHECK-NEXT:        }
+#  CHECK-NEXT:        Frame Row Entry {
+#  CHECK-NEXT:          Start Address: 0xDE001D
+#  CHECK-NEXT:          Return Address Signed: No
+#  CHECK-NEXT:          Offset Size: B1 (0x0)
+#  CHECK-NEXT:          Base Register: FP (0x0)
+#  CHECK-NEXT:          CFA Offset: 288
+#  CHECK-NEXT:        }
+#  CHECK-NEXT:        Frame Row Entry {
+#  CHECK-NEXT:          Start Address: 0xDE001E
+#  CHECK-NEXT:          Return Address Signed: No
+#  CHECK-NEXT:          Offset Size: B1 (0x0)
+#  CHECK-NEXT:          Base Register: FP (0x0)
+#  CHECK-NEXT:          CFA Offset: 288
+#  CHECK-NEXT:          RA Offset {
+#  CHECK-NEXT:            Kind: Register
+#  CHECK-NEXT:            Value: 16
+#  CHECK-NEXT:          }
+#  CHECK-NEXT:        }
+#  CHECK-NEXT:        Frame Row Entry {
+#  CHECK-NEXT:          Start Address: 0xDE001F
+#  CHECK-NEXT:          Return Address Signed: No
+#  CHECK-NEXT:          Offset Size: B1 (0x0)
+#  CHECK-NEXT:          Base Register: SP (0x1)
+#  CHECK-NEXT:          CFA Offset: 288
+#  CHECK-NEXT:          FP Offset {
+#  CHECK-NEXT:            Kind: Stack Slot
+#  CHECK-NEXT:            Value: 48
+#  CHECK-NEXT:          }
+#  CHECK-NEXT:        }
+#  CHECK-NEXT:        Frame Row Entry {
+#  CHECK-NEXT:          Start Address: 0xDE0020
+#  CHECK-NEXT:          Return Address Signed: No
+#  CHECK-NEXT:          Offset Size: B1 (0x0)
+#  CHECK-NEXT:          Base Register: FP (0x0)
+#  CHECK-NEXT:          CFA Offset: 288
+#  CHECK-NEXT:          RA Offset {
+#  CHECK-NEXT:            Kind: Stack Slot
+#  CHECK-NEXT:            Value: 32
+#  CHECK-NEXT:          }
+#  CHECK-NEXT:          FP Offset {
+#  CHECK-NEXT:            Kind: Register
+#  CHECK-NEXT:            Value: 24
+#  CHECK-NEXT:          }
+#  CHECK-NEXT:          Extra Offsets: [64]
+#  CHECK-NEXT:        }
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index a440bad130f4c..56c7d8c09a904 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -6522,6 +6522,7 @@ void ELFDumper<ELFT>::printSFrameFDEs(
                     sframe::getAArch64PAuthKeys());
         break;
       case sframe::ABI::AMD64EndianLittle:
+      case sframe::ABI::S390xEndianBig:
         // unused
         break;
       }
@@ -6555,10 +6556,22 @@ void ELFDumper<ELFT>::printSFrameFDEs(
                   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);
+
+      using RegisterLocation =
+          typename SFrameParser<ELFT::Endianness>::RegisterLocation;
+      auto PrintLocation = [&](const char *Name,
+                               std::optional<RegisterLocation> Loc) {
+        if (!Loc)
+          return;
+        DictScope LocScope(W, Name);
+        W.printString("Kind", Loc->Kind == RegisterLocation::Register
+                                  ? "Register"
+                                  : "Stack Slot");
+        W.printNumber("Value", Loc->Value);
+      };
+      PrintLocation("RA Offset", Parser.getRAOffset(FRE));
+      PrintLocation("FP Offset", Parser.getFPOffset(FRE));
+
       if (ArrayRef<int32_t> Offs = Parser.getExtraOffsets(FRE); !Offs.empty())
         W.printList("Extra Offsets", Offs);
     }



More information about the llvm-commits mailing list