//===-- X86WinCOFFTargetStreamer.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 "X86MCTargetDesc.h" #include "X86TargetStreamer.h" #include "llvm/DebugInfo/CodeView/CodeView.h" #include "llvm/MC/MCCodeView.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCInstPrinter.h" #include "llvm/MC/MCRegisterInfo.h" #include "llvm/MC/MCSubtargetInfo.h" #include "llvm/MC/MCSymbol.h" #include "llvm/Support/FormattedStream.h" using namespace llvm; using namespace llvm::codeview; namespace { /// Implements Windows x86-only directives for assembly emission. class X86WinCOFFAsmTargetStreamer : public X86TargetStreamer { formatted_raw_ostream &OS; MCInstPrinter &InstPrinter; public: X86WinCOFFAsmTargetStreamer(MCStreamer &S, formatted_raw_ostream &OS, MCInstPrinter &InstPrinter) : X86TargetStreamer(S), OS(OS), InstPrinter(InstPrinter) {} bool emitFPOProc(const MCSymbol *ProcSym, unsigned ParamsSize, SMLoc L) override; bool emitFPOEndPrologue(SMLoc L) override; bool emitFPOEndProc(SMLoc L) override; bool emitFPOData(const MCSymbol *ProcSym, SMLoc L) override; bool emitFPOPushReg(unsigned Reg, SMLoc L) override; bool emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) override; bool emitFPOStackAlign(unsigned Align, SMLoc L) override; bool emitFPOSetFrame(unsigned Reg, SMLoc L) override; }; /// Represents a single FPO directive. struct FPOInstruction { MCSymbol *Label; enum Operation { PushReg, StackAlloc, StackAlign, SetFrame, } Op; unsigned RegOrOffset; }; struct FPOData { const MCSymbol *Function = nullptr; MCSymbol *Begin = nullptr; MCSymbol *PrologueEnd = nullptr; MCSymbol *End = nullptr; unsigned ParamsSize = 0; SmallVector Instructions; }; /// Implements Windows x86-only directives for object emission. class X86WinCOFFTargetStreamer : public X86TargetStreamer { /// Map from function symbol to its FPO data. DenseMap> AllFPOData; /// Current FPO data created by .cv_fpo_proc. std::unique_ptr CurFPOData; bool haveOpenFPOData() { return !!CurFPOData; } /// Diagnoses an error at L if we are not in an FPO prologue. Return true on /// error. bool checkInFPOPrologue(SMLoc L); MCSymbol *emitFPOLabel(); MCContext &getContext() { return getStreamer().getContext(); } public: X86WinCOFFTargetStreamer(MCStreamer &S) : X86TargetStreamer(S) {} bool emitFPOProc(const MCSymbol *ProcSym, unsigned ParamsSize, SMLoc L) override; bool emitFPOEndPrologue(SMLoc L) override; bool emitFPOEndProc(SMLoc L) override; bool emitFPOData(const MCSymbol *ProcSym, SMLoc L) override; bool emitFPOPushReg(unsigned Reg, SMLoc L) override; bool emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) override; bool emitFPOStackAlign(unsigned Align, SMLoc L) override; bool emitFPOSetFrame(unsigned Reg, SMLoc L) override; }; } // end namespace bool X86WinCOFFAsmTargetStreamer::emitFPOProc(const MCSymbol *ProcSym, unsigned ParamsSize, SMLoc L) { OS << "\t.cv_fpo_proc\t"; ProcSym->print(OS, getStreamer().getContext().getAsmInfo()); OS << ' ' << ParamsSize << '\n'; return false; } bool X86WinCOFFAsmTargetStreamer::emitFPOEndPrologue(SMLoc L) { OS << "\t.cv_fpo_endprologue\n"; return false; } bool X86WinCOFFAsmTargetStreamer::emitFPOEndProc(SMLoc L) { OS << "\t.cv_fpo_endproc\n"; return false; } bool X86WinCOFFAsmTargetStreamer::emitFPOData(const MCSymbol *ProcSym, SMLoc L) { OS << "\t.cv_fpo_data\t"; ProcSym->print(OS, getStreamer().getContext().getAsmInfo()); OS << '\n'; return false; } bool X86WinCOFFAsmTargetStreamer::emitFPOPushReg(unsigned Reg, SMLoc L) { OS << "\t.cv_fpo_pushreg\t"; InstPrinter.printRegName(OS, Reg); OS << '\n'; return false; } bool X86WinCOFFAsmTargetStreamer::emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) { OS << "\t.cv_fpo_stackalloc\t" << StackAlloc << '\n'; return false; } bool X86WinCOFFAsmTargetStreamer::emitFPOStackAlign(unsigned Align, SMLoc L) { OS << "\t.cv_fpo_stackalign\t" << Align << '\n'; return false; } bool X86WinCOFFAsmTargetStreamer::emitFPOSetFrame(unsigned Reg, SMLoc L) { OS << "\t.cv_fpo_setframe\t"; InstPrinter.printRegName(OS, Reg); OS << '\n'; return false; } bool X86WinCOFFTargetStreamer::checkInFPOPrologue(SMLoc L) { if (!haveOpenFPOData() || CurFPOData->PrologueEnd) { getContext().reportError( L, "directive must appear between .cv_fpo_proc and .cv_fpo_endprologue"); return true; } return false; } MCSymbol *X86WinCOFFTargetStreamer::emitFPOLabel() { MCSymbol *Label = getContext().createTempSymbol("cfi", true); getStreamer().emitLabel(Label); return Label; } bool X86WinCOFFTargetStreamer::emitFPOProc(const MCSymbol *ProcSym, unsigned ParamsSize, SMLoc L) { if (haveOpenFPOData()) { getContext().reportError( L, "opening new .cv_fpo_proc before closing previous frame"); return true; } CurFPOData = std::make_unique(); CurFPOData->Function = ProcSym; CurFPOData->Begin = emitFPOLabel(); CurFPOData->ParamsSize = ParamsSize; return false; } bool X86WinCOFFTargetStreamer::emitFPOEndProc(SMLoc L) { if (!haveOpenFPOData()) { getContext().reportError(L, ".cv_fpo_endproc must appear after .cv_proc"); return true; } if (!CurFPOData->PrologueEnd) { // Complain if there were prologue setup instructions but no end prologue. if (!CurFPOData->Instructions.empty()) { getContext().reportError(L, "missing .cv_fpo_endprologue"); CurFPOData->Instructions.clear(); } // Claim there is a zero-length prologue to make the label math work out // later. CurFPOData->PrologueEnd = CurFPOData->Begin; } CurFPOData->End = emitFPOLabel(); const MCSymbol *Fn = CurFPOData->Function; AllFPOData.insert({Fn, std::move(CurFPOData)}); return false; } bool X86WinCOFFTargetStreamer::emitFPOSetFrame(unsigned Reg, SMLoc L) { if (checkInFPOPrologue(L)) return true; FPOInstruction Inst; Inst.Label = emitFPOLabel(); Inst.Op = FPOInstruction::SetFrame; Inst.RegOrOffset = Reg; CurFPOData->Instructions.push_back(Inst); return false; } bool X86WinCOFFTargetStreamer::emitFPOPushReg(unsigned Reg, SMLoc L) { if (checkInFPOPrologue(L)) return true; FPOInstruction Inst; Inst.Label = emitFPOLabel(); Inst.Op = FPOInstruction::PushReg; Inst.RegOrOffset = Reg; CurFPOData->Instructions.push_back(Inst); return false; } bool X86WinCOFFTargetStreamer::emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) { if (checkInFPOPrologue(L)) return true; FPOInstruction Inst; Inst.Label = emitFPOLabel(); Inst.Op = FPOInstruction::StackAlloc; Inst.RegOrOffset = StackAlloc; CurFPOData->Instructions.push_back(Inst); return false; } bool X86WinCOFFTargetStreamer::emitFPOStackAlign(unsigned Align, SMLoc L) { if (checkInFPOPrologue(L)) return true; if (llvm::none_of(CurFPOData->Instructions, [](const FPOInstruction &Inst) { return Inst.Op == FPOInstruction::SetFrame; })) { getContext().reportError( L, "a frame register must be established before aligning the stack"); return true; } FPOInstruction Inst; Inst.Label = emitFPOLabel(); Inst.Op = FPOInstruction::StackAlign; Inst.RegOrOffset = Align; CurFPOData->Instructions.push_back(Inst); return false; } bool X86WinCOFFTargetStreamer::emitFPOEndPrologue(SMLoc L) { if (checkInFPOPrologue(L)) return true; CurFPOData->PrologueEnd = emitFPOLabel(); return false; } namespace { struct RegSaveOffset { RegSaveOffset(unsigned Reg, unsigned Offset) : Reg(Reg), Offset(Offset) {} unsigned Reg = 0; unsigned Offset = 0; }; struct FPOStateMachine { explicit FPOStateMachine(const FPOData *FPO) : FPO(FPO) {} const FPOData *FPO = nullptr; unsigned FrameReg = 0; unsigned FrameRegOff = 0; unsigned CurOffset = 0; unsigned LocalSize = 0; unsigned SavedRegSize = 0; unsigned StackOffsetBeforeAlign = 0; unsigned StackAlign = 0; unsigned Flags = 0; // FIXME: Set HasSEH / HasEH. SmallString<128> FrameFunc; SmallVector RegSaveOffsets; void emitFrameDataRecord(MCStreamer &OS, MCSymbol *Label); }; } // end namespace static Printable printFPOReg(const MCRegisterInfo *MRI, unsigned LLVMReg) { return Printable([MRI, LLVMReg](raw_ostream &OS) { switch (LLVMReg) { // MSVC only seems to emit symbolic register names for EIP, EBP, and ESP, // but the format seems to support more than that, so we emit them. case X86::EAX: OS << "$eax"; break; case X86::EBX: OS << "$ebx"; break; case X86::ECX: OS << "$ecx"; break; case X86::EDX: OS << "$edx"; break; case X86::EDI: OS << "$edi"; break; case X86::ESI: OS << "$esi"; break; case X86::ESP: OS << "$esp"; break; case X86::EBP: OS << "$ebp"; break; case X86::EIP: OS << "$eip"; break; // Otherwise, get the codeview register number and print $N. default: OS << '$' << MRI->getCodeViewRegNum(LLVMReg); break; } }); } void FPOStateMachine::emitFrameDataRecord(MCStreamer &OS, MCSymbol *Label) { unsigned CurFlags = Flags; if (Label == FPO->Begin) CurFlags |= FrameData::IsFunctionStart; // Compute the new FrameFunc string. FrameFunc.clear(); raw_svector_ostream FuncOS(FrameFunc); const MCRegisterInfo *MRI = OS.getContext().getRegisterInfo(); assert((StackAlign == 0 || FrameReg != 0) && "cannot align stack without frame reg"); StringRef CFAVar = StackAlign == 0 ? "$T0" : "$T1"; if (FrameReg) { // CFA is FrameReg + FrameRegOff. FuncOS << CFAVar << ' ' << printFPOReg(MRI, FrameReg) << ' ' << FrameRegOff << " + = "; // Assign $T0, the VFRAME register, the value of ESP after it is aligned. // Starting from the CFA, we subtract the size of all pushed registers, and // align the result. While we don't store any CSRs in this area, $T0 is used // by S_DEFRANGE_FRAMEPOINTER_REL records to find local variables. if (StackAlign) { FuncOS << "$T0 " << CFAVar << ' ' << StackOffsetBeforeAlign << " - " << StackAlign << " @ = "; } } else { // The address of return address is ESP + CurOffset, but we use .raSearch to // match MSVC. This seems to ask the debugger to subtract some combination // of LocalSize and SavedRegSize from ESP and grovel around in that memory // to find the address of a plausible return address. FuncOS << CFAVar << " .raSearch = "; } // Caller's $eip should be dereferenced CFA, and $esp should be CFA plus 4. FuncOS << "$eip " << CFAVar << " ^ = "; FuncOS << "$esp " << CFAVar << " 4 + = "; // Each saved register is stored at an unchanging negative CFA offset. for (RegSaveOffset RO : RegSaveOffsets) FuncOS << printFPOReg(MRI, RO.Reg) << ' ' << CFAVar << ' ' << RO.Offset << " - ^ = "; // Add it to the CV string table. CodeViewContext &CVCtx = OS.getContext().getCVContext(); unsigned FrameFuncStrTabOff = CVCtx.addToStringTable(FuncOS.str()).second; // MSVC has only ever been observed to emit a MaxStackSize of zero. unsigned MaxStackSize = 0; // The FrameData record format is: // ulittle32_t RvaStart; // ulittle32_t CodeSize; // ulittle32_t LocalSize; // ulittle32_t ParamsSize; // ulittle32_t MaxStackSize; // ulittle32_t FrameFunc; // String table offset // ulittle16_t PrologSize; // ulittle16_t SavedRegsSize; // ulittle32_t Flags; OS.emitAbsoluteSymbolDiff(Label, FPO->Begin, 4); // RvaStart OS.emitAbsoluteSymbolDiff(FPO->End, Label, 4); // CodeSize OS.emitInt32(LocalSize); OS.emitInt32(FPO->ParamsSize); OS.emitInt32(MaxStackSize); OS.emitInt32(FrameFuncStrTabOff); // FrameFunc OS.emitAbsoluteSymbolDiff(FPO->PrologueEnd, Label, 2); OS.emitInt16(SavedRegSize); OS.emitInt32(CurFlags); } /// Compute and emit the real CodeView FrameData subsection. bool X86WinCOFFTargetStreamer::emitFPOData(const MCSymbol *ProcSym, SMLoc L) { MCStreamer &OS = getStreamer(); MCContext &Ctx = OS.getContext(); auto I = AllFPOData.find(ProcSym); if (I == AllFPOData.end()) { Ctx.reportError(L, Twine("no FPO data found for symbol ") + ProcSym->getName()); return true; } const FPOData *FPO = I->second.get(); assert(FPO->Begin && FPO->End && FPO->PrologueEnd && "missing FPO label"); MCSymbol *FrameBegin = Ctx.createTempSymbol(), *FrameEnd = Ctx.createTempSymbol(); OS.emitInt32(unsigned(DebugSubsectionKind::FrameData)); OS.emitAbsoluteSymbolDiff(FrameEnd, FrameBegin, 4); OS.emitLabel(FrameBegin); // Start with the RVA of the function in question. OS.emitValue(MCSymbolRefExpr::create(FPO->Function, MCSymbolRefExpr::VK_COFF_IMGREL32, Ctx), 4); // Emit a sequence of FrameData records. FPOStateMachine FSM(FPO); FSM.emitFrameDataRecord(OS, FPO->Begin); for (const FPOInstruction &Inst : FPO->Instructions) { switch (Inst.Op) { case FPOInstruction::PushReg: FSM.CurOffset += 4; FSM.SavedRegSize += 4; FSM.RegSaveOffsets.push_back({Inst.RegOrOffset, FSM.CurOffset}); break; case FPOInstruction::SetFrame: FSM.FrameReg = Inst.RegOrOffset; FSM.FrameRegOff = FSM.CurOffset; break; case FPOInstruction::StackAlign: FSM.StackOffsetBeforeAlign = FSM.CurOffset; FSM.StackAlign = Inst.RegOrOffset; break; case FPOInstruction::StackAlloc: FSM.CurOffset += Inst.RegOrOffset; FSM.LocalSize += Inst.RegOrOffset; // No need to emit FrameData for stack allocations with a frame pointer. if (FSM.FrameReg) continue; break; } FSM.emitFrameDataRecord(OS, Inst.Label); } OS.emitValueToAlignment(Align(4), 0); OS.emitLabel(FrameEnd); return false; } MCTargetStreamer *llvm::createX86AsmTargetStreamer(MCStreamer &S, formatted_raw_ostream &OS, MCInstPrinter *InstPrinter) { // FIXME: This makes it so we textually assemble COFF directives on ELF. // That's kind of nonsensical. return new X86WinCOFFAsmTargetStreamer(S, OS, *InstPrinter); } MCTargetStreamer * llvm::createX86ObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI) { // No need to register a target streamer. if (!STI.getTargetTriple().isOSBinFormatCOFF()) return nullptr; // Registers itself to the MCStreamer. return new X86WinCOFFTargetStreamer(S); }