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 | 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 | 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 | MCContext &getContext() { return getStreamer().getContext(); } |
84 | |
85 | public: |
86 | 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 | unsigned ParamsSize, SMLoc L) { |
102 | OS << "\t.cv_fpo_proc\t" ; |
103 | ProcSym->print(OS, MAI: getStreamer().getContext().getAsmInfo()); |
104 | OS << ' ' << ParamsSize << '\n'; |
105 | return false; |
106 | } |
107 | |
108 | bool X86WinCOFFAsmTargetStreamer::emitFPOEndPrologue(SMLoc L) { |
109 | OS << "\t.cv_fpo_endprologue\n" ; |
110 | return false; |
111 | } |
112 | |
113 | bool X86WinCOFFAsmTargetStreamer::emitFPOEndProc(SMLoc L) { |
114 | OS << "\t.cv_fpo_endproc\n" ; |
115 | return false; |
116 | } |
117 | |
118 | bool X86WinCOFFAsmTargetStreamer::emitFPOData(const MCSymbol *ProcSym, |
119 | SMLoc L) { |
120 | OS << "\t.cv_fpo_data\t" ; |
121 | ProcSym->print(OS, MAI: getStreamer().getContext().getAsmInfo()); |
122 | OS << '\n'; |
123 | return false; |
124 | } |
125 | |
126 | bool X86WinCOFFAsmTargetStreamer::emitFPOPushReg(unsigned Reg, SMLoc L) { |
127 | OS << "\t.cv_fpo_pushreg\t" ; |
128 | InstPrinter.printRegName(OS, Reg); |
129 | OS << '\n'; |
130 | return false; |
131 | } |
132 | |
133 | bool X86WinCOFFAsmTargetStreamer::emitFPOStackAlloc(unsigned StackAlloc, |
134 | SMLoc L) { |
135 | OS << "\t.cv_fpo_stackalloc\t" << StackAlloc << '\n'; |
136 | return false; |
137 | } |
138 | |
139 | bool X86WinCOFFAsmTargetStreamer::emitFPOStackAlign(unsigned Align, SMLoc L) { |
140 | OS << "\t.cv_fpo_stackalign\t" << Align << '\n'; |
141 | return false; |
142 | } |
143 | |
144 | bool X86WinCOFFAsmTargetStreamer::emitFPOSetFrame(unsigned Reg, SMLoc L) { |
145 | OS << "\t.cv_fpo_setframe\t" ; |
146 | InstPrinter.printRegName(OS, Reg); |
147 | OS << '\n'; |
148 | return false; |
149 | } |
150 | |
151 | bool X86WinCOFFTargetStreamer::checkInFPOPrologue(SMLoc L) { |
152 | if (!haveOpenFPOData() || CurFPOData->PrologueEnd) { |
153 | getContext().reportError( |
154 | L, |
155 | Msg: "directive must appear between .cv_fpo_proc and .cv_fpo_endprologue" ); |
156 | return true; |
157 | } |
158 | return false; |
159 | } |
160 | |
161 | MCSymbol *X86WinCOFFTargetStreamer::emitFPOLabel() { |
162 | MCSymbol *Label = getContext().createTempSymbol(Name: "cfi" , AlwaysAddSuffix: true); |
163 | getStreamer().emitLabel(Symbol: Label); |
164 | return Label; |
165 | } |
166 | |
167 | bool X86WinCOFFTargetStreamer::emitFPOProc(const MCSymbol *ProcSym, |
168 | unsigned ParamsSize, SMLoc L) { |
169 | if (haveOpenFPOData()) { |
170 | getContext().reportError( |
171 | L, Msg: "opening new .cv_fpo_proc before closing previous frame" ); |
172 | return true; |
173 | } |
174 | CurFPOData = std::make_unique<FPOData>(); |
175 | CurFPOData->Function = ProcSym; |
176 | CurFPOData->Begin = emitFPOLabel(); |
177 | CurFPOData->ParamsSize = ParamsSize; |
178 | return false; |
179 | } |
180 | |
181 | bool X86WinCOFFTargetStreamer::emitFPOEndProc(SMLoc L) { |
182 | if (!haveOpenFPOData()) { |
183 | getContext().reportError(L, Msg: ".cv_fpo_endproc must appear after .cv_proc" ); |
184 | return true; |
185 | } |
186 | if (!CurFPOData->PrologueEnd) { |
187 | // Complain if there were prologue setup instructions but no end prologue. |
188 | if (!CurFPOData->Instructions.empty()) { |
189 | getContext().reportError(L, Msg: "missing .cv_fpo_endprologue" ); |
190 | CurFPOData->Instructions.clear(); |
191 | } |
192 | |
193 | // Claim there is a zero-length prologue to make the label math work out |
194 | // later. |
195 | CurFPOData->PrologueEnd = CurFPOData->Begin; |
196 | } |
197 | |
198 | CurFPOData->End = emitFPOLabel(); |
199 | const MCSymbol *Fn = CurFPOData->Function; |
200 | AllFPOData.insert(KV: {Fn, std::move(CurFPOData)}); |
201 | return false; |
202 | } |
203 | |
204 | bool X86WinCOFFTargetStreamer::emitFPOSetFrame(unsigned Reg, SMLoc L) { |
205 | if (checkInFPOPrologue(L)) |
206 | return true; |
207 | FPOInstruction Inst; |
208 | Inst.Label = emitFPOLabel(); |
209 | Inst.Op = FPOInstruction::SetFrame; |
210 | Inst.RegOrOffset = Reg; |
211 | CurFPOData->Instructions.push_back(Elt: Inst); |
212 | return false; |
213 | } |
214 | |
215 | bool X86WinCOFFTargetStreamer::emitFPOPushReg(unsigned Reg, SMLoc L) { |
216 | if (checkInFPOPrologue(L)) |
217 | return true; |
218 | FPOInstruction Inst; |
219 | Inst.Label = emitFPOLabel(); |
220 | Inst.Op = FPOInstruction::PushReg; |
221 | Inst.RegOrOffset = Reg; |
222 | CurFPOData->Instructions.push_back(Elt: Inst); |
223 | return false; |
224 | } |
225 | |
226 | bool X86WinCOFFTargetStreamer::emitFPOStackAlloc(unsigned StackAlloc, SMLoc L) { |
227 | if (checkInFPOPrologue(L)) |
228 | return true; |
229 | FPOInstruction Inst; |
230 | Inst.Label = emitFPOLabel(); |
231 | Inst.Op = FPOInstruction::StackAlloc; |
232 | Inst.RegOrOffset = StackAlloc; |
233 | CurFPOData->Instructions.push_back(Elt: Inst); |
234 | return false; |
235 | } |
236 | |
237 | bool X86WinCOFFTargetStreamer::emitFPOStackAlign(unsigned Align, SMLoc L) { |
238 | if (checkInFPOPrologue(L)) |
239 | return true; |
240 | if (llvm::none_of(Range&: CurFPOData->Instructions, P: [](const FPOInstruction &Inst) { |
241 | return Inst.Op == FPOInstruction::SetFrame; |
242 | })) { |
243 | getContext().reportError( |
244 | L, Msg: "a frame register must be established before aligning the stack" ); |
245 | return true; |
246 | } |
247 | FPOInstruction Inst; |
248 | Inst.Label = emitFPOLabel(); |
249 | Inst.Op = FPOInstruction::StackAlign; |
250 | Inst.RegOrOffset = Align; |
251 | CurFPOData->Instructions.push_back(Elt: Inst); |
252 | return false; |
253 | } |
254 | |
255 | bool X86WinCOFFTargetStreamer::emitFPOEndPrologue(SMLoc L) { |
256 | if (checkInFPOPrologue(L)) |
257 | return true; |
258 | CurFPOData->PrologueEnd = emitFPOLabel(); |
259 | return false; |
260 | } |
261 | |
262 | namespace { |
263 | struct RegSaveOffset { |
264 | RegSaveOffset(unsigned Reg, unsigned Offset) : Reg(Reg), Offset(Offset) {} |
265 | |
266 | unsigned Reg = 0; |
267 | unsigned Offset = 0; |
268 | }; |
269 | |
270 | struct FPOStateMachine { |
271 | 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 | static Printable printFPOReg(const MCRegisterInfo *MRI, unsigned LLVMReg) { |
292 | return Printable([MRI, LLVMReg](raw_ostream &OS) { |
293 | 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 | case X86::EAX: OS << "$eax" ; break; |
297 | case X86::EBX: OS << "$ebx" ; break; |
298 | case X86::ECX: OS << "$ecx" ; break; |
299 | case X86::EDX: OS << "$edx" ; break; |
300 | case X86::EDI: OS << "$edi" ; break; |
301 | case X86::ESI: OS << "$esi" ; break; |
302 | case X86::ESP: OS << "$esp" ; break; |
303 | case X86::EBP: OS << "$ebp" ; break; |
304 | case X86::EIP: OS << "$eip" ; break; |
305 | // Otherwise, get the codeview register number and print $N. |
306 | default: |
307 | OS << '$' << MRI->getCodeViewRegNum(RegNum: LLVMReg); |
308 | break; |
309 | } |
310 | }); |
311 | } |
312 | |
313 | void FPOStateMachine::emitFrameDataRecord(MCStreamer &OS, MCSymbol *Label) { |
314 | unsigned CurFlags = Flags; |
315 | if (Label == FPO->Begin) |
316 | CurFlags |= FrameData::IsFunctionStart; |
317 | |
318 | // Compute the new FrameFunc string. |
319 | FrameFunc.clear(); |
320 | raw_svector_ostream FuncOS(FrameFunc); |
321 | const MCRegisterInfo *MRI = OS.getContext().getRegisterInfo(); |
322 | assert((StackAlign == 0 || FrameReg != 0) && |
323 | "cannot align stack without frame reg" ); |
324 | StringRef CFAVar = StackAlign == 0 ? "$T0" : "$T1" ; |
325 | |
326 | if (FrameReg) { |
327 | // CFA is FrameReg + FrameRegOff. |
328 | FuncOS << CFAVar << ' ' << printFPOReg(MRI, LLVMReg: FrameReg) << ' ' << FrameRegOff |
329 | << " + = " ; |
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 | if (StackAlign) { |
336 | FuncOS << "$T0 " << CFAVar << ' ' << StackOffsetBeforeAlign << " - " |
337 | << StackAlign << " @ = " ; |
338 | } |
339 | } 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 | FuncOS << CFAVar << " .raSearch = " ; |
345 | } |
346 | |
347 | // Caller's $eip should be dereferenced CFA, and $esp should be CFA plus 4. |
348 | FuncOS << "$eip " << CFAVar << " ^ = " ; |
349 | FuncOS << "$esp " << CFAVar << " 4 + = " ; |
350 | |
351 | // Each saved register is stored at an unchanging negative CFA offset. |
352 | for (RegSaveOffset RO : RegSaveOffsets) |
353 | FuncOS << printFPOReg(MRI, LLVMReg: RO.Reg) << ' ' << CFAVar << ' ' << RO.Offset |
354 | << " - ^ = " ; |
355 | |
356 | // Add it to the CV string table. |
357 | CodeViewContext &CVCtx = OS.getContext().getCVContext(); |
358 | unsigned FrameFuncStrTabOff = CVCtx.addToStringTable(S: FuncOS.str()).second; |
359 | |
360 | // MSVC has only ever been observed to emit a MaxStackSize of zero. |
361 | 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 | OS.emitAbsoluteSymbolDiff(Hi: Label, Lo: FPO->Begin, Size: 4); // RvaStart |
375 | OS.emitAbsoluteSymbolDiff(Hi: FPO->End, Lo: Label, Size: 4); // CodeSize |
376 | OS.emitInt32(Value: LocalSize); |
377 | OS.emitInt32(Value: FPO->ParamsSize); |
378 | OS.emitInt32(Value: MaxStackSize); |
379 | OS.emitInt32(Value: FrameFuncStrTabOff); // FrameFunc |
380 | OS.emitAbsoluteSymbolDiff(Hi: FPO->PrologueEnd, Lo: Label, Size: 2); |
381 | OS.emitInt16(Value: SavedRegSize); |
382 | OS.emitInt32(Value: CurFlags); |
383 | } |
384 | |
385 | /// Compute and emit the real CodeView FrameData subsection. |
386 | bool X86WinCOFFTargetStreamer::emitFPOData(const MCSymbol *ProcSym, SMLoc L) { |
387 | MCStreamer &OS = getStreamer(); |
388 | MCContext &Ctx = OS.getContext(); |
389 | |
390 | auto I = AllFPOData.find(Val: ProcSym); |
391 | if (I == AllFPOData.end()) { |
392 | Ctx.reportError(L, Msg: Twine("no FPO data found for symbol " ) + |
393 | ProcSym->getName()); |
394 | return true; |
395 | } |
396 | const FPOData *FPO = I->second.get(); |
397 | assert(FPO->Begin && FPO->End && FPO->PrologueEnd && "missing FPO label" ); |
398 | |
399 | MCSymbol *FrameBegin = Ctx.createTempSymbol(), |
400 | *FrameEnd = Ctx.createTempSymbol(); |
401 | |
402 | OS.emitInt32(Value: unsigned(DebugSubsectionKind::FrameData)); |
403 | OS.emitAbsoluteSymbolDiff(Hi: FrameEnd, Lo: FrameBegin, Size: 4); |
404 | OS.emitLabel(Symbol: FrameBegin); |
405 | |
406 | // Start with the RVA of the function in question. |
407 | OS.emitValue(Value: MCSymbolRefExpr::create(Symbol: FPO->Function, |
408 | Kind: MCSymbolRefExpr::VK_COFF_IMGREL32, Ctx), |
409 | Size: 4); |
410 | |
411 | // Emit a sequence of FrameData records. |
412 | FPOStateMachine FSM(FPO); |
413 | |
414 | FSM.emitFrameDataRecord(OS, Label: FPO->Begin); |
415 | for (const FPOInstruction &Inst : FPO->Instructions) { |
416 | switch (Inst.Op) { |
417 | case FPOInstruction::PushReg: |
418 | FSM.CurOffset += 4; |
419 | FSM.SavedRegSize += 4; |
420 | FSM.RegSaveOffsets.push_back(Elt: {Inst.RegOrOffset, FSM.CurOffset}); |
421 | break; |
422 | case FPOInstruction::SetFrame: |
423 | FSM.FrameReg = Inst.RegOrOffset; |
424 | FSM.FrameRegOff = FSM.CurOffset; |
425 | break; |
426 | case FPOInstruction::StackAlign: |
427 | FSM.StackOffsetBeforeAlign = FSM.CurOffset; |
428 | FSM.StackAlign = Inst.RegOrOffset; |
429 | break; |
430 | case FPOInstruction::StackAlloc: |
431 | FSM.CurOffset += Inst.RegOrOffset; |
432 | FSM.LocalSize += Inst.RegOrOffset; |
433 | // No need to emit FrameData for stack allocations with a frame pointer. |
434 | if (FSM.FrameReg) |
435 | continue; |
436 | break; |
437 | } |
438 | FSM.emitFrameDataRecord(OS, Label: Inst.Label); |
439 | } |
440 | |
441 | OS.emitValueToAlignment(Alignment: Align(4), Value: 0); |
442 | OS.emitLabel(Symbol: FrameEnd); |
443 | return false; |
444 | } |
445 | |
446 | MCTargetStreamer *llvm::createX86AsmTargetStreamer(MCStreamer &S, |
447 | formatted_raw_ostream &OS, |
448 | MCInstPrinter *InstPrinter) { |
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 | |
454 | MCTargetStreamer * |
455 | llvm::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 | |