1//===- COFFMasmParser.cpp - COFF MASM Assembly Parser ---------------------===//
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 "llvm/ADT/StringRef.h"
10#include "llvm/ADT/Twine.h"
11#include "llvm/BinaryFormat/COFF.h"
12#include "llvm/MC/MCAsmMacro.h"
13#include "llvm/MC/MCContext.h"
14#include "llvm/MC/MCParser/AsmLexer.h"
15#include "llvm/MC/MCParser/MCAsmParserExtension.h"
16#include "llvm/MC/MCParser/MCTargetAsmParser.h"
17#include "llvm/MC/MCSectionCOFF.h"
18#include "llvm/MC/MCStreamer.h"
19#include "llvm/MC/MCSymbolCOFF.h"
20#include "llvm/MC/SectionKind.h"
21#include "llvm/Support/SMLoc.h"
22#include <cstdint>
23
24using namespace llvm;
25
26namespace {
27
28class COFFMasmParser : public MCAsmParserExtension {
29 template <bool (COFFMasmParser::*HandlerMethod)(StringRef, SMLoc)>
30 void addDirectiveHandler(StringRef Directive) {
31 MCAsmParser::ExtensionDirectiveHandler Handler =
32 std::make_pair(this, HandleDirective<COFFMasmParser, HandlerMethod>);
33 getParser().addDirectiveHandler(Directive, Handler);
34 }
35
36 bool parseSectionSwitch(StringRef SectionName, unsigned Characteristics);
37
38 bool parseSectionSwitch(StringRef SectionName, unsigned Characteristics,
39 StringRef COMDATSymName, COFF::COMDATType Type,
40 Align Alignment);
41
42 bool parseDirectiveProc(StringRef, SMLoc);
43 bool parseDirectiveEndProc(StringRef, SMLoc);
44 bool parseDirectiveSegment(StringRef, SMLoc);
45 bool parseDirectiveSegmentEnd(StringRef, SMLoc);
46 bool parseDirectiveIncludelib(StringRef, SMLoc);
47 bool parseDirectiveOption(StringRef, SMLoc);
48
49 bool parseDirectiveAlias(StringRef, SMLoc);
50
51 bool parseSEHDirectiveAllocStack(StringRef, SMLoc);
52 bool parseSEHDirectiveEndProlog(StringRef, SMLoc);
53
54 bool IgnoreDirective(StringRef, SMLoc) {
55 while (!getLexer().is(K: AsmToken::EndOfStatement)) {
56 Lex();
57 }
58 return false;
59 }
60
61 void Initialize(MCAsmParser &Parser) override {
62 // Call the base implementation.
63 MCAsmParserExtension::Initialize(Parser);
64
65 // x64 directives
66 addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveAllocStack>(
67 Directive: ".allocstack");
68 addDirectiveHandler<&COFFMasmParser::parseSEHDirectiveEndProlog>(
69 Directive: ".endprolog");
70
71 // Code label directives
72 // label
73 // org
74
75 // Conditional control flow directives
76 // .break
77 // .continue
78 // .else
79 // .elseif
80 // .endif
81 // .endw
82 // .if
83 // .repeat
84 // .until
85 // .untilcxz
86 // .while
87
88 // Data allocation directives
89 // align
90 // even
91 // mmword
92 // tbyte
93 // xmmword
94 // ymmword
95
96 // Listing control directives
97 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".cref");
98 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".list");
99 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".listall");
100 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".listif");
101 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".listmacro");
102 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".listmacroall");
103 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".nocref");
104 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".nolist");
105 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".nolistif");
106 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".nolistmacro");
107 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: "page");
108 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: "subtitle");
109 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".tfcond");
110 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: "title");
111
112 // Macro directives
113 // goto
114
115 // Miscellaneous directives
116 addDirectiveHandler<&COFFMasmParser::parseDirectiveAlias>(Directive: "alias");
117 // assume
118 // .fpo
119 addDirectiveHandler<&COFFMasmParser::parseDirectiveIncludelib>(
120 Directive: "includelib");
121 addDirectiveHandler<&COFFMasmParser::parseDirectiveOption>(Directive: "option");
122 // popcontext
123 // pushcontext
124 // .safeseh
125
126 // Procedure directives
127 addDirectiveHandler<&COFFMasmParser::parseDirectiveEndProc>(Directive: "endp");
128 // invoke (32-bit only)
129 addDirectiveHandler<&COFFMasmParser::parseDirectiveProc>(Directive: "proc");
130 // proto
131
132 // Processor directives; all ignored
133 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".386");
134 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".386p");
135 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".387");
136 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".486");
137 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".486p");
138 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".586");
139 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".586p");
140 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".686");
141 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".686p");
142 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".k3d");
143 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".mmx");
144 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".xmm");
145
146 // Scope directives
147 // comm
148 // externdef
149
150 // Segment directives
151 // .alpha (32-bit only, order segments alphabetically)
152 // .dosseg (32-bit only, order segments in DOS convention)
153 // .seq (32-bit only, order segments sequentially)
154 addDirectiveHandler<&COFFMasmParser::parseDirectiveSegmentEnd>(Directive: "ends");
155 // group (32-bit only)
156 addDirectiveHandler<&COFFMasmParser::parseDirectiveSegment>(Directive: "segment");
157
158 // Simplified segment directives
159 addDirectiveHandler<&COFFMasmParser::parseSectionDirectiveCode>(Directive: ".code");
160 // .const
161 addDirectiveHandler<&COFFMasmParser::parseSectionDirectiveInitializedData>(
162 Directive: ".data");
163 addDirectiveHandler<
164 &COFFMasmParser::parseSectionDirectiveUninitializedData>(Directive: ".data?");
165 // .exit
166 // .fardata
167 // .fardata?
168 addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(Directive: ".model");
169 // .stack
170 // .startup
171
172 // String directives, written <name> <directive> <params>
173 // catstr (equivalent to <name> TEXTEQU <params>)
174 // instr (equivalent to <name> = @InStr(<params>))
175 // sizestr (equivalent to <name> = @SizeStr(<params>))
176 // substr (equivalent to <name> TEXTEQU @SubStr(<params>))
177
178 // Structure and record directives
179 // record
180 // typedef
181 }
182
183 bool parseSectionDirectiveCode(StringRef, SMLoc) {
184 return parseSectionSwitch(SectionName: ".text", Characteristics: COFF::IMAGE_SCN_CNT_CODE |
185 COFF::IMAGE_SCN_MEM_EXECUTE |
186 COFF::IMAGE_SCN_MEM_READ);
187 }
188
189 bool parseSectionDirectiveInitializedData(StringRef, SMLoc) {
190 return parseSectionSwitch(SectionName: ".data", Characteristics: COFF::IMAGE_SCN_CNT_INITIALIZED_DATA |
191 COFF::IMAGE_SCN_MEM_READ |
192 COFF::IMAGE_SCN_MEM_WRITE);
193 }
194
195 bool parseSectionDirectiveUninitializedData(StringRef, SMLoc) {
196 return parseSectionSwitch(SectionName: ".bss", Characteristics: COFF::IMAGE_SCN_CNT_UNINITIALIZED_DATA |
197 COFF::IMAGE_SCN_MEM_READ |
198 COFF::IMAGE_SCN_MEM_WRITE);
199 }
200
201 /// Stack of active procedure definitions.
202 SmallVector<StringRef, 1> CurrentProcedures;
203 SmallVector<bool, 1> CurrentProceduresFramed;
204
205public:
206 COFFMasmParser() = default;
207};
208
209} // end anonymous namespace.
210
211bool COFFMasmParser::parseSectionSwitch(StringRef SectionName,
212 unsigned Characteristics) {
213 return parseSectionSwitch(SectionName, Characteristics, COMDATSymName: "",
214 Type: (COFF::COMDATType)0, Alignment: Align(16));
215}
216
217bool COFFMasmParser::parseSectionSwitch(StringRef SectionName,
218 unsigned Characteristics,
219 StringRef COMDATSymName,
220 COFF::COMDATType Type,
221 Align Alignment) {
222 if (getLexer().isNot(K: AsmToken::EndOfStatement))
223 return TokError(Msg: "unexpected token in section switching directive");
224 Lex();
225
226 MCSection *Section = getContext().getCOFFSection(Section: SectionName, Characteristics,
227 COMDATSymName, Selection: Type);
228 Section->setAlignment(Alignment);
229 getStreamer().switchSection(Section);
230
231 return false;
232}
233
234bool COFFMasmParser::parseDirectiveSegment(StringRef Directive, SMLoc Loc) {
235 StringRef SegmentName;
236 if (!getLexer().is(K: AsmToken::Identifier))
237 return TokError(Msg: "expected identifier in directive");
238 SegmentName = getTok().getIdentifier();
239 Lex();
240
241 StringRef SectionName = SegmentName;
242 SmallVector<char, 247> SectionNameVector;
243
244 StringRef Class;
245 if (SegmentName == "_TEXT" || SegmentName.starts_with(Prefix: "_TEXT$")) {
246 if (SegmentName.size() == 5) {
247 SectionName = ".text";
248 } else {
249 SectionName =
250 (".text$" + SegmentName.substr(Start: 6)).toStringRef(Out&: SectionNameVector);
251 }
252 Class = "CODE";
253 }
254
255 // Parse all options to end of statement.
256 // Alignment defaults to PARA if unspecified.
257 int64_t Alignment = 16;
258 // Default flags are used only if no characteristics are set.
259 bool DefaultCharacteristics = true;
260 unsigned Flags = 0;
261 // "obsolete" according to the documentation, but still supported.
262 bool Readonly = false;
263 while (getLexer().isNot(K: AsmToken::EndOfStatement)) {
264 switch (getTok().getKind()) {
265 default:
266 break;
267 case AsmToken::String: {
268 // Class identifier; overrides Kind.
269 Class = getTok().getStringContents();
270 Lex();
271 break;
272 }
273 case AsmToken::Identifier: {
274 SMLoc KeywordLoc = getTok().getLoc();
275 StringRef Keyword;
276 if (getParser().parseIdentifier(Res&: Keyword)) {
277 llvm_unreachable("failed to parse identifier at an identifier token");
278 }
279 if (Keyword.equals_insensitive(RHS: "byte")) {
280 Alignment = 1;
281 } else if (Keyword.equals_insensitive(RHS: "word")) {
282 Alignment = 2;
283 } else if (Keyword.equals_insensitive(RHS: "dword")) {
284 Alignment = 4;
285 } else if (Keyword.equals_insensitive(RHS: "para")) {
286 Alignment = 16;
287 } else if (Keyword.equals_insensitive(RHS: "page")) {
288 Alignment = 256;
289 } else if (Keyword.equals_insensitive(RHS: "align")) {
290 if (getParser().parseToken(T: AsmToken::LParen) ||
291 getParser().parseIntToken(V&: Alignment,
292 ErrMsg: "Expected integer alignment") ||
293 getParser().parseToken(T: AsmToken::RParen)) {
294 return Error(L: getTok().getLoc(),
295 Msg: "Expected (n) following ALIGN in SEGMENT directive");
296 }
297 if (!isPowerOf2_64(Value: Alignment) || Alignment > 8192) {
298 return Error(L: KeywordLoc,
299 Msg: "ALIGN argument must be a power of 2 from 1 to 8192");
300 }
301 } else if (Keyword.equals_insensitive(RHS: "alias")) {
302 if (getParser().parseToken(T: AsmToken::LParen) ||
303 !getTok().is(K: AsmToken::String))
304 return Error(
305 L: getTok().getLoc(),
306 Msg: "Expected (string) following ALIAS in SEGMENT directive");
307 SectionName = getTok().getStringContents();
308 Lex();
309 if (getParser().parseToken(T: AsmToken::RParen))
310 return Error(
311 L: getTok().getLoc(),
312 Msg: "Expected (string) following ALIAS in SEGMENT directive");
313 } else if (Keyword.equals_insensitive(RHS: "readonly")) {
314 Readonly = true;
315 } else {
316 unsigned Characteristic =
317 StringSwitch<unsigned>(Keyword)
318 .CaseLower(S: "info", Value: COFF::IMAGE_SCN_LNK_INFO)
319 .CaseLower(S: "read", Value: COFF::IMAGE_SCN_MEM_READ)
320 .CaseLower(S: "write", Value: COFF::IMAGE_SCN_MEM_WRITE)
321 .CaseLower(S: "execute", Value: COFF::IMAGE_SCN_MEM_EXECUTE)
322 .CaseLower(S: "shared", Value: COFF::IMAGE_SCN_MEM_SHARED)
323 .CaseLower(S: "nopage", Value: COFF::IMAGE_SCN_MEM_NOT_PAGED)
324 .CaseLower(S: "nocache", Value: COFF::IMAGE_SCN_MEM_NOT_CACHED)
325 .CaseLower(S: "discard", Value: COFF::IMAGE_SCN_MEM_DISCARDABLE)
326 .Default(Value: -1);
327 if (Characteristic == static_cast<unsigned>(-1)) {
328 return Error(L: KeywordLoc,
329 Msg: "Expected characteristic in SEGMENT directive; found '" +
330 Keyword + "'");
331 }
332 Flags |= Characteristic;
333 DefaultCharacteristics = false;
334 }
335 }
336 }
337 }
338
339 SectionKind Kind = StringSwitch<SectionKind>(Class)
340 .CaseLower(S: "data", Value: SectionKind::getData())
341 .CaseLower(S: "code", Value: SectionKind::getText())
342 .CaseLower(S: "const", Value: SectionKind::getReadOnly())
343 .Default(Value: SectionKind::getData());
344 if (Kind.isText()) {
345 if (DefaultCharacteristics) {
346 Flags |= COFF::IMAGE_SCN_MEM_EXECUTE | COFF::IMAGE_SCN_MEM_READ;
347 }
348 Flags |= COFF::IMAGE_SCN_CNT_CODE;
349 } else {
350 if (DefaultCharacteristics) {
351 Flags |= COFF::IMAGE_SCN_MEM_READ | COFF::IMAGE_SCN_MEM_WRITE;
352 }
353 Flags |= COFF::IMAGE_SCN_CNT_INITIALIZED_DATA;
354 }
355 if (Readonly) {
356 Flags &= ~COFF::IMAGE_SCN_MEM_WRITE;
357 }
358
359 MCSection *Section = getContext().getCOFFSection(Section: SectionName, Characteristics: Flags, COMDATSymName: "",
360 Selection: (COFF::COMDATType)(0));
361 if (Alignment != 0) {
362 Section->setAlignment(Align(Alignment));
363 }
364 getStreamer().switchSection(Section);
365 return false;
366}
367
368/// parseDirectiveSegmentEnd
369/// ::= identifier "ends"
370bool COFFMasmParser::parseDirectiveSegmentEnd(StringRef Directive, SMLoc Loc) {
371 StringRef SegmentName;
372 if (!getLexer().is(K: AsmToken::Identifier))
373 return TokError(Msg: "expected identifier in directive");
374 SegmentName = getTok().getIdentifier();
375
376 // Ignore; no action necessary.
377 Lex();
378 return false;
379}
380
381/// parseDirectiveIncludelib
382/// ::= "includelib" identifier
383bool COFFMasmParser::parseDirectiveIncludelib(StringRef Directive, SMLoc Loc) {
384 StringRef Lib;
385 if (getParser().parseIdentifier(Res&: Lib))
386 return TokError(Msg: "expected identifier in includelib directive");
387
388 unsigned Flags = COFF::IMAGE_SCN_MEM_PRELOAD | COFF::IMAGE_SCN_MEM_16BIT;
389 getStreamer().pushSection();
390 getStreamer().switchSection(Section: getContext().getCOFFSection(
391 Section: ".drectve", Characteristics: Flags, COMDATSymName: "", Selection: (COFF::COMDATType)(0)));
392 getStreamer().emitBytes(Data: "/DEFAULTLIB:");
393 getStreamer().emitBytes(Data: Lib);
394 getStreamer().emitBytes(Data: " ");
395 getStreamer().popSection();
396 return false;
397}
398
399/// parseDirectiveOption
400/// ::= "option" option-list
401bool COFFMasmParser::parseDirectiveOption(StringRef Directive, SMLoc Loc) {
402 auto parseOption = [&]() -> bool {
403 StringRef Option;
404 if (getParser().parseIdentifier(Res&: Option))
405 return TokError(Msg: "expected identifier for option name");
406 if (Option.equals_insensitive(RHS: "prologue")) {
407 StringRef MacroId;
408 if (parseToken(T: AsmToken::Colon) || getParser().parseIdentifier(Res&: MacroId))
409 return TokError(Msg: "expected :macroId after OPTION PROLOGUE");
410 if (MacroId.equals_insensitive(RHS: "none")) {
411 // Since we currently don't implement prologues/epilogues, NONE is our
412 // default.
413 return false;
414 }
415 return TokError(Msg: "OPTION PROLOGUE is currently unsupported");
416 }
417 if (Option.equals_insensitive(RHS: "epilogue")) {
418 StringRef MacroId;
419 if (parseToken(T: AsmToken::Colon) || getParser().parseIdentifier(Res&: MacroId))
420 return TokError(Msg: "expected :macroId after OPTION EPILOGUE");
421 if (MacroId.equals_insensitive(RHS: "none")) {
422 // Since we currently don't implement prologues/epilogues, NONE is our
423 // default.
424 return false;
425 }
426 return TokError(Msg: "OPTION EPILOGUE is currently unsupported");
427 }
428 return TokError(Msg: "OPTION '" + Option + "' is currently unsupported");
429 };
430
431 if (parseMany(parseOne: parseOption))
432 return addErrorSuffix(Suffix: " in OPTION directive");
433 return false;
434}
435
436/// parseDirectiveProc
437/// TODO(epastor): Implement parameters and other attributes.
438/// ::= label "proc" [[distance]]
439/// statements
440/// label "endproc"
441bool COFFMasmParser::parseDirectiveProc(StringRef Directive, SMLoc Loc) {
442 if (!getStreamer().getCurrentFragment())
443 return Error(L: getTok().getLoc(), Msg: "expected section directive");
444
445 MCSymbol *Sym;
446 if (getParser().parseSymbol(Res&: Sym))
447 return Error(L: Loc, Msg: "expected identifier for procedure");
448 if (getLexer().is(K: AsmToken::Identifier)) {
449 StringRef nextVal = getTok().getString();
450 SMLoc nextLoc = getTok().getLoc();
451 if (nextVal.equals_insensitive(RHS: "far")) {
452 // TODO(epastor): Handle far procedure definitions.
453 Lex();
454 return Error(L: nextLoc, Msg: "far procedure definitions not yet supported");
455 } else if (nextVal.equals_insensitive(RHS: "near")) {
456 Lex();
457 nextVal = getTok().getString();
458 nextLoc = getTok().getLoc();
459 }
460 }
461
462 // Define symbol as simple external function
463 auto *COFFSym = static_cast<MCSymbolCOFF *>(Sym);
464 COFFSym->setExternal(true);
465 COFFSym->setType(COFF::IMAGE_SYM_DTYPE_FUNCTION
466 << COFF::SCT_COMPLEX_TYPE_SHIFT);
467
468 bool Framed = false;
469 if (getLexer().is(K: AsmToken::Identifier) &&
470 getTok().getString().equals_insensitive(RHS: "frame")) {
471 Lex();
472 Framed = true;
473 getStreamer().emitWinCFIStartProc(Symbol: Sym, Loc);
474 }
475 getStreamer().emitLabel(Symbol: Sym, Loc);
476
477 CurrentProcedures.push_back(Elt: Sym->getName());
478 CurrentProceduresFramed.push_back(Elt: Framed);
479 return false;
480}
481bool COFFMasmParser::parseDirectiveEndProc(StringRef Directive, SMLoc Loc) {
482 StringRef Label;
483 SMLoc LabelLoc = getTok().getLoc();
484 if (getParser().parseIdentifier(Res&: Label))
485 return Error(L: LabelLoc, Msg: "expected identifier for procedure end");
486
487 if (CurrentProcedures.empty())
488 return Error(L: Loc, Msg: "endp outside of procedure block");
489 else if (!CurrentProcedures.back().equals_insensitive(RHS: Label))
490 return Error(L: LabelLoc, Msg: "endp does not match current procedure '" +
491 CurrentProcedures.back() + "'");
492
493 if (CurrentProceduresFramed.back()) {
494 getStreamer().emitWinCFIEndProc(Loc);
495 }
496 CurrentProcedures.pop_back();
497 CurrentProceduresFramed.pop_back();
498 return false;
499}
500
501bool COFFMasmParser::parseDirectiveAlias(StringRef Directive, SMLoc Loc) {
502 std::string AliasName, ActualName;
503 if (getTok().isNot(K: AsmToken::Less) ||
504 getParser().parseAngleBracketString(Data&: AliasName))
505 return Error(L: getTok().getLoc(), Msg: "expected <aliasName>");
506 if (getParser().parseToken(T: AsmToken::Equal))
507 return addErrorSuffix(Suffix: " in " + Directive + " directive");
508 if (getTok().isNot(K: AsmToken::Less) ||
509 getParser().parseAngleBracketString(Data&: ActualName))
510 return Error(L: getTok().getLoc(), Msg: "expected <actualName>");
511
512 MCSymbol *Alias = getContext().parseSymbol(Name: AliasName);
513 MCSymbol *Actual = getContext().parseSymbol(Name: ActualName);
514
515 getStreamer().emitWeakReference(Alias, Symbol: Actual);
516
517 return false;
518}
519
520bool COFFMasmParser::parseSEHDirectiveAllocStack(StringRef Directive,
521 SMLoc Loc) {
522 int64_t Size;
523 SMLoc SizeLoc = getTok().getLoc();
524 if (getParser().parseAbsoluteExpression(Res&: Size))
525 return Error(L: SizeLoc, Msg: "expected integer size");
526 if (Size % 8 != 0)
527 return Error(L: SizeLoc, Msg: "stack size must be a multiple of 8");
528 getStreamer().emitWinCFIAllocStack(Size: static_cast<unsigned>(Size), Loc);
529 return false;
530}
531
532bool COFFMasmParser::parseSEHDirectiveEndProlog(StringRef Directive,
533 SMLoc Loc) {
534 getStreamer().emitWinCFIEndProlog(Loc);
535 return false;
536}
537
538MCAsmParserExtension *llvm::createCOFFMasmParser() {
539 return new COFFMasmParser;
540}
541