Coverage Report

Created: 2024-01-17 10:31

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