blob: d6fa54c087ca39940ce4847e6c6653cc9458c8ec [file] [log] [blame]
//===- lib/MC/MCSFrame.cpp - MCSFrame implementation ----------------------===//
//
// 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/MC/MCSFrame.h"
#include "llvm/BinaryFormat/SFrame.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectStreamer.h"
#include "llvm/MC/MCSection.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/EndianStream.h"
using namespace llvm;
using namespace sframe;
namespace {
// High-level structure to track info needed to emit a
// sframe_frame_row_entry_addrX. On disk these have both a fixed portion of type
// sframe_frame_row_entry_addrX and trailing data of X * S bytes, where X is the
// datum size, and S is 1, 2, or 3 depending on which of CFA, SP, and FP are
// being tracked.
struct SFrameFRE {
// An FRE describes how to find the registers when the PC is at this
// Label from function start.
const MCSymbol *Label = nullptr;
size_t CFAOffset = 0;
size_t FPOffset = 0;
size_t RAOffset = 0;
FREInfo<endianness::native> Info;
bool CFARegSet = false;
SFrameFRE(const MCSymbol *Start) : Label(Start) { Info.Info = 0; }
void emitOffset(MCObjectStreamer &S, FREOffset OffsetSize, size_t Offset) {
switch (OffsetSize) {
case (FREOffset::B1):
S.emitInt8(Offset);
return;
case (FREOffset::B2):
S.emitInt16(Offset);
return;
case (FREOffset::B4):
S.emitInt32(Offset);
return;
}
}
void emit(MCObjectStreamer &S, const MCSymbol *FuncBegin,
MCFragment *FDEFrag) {
S.emitSFrameCalculateFuncOffset(FuncBegin, Label, FDEFrag, SMLoc());
// fre_cfa_base_reg_id already set during parsing
// fre_offset_count
unsigned RegsTracked = 1; // always track the cfa.
if (FPOffset != 0)
++RegsTracked;
if (RAOffset != 0)
++RegsTracked;
Info.setOffsetCount(RegsTracked);
// fre_offset_size
if (isInt<8>(CFAOffset) && isInt<8>(FPOffset) && isInt<8>(RAOffset))
Info.setOffsetSize(FREOffset::B1);
else if (isInt<16>(CFAOffset) && isInt<16>(FPOffset) && isInt<16>(RAOffset))
Info.setOffsetSize(FREOffset::B2);
else {
assert(isInt<32>(CFAOffset) && isInt<32>(FPOffset) &&
isInt<32>(RAOffset) && "Offset too big for sframe");
Info.setOffsetSize(FREOffset::B4);
}
// No support for fre_mangled_ra_p yet.
Info.setReturnAddressSigned(false);
// sframe_fre_info_word
S.emitInt8(Info.getFREInfo());
// FRE Offsets
[[maybe_unused]] unsigned OffsetsEmitted = 1;
emitOffset(S, Info.getOffsetSize(), CFAOffset);
if (FPOffset) {
++OffsetsEmitted;
emitOffset(S, Info.getOffsetSize(), FPOffset);
}
if (RAOffset) {
++OffsetsEmitted;
emitOffset(S, Info.getOffsetSize(), RAOffset);
}
assert(OffsetsEmitted == RegsTracked &&
"Didn't emit the right number of offsets");
}
};
// High-level structure to track info needed to emit a sframe_func_desc_entry
// and its associated FREs.
struct SFrameFDE {
// Reference to the original dwarf frame to avoid copying.
const MCDwarfFrameInfo &DFrame;
// Label where this FDE's FREs start.
MCSymbol *FREStart;
// Frag where this FDE is emitted.
MCFragment *Frag;
// Unwinding fres
SmallVector<SFrameFRE> FREs;
// .cfi_remember_state stack
SmallVector<SFrameFRE> SaveState;
SFrameFDE(const MCDwarfFrameInfo &DF, MCSymbol *FRES)
: DFrame(DF), FREStart(FRES), Frag(nullptr) {}
void emit(MCObjectStreamer &S, const MCSymbol *FRESubSectionStart) {
MCContext &C = S.getContext();
// sfde_func_start_address
const MCExpr *V = C.getAsmInfo()->getExprForFDESymbol(
&(*DFrame.Begin), C.getObjectFileInfo()->getFDEEncoding(), S);
S.emitValue(V, sizeof(int32_t));
// sfde_func_size
S.emitAbsoluteSymbolDiff(DFrame.End, DFrame.Begin, sizeof(uint32_t));
// sfde_func_start_fre_off
auto *F = S.getCurrentFragment();
const MCExpr *Diff = MCBinaryExpr::createSub(
MCSymbolRefExpr::create(FREStart, C),
MCSymbolRefExpr::create(FRESubSectionStart, C), C);
F->addFixup(MCFixup::create(F->getContents().size(), Diff,
MCFixup::getDataKindForSize(4)));
S.emitInt32(0);
// sfde_func_num_fres
S.emitInt32(FREs.size());
// sfde_func_info word
// All FREs within an FDE share the same sframe::FREType::AddrX. The value
// of 'X' is determined by the FRE with the largest offset, which is the
// last. This offset isn't known until relax time, so emit a frag which can
// calculate that now.
//
// At relax time, this FDE frag calculates the proper AddrX value (as well
// as the rest of the FDE FuncInfo word). Subsequent FRE frags will read it
// from this frag and emit the proper number of bytes.
Frag = S.getCurrentFragment();
S.emitSFrameCalculateFuncOffset(DFrame.Begin, FREs.back().Label, nullptr,
SMLoc());
// sfde_func_rep_size. Not relevant in non-PCMASK fdes.
S.emitInt8(0);
// sfde_func_padding2
S.emitInt16(0);
}
};
// Emitting these field-by-field, instead of constructing the actual structures
// lets Streamer do target endian-fixups for free.
class SFrameEmitterImpl {
MCObjectStreamer &Streamer;
SmallVector<SFrameFDE> FDEs;
uint32_t TotalFREs;
ABI SFrameABI;
// Target-specific convenience variables to detect when a CFI instruction
// references these registers. Unlike in dwarf frame descriptions, they never
// escape into the sframe section itself. TODO: These should be retrieved from
// the target.
unsigned SPReg;
unsigned FPReg;
unsigned RAReg;
int8_t FixedRAOffset;
MCSymbol *FDESubSectionStart;
MCSymbol *FRESubSectionStart;
MCSymbol *FRESubSectionEnd;
bool setCFARegister(SFrameFRE &FRE, const MCCFIInstruction &I) {
if (I.getRegister() == SPReg) {
FRE.CFARegSet = true;
FRE.Info.setBaseRegister(BaseReg::SP);
return true;
}
if (I.getRegister() == FPReg) {
FRE.CFARegSet = true;
FRE.Info.setBaseRegister(BaseReg::FP);
return true;
}
Streamer.getContext().reportWarning(
I.getLoc(), "canonical Frame Address not in stack- or frame-pointer. "
"Omitting SFrame unwind info for this function");
return false;
}
bool setCFAOffset(SFrameFRE &FRE, SMLoc Loc, size_t Offset) {
if (!FRE.CFARegSet) {
Streamer.getContext().reportWarning(
Loc, "adjusting CFA offset without a base register. "
"Omitting SFrame unwind info for this function");
return false;
}
FRE.CFAOffset = Offset;
return true;
}
// Add the effects of CFI to the current FDE, creating a new FRE when
// necessary.
bool handleCFI(SFrameFDE &FDE, SFrameFRE &FRE, const MCCFIInstruction &CFI) {
switch (CFI.getOperation()) {
case MCCFIInstruction::OpDefCfaRegister:
return setCFARegister(FRE, CFI);
case MCCFIInstruction::OpDefCfa:
case MCCFIInstruction::OpLLVMDefAspaceCfa:
if (!setCFARegister(FRE, CFI))
return false;
return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
case MCCFIInstruction::OpOffset:
if (CFI.getRegister() == FPReg)
FRE.FPOffset = CFI.getOffset();
else if (CFI.getRegister() == RAReg)
FRE.RAOffset = CFI.getOffset();
return true;
case MCCFIInstruction::OpRelOffset:
if (CFI.getRegister() == FPReg)
FRE.FPOffset += CFI.getOffset();
else if (CFI.getRegister() == RAReg)
FRE.RAOffset += CFI.getOffset();
return true;
case MCCFIInstruction::OpDefCfaOffset:
return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
case MCCFIInstruction::OpAdjustCfaOffset:
return setCFAOffset(FRE, CFI.getLoc(), FRE.CFAOffset + CFI.getOffset());
case MCCFIInstruction::OpRememberState:
if (FDE.FREs.size() == 1) {
// Error for gas compatibility: If the initial FRE isn't complete,
// then any state is incomplete. FIXME: Dwarf doesn't error here.
// Why should sframe?
Streamer.getContext().reportWarning(
CFI.getLoc(), "skipping SFrame FDE; .cfi_remember_state without "
"prior SFrame FRE state");
return false;
}
FDE.SaveState.push_back(FRE);
return true;
case MCCFIInstruction::OpRestore:
// The first FRE generated has the original state.
if (CFI.getRegister() == FPReg)
FRE.FPOffset = FDE.FREs.front().FPOffset;
else if (CFI.getRegister() == RAReg)
FRE.RAOffset = FDE.FREs.front().RAOffset;
return true;
case MCCFIInstruction::OpRestoreState:
// The cfi parser will have caught unbalanced directives earlier, so a
// mismatch here is an implementation error.
assert(!FDE.SaveState.empty() &&
"cfi_restore_state without cfi_save_state");
FRE = FDE.SaveState.pop_back_val();
return true;
case MCCFIInstruction::OpEscape:
// TODO: Implement. Will use FDE.
return true;
default:
// Instructions that don't affect the CFA, RA, and SP can be safely
// ignored.
return true;
}
}
public:
SFrameEmitterImpl(MCObjectStreamer &Streamer)
: Streamer(Streamer), TotalFREs(0) {
assert(Streamer.getContext()
.getObjectFileInfo()
->getSFrameABIArch()
.has_value());
FDEs.reserve(Streamer.getDwarfFrameInfos().size());
SFrameABI = *Streamer.getContext().getObjectFileInfo()->getSFrameABIArch();
switch (SFrameABI) {
case ABI::AArch64EndianBig:
case ABI::AArch64EndianLittle:
SPReg = 31;
RAReg = 29;
FPReg = 30;
FixedRAOffset = 0;
break;
case ABI::AMD64EndianLittle:
SPReg = 7;
// RARegister untracked in this abi. Value chosen to match
// MCDwarfFrameInfo constructor.
RAReg = static_cast<unsigned>(INT_MAX);
FPReg = 6;
FixedRAOffset = -8;
break;
}
FDESubSectionStart = Streamer.getContext().createTempSymbol();
FRESubSectionStart = Streamer.getContext().createTempSymbol();
FRESubSectionEnd = Streamer.getContext().createTempSymbol();
}
bool atSameLocation(const MCSymbol *Left, const MCSymbol *Right) {
return Left != nullptr && Right != nullptr &&
Left->getFragment() == Right->getFragment() &&
Left->getOffset() == Right->getOffset();
}
bool equalIgnoringLocation(const SFrameFRE &Left, const SFrameFRE &Right) {
return Left.CFAOffset == Right.CFAOffset &&
Left.FPOffset == Right.FPOffset && Left.RAOffset == Right.RAOffset &&
Left.Info.getFREInfo() == Right.Info.getFREInfo() &&
Left.CFARegSet == Right.CFARegSet;
}
void buildSFDE(const MCDwarfFrameInfo &DF) {
// Functions with zero size can happen with assembler macros and
// machine-generated code. They don't need unwind info at all, so
// no need to warn.
if (atSameLocation(DF.Begin, DF.End))
return;
bool Valid = true;
SFrameFDE FDE(DF, Streamer.getContext().createTempSymbol());
// This would have been set via ".cfi_return_column", but
// MCObjectStreamer doesn't emit an MCCFIInstruction for that. It just
// sets the DF.RAReg.
// FIXME: This also prevents providing a proper location for the error.
// LLVM doesn't change the return column itself, so this was
// hand-written assembly.
if (DF.RAReg != RAReg) {
Streamer.getContext().reportWarning(
SMLoc(), "non-default RA register in .cfi_return_column " +
Twine(DF.RAReg) +
". Omitting SFrame unwind info for this function");
Valid = false;
}
MCSymbol *LastLabel = DF.Begin;
SFrameFRE BaseFRE(LastLabel);
if (!DF.IsSimple) {
for (const auto &CFI :
Streamer.getContext().getAsmInfo()->getInitialFrameState())
if (!handleCFI(FDE, BaseFRE, CFI))
Valid = false;
}
FDE.FREs.push_back(BaseFRE);
for (const auto &CFI : DF.Instructions) {
// Instructions from InitialFrameState may not have a label, but if these
// instructions don't, then they are in dead code or otherwise unused.
// TODO: This check follows MCDwarf.cpp
// FrameEmitterImplementation::emitCFIInstructions, but nothing in the
// testsuite triggers it. We should see if it can be removed in both
// places, or alternately, add a test to exercise it.
auto *L = CFI.getLabel();
if (L && !L->isDefined())
continue;
SFrameFRE FRE = FDE.FREs.back();
if (!handleCFI(FDE, FRE, CFI))
Valid = false;
// If nothing relevant but the location changed, don't add the FRE.
if (equalIgnoringLocation(FRE, FDE.FREs.back()))
continue;
// If the location stayed the same, then update the current
// row. Otherwise, add a new one.
if (atSameLocation(LastLabel, L))
FDE.FREs.back() = FRE;
else {
FDE.FREs.push_back(FRE);
FDE.FREs.back().Label = L;
LastLabel = L;
}
}
if (Valid) {
FDEs.push_back(FDE);
TotalFREs += FDE.FREs.size();
}
}
void emitPreamble() {
Streamer.emitInt16(Magic);
Streamer.emitInt8(static_cast<uint8_t>(Version::V2));
Streamer.emitInt8(static_cast<uint8_t>(Flags::FDEFuncStartPCRel));
}
void emitHeader() {
emitPreamble();
// sfh_abi_arch
Streamer.emitInt8(static_cast<uint8_t>(SFrameABI));
// sfh_cfa_fixed_fp_offset
Streamer.emitInt8(0);
// sfh_cfa_fixed_ra_offset
Streamer.emitInt8(FixedRAOffset);
// sfh_auxhdr_len
Streamer.emitInt8(0);
// shf_num_fdes
Streamer.emitInt32(FDEs.size());
// shf_num_fres
Streamer.emitInt32(TotalFREs);
// shf_fre_len
Streamer.emitAbsoluteSymbolDiff(FRESubSectionEnd, FRESubSectionStart,
sizeof(int32_t));
// shf_fdeoff. With no sfh_auxhdr, these immediately follow this header.
Streamer.emitInt32(0);
// shf_freoff
Streamer.emitInt32(FDEs.size() *
sizeof(sframe::FuncDescEntry<endianness::native>));
}
void emitFDEs() {
Streamer.emitLabel(FDESubSectionStart);
for (auto &FDE : FDEs) {
FDE.emit(Streamer, FRESubSectionStart);
}
}
void emitFREs() {
Streamer.emitLabel(FRESubSectionStart);
for (auto &FDE : FDEs) {
Streamer.emitLabel(FDE.FREStart);
for (auto &FRE : FDE.FREs)
FRE.emit(Streamer, FDE.DFrame.Begin, FDE.Frag);
}
Streamer.emitLabel(FRESubSectionEnd);
}
};
} // end anonymous namespace
void MCSFrameEmitter::emit(MCObjectStreamer &Streamer) {
MCContext &Context = Streamer.getContext();
// If this target doesn't support sframes, return now. Gas doesn't warn in
// this case, but if we want to, it should be done at option-parsing time,
// rather than here.
if (!Streamer.getContext()
.getObjectFileInfo()
->getSFrameABIArch()
.has_value())
return;
SFrameEmitterImpl Emitter(Streamer);
ArrayRef<MCDwarfFrameInfo> FrameArray = Streamer.getDwarfFrameInfos();
// Both the header itself and the FDEs include various offsets and counts.
// Therefore, all of this must be precomputed.
for (const auto &DFrame : FrameArray)
Emitter.buildSFDE(DFrame);
MCSection *Section = Context.getObjectFileInfo()->getSFrameSection();
// Not strictly necessary, but gas always aligns to 8, so match that.
Section->ensureMinAlignment(Align(8));
Streamer.switchSection(Section);
MCSymbol *SectionStart = Context.createTempSymbol();
Streamer.emitLabel(SectionStart);
Emitter.emitHeader();
Emitter.emitFDEs();
Emitter.emitFREs();
}
void MCSFrameEmitter::encodeFuncOffset(MCContext &C, uint64_t Offset,
SmallVectorImpl<char> &Out,
MCFragment *FDEFrag) {
// If encoding into the FDE Frag itself, generate the sfde_func_info.
if (FDEFrag == nullptr) {
// sfde_func_info
// Offset is the difference between the function start label and the final
// FRE's offset, which is the max offset for this FDE.
FDEInfo<endianness::native> I;
I.Info = 0;
if (isUInt<8>(Offset))
I.setFREType(FREType::Addr1);
else if (isUInt<16>(Offset))
I.setFREType(FREType::Addr2);
else {
assert(isUInt<32>(Offset));
I.setFREType(FREType::Addr4);
}
I.setFDEType(FDEType::PCInc);
// TODO: When we support pauth keys, this will need to be retrieved
// from the frag itself.
I.setPAuthKey(0);
Out.push_back(I.getFuncInfo());
return;
}
const auto &FDEData = FDEFrag->getVarContents();
FDEInfo<endianness::native> I;
I.Info = FDEData.back();
FREType T = I.getFREType();
llvm::endianness E = C.getAsmInfo()->isLittleEndian()
? llvm::endianness::little
: llvm::endianness::big;
// sfre_start_address
switch (T) {
case FREType::Addr1:
assert(isUInt<8>(Offset) && "Miscalculated Sframe FREType");
support::endian::write<uint8_t>(Out, Offset, E);
break;
case FREType::Addr2:
assert(isUInt<16>(Offset) && "Miscalculated Sframe FREType");
support::endian::write<uint16_t>(Out, Offset, E);
break;
case FREType::Addr4:
assert(isUInt<32>(Offset) && "Miscalculated Sframe FREType");
support::endian::write<uint32_t>(Out, Offset, E);
break;
}
}