[llvm] [Object] Beginnings of SFrame parser and dumper (PR #147294)
Pavel Labath via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 14 04:21:44 PDT 2025
https://github.com/labath updated https://github.com/llvm/llvm-project/pull/147294
>From 353fe37eef1f80aea096e3e74d803aa1b8c1e40c Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Fri, 4 Jul 2025 12:09:49 +0200
Subject: [PATCH 1/2] [BinaryFormat] Add "SFrame" structures and constants
This patch defines the structures and constants used by the SFrame
unwind info format supported by GNU binutils. For more information about
the format, see https://sourceware.org/binutils/wiki/sframe and
https://discourse.llvm.org/t/rfc-adding-sframe-support-to-llvm/86900
In the patch, I've used the naming convention for everything that has a
direct equivalent to the specification (modulo changing macros to
constants), and used the llvm convention for everything else.
---
llvm/include/llvm/BinaryFormat/SFrame.h | 165 +++++++++++++++++++++
llvm/unittests/BinaryFormat/CMakeLists.txt | 1 +
llvm/unittests/BinaryFormat/SFrameTest.cpp | 118 +++++++++++++++
3 files changed, 284 insertions(+)
create mode 100644 llvm/include/llvm/BinaryFormat/SFrame.h
create mode 100644 llvm/unittests/BinaryFormat/SFrameTest.cpp
diff --git a/llvm/include/llvm/BinaryFormat/SFrame.h b/llvm/include/llvm/BinaryFormat/SFrame.h
new file mode 100644
index 0000000000000..16d3b16c6c2d3
--- /dev/null
+++ b/llvm/include/llvm/BinaryFormat/SFrame.h
@@ -0,0 +1,165 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// This file contains data-structure definitions and constants to support
+/// unwinding based on .sframe sections. This only supports SFRAME_VERSION_2
+/// as described at https://sourceware.org/binutils/docs/sframe-spec.html
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_BINARYFORMAT_SFRAME_H
+#define LLVM_BINARYFORMAT_SFRAME_H
+
+#include "llvm/ADT/BitmaskEnum.h"
+#include "llvm/Support/DataTypes.h"
+#include "llvm/Support/Endian.h"
+
+namespace llvm::sframe {
+
+LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
+
+constexpr uint16_t Magic = 0xdee2;
+
+enum class Version : uint8_t {
+ V1 = 1,
+ V2 = 2,
+};
+
+enum class Flags : uint8_t {
+ FDESorted = 0x01,
+ FramePointer = 0x02,
+ FDEFuncStartPCRel = 0x04,
+ V2AllFlags = FDESorted | FramePointer | FDEFuncStartPCRel,
+ LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/0xff),
+};
+
+enum class ABI : uint8_t {
+ AArch64EndianBig = 1,
+ AArch64EndianLittle = 2,
+ AMD64EndianLittle = 3,
+};
+
+/// SFrame FRE Types. Bits 0-3 of FuncDescEntry.Info.
+enum class FREType : uint8_t {
+ Addr1 = 0,
+ Addr2 = 1,
+ Addr4 = 2,
+};
+
+/// SFrame FDE Types. Bit 4 of FuncDescEntry.Info.
+enum class FDEType : uint8_t {
+ PCInc = 0,
+ PCMask = 1,
+};
+
+/// Speficies key used for signing return addresses. Bit 5 of
+/// FuncDescEntry.Info.
+enum class AArch64PAuthKey : uint8_t {
+ A = 0,
+ B = 1,
+};
+
+/// Size of stack offsets. Bits 5-6 of FREInfo.Info.
+enum class FREOffset : uint8_t {
+ B1 = 0,
+ B2 = 1,
+ B4 = 2,
+};
+
+/// Stack frame base register. Bit 0 of FREInfo.Info.
+enum class BaseReg : uint8_t {
+ FP = 0,
+ SP = 1,
+};
+
+namespace detail {
+template <typename T, endianness E>
+using packed =
+ support::detail::packed_endian_specific_integral<T, E, support::unaligned>;
+}
+
+template <endianness E> struct Preamble {
+ detail::packed<uint16_t, E> Magic;
+ detail::packed<enum Version, E> Version;
+ detail::packed<enum Flags, E> Flags;
+};
+
+template <endianness E> struct Header {
+ struct Preamble<E> Preamble;
+ detail::packed<ABI, E> ABIArch;
+ detail::packed<int8_t, E> CFAFixedFPOffset;
+ detail::packed<int8_t, E> CFAFixedRAOffset;
+ detail::packed<uint8_t, E> AuxHdrLen;
+ detail::packed<uint32_t, E> NumFDEs;
+ detail::packed<uint32_t, E> NumFREs;
+ detail::packed<uint32_t, E> FRELen;
+ detail::packed<uint32_t, E> FDEOff;
+ detail::packed<uint32_t, E> FREOff;
+};
+
+template <endianness E> struct FuncDescEntry {
+ detail::packed<int32_t, E> StartAddress;
+ detail::packed<uint32_t, E> Size;
+ detail::packed<uint32_t, E> StartFREOff;
+ detail::packed<uint32_t, E> NumFREs;
+ detail::packed<uint8_t, E> Info;
+ detail::packed<uint8_t, E> RepSize;
+ detail::packed<uint16_t, E> Padding2;
+
+ uint8_t getPAuthKey() const { return (Info >> 5) & 1; }
+ FDEType getFDEType() const { return static_cast<FDEType>((Info >> 4) & 1); }
+ FREType getFREType() const { return static_cast<FREType>(Info & 0xf); }
+ void setPAuthKey(uint8_t P) { setFuncInfo(P, getFDEType(), getFREType()); }
+ void setFDEType(FDEType D) { setFuncInfo(getPAuthKey(), D, getFREType()); }
+ void setFREType(FREType R) { setFuncInfo(getPAuthKey(), getFDEType(), R); }
+ void setFuncInfo(uint8_t PAuthKey, FDEType FDE, FREType FRE) {
+ Info = ((PAuthKey & 1) << 5) | ((static_cast<uint8_t>(FDE) & 1) << 4) |
+ (static_cast<uint8_t>(FRE) & 0xf);
+ }
+};
+
+template <endianness E> struct FREInfo {
+ detail::packed<uint8_t, E> Info;
+
+ bool isReturnAddressSigned() const { return Info >> 7; }
+ FREOffset getOffsetSize() const {
+ return static_cast<FREOffset>((Info >> 5) & 3);
+ }
+ uint8_t getOffsetCount() const { return (Info >> 1) & 0xf; }
+ BaseReg getBaseRegister() const { return static_cast<BaseReg>(Info & 1); }
+ void setReturnAddressSigned(bool RA) {
+ setFREInfo(RA, getOffsetSize(), getOffsetCount(), getBaseRegister());
+ }
+ void setOffsetSize(FREOffset Sz) {
+ setFREInfo(isReturnAddressSigned(), Sz, getOffsetCount(),
+ getBaseRegister());
+ }
+ void setOffsetCount(uint8_t N) {
+ setFREInfo(isReturnAddressSigned(), getOffsetSize(), N, getBaseRegister());
+ }
+ void setBaseRegister(BaseReg Reg) {
+ setFREInfo(isReturnAddressSigned(), getOffsetSize(), getOffsetCount(), Reg);
+ }
+ void setFREInfo(bool RA, FREOffset Sz, uint8_t N, BaseReg Reg) {
+ Info = ((RA & 1) << 7) | ((static_cast<uint8_t>(Sz) & 3) << 5) |
+ ((N & 0xf) << 1) | (static_cast<uint8_t>(Reg) & 1);
+ }
+};
+
+template <typename T, endianness E> struct FrameRowEntry {
+ detail::packed<T, E> StartAddress;
+ FREInfo<E> Info;
+};
+
+template <endianness E> using FrameRowEntryAddr1 = FrameRowEntry<uint8_t, E>;
+template <endianness E> using FrameRowEntryAddr2 = FrameRowEntry<uint16_t, E>;
+template <endianness E> using FrameRowEntryAddr4 = FrameRowEntry<uint32_t, E>;
+
+} // namespace llvm::sframe
+
+#endif // LLVM_BINARYFORMAT_SFRAME_H
diff --git a/llvm/unittests/BinaryFormat/CMakeLists.txt b/llvm/unittests/BinaryFormat/CMakeLists.txt
index 40d3bc4dca0b6..eac5977a2c1c3 100644
--- a/llvm/unittests/BinaryFormat/CMakeLists.txt
+++ b/llvm/unittests/BinaryFormat/CMakeLists.txt
@@ -10,6 +10,7 @@ add_llvm_unittest(BinaryFormatTests
MsgPackDocumentTest.cpp
MsgPackReaderTest.cpp
MsgPackWriterTest.cpp
+ SFrameTest.cpp
TestFileMagic.cpp
)
diff --git a/llvm/unittests/BinaryFormat/SFrameTest.cpp b/llvm/unittests/BinaryFormat/SFrameTest.cpp
new file mode 100644
index 0000000000000..394e382e041e9
--- /dev/null
+++ b/llvm/unittests/BinaryFormat/SFrameTest.cpp
@@ -0,0 +1,118 @@
+//===- SFrameTest.cpp -----------------------------------------------------===//
+//
+// 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/BinaryFormat/SFrame.h"
+#include "gtest/gtest.h"
+#include <type_traits>
+
+using namespace llvm;
+using namespace llvm::sframe;
+
+namespace {
+
+template <typename EndianT> class SFrameTest : public testing::Test {
+protected:
+ static constexpr endianness Endian = EndianT::value;
+
+ // Test structure sizes and triviality.
+ static_assert(std::is_trivial_v<Preamble<Endian>>);
+ static_assert(sizeof(Preamble<Endian>) == 4);
+
+ static_assert(std::is_trivial_v<Header<Endian>>);
+ static_assert(sizeof(Header<Endian>) == 28);
+
+ static_assert(std::is_trivial_v<FuncDescEntry<Endian>>);
+ static_assert(sizeof(FuncDescEntry<Endian>) == 20);
+
+ static_assert(std::is_trivial_v<FrameRowEntryAddr1<Endian>>);
+ static_assert(sizeof(FrameRowEntryAddr1<Endian>) == 2);
+
+ static_assert(std::is_trivial_v<FrameRowEntryAddr2<Endian>>);
+ static_assert(sizeof(FrameRowEntryAddr2<Endian>) == 3);
+
+ static_assert(std::is_trivial_v<FrameRowEntryAddr4<Endian>>);
+ static_assert(sizeof(FrameRowEntryAddr4<Endian>) == 5);
+};
+
+struct NameGenerator {
+ template <typename T> static constexpr const char *GetName(int) {
+ if constexpr (T::value == endianness::little)
+ return "little";
+ if constexpr (T::value == endianness::big)
+ return "big";
+ }
+};
+using Types =
+ testing::Types<std::integral_constant<endianness, endianness::little>,
+ std::integral_constant<endianness, endianness::big>>;
+TYPED_TEST_SUITE(SFrameTest, Types, NameGenerator);
+
+TYPED_TEST(SFrameTest, FDEFlags) {
+ FuncDescEntry<TestFixture::Endian> FDE = {};
+ EXPECT_EQ(FDE.Info, 0u);
+ EXPECT_EQ(FDE.getPAuthKey(), 0);
+ EXPECT_EQ(FDE.getFDEType(), FDEType::PCInc);
+ EXPECT_EQ(FDE.getFREType(), FREType::Addr1);
+
+ FDE.setPAuthKey(1);
+ EXPECT_EQ(FDE.Info, 0x20u);
+ EXPECT_EQ(FDE.getPAuthKey(), 1);
+ EXPECT_EQ(FDE.getFDEType(), FDEType::PCInc);
+ EXPECT_EQ(FDE.getFREType(), FREType::Addr1);
+
+ FDE.setFDEType(FDEType::PCMask);
+ EXPECT_EQ(FDE.Info, 0x30u);
+ EXPECT_EQ(FDE.getPAuthKey(), 1);
+ EXPECT_EQ(FDE.getFDEType(), FDEType::PCMask);
+ EXPECT_EQ(FDE.getFREType(), FREType::Addr1);
+
+ FDE.setFREType(FREType::Addr4);
+ EXPECT_EQ(FDE.Info, 0x32u);
+ EXPECT_EQ(FDE.getPAuthKey(), 1);
+ EXPECT_EQ(FDE.getFDEType(), FDEType::PCMask);
+ EXPECT_EQ(FDE.getFREType(), FREType::Addr4);
+}
+
+TYPED_TEST(SFrameTest, FREFlags) {
+ FREInfo<TestFixture::Endian> Info = {};
+ EXPECT_EQ(Info.Info, 0u);
+ EXPECT_FALSE(Info.isReturnAddressSigned());
+ EXPECT_EQ(Info.getOffsetSize(), FREOffset::B1);
+ EXPECT_EQ(Info.getOffsetCount(), 0u);
+ EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+ Info.setReturnAddressSigned(true);
+ EXPECT_EQ(Info.Info, 0x80u);
+ EXPECT_TRUE(Info.isReturnAddressSigned());
+ EXPECT_EQ(Info.getOffsetSize(), FREOffset::B1);
+ EXPECT_EQ(Info.getOffsetCount(), 0u);
+ EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+ Info.setOffsetSize(FREOffset::B4);
+ EXPECT_EQ(Info.Info, 0xc0u);
+ EXPECT_TRUE(Info.isReturnAddressSigned());
+ EXPECT_EQ(Info.getOffsetSize(), FREOffset::B4);
+ EXPECT_EQ(Info.getOffsetCount(), 0u);
+ EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+ Info.setOffsetCount(3);
+ EXPECT_EQ(Info.Info, 0xc6u);
+ EXPECT_TRUE(Info.isReturnAddressSigned());
+ EXPECT_EQ(Info.getOffsetSize(), FREOffset::B4);
+ EXPECT_EQ(Info.getOffsetCount(), 3u);
+ EXPECT_EQ(Info.getBaseRegister(), BaseReg::FP);
+
+ Info.setBaseRegister(BaseReg::SP);
+ EXPECT_EQ(Info.Info, 0xc7u);
+ EXPECT_TRUE(Info.isReturnAddressSigned());
+ EXPECT_EQ(Info.getOffsetSize(), FREOffset::B4);
+ EXPECT_EQ(Info.getOffsetCount(), 3u);
+ EXPECT_EQ(Info.getBaseRegister(), BaseReg::SP);
+}
+
+} // namespace
>From 2a4d8453014a0a489b247467b8259ef3f223c801 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Fri, 4 Jul 2025 16:04:43 +0200
Subject: [PATCH 2/2] [Object] Beginnings of SFrame parser and dumper
This PR adds the SFrameParser class and uses it from llvm-readobj to
dump the section contents. Currently, it only supports parsing the
SFrame section header. Other parts of the section will be added in
follow-up patches.
llvm-readobj uses the same sframe flag syntax as GNU readelf, but I have
not attempted match the output format of the tool. I'm starting with the
"llvm" output format because it's easier to generate and lets us
tweak the format to make it useful for testing the generation code. If
needed, support for the GNU format could be added by overriding this
functionality in the GNU ELF Dumper.
---
llvm/include/llvm/BinaryFormat/SFrame.h | 28 +++--
.../llvm/BinaryFormat/SFrameConstants.def | 39 ++++++
llvm/include/llvm/Object/SFrameParser.h | 48 +++++++
llvm/lib/BinaryFormat/CMakeLists.txt | 1 +
llvm/lib/BinaryFormat/SFrame.cpp | 37 ++++++
llvm/lib/Object/CMakeLists.txt | 1 +
llvm/lib/Object/SFrameParser.cpp | 56 +++++++++
.../tools/llvm-readobj/ELF/sframe-header.test | 118 ++++++++++++++++++
llvm/tools/llvm-readobj/ELFDumper.cpp | 53 ++++++++
llvm/tools/llvm-readobj/ObjDumper.cpp | 6 +-
llvm/tools/llvm-readobj/ObjDumper.h | 5 +
llvm/tools/llvm-readobj/Opts.td | 2 +
llvm/tools/llvm-readobj/llvm-readobj.cpp | 4 +
13 files changed, 385 insertions(+), 13 deletions(-)
create mode 100644 llvm/include/llvm/BinaryFormat/SFrameConstants.def
create mode 100644 llvm/include/llvm/Object/SFrameParser.h
create mode 100644 llvm/lib/BinaryFormat/SFrame.cpp
create mode 100644 llvm/lib/Object/SFrameParser.cpp
create mode 100644 llvm/test/tools/llvm-readobj/ELF/sframe-header.test
diff --git a/llvm/include/llvm/BinaryFormat/SFrame.h b/llvm/include/llvm/BinaryFormat/SFrame.h
index 16d3b16c6c2d3..98dbe38fb2bc4 100644
--- a/llvm/include/llvm/BinaryFormat/SFrame.h
+++ b/llvm/include/llvm/BinaryFormat/SFrame.h
@@ -15,33 +15,36 @@
#ifndef LLVM_BINARYFORMAT_SFRAME_H
#define LLVM_BINARYFORMAT_SFRAME_H
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/Support/DataTypes.h"
#include "llvm/Support/Endian.h"
-namespace llvm::sframe {
+namespace llvm {
+
+template <typename T> struct EnumEntry;
+
+namespace sframe {
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
constexpr uint16_t Magic = 0xdee2;
enum class Version : uint8_t {
- V1 = 1,
- V2 = 2,
+#define HANDLE_SFRAME_VERSION(CODE, NAME) NAME = CODE,
+#include "llvm/BinaryFormat/SFrameConstants.def"
};
enum class Flags : uint8_t {
- FDESorted = 0x01,
- FramePointer = 0x02,
- FDEFuncStartPCRel = 0x04,
+#define HANDLE_SFRAME_FLAG(CODE, NAME) NAME = CODE,
+#include "llvm/BinaryFormat/SFrameConstants.def"
V2AllFlags = FDESorted | FramePointer | FDEFuncStartPCRel,
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/0xff),
};
enum class ABI : uint8_t {
- AArch64EndianBig = 1,
- AArch64EndianLittle = 2,
- AMD64EndianLittle = 3,
+#define HANDLE_SFRAME_ABI(CODE, NAME) NAME = CODE,
+#include "llvm/BinaryFormat/SFrameConstants.def"
};
/// SFrame FRE Types. Bits 0-3 of FuncDescEntry.Info.
@@ -160,6 +163,11 @@ template <endianness E> using FrameRowEntryAddr1 = FrameRowEntry<uint8_t, E>;
template <endianness E> using FrameRowEntryAddr2 = FrameRowEntry<uint16_t, E>;
template <endianness E> using FrameRowEntryAddr4 = FrameRowEntry<uint32_t, E>;
-} // namespace llvm::sframe
+ArrayRef<EnumEntry<Version>> getVersions();
+ArrayRef<EnumEntry<Flags>> getFlags();
+ArrayRef<EnumEntry<ABI>> getABIs();
+
+} // namespace sframe
+} // namespace llvm
#endif // LLVM_BINARYFORMAT_SFRAME_H
diff --git a/llvm/include/llvm/BinaryFormat/SFrameConstants.def b/llvm/include/llvm/BinaryFormat/SFrameConstants.def
new file mode 100644
index 0000000000000..643b15f438c86
--- /dev/null
+++ b/llvm/include/llvm/BinaryFormat/SFrameConstants.def
@@ -0,0 +1,39 @@
+//===- SFrameConstants.def --------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#if !(defined(HANDLE_SFRAME_VERSION) || defined(HANDLE_SFRAME_FLAG) || \
+ defined(HANDLE_SFRAME_ABI))
+#error "Missing HANDLE_SFRAME definition"
+#endif
+
+#ifndef HANDLE_SFRAME_VERSION
+#define HANDLE_SFRAME_VERSION(CODE, NAME)
+#endif
+
+#ifndef HANDLE_SFRAME_FLAG
+#define HANDLE_SFRAME_FLAG(CODE, NAME)
+#endif
+
+#ifndef HANDLE_SFRAME_ABI
+#define HANDLE_SFRAME_ABI(CODE, NAME)
+#endif
+
+HANDLE_SFRAME_VERSION(0x01, V1)
+HANDLE_SFRAME_VERSION(0x02, V2)
+
+HANDLE_SFRAME_FLAG(0x01, FDESorted)
+HANDLE_SFRAME_FLAG(0x02, FramePointer)
+HANDLE_SFRAME_FLAG(0x04, FDEFuncStartPCRel)
+
+HANDLE_SFRAME_ABI(0x01, AArch64EndianBig)
+HANDLE_SFRAME_ABI(0x02, AArch64EndianLittle)
+HANDLE_SFRAME_ABI(0x03, AMD64EndianLittle)
+
+#undef HANDLE_SFRAME_VERSION
+#undef HANDLE_SFRAME_FLAG
+#undef HANDLE_SFRAME_ABI
diff --git a/llvm/include/llvm/Object/SFrameParser.h b/llvm/include/llvm/Object/SFrameParser.h
new file mode 100644
index 0000000000000..cf4fe20e84431
--- /dev/null
+++ b/llvm/include/llvm/Object/SFrameParser.h
@@ -0,0 +1,48 @@
+//===- SFrameParser.h -------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_OBJECT_SFRAME_H
+#define LLVM_OBJECT_SFRAME_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/BinaryFormat/SFrame.h"
+#include "llvm/Support/Error.h"
+#include <cstdint>
+
+namespace llvm {
+namespace object {
+
+template <endianness E> class SFrameParser {
+public:
+ static Expected<SFrameParser> create(ArrayRef<uint8_t> Contents);
+
+ const sframe::Preamble<E> &getPreamble() const { return Header.Preamble; }
+ const sframe::Header<E> &getHeader() const { return Header; }
+
+ bool usesFixedRAOffset() const {
+ return getHeader().ABIArch == sframe::ABI::AMD64EndianLittle;
+ }
+ bool usesFixedFPOffset() const {
+ return false; // Not used in any currently defined ABI.
+ }
+
+private:
+ ArrayRef<uint8_t> Data;
+ const sframe::Header<E> &Header;
+
+ SFrameParser(ArrayRef<uint8_t> Data, const sframe::Header<E> &Header)
+ : Data(Data), Header(Header) {}
+};
+
+extern template class SFrameParser<endianness::big>;
+extern template class SFrameParser<endianness::little>;
+
+} // end namespace object
+} // end namespace llvm
+
+#endif // LLVM_OBJECT_SFRAME_H
diff --git a/llvm/lib/BinaryFormat/CMakeLists.txt b/llvm/lib/BinaryFormat/CMakeLists.txt
index 38ba2d9e85a06..4b2debb7ae236 100644
--- a/llvm/lib/BinaryFormat/CMakeLists.txt
+++ b/llvm/lib/BinaryFormat/CMakeLists.txt
@@ -11,6 +11,7 @@ add_llvm_component_library(LLVMBinaryFormat
MsgPackDocumentYAML.cpp
MsgPackReader.cpp
MsgPackWriter.cpp
+ SFrame.cpp
Wasm.cpp
XCOFF.cpp
diff --git a/llvm/lib/BinaryFormat/SFrame.cpp b/llvm/lib/BinaryFormat/SFrame.cpp
new file mode 100644
index 0000000000000..3b436afd32083
--- /dev/null
+++ b/llvm/lib/BinaryFormat/SFrame.cpp
@@ -0,0 +1,37 @@
+//===-- SFrame.cpp -----------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/BinaryFormat/SFrame.h"
+#include "llvm/Support/ScopedPrinter.h"
+
+using namespace llvm;
+
+ArrayRef<EnumEntry<sframe::Version>> sframe::getVersions() {
+ static constexpr EnumEntry<Version> Versions[] = {
+#define HANDLE_SFRAME_VERSION(CODE, NAME) {#NAME, sframe::Version::NAME},
+#include "llvm/BinaryFormat/SFrameConstants.def"
+ };
+
+ return ArrayRef(Versions);
+}
+
+ArrayRef<EnumEntry<sframe::Flags>> sframe::getFlags() {
+ static constexpr EnumEntry<sframe::Flags> Flags[] = {
+#define HANDLE_SFRAME_FLAG(CODE, NAME) {#NAME, sframe::Flags::NAME},
+#include "llvm/BinaryFormat/SFrameConstants.def"
+ };
+ return ArrayRef(Flags);
+}
+
+ArrayRef<EnumEntry<sframe::ABI>> sframe::getABIs() {
+ static constexpr EnumEntry<sframe::ABI> ABIs[] = {
+#define HANDLE_SFRAME_ABI(CODE, NAME) {#NAME, sframe::ABI::NAME},
+#include "llvm/BinaryFormat/SFrameConstants.def"
+ };
+ return ArrayRef(ABIs);
+}
diff --git a/llvm/lib/Object/CMakeLists.txt b/llvm/lib/Object/CMakeLists.txt
index 870169a83174f..0f6d2f7c59a5c 100644
--- a/llvm/lib/Object/CMakeLists.txt
+++ b/llvm/lib/Object/CMakeLists.txt
@@ -25,6 +25,7 @@ add_llvm_component_library(LLVMObject
OffloadBundle.cpp
RecordStreamer.cpp
RelocationResolver.cpp
+ SFrameParser.cpp
SymbolicFile.cpp
SymbolSize.cpp
TapiFile.cpp
diff --git a/llvm/lib/Object/SFrameParser.cpp b/llvm/lib/Object/SFrameParser.cpp
new file mode 100644
index 0000000000000..94856409c24f6
--- /dev/null
+++ b/llvm/lib/Object/SFrameParser.cpp
@@ -0,0 +1,56 @@
+//===- SFrameParser.cpp ---------------------------------------------------===//
+//
+// 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/Object/SFrameParser.h"
+#include "llvm/BinaryFormat/SFrame.h"
+#include "llvm/Object/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+
+using namespace llvm;
+using namespace llvm::object;
+
+template <typename T>
+static Expected<const T &> getDataSliceAs(ArrayRef<uint8_t> Data,
+ uint64_t Offset) {
+ static_assert(std::is_trivial_v<T>);
+ if (Data.size() < Offset + sizeof(T)) {
+ return make_error<GenericBinaryError>(
+ formatv("unexpected end of data at offset {0:x} while reading [{1:x}, "
+ "{2:x})",
+ Data.size(), Offset, Offset + sizeof(T))
+ .str(),
+ object_error::unexpected_eof);
+ }
+ return *reinterpret_cast<const T *>(Data.data() + Offset);
+}
+
+template <endianness E>
+Expected<SFrameParser<E>> SFrameParser<E>::create(ArrayRef<uint8_t> Contents) {
+ Expected<const sframe::Preamble<E> &> Preamble =
+ getDataSliceAs<sframe::Preamble<E>>(Contents, 0);
+ if (!Preamble)
+ return Preamble.takeError();
+
+ if (Preamble->Magic != sframe::Magic) {
+ return make_error<GenericBinaryError>("invalid magic number",
+ object_error::parse_failed);
+ }
+ if (Preamble->Version != sframe::Version::V2) {
+ return make_error<GenericBinaryError>("invalid/unsupported version number",
+ object_error::parse_failed);
+ }
+
+ Expected<const sframe::Header<E> &> Header =
+ getDataSliceAs<sframe::Header<E>>(Contents, 0);
+ if (!Header)
+ return Header.takeError();
+ return SFrameParser(Contents, *Header);
+}
+
+template class llvm::object::SFrameParser<endianness::big>;
+template class llvm::object::SFrameParser<endianness::little>;
diff --git a/llvm/test/tools/llvm-readobj/ELF/sframe-header.test b/llvm/test/tools/llvm-readobj/ELF/sframe-header.test
new file mode 100644
index 0000000000000..c80bb28814105
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/sframe-header.test
@@ -0,0 +1,118 @@
+## Check parsing and dumping of the SFrame header.
+# RUN: yaml2obj --docnum=1 %s -o %t.1
+# RUN: llvm-readobj --sframe=.sframe_1b --sframe=.sframe_bad_magic \
+# RUN: --sframe=.sframe_bad_version --sframe=.sframe_6b \
+# RUN: --sframe=.sframe_header %t.1 2>&1 | FileCheck %s --check-prefix=CASE1
+
+## Check big-endian support and the handling of --sframe argument default.
+# RUN: yaml2obj --docnum=2 %s -o %t.2
+# RUN: llvm-readobj --sframe %t.2 2>&1 | FileCheck %s --check-prefix=CASE2
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_EXEC
+ Machine: EM_X86_64
+Sections:
+ - Name: .sframe_1b
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ ContentArray: [ 0x00 ]
+# CASE1-LABEL: Contents of SFrame section '.sframe_1b':
+# CASE1: warning: {{.*}}: unexpected end of data at offset 0x1 while reading [0x0, 0x4)
+
+ - Name: .sframe_bad_magic
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ ContentArray: [ 0xde, 0xad, 0xbe, 0xef]
+# CASE1-LABEL: Contents of SFrame section '.sframe_bad_magic':
+# CASE1: warning: {{.*}}: invalid magic number
+
+ - Name: .sframe_bad_version
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ ContentArray: [
+ 0xe2, 0xde, 0x01, 0x00 # Preamble (magic, version, flags)
+ ]
+# CASE1-LABEL: Contents of SFrame section '.sframe_bad_version':
+# CASE1: warning: {{.*}}: invalid/unsupported version number
+
+ - Name: .sframe_6b
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ ContentArray: [
+ 0xe2, 0xde, 0x02, 0x00, # Preamble (magic, version, flags)
+ 0x01, 0x02
+ ]
+
+# CASE1-LABEL: Contents of SFrame section '.sframe_6b':
+# CASE1: warning: {{.*}}: unexpected end of data at offset 0x6 while reading [0x0, 0x1c)
+
+ - Name: .sframe_header
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ ContentArray: [
+ 0xe2, 0xde, 0x02, 0x00, # 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
+ 0x10, 0x00, 0x00, 0x00, # Number of FREs
+ 0x00, 0x10, 0x00, 0x00, # FRE length
+ 0x04, 0x00, 0x00, 0x00, # FDE offset
+ 0x00, 0x01, 0x00, 0x00, # FRE offset
+ ]
+# CASE1-LABEL: Contents of SFrame section '.sframe_header':
+# CASE1: Header {
+# CASE1-NEXT: Magic: 0xDEE2
+# CASE1-NEXT: Version: V2 (0x2)
+# CASE1-NEXT: Flags [ (0x0)
+# CASE1-NEXT: ]
+# CASE1-NEXT: ABI: AMD64EndianLittle (0x3)
+# CASE1-NEXT: CFA fixed RA offset: 71
+# CASE1-NEXT: CFA fixed FP offset (unused): 66
+# CASE1-NEXT: Auxiliary header length: 0
+# CASE1-NEXT: Num FDEs: 1
+# CASE1-NEXT: Num FREs: 16
+# CASE1-NEXT: FRE subsection length: 4096
+# CASE1-NEXT: FDE subsection offset: 4
+# CASE1-NEXT: FRE subsection offset: 256
+# CASE1-NEXT: }
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2MSB
+ Type: ET_EXEC
+ Machine: EM_AARCH64
+Sections:
+ - Name: .sframe
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ ContentArray: [
+ 0xde, 0xe2, 0x02, 0x01, # 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, 0x04, # FDE offset
+ 0x00, 0x00, 0x01, 0x00, # FRE offset
+ ]
+# CASE2-LABEL: Contents of SFrame section '.sframe':
+# CASE2: Header {
+# CASE2-NEXT: Magic: 0xDEE2
+# CASE2-NEXT: Version: V2 (0x2)
+# CASE2-NEXT: Flags [ (0x1)
+# CASE2-NEXT: FDESorted (0x1)
+# CASE2-NEXT: ]
+# CASE2-NEXT: ABI: AArch64EndianBig (0x1)
+# CASE2-NEXT: CFA fixed RA offset (unused): 71
+# CASE2-NEXT: CFA fixed FP offset (unused): 66
+# CASE2-NEXT: Auxiliary header length: 0
+# CASE2-NEXT: Num FDEs: 1
+# CASE2-NEXT: Num FREs: 16
+# CASE2-NEXT: FRE subsection length: 4096
+# CASE2-NEXT: FDE subsection offset: 4
+# CASE2-NEXT: FRE subsection offset: 256
+# CASE2-NEXT: }
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 101079f09e1d2..3f69371d267b6 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -30,6 +30,7 @@
#include "llvm/BinaryFormat/AMDGPUMetadataVerifier.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/BinaryFormat/MsgPackDocument.h"
+#include "llvm/BinaryFormat/SFrame.h"
#include "llvm/Demangle/Demangle.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/ELF.h"
@@ -38,6 +39,7 @@
#include "llvm/Object/Error.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Object/RelocationResolver.h"
+#include "llvm/Object/SFrameParser.h"
#include "llvm/Object/StackMapParser.h"
#include "llvm/Support/AArch64AttributeParser.h"
#include "llvm/Support/AMDGPUMetadata.h"
@@ -225,6 +227,8 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
void printArchSpecificInfo() override;
void printStackMap() const override;
void printMemtag() override;
+ void printSectionsAsSFrame(ArrayRef<std::string> Sections) override;
+
ArrayRef<uint8_t> getMemtagGlobalsSectionContents(uint64_t ExpectedAddr);
// Hash histogram shows statistics of how efficient the hash was for the
@@ -6428,6 +6432,55 @@ template <typename ELFT> void ELFDumper<ELFT>::printMemtag() {
printMemtag(DynamicEntries, AndroidNoteDesc, GlobalDescriptors);
}
+template <typename ELFT>
+void ELFDumper<ELFT>::printSectionsAsSFrame(ArrayRef<std::string> Sections) {
+ constexpr endianness E = ELFT::Endianness;
+ for (object::SectionRef Section :
+ getSectionRefsByNameOrIndex(ObjF, Sections)) {
+ StringRef SectionName = unwrapOrError(FileName, Section.getName());
+ W.getOStream() << '\n';
+ W.startLine() << "Contents of SFrame section '" << SectionName << "':\n";
+
+ StringRef SectionContent = unwrapOrError(FileName, Section.getContents());
+ Expected<object::SFrameParser<E>> Parser =
+ object::SFrameParser<E>::create(arrayRefFromStringRef(SectionContent));
+ if (!Parser) {
+ reportWarning(Parser.takeError(), FileName);
+ continue;
+ }
+
+ W.indent();
+ W.objectBegin("Header");
+
+ const sframe::Preamble<E> &Preamble = Parser->getPreamble();
+ W.printHex("Magic", Preamble.Magic.value());
+ W.printEnum("Version", Preamble.Version.value(), sframe::getVersions());
+ W.printFlags("Flags", Preamble.Flags.value(), sframe::getFlags());
+
+ const sframe::Header<E> &Header = Parser->getHeader();
+ W.printEnum("ABI", Header.ABIArch.value(), sframe::getABIs());
+
+ W.printNumber(("CFA fixed RA offset" +
+ Twine(Parser->usesFixedRAOffset() ? "" : " (unused)"))
+ .str(),
+ Header.CFAFixedRAOffset.value());
+
+ W.printNumber(("CFA fixed FP offset" +
+ Twine(Parser->usesFixedFPOffset() ? "" : " (unused)"))
+ .str(),
+ Header.CFAFixedFPOffset.value());
+
+ W.printNumber("Auxiliary header length", Header.AuxHdrLen.value());
+ W.printNumber("Num FDEs", Header.NumFDEs.value());
+ W.printNumber("Num FREs", Header.NumFREs.value());
+ W.printNumber("FRE subsection length", Header.FRELen.value());
+ W.printNumber("FDE subsection offset", Header.FDEOff.value());
+ W.printNumber("FRE subsection offset", Header.FREOff.value());
+ W.objectEnd();
+ W.unindent();
+ }
+}
+
template <class ELFT> void GNUELFDumper<ELFT>::printELFLinkerOptions() {
OS << "printELFLinkerOptions not implemented!\n";
}
diff --git a/llvm/tools/llvm-readobj/ObjDumper.cpp b/llvm/tools/llvm-readobj/ObjDumper.cpp
index 1a535ede07096..bd670aeab9ed8 100644
--- a/llvm/tools/llvm-readobj/ObjDumper.cpp
+++ b/llvm/tools/llvm-readobj/ObjDumper.cpp
@@ -102,9 +102,9 @@ void ObjDumper::printFileSummary(StringRef FileStr, object::ObjectFile &Obj,
this->printLoadName();
}
-static std::vector<object::SectionRef>
-getSectionRefsByNameOrIndex(const object::ObjectFile &Obj,
- ArrayRef<std::string> Sections) {
+std::vector<object::SectionRef>
+ObjDumper::getSectionRefsByNameOrIndex(const object::ObjectFile &Obj,
+ ArrayRef<std::string> Sections) {
std::vector<object::SectionRef> Ret;
std::map<std::string, bool, std::less<>> SecNames;
std::map<unsigned, bool> SecIndices;
diff --git a/llvm/tools/llvm-readobj/ObjDumper.h b/llvm/tools/llvm-readobj/ObjDumper.h
index a76afbe9c88c7..1dc29661f7178 100644
--- a/llvm/tools/llvm-readobj/ObjDumper.h
+++ b/llvm/tools/llvm-readobj/ObjDumper.h
@@ -139,6 +139,7 @@ class ObjDumper {
virtual void printSectionDetails() {}
virtual void printArchSpecificInfo() {}
virtual void printMemtag() {}
+ virtual void printSectionsAsSFrame(ArrayRef<std::string> Sections) {}
// Only implemented for PE/COFF.
virtual void printCOFFImports() { }
@@ -190,6 +191,10 @@ class ObjDumper {
protected:
ScopedPrinter &W;
+ static std::vector<object::SectionRef>
+ getSectionRefsByNameOrIndex(const object::ObjectFile &Obj,
+ ArrayRef<std::string> Sections);
+
private:
virtual void printSymbols(bool ExtraSymInfo) {}
virtual void printSymbols(std::optional<SymbolComparator> Comp) {}
diff --git a/llvm/tools/llvm-readobj/Opts.td b/llvm/tools/llvm-readobj/Opts.td
index f95461aaca1a7..48d43cc635a4f 100644
--- a/llvm/tools/llvm-readobj/Opts.td
+++ b/llvm/tools/llvm-readobj/Opts.td
@@ -62,6 +62,8 @@ def memtag : FF<"memtag", "Display memory tagging metadata (modes, Android notes
def needed_libs : FF<"needed-libs", "Display the needed libraries">, Group<grp_elf>;
def notes : FF<"notes", "Display notes">, Group<grp_elf>;
def program_headers : FF<"program-headers", "Display program headers">, Group<grp_elf>;
+def sframe_EQ : Joined<["--"], "sframe=">, HelpText<"Display SFrame section <name>">, MetaVarName<"<name>">, Group<grp_elf>;
+def sframe: FF<"sframe", "Alias for --sframe=.sframe">, Alias<sframe_EQ>, AliasArgs<[".sframe"]>, Group<grp_elf>;
def version_info : FF<"version-info", "Display version sections">, Group<grp_elf>;
// Mach-O specific options.
diff --git a/llvm/tools/llvm-readobj/llvm-readobj.cpp b/llvm/tools/llvm-readobj/llvm-readobj.cpp
index 1231c02035d1f..4c84ed701bb9a 100644
--- a/llvm/tools/llvm-readobj/llvm-readobj.cpp
+++ b/llvm/tools/llvm-readobj/llvm-readobj.cpp
@@ -137,6 +137,7 @@ static bool NeededLibraries;
static bool Notes;
static bool ProgramHeaders;
static bool SectionGroups;
+static std::vector<std::string> SFrame;
static bool VersionInfo;
// Mach-O specific options.
@@ -275,6 +276,7 @@ static void parseOptions(const opt::InputArgList &Args) {
opts::PrettyPrint = Args.hasArg(OPT_pretty_print);
opts::ProgramHeaders = Args.hasArg(OPT_program_headers);
opts::SectionGroups = Args.hasArg(OPT_section_groups);
+ opts::SFrame = Args.getAllArgValues(OPT_sframe_EQ);
if (Arg *A = Args.getLastArg(OPT_sort_symbols_EQ)) {
for (StringRef KeyStr : llvm::split(A->getValue(), ",")) {
SortSymbolKeyTy KeyType = StringSwitch<SortSymbolKeyTy>(KeyStr)
@@ -478,6 +480,8 @@ static void dumpObject(ObjectFile &Obj, ScopedPrinter &Writer,
Dumper->printNotes();
if (opts::Memtag)
Dumper->printMemtag();
+ if (!opts::SFrame.empty())
+ Dumper->printSectionsAsSFrame(opts::SFrame);
}
if (Obj.isCOFF()) {
if (opts::COFFImports)
More information about the llvm-commits
mailing list