X86WinCOFFTargetStreamer.cpp revision 360784
1//===-- X86WinCOFFTargetStreamer.cpp ----------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "X86MCTargetDesc.h"
10#include "X86TargetStreamer.h"
11#include "llvm/DebugInfo/CodeView/CodeView.h"
12#include "llvm/MC/MCCodeView.h"
13#include "llvm/MC/MCContext.h"
14#include "llvm/MC/MCInstPrinter.h"
15#include "llvm/MC/MCRegisterInfo.h"
16#include "llvm/MC/MCSubtargetInfo.h"
17#include "llvm/Support/FormattedStream.h"
18
19using namespace llvm;
20using namespace llvm::codeview;
21
22namespace {
23/// Implements Windows x86-only directives for assembly emission.
24class X86WinCOFFAsmTargetStreamer : public X86TargetStreamer {
25  formatted_raw_ostream &OS;
26  MCInstPrinter &InstPrinter;
27
28public:
29  X86WinCOFFAsmTargetStreamer(MCStreamer &S, formatted_raw_ostream &OS,
30                              MCInstPrinter &InstPrinter)
31      : X86TargetStreamer(S), OS(OS), InstPrinter(InstPrinter) {}
32
33  bool emitFPOProc(const MCSymbol *ProcSym, unsigned ParamsSize,
34                   SMLoc L) override;
35  bool emitFPOEndPrologue(SMLoc L) override;
36  bool emitFPOEndProc(SMLoc L) override;
37  bool emitFPOData(const MCSymbol *ProcSym, SMLoc L) override;
38  bool emitFPOPushReg(unsigned Reg, SMLoc L) override;
39  bool emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) override;
40  bool emitFPOStackAlign(unsigned Align, SMLoc L) override;
41  bool emitFPOSetFrame(unsigned Reg, SMLoc L) override;
42};
43
44/// Represents a single FPO directive.
45struct FPOInstruction {
46  MCSymbol *Label;
47  enum Operation {
48    PushReg,
49    StackAlloc,
50    StackAlign,
51    SetFrame,
52  } Op;
53  unsigned RegOrOffset;
54};
55
56struct FPOData {
57  const MCSymbol *Function = nullptr;
58  MCSymbol *Begin = nullptr;
59  MCSymbol *PrologueEnd = nullptr;
60  MCSymbol *End = nullptr;
61  unsigned ParamsSize = 0;
62
63  SmallVector<FPOInstruction, 5> Instructions;
64};
65
66/// Implements Windows x86-only directives for object emission.
67class X86WinCOFFTargetStreamer : public X86TargetStreamer {
68  /// Map from function symbol to its FPO data.
69  DenseMap<const MCSymbol *, std::unique_ptr<FPOData>> AllFPOData;
70
71  /// Current FPO data created by .cv_fpo_proc.
72  std::unique_ptr<FPOData> CurFPOData;
73
74  bool haveOpenFPOData() { return !!CurFPOData; }
75
76  /// Diagnoses an error at L if we are not in an FPO prologue. Return true on
77  /// error.
78  bool checkInFPOPrologue(SMLoc L);
79
80  MCSymbol *emitFPOLabel();
81
82  MCContext &getContext() { return getStreamer().getContext(); }
83
84public:
85  X86WinCOFFTargetStreamer(MCStreamer &S) : X86TargetStreamer(S) {}
86
87  bool emitFPOProc(const MCSymbol *ProcSym, unsigned ParamsSize,
88                   SMLoc L) override;
89  bool emitFPOEndPrologue(SMLoc L) override;
90  bool emitFPOEndProc(SMLoc L) override;
91  bool emitFPOData(const MCSymbol *ProcSym, SMLoc L) override;
92  bool emitFPOPushReg(unsigned Reg, SMLoc L) override;
93  bool emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) override;
94  bool emitFPOStackAlign(unsigned Align, SMLoc L) override;
95  bool emitFPOSetFrame(unsigned Reg, SMLoc L) override;
96};
97} // end namespace
98
99bool X86WinCOFFAsmTargetStreamer::emitFPOProc(const MCSymbol *ProcSym,
100                                              unsigned ParamsSize, SMLoc L) {
101  OS << "\t.cv_fpo_proc\t";
102  ProcSym->print(OS, getStreamer().getContext().getAsmInfo());
103  OS << ' ' << ParamsSize << '\n';
104  return false;
105}
106
107bool X86WinCOFFAsmTargetStreamer::emitFPOEndPrologue(SMLoc L) {
108  OS << "\t.cv_fpo_endprologue\n";
109  return false;
110}
111
112bool X86WinCOFFAsmTargetStreamer::emitFPOEndProc(SMLoc L) {
113  OS << "\t.cv_fpo_endproc\n";
114  return false;
115}
116
117bool X86WinCOFFAsmTargetStreamer::emitFPOData(const MCSymbol *ProcSym,
118                                              SMLoc L) {
119  OS << "\t.cv_fpo_data\t";
120  ProcSym->print(OS, getStreamer().getContext().getAsmInfo());
121  OS << '\n';
122  return false;
123}
124
125bool X86WinCOFFAsmTargetStreamer::emitFPOPushReg(unsigned Reg, SMLoc L) {
126  OS << "\t.cv_fpo_pushreg\t";
127  InstPrinter.printRegName(OS, Reg);
128  OS << '\n';
129  return false;
130}
131
132bool X86WinCOFFAsmTargetStreamer::emitFPOStackAlloc(unsigned StackAlloc,
133                                                    SMLoc L) {
134  OS << "\t.cv_fpo_stackalloc\t" << StackAlloc << '\n';
135  return false;
136}
137
138bool X86WinCOFFAsmTargetStreamer::emitFPOStackAlign(unsigned Align, SMLoc L) {
139  OS << "\t.cv_fpo_stackalign\t" << Align << '\n';
140  return false;
141}
142
143bool X86WinCOFFAsmTargetStreamer::emitFPOSetFrame(unsigned Reg, SMLoc L) {
144  OS << "\t.cv_fpo_setframe\t";
145  InstPrinter.printRegName(OS, Reg);
146  OS << '\n';
147  return false;
148}
149
150bool X86WinCOFFTargetStreamer::checkInFPOPrologue(SMLoc L) {
151  if (!haveOpenFPOData() || CurFPOData->PrologueEnd) {
152    getContext().reportError(
153        L,
154        "directive must appear between .cv_fpo_proc and .cv_fpo_endprologue");
155    return true;
156  }
157  return false;
158}
159
160MCSymbol *X86WinCOFFTargetStreamer::emitFPOLabel() {
161  MCSymbol *Label = getContext().createTempSymbol("cfi", true);
162  getStreamer().EmitLabel(Label);
163  return Label;
164}
165
166bool X86WinCOFFTargetStreamer::emitFPOProc(const MCSymbol *ProcSym,
167                                           unsigned ParamsSize, SMLoc L) {
168  if (haveOpenFPOData()) {
169    getContext().reportError(
170        L, "opening new .cv_fpo_proc before closing previous frame");
171    return true;
172  }
173  CurFPOData = std::make_unique<FPOData>();
174  CurFPOData->Function = ProcSym;
175  CurFPOData->Begin = emitFPOLabel();
176  CurFPOData->ParamsSize = ParamsSize;
177  return false;
178}
179
180bool X86WinCOFFTargetStreamer::emitFPOEndProc(SMLoc L) {
181  if (!haveOpenFPOData()) {
182    getContext().reportError(L, ".cv_fpo_endproc must appear after .cv_proc");
183    return true;
184  }
185  if (!CurFPOData->PrologueEnd) {
186    // Complain if there were prologue setup instructions but no end prologue.
187    if (!CurFPOData->Instructions.empty()) {
188      getContext().reportError(L, "missing .cv_fpo_endprologue");
189      CurFPOData->Instructions.clear();
190    }
191
192    // Claim there is a zero-length prologue to make the label math work out
193    // later.
194    CurFPOData->PrologueEnd = CurFPOData->Begin;
195  }
196
197  CurFPOData->End = emitFPOLabel();
198  const MCSymbol *Fn = CurFPOData->Function;
199  AllFPOData.insert({Fn, std::move(CurFPOData)});
200  return false;
201}
202
203bool X86WinCOFFTargetStreamer::emitFPOSetFrame(unsigned Reg, SMLoc L) {
204  if (checkInFPOPrologue(L))
205    return true;
206  FPOInstruction Inst;
207  Inst.Label = emitFPOLabel();
208  Inst.Op = FPOInstruction::SetFrame;
209  Inst.RegOrOffset = Reg;
210  CurFPOData->Instructions.push_back(Inst);
211  return false;
212}
213
214bool X86WinCOFFTargetStreamer::emitFPOPushReg(unsigned Reg, SMLoc L) {
215  if (checkInFPOPrologue(L))
216    return true;
217  FPOInstruction Inst;
218  Inst.Label = emitFPOLabel();
219  Inst.Op = FPOInstruction::PushReg;
220  Inst.RegOrOffset = Reg;
221  CurFPOData->Instructions.push_back(Inst);
222  return false;
223}
224
225bool X86WinCOFFTargetStreamer::emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) {
226  if (checkInFPOPrologue(L))
227    return true;
228  FPOInstruction Inst;
229  Inst.Label = emitFPOLabel();
230  Inst.Op = FPOInstruction::StackAlloc;
231  Inst.RegOrOffset = StackAlloc;
232  CurFPOData->Instructions.push_back(Inst);
233  return false;
234}
235
236bool X86WinCOFFTargetStreamer::emitFPOStackAlign(unsigned Align, SMLoc L) {
237  if (checkInFPOPrologue(L))
238    return true;
239  if (!llvm::any_of(CurFPOData->Instructions, [](const FPOInstruction &Inst) {
240        return Inst.Op == FPOInstruction::SetFrame;
241      })) {
242    getContext().reportError(
243        L, "a frame register must be established before aligning the stack");
244    return true;
245  }
246  FPOInstruction Inst;
247  Inst.Label = emitFPOLabel();
248  Inst.Op = FPOInstruction::StackAlign;
249  Inst.RegOrOffset = Align;
250  CurFPOData->Instructions.push_back(Inst);
251  return false;
252}
253
254bool X86WinCOFFTargetStreamer::emitFPOEndPrologue(SMLoc L) {
255  if (checkInFPOPrologue(L))
256    return true;
257  CurFPOData->PrologueEnd = emitFPOLabel();
258  return false;
259}
260
261namespace {
262struct RegSaveOffset {
263  RegSaveOffset(unsigned Reg, unsigned Offset) : Reg(Reg), Offset(Offset) {}
264
265  unsigned Reg = 0;
266  unsigned Offset = 0;
267};
268
269struct FPOStateMachine {
270  explicit FPOStateMachine(const FPOData *FPO) : FPO(FPO) {}
271
272  const FPOData *FPO = nullptr;
273  unsigned FrameReg = 0;
274  unsigned FrameRegOff = 0;
275  unsigned CurOffset = 0;
276  unsigned LocalSize = 0;
277  unsigned SavedRegSize = 0;
278  unsigned StackOffsetBeforeAlign = 0;
279  unsigned StackAlign = 0;
280  unsigned Flags = 0; // FIXME: Set HasSEH / HasEH.
281
282  SmallString<128> FrameFunc;
283
284  SmallVector<RegSaveOffset, 4> RegSaveOffsets;
285
286  void emitFrameDataRecord(MCStreamer &OS, MCSymbol *Label);
287};
288} // end namespace
289
290static Printable printFPOReg(const MCRegisterInfo *MRI, unsigned LLVMReg) {
291  return Printable([MRI, LLVMReg](raw_ostream &OS) {
292    switch (LLVMReg) {
293    // MSVC only seems to emit symbolic register names for EIP, EBP, and ESP,
294    // but the format seems to support more than that, so we emit them.
295    case X86::EAX: OS << "$eax"; break;
296    case X86::EBX: OS << "$ebx"; break;
297    case X86::ECX: OS << "$ecx"; break;
298    case X86::EDX: OS << "$edx"; break;
299    case X86::EDI: OS << "$edi"; break;
300    case X86::ESI: OS << "$esi"; break;
301    case X86::ESP: OS << "$esp"; break;
302    case X86::EBP: OS << "$ebp"; break;
303    case X86::EIP: OS << "$eip"; break;
304    // Otherwise, get the codeview register number and print $N.
305    default:
306      OS << '$' << MRI->getCodeViewRegNum(LLVMReg);
307      break;
308    }
309  });
310}
311
312void FPOStateMachine::emitFrameDataRecord(MCStreamer &OS, MCSymbol *Label) {
313  unsigned CurFlags = Flags;
314  if (Label == FPO->Begin)
315    CurFlags |= FrameData::IsFunctionStart;
316
317  // Compute the new FrameFunc string.
318  FrameFunc.clear();
319  raw_svector_ostream FuncOS(FrameFunc);
320  const MCRegisterInfo *MRI = OS.getContext().getRegisterInfo();
321  assert((StackAlign == 0 || FrameReg != 0) &&
322         "cannot align stack without frame reg");
323  StringRef CFAVar = StackAlign == 0 ? "$T0" : "$T1";
324
325  if (FrameReg) {
326    // CFA is FrameReg + FrameRegOff.
327    FuncOS << CFAVar << ' ' << printFPOReg(MRI, FrameReg) << ' ' << FrameRegOff
328           << " + = ";
329
330    // Assign $T0, the VFRAME register, the value of ESP after it is aligned.
331    // Starting from the CFA, we subtract the size of all pushed registers, and
332    // align the result. While we don't store any CSRs in this area, $T0 is used
333    // by S_DEFRANGE_FRAMEPOINTER_REL records to find local variables.
334    if (StackAlign) {
335      FuncOS << "$T0 " << CFAVar << ' ' << StackOffsetBeforeAlign << " - "
336             << StackAlign << " @ = ";
337    }
338  } else {
339    // The address of return address is ESP + CurOffset, but we use .raSearch to
340    // match MSVC. This seems to ask the debugger to subtract some combination
341    // of LocalSize and SavedRegSize from ESP and grovel around in that memory
342    // to find the address of a plausible return address.
343    FuncOS << CFAVar << " .raSearch = ";
344  }
345
346  // Caller's $eip should be dereferenced CFA, and $esp should be CFA plus 4.
347  FuncOS << "$eip " << CFAVar << " ^ = ";
348  FuncOS << "$esp " << CFAVar << " 4 + = ";
349
350  // Each saved register is stored at an unchanging negative CFA offset.
351  for (RegSaveOffset RO : RegSaveOffsets)
352    FuncOS << printFPOReg(MRI, RO.Reg) << ' ' << CFAVar << ' ' << RO.Offset
353           << " - ^ = ";
354
355  // Add it to the CV string table.
356  CodeViewContext &CVCtx = OS.getContext().getCVContext();
357  unsigned FrameFuncStrTabOff = CVCtx.addToStringTable(FuncOS.str()).second;
358
359  // MSVC has only ever been observed to emit a MaxStackSize of zero.
360  unsigned MaxStackSize = 0;
361
362  // The FrameData record format is:
363  //   ulittle32_t RvaStart;
364  //   ulittle32_t CodeSize;
365  //   ulittle32_t LocalSize;
366  //   ulittle32_t ParamsSize;
367  //   ulittle32_t MaxStackSize;
368  //   ulittle32_t FrameFunc; // String table offset
369  //   ulittle16_t PrologSize;
370  //   ulittle16_t SavedRegsSize;
371  //   ulittle32_t Flags;
372
373  OS.emitAbsoluteSymbolDiff(Label, FPO->Begin, 4); // RvaStart
374  OS.emitAbsoluteSymbolDiff(FPO->End, Label, 4);   // CodeSize
375  OS.EmitIntValue(LocalSize, 4);
376  OS.EmitIntValue(FPO->ParamsSize, 4);
377  OS.EmitIntValue(MaxStackSize, 4);
378  OS.EmitIntValue(FrameFuncStrTabOff, 4); // FrameFunc
379  OS.emitAbsoluteSymbolDiff(FPO->PrologueEnd, Label, 2);
380  OS.EmitIntValue(SavedRegSize, 2);
381  OS.EmitIntValue(CurFlags, 4);
382}
383
384/// Compute and emit the real CodeView FrameData subsection.
385bool X86WinCOFFTargetStreamer::emitFPOData(const MCSymbol *ProcSym, SMLoc L) {
386  MCStreamer &OS = getStreamer();
387  MCContext &Ctx = OS.getContext();
388
389  auto I = AllFPOData.find(ProcSym);
390  if (I == AllFPOData.end()) {
391    Ctx.reportError(L, Twine("no FPO data found for symbol ") +
392                           ProcSym->getName());
393    return true;
394  }
395  const FPOData *FPO = I->second.get();
396  assert(FPO->Begin && FPO->End && FPO->PrologueEnd && "missing FPO label");
397
398  MCSymbol *FrameBegin = Ctx.createTempSymbol(),
399           *FrameEnd = Ctx.createTempSymbol();
400
401  OS.EmitIntValue(unsigned(DebugSubsectionKind::FrameData), 4);
402  OS.emitAbsoluteSymbolDiff(FrameEnd, FrameBegin, 4);
403  OS.EmitLabel(FrameBegin);
404
405  // Start with the RVA of the function in question.
406  OS.EmitValue(MCSymbolRefExpr::create(FPO->Function,
407                                       MCSymbolRefExpr::VK_COFF_IMGREL32, Ctx),
408               4);
409
410  // Emit a sequence of FrameData records.
411  FPOStateMachine FSM(FPO);
412
413  FSM.emitFrameDataRecord(OS, FPO->Begin);
414  for (const FPOInstruction &Inst : FPO->Instructions) {
415    switch (Inst.Op) {
416    case FPOInstruction::PushReg:
417      FSM.CurOffset += 4;
418      FSM.SavedRegSize += 4;
419      FSM.RegSaveOffsets.push_back({Inst.RegOrOffset, FSM.CurOffset});
420      break;
421    case FPOInstruction::SetFrame:
422      FSM.FrameReg = Inst.RegOrOffset;
423      FSM.FrameRegOff = FSM.CurOffset;
424      break;
425    case FPOInstruction::StackAlign:
426      FSM.StackOffsetBeforeAlign = FSM.CurOffset;
427      FSM.StackAlign = Inst.RegOrOffset;
428      break;
429    case FPOInstruction::StackAlloc:
430      FSM.CurOffset += Inst.RegOrOffset;
431      FSM.LocalSize += Inst.RegOrOffset;
432      // No need to emit FrameData for stack allocations with a frame pointer.
433      if (FSM.FrameReg)
434        continue;
435      break;
436    }
437    FSM.emitFrameDataRecord(OS, Inst.Label);
438  }
439
440  OS.EmitValueToAlignment(4, 0);
441  OS.EmitLabel(FrameEnd);
442  return false;
443}
444
445MCTargetStreamer *llvm::createX86AsmTargetStreamer(MCStreamer &S,
446                                                   formatted_raw_ostream &OS,
447                                                   MCInstPrinter *InstPrinter,
448                                                   bool IsVerboseAsm) {
449  // FIXME: This makes it so we textually assemble COFF directives on ELF.
450  // That's kind of nonsensical.
451  return new X86WinCOFFAsmTargetStreamer(S, OS, *InstPrinter);
452}
453
454MCTargetStreamer *
455llvm::createX86ObjectTargetStreamer(MCStreamer &S, const MCSubtargetInfo &STI) {
456  // No need to register a target streamer.
457  if (!STI.getTargetTriple().isOSBinFormatCOFF())
458    return nullptr;
459  // Registers itself to the MCStreamer.
460  return new X86WinCOFFTargetStreamer(S);
461}
462