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