[Object] Beginnings of SFrame parser and dumper (#147294)

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.

For more information, see the [sframe
specification](https://sourceware.org/binutils/wiki/sframe) and the
related
[RFC](https://discourse.llvm.org/t/rfc-adding-sframe-support-to-llvm/86900).
This commit is contained in:
Pavel Labath 2025-07-21 08:46:21 +02:00 committed by GitHub
parent 84e689b1db
commit aa7ada1dfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 420 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -11,6 +11,7 @@ add_llvm_component_library(LLVMBinaryFormat
MsgPackDocumentYAML.cpp
MsgPackReader.cpp
MsgPackWriter.cpp
SFrame.cpp
Wasm.cpp
XCOFF.cpp

View File

@ -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);
}

View File

@ -25,6 +25,7 @@ add_llvm_component_library(LLVMObject
OffloadBundle.cpp
RecordStreamer.cpp
RelocationResolver.cpp
SFrameParser.cpp
SymbolicFile.cpp
SymbolSize.cpp
TapiFile.cpp

View File

@ -0,0 +1,55 @@
//===- 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 createStringError(
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 createError(
formatv("invalid magic number ({0:x+4})", Preamble->Magic.value()));
if (Preamble->Version != sframe::Version::V2)
return createError(
formatv("invalid/unsupported version number ({0})",
static_cast<unsigned>(Preamble->Version.value())));
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>;

View File

@ -0,0 +1,148 @@
## Check parsing and dumping of the SFrame header.
# RUN: yaml2obj --docnum=1 %s -o %t.1
# RUN: llvm-readobj --sframe=.sframe_bad_sh_size --sframe=.sframe_1b \
# RUN: --sframe=.sframe_bad_magic --sframe=.sframe_bad_version \
# RUN: --sframe=.sframe_6b --sframe=.sframe_header %t.1 2>&1 | \
# RUN: FileCheck %s --strict-whitespace --match-full-lines \
# RUN: -DFILE=%t.1 --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 | \
# RUN: FileCheck %s --strict-whitespace --match-full-lines \
# RUN: -DFILE=%t.2 --check-prefix=CASE2
## Check handling of corrupted elf files (bad sh_name)
# RUN: yaml2obj --docnum=3 %s -o %t.3
# RUN: not llvm-readobj --sframe %t.3 2>&1 | \
# RUN: FileCheck %s --strict-whitespace --match-full-lines \
# RUN: -DFILE=%t.3 --check-prefix=CASE3
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Sections:
- Name: .sframe_bad_sh_size
Type: SHT_GNU_SFRAME
Flags: [ SHF_ALLOC ]
ShSize: 0xfffff
# CASE1-LABEL:SFrame section '.sframe_bad_sh_size' {
# CASE1:{{.*}}: warning: '[[FILE]]': The end of the file was unexpectedly encountered
- Name: .sframe_1b
Type: SHT_GNU_SFRAME
Flags: [ SHF_ALLOC ]
ContentArray: [ 0x00 ]
# CASE1-LABEL:SFrame section '.sframe_1b' {
# CASE1:{{.*}}: warning: '[[FILE]]': invalid sframe section: unexpected end of data at offset 0x1 while reading [0x0, 0x4)
- Name: .sframe_bad_magic
Type: SHT_GNU_SFRAME
Flags: [ SHF_ALLOC ]
ContentArray: [ 0xde, 0xad, 0xbe, 0xef]
# CASE1-LABEL:SFrame section '.sframe_bad_magic' {
# CASE1:{{.*}}: warning: '[[FILE]]': invalid sframe section: invalid magic number (0xadde)
- Name: .sframe_bad_version
Type: SHT_GNU_SFRAME
Flags: [ SHF_ALLOC ]
ContentArray: [
0xe2, 0xde, 0x01, 0x00 # Preamble (magic, version, flags)
]
# CASE1-LABEL:SFrame section '.sframe_bad_version' {
# CASE1:{{.*}}: warning: '[[FILE]]': invalid sframe section: invalid/unsupported version number (1)
- Name: .sframe_6b
Type: SHT_GNU_SFRAME
Flags: [ SHF_ALLOC ]
ContentArray: [
0xe2, 0xde, 0x02, 0x00, # Preamble (magic, version, flags)
0x01, 0x02
]
# CASE1-LABEL:SFrame section '.sframe_6b' {
# CASE1:{{.*}}: warning: '[[FILE]]': invalid sframe section: unexpected end of data at offset 0x6 while reading [0x0, 0x1c)
- Name: .sframe_header
Type: SHT_GNU_SFRAME
Flags: [ SHF_ALLOC ]
ContentArray: [
0xe2, 0xde, 0x02, 0x06, # 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:SFrame section '.sframe_header' {
# CASE1: Header {
# CASE1-NEXT: Magic: 0xDEE2
# CASE1-NEXT: Version: V2 (0x2)
# CASE1-NEXT: Flags [ (0x6)
# CASE1-NEXT: FDEFuncStartPCRel (0x4){{ *}}
# CASE1-NEXT: FramePointer (0x2){{ *}}
# CASE1-NEXT: ]
# CASE1-NEXT: ABI: AMD64EndianLittle (0x3)
# CASE1-NEXT: CFA fixed FP offset (unused): 66
# CASE1-NEXT: CFA fixed RA offset: 71
# 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: }
# CASE1-NEXT:}
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2MSB
Type: ET_EXEC
Sections:
- Name: .sframe
Type: SHT_GNU_SFRAME
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: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 FP offset (unused): 66
# CASE2-NEXT: CFA fixed RA offset (unused): 71
# 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: }
# CASE2-NEXT:}
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2MSB
Type: ET_EXEC
Sections:
- Name: .corrupted
Type: SHT_GNU_SFRAME
Flags: [ SHF_ALLOC ]
ShName: 0x10000
# CASE3:{{.*}}: error: '[[FILE]]': a section [index 1] has an invalid sh_name (0x10000) offset which goes past the end of the section name string table

View File

@ -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 @@ public:
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
@ -6429,6 +6433,61 @@ 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)) {
// Validity of sections names checked in getSectionRefsByNameOrIndex.
StringRef SectionName = cantFail(Section.getName());
DictScope SectionScope(W,
formatv("SFrame section '{0}'", SectionName).str());
StringRef SectionContent;
if (Error Err = Section.getContents().moveInto(SectionContent)) {
reportWarning(std::move(Err), FileName);
continue;
}
Expected<object::SFrameParser<E>> Parser =
object::SFrameParser<E>::create(arrayRefFromStringRef(SectionContent));
if (!Parser) {
reportWarning(createError("invalid sframe section: " +
toString(Parser.takeError())),
FileName);
continue;
}
DictScope HeaderScope(W, "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 FP offset" +
Twine(Parser->usesFixedFPOffset() ? "" : " (unused)"))
.str(),
Header.CFAFixedFPOffset.value());
W.printNumber(("CFA fixed RA offset" +
Twine(Parser->usesFixedRAOffset() ? "" : " (unused)"))
.str(),
Header.CFAFixedRAOffset.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());
}
}
template <class ELFT> void GNUELFDumper<ELFT>::printELFLinkerOptions() {
OS << "printELFLinkerOptions not implemented!\n";
}

View File

@ -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;

View File

@ -139,6 +139,7 @@ public:
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 @@ public:
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) {}

View File

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

View File

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