Adds support for emitting Windows x64 Unwind V2 information, includes support `/d2epilogunwind` in clang-cl. Unwind v2 adds information about the epilogs in functions such that the unwinder can unwind even in the middle of an epilog, without having to disassembly the function to see what has or has not been cleaned up. Unwind v2 requires that all epilogs are in "canonical" form: * If there was a stack allocation (fixed or dynamic) in the prolog, then the first instruction in the epilog must be a stack deallocation. * Next, for each `PUSH` in the prolog there must be a corresponding `POP` instruction in exact reverse order. * Finally, the epilog must end with the terminator. This change adds a pass to validate epilogs in modules that have Unwind v2 enabled and, if they pass, emits new pseudo instructions to MC that 1) note that the function is using unwind v2 and 2) mark the start of the epilog (this is either the first `POP` if there is one, otherwise the terminator instruction). If a function does not meet these requirements, it is downgraded to Unwind v1 (i.e., these new pseudo instructions are not emitted). Note that the unwind v2 table only marks the size of the epilog in the "header" unwind code, but it's possible for epilogs to use different terminator instructions thus they are not all the same size. As a work around for this, MC will assume that all terminator instructions are 1-byte long - this still works correctly with the Windows unwinder as it is only using the size to do a range check to see if a thread is in an epilog or not, and since the instruction pointer will never be in the middle of an instruction and the terminator is always at the end of an epilog the range check will function correctly. This does mean, however, that the "at end" optimization (where an epilog unwind code can be elided if the last epilog is at the end of the function) can only be used if the terminator is 1-byte long. One other complication with the implementation is that the unwind table for a function is emitted during streaming, however we can't calculate the distance between an epilog and the end of the function at that time as layout hasn't been completed yet (thus some instructions may be relaxed). To work around this, epilog unwind codes are emitted via a fixup. This also means that we can't pre-emptively downgrade a function to Unwind v1 if one of these offsets is too large, so instead we raise an error (but I've passed through the location information, so the user will know which of their functions is problematic).
267 lines
8.6 KiB
C++
267 lines
8.6 KiB
C++
//===-- ARMWinCOFFStreamer.cpp - ARM Target WinCOFF Streamer ----*- 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 "ARMMCTargetDesc.h"
|
|
#include "llvm/MC/MCAsmBackend.h"
|
|
#include "llvm/MC/MCAssembler.h"
|
|
#include "llvm/MC/MCCodeEmitter.h"
|
|
#include "llvm/MC/MCContext.h"
|
|
#include "llvm/MC/MCObjectWriter.h"
|
|
#include "llvm/MC/MCWin64EH.h"
|
|
#include "llvm/MC/MCWinCOFFStreamer.h"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
class ARMWinCOFFStreamer : public MCWinCOFFStreamer {
|
|
Win64EH::ARMUnwindEmitter EHStreamer;
|
|
|
|
public:
|
|
ARMWinCOFFStreamer(MCContext &C, std::unique_ptr<MCAsmBackend> AB,
|
|
std::unique_ptr<MCCodeEmitter> CE,
|
|
std::unique_ptr<MCObjectWriter> OW)
|
|
: MCWinCOFFStreamer(C, std::move(AB), std::move(CE), std::move(OW)) {}
|
|
|
|
void emitWinEHHandlerData(SMLoc Loc) override;
|
|
void emitWindowsUnwindTables() override;
|
|
void emitWindowsUnwindTables(WinEH::FrameInfo *Frame) override;
|
|
|
|
void finishImpl() override;
|
|
};
|
|
|
|
void ARMWinCOFFStreamer::emitWinEHHandlerData(SMLoc Loc) {
|
|
MCStreamer::emitWinEHHandlerData(Loc);
|
|
|
|
// We have to emit the unwind info now, because this directive
|
|
// actually switches to the .xdata section!
|
|
EHStreamer.EmitUnwindInfo(*this, getCurrentWinFrameInfo(),
|
|
/* HandlerData = */ true);
|
|
}
|
|
|
|
void ARMWinCOFFStreamer::emitWindowsUnwindTables(WinEH::FrameInfo *Frame) {
|
|
EHStreamer.EmitUnwindInfo(*this, Frame, /* HandlerData = */ false);
|
|
}
|
|
|
|
void ARMWinCOFFStreamer::emitWindowsUnwindTables() {
|
|
if (!getNumWinFrameInfos())
|
|
return;
|
|
EHStreamer.Emit(*this);
|
|
}
|
|
|
|
void ARMWinCOFFStreamer::finishImpl() {
|
|
emitFrames(nullptr);
|
|
emitWindowsUnwindTables();
|
|
|
|
MCWinCOFFStreamer::finishImpl();
|
|
}
|
|
}
|
|
|
|
MCStreamer *
|
|
llvm::createARMWinCOFFStreamer(MCContext &Context,
|
|
std::unique_ptr<MCAsmBackend> &&MAB,
|
|
std::unique_ptr<MCObjectWriter> &&OW,
|
|
std::unique_ptr<MCCodeEmitter> &&Emitter) {
|
|
return new ARMWinCOFFStreamer(Context, std::move(MAB), std::move(Emitter),
|
|
std::move(OW));
|
|
}
|
|
|
|
namespace {
|
|
class ARMTargetWinCOFFStreamer : public llvm::ARMTargetStreamer {
|
|
public:
|
|
ARMTargetWinCOFFStreamer(llvm::MCStreamer &S) : ARMTargetStreamer(S) {}
|
|
|
|
ARMWinCOFFStreamer &getStreamer() {
|
|
return static_cast<ARMWinCOFFStreamer &>(Streamer);
|
|
}
|
|
void emitThumbFunc(MCSymbol *Symbol) override;
|
|
|
|
// The unwind codes on ARM Windows are documented at
|
|
// https://docs.microsoft.com/en-us/cpp/build/arm-exception-handling
|
|
void emitARMWinCFIAllocStack(unsigned Size, bool Wide) override;
|
|
void emitARMWinCFISaveRegMask(unsigned Mask, bool Wide) override;
|
|
void emitARMWinCFISaveSP(unsigned Reg) override;
|
|
void emitARMWinCFISaveFRegs(unsigned First, unsigned Last) override;
|
|
void emitARMWinCFISaveLR(unsigned Offset) override;
|
|
void emitARMWinCFIPrologEnd(bool Fragment) override;
|
|
void emitARMWinCFINop(bool Wide) override;
|
|
void emitARMWinCFIEpilogStart(unsigned Condition) override;
|
|
void emitARMWinCFIEpilogEnd() override;
|
|
void emitARMWinCFICustom(unsigned Opcode) override;
|
|
|
|
private:
|
|
void emitARMWinUnwindCode(unsigned UnwindCode, int Reg, int Offset);
|
|
};
|
|
|
|
void ARMTargetWinCOFFStreamer::emitThumbFunc(MCSymbol *Symbol) {
|
|
getStreamer().getAssembler().setIsThumbFunc(Symbol);
|
|
}
|
|
|
|
// Helper function to common out unwind code setup for those codes that can
|
|
// belong to both prolog and epilog.
|
|
void ARMTargetWinCOFFStreamer::emitARMWinUnwindCode(unsigned UnwindCode,
|
|
int Reg, int Offset) {
|
|
auto &S = getStreamer();
|
|
WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
|
|
if (!CurFrame)
|
|
return;
|
|
MCSymbol *Label = S.emitCFILabel();
|
|
auto Inst = WinEH::Instruction(UnwindCode, Label, Reg, Offset);
|
|
if (S.isInEpilogCFI())
|
|
S.getCurrentWinEpilog()->Instructions.push_back(Inst);
|
|
else
|
|
CurFrame->Instructions.push_back(Inst);
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFIAllocStack(unsigned Size,
|
|
bool Wide) {
|
|
unsigned Op = Win64EH::UOP_AllocSmall;
|
|
if (!Wide) {
|
|
if (Size / 4 > 0xffff)
|
|
Op = Win64EH::UOP_AllocHuge;
|
|
else if (Size / 4 > 0x7f)
|
|
Op = Win64EH::UOP_AllocLarge;
|
|
} else {
|
|
Op = Win64EH::UOP_WideAllocMedium;
|
|
if (Size / 4 > 0xffff)
|
|
Op = Win64EH::UOP_WideAllocHuge;
|
|
else if (Size / 4 > 0x3ff)
|
|
Op = Win64EH::UOP_WideAllocLarge;
|
|
}
|
|
emitARMWinUnwindCode(Op, -1, Size);
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFISaveRegMask(unsigned Mask,
|
|
bool Wide) {
|
|
assert(Mask != 0);
|
|
int Lr = (Mask & 0x4000) ? 1 : 0;
|
|
Mask &= ~0x4000;
|
|
if (Wide)
|
|
assert((Mask & ~0x1fff) == 0);
|
|
else
|
|
assert((Mask & ~0x00ff) == 0);
|
|
if (Mask && ((Mask + (1 << 4)) & Mask) == 0) {
|
|
if (Wide && (Mask & 0x1000) == 0 && (Mask & 0xff) == 0xf0) {
|
|
// One continuous range from r4 to r8-r11
|
|
for (int I = 11; I >= 8; I--) {
|
|
if (Mask & (1 << I)) {
|
|
emitARMWinUnwindCode(Win64EH::UOP_WideSaveRegsR4R11LR, I, Lr);
|
|
return;
|
|
}
|
|
}
|
|
// If it actually was from r4 to r4-r7, continue below.
|
|
} else if (!Wide) {
|
|
// One continuous range from r4 to r4-r7
|
|
for (int I = 7; I >= 4; I--) {
|
|
if (Mask & (1 << I)) {
|
|
emitARMWinUnwindCode(Win64EH::UOP_SaveRegsR4R7LR, I, Lr);
|
|
return;
|
|
}
|
|
}
|
|
llvm_unreachable("logic error");
|
|
}
|
|
}
|
|
Mask |= Lr << 14;
|
|
if (Wide)
|
|
emitARMWinUnwindCode(Win64EH::UOP_WideSaveRegMask, Mask, 0);
|
|
else
|
|
emitARMWinUnwindCode(Win64EH::UOP_SaveRegMask, Mask, 0);
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFISaveSP(unsigned Reg) {
|
|
emitARMWinUnwindCode(Win64EH::UOP_SaveSP, Reg, 0);
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFISaveFRegs(unsigned First,
|
|
unsigned Last) {
|
|
assert(First <= Last);
|
|
assert(First >= 16 || Last < 16);
|
|
assert(First <= 31 && Last <= 31);
|
|
if (First == 8)
|
|
emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD8D15, Last, 0);
|
|
else if (First <= 15)
|
|
emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD0D15, First, Last);
|
|
else
|
|
emitARMWinUnwindCode(Win64EH::UOP_SaveFRegD16D31, First, Last);
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFISaveLR(unsigned Offset) {
|
|
emitARMWinUnwindCode(Win64EH::UOP_SaveLR, 0, Offset);
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFINop(bool Wide) {
|
|
if (Wide)
|
|
emitARMWinUnwindCode(Win64EH::UOP_WideNop, -1, 0);
|
|
else
|
|
emitARMWinUnwindCode(Win64EH::UOP_Nop, -1, 0);
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFIPrologEnd(bool Fragment) {
|
|
auto &S = getStreamer();
|
|
WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
|
|
if (!CurFrame)
|
|
return;
|
|
|
|
MCSymbol *Label = S.emitCFILabel();
|
|
CurFrame->PrologEnd = Label;
|
|
WinEH::Instruction Inst =
|
|
WinEH::Instruction(Win64EH::UOP_End, /*Label=*/nullptr, -1, 0);
|
|
auto it = CurFrame->Instructions.begin();
|
|
CurFrame->Instructions.insert(it, Inst);
|
|
CurFrame->Fragment = Fragment;
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogStart(unsigned Condition) {
|
|
auto &S = getStreamer();
|
|
WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
|
|
if (!CurFrame)
|
|
return;
|
|
|
|
S.emitWinCFIBeginEpilogue();
|
|
if (S.isInEpilogCFI()) {
|
|
S.getCurrentWinEpilog()->Condition = Condition;
|
|
}
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFIEpilogEnd() {
|
|
auto &S = getStreamer();
|
|
WinEH::FrameInfo *CurFrame = S.EnsureValidWinFrameInfo(SMLoc());
|
|
if (!CurFrame)
|
|
return;
|
|
|
|
if (S.isInEpilogCFI()) {
|
|
std::vector<WinEH::Instruction> &Epilog =
|
|
S.getCurrentWinEpilog()->Instructions;
|
|
|
|
unsigned UnwindCode = Win64EH::UOP_End;
|
|
if (!Epilog.empty()) {
|
|
WinEH::Instruction EndInstr = Epilog.back();
|
|
if (EndInstr.Operation == Win64EH::UOP_Nop) {
|
|
UnwindCode = Win64EH::UOP_EndNop;
|
|
Epilog.pop_back();
|
|
} else if (EndInstr.Operation == Win64EH::UOP_WideNop) {
|
|
UnwindCode = Win64EH::UOP_WideEndNop;
|
|
Epilog.pop_back();
|
|
}
|
|
}
|
|
|
|
WinEH::Instruction Inst = WinEH::Instruction(UnwindCode, nullptr, -1, 0);
|
|
S.getCurrentWinEpilog()->Instructions.push_back(Inst);
|
|
}
|
|
S.emitWinCFIEndEpilogue();
|
|
}
|
|
|
|
void ARMTargetWinCOFFStreamer::emitARMWinCFICustom(unsigned Opcode) {
|
|
emitARMWinUnwindCode(Win64EH::UOP_Custom, 0, Opcode);
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
MCTargetStreamer *llvm::createARMObjectTargetWinCOFFStreamer(MCStreamer &S) {
|
|
return new ARMTargetWinCOFFStreamer(S);
|
|
}
|