| //===- COFFMasmParser.cpp - COFF MASM Assembly Parser ---------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/ADT/Twine.h" |
| #include "llvm/BinaryFormat/COFF.h" |
| #include "llvm/MC/MCAsmMacro.h" |
| #include "llvm/MC/MCContext.h" |
| #include "llvm/MC/MCParser/MCAsmLexer.h" |
| #include "llvm/MC/MCParser/MCAsmParserExtension.h" |
| #include "llvm/MC/MCParser/MCTargetAsmParser.h" |
| #include "llvm/MC/MCSectionCOFF.h" |
| #include "llvm/MC/MCStreamer.h" |
| #include "llvm/MC/MCSymbolCOFF.h" |
| #include "llvm/MC/SectionKind.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/SMLoc.h" |
| #include <cstdint> |
| #include <utility> |
| |
| using namespace llvm; |
| |
| namespace { |
| |
| class COFFMasmParser : public MCAsmParserExtension { |
| template <bool (COFFMasmParser::*HandlerMethod)(StringRef, SMLoc)> |
| void addDirectiveHandler(StringRef Directive) { |
| MCAsmParser::ExtensionDirectiveHandler Handler = |
| std::make_pair(this, HandleDirective<COFFMasmParser, HandlerMethod>); |
| getParser().addDirectiveHandler(Directive, Handler); |
| } |
| |
| bool ParseSectionSwitch(StringRef SectionName, unsigned Characteristics, |
| SectionKind Kind); |
| |
| bool ParseSectionSwitch(StringRef SectionName, unsigned Characteristics, |
| SectionKind Kind, StringRef COMDATSymName, |
| COFF::COMDATType Type, Align Alignment); |
| |
| bool ParseDirectiveProc(StringRef, SMLoc); |
| bool ParseDirectiveEndProc(StringRef, SMLoc); |
| bool ParseDirectiveSegment(StringRef, SMLoc); |
| bool ParseDirectiveSegmentEnd(StringRef, SMLoc); |
| bool ParseDirectiveIncludelib(StringRef, SMLoc); |
| bool ParseDirectiveOption(StringRef, SMLoc); |
| |
| bool ParseDirectiveAlias(StringRef, SMLoc); |
| |
| bool ParseSEHDirectiveAllocStack(StringRef, SMLoc); |
| bool ParseSEHDirectiveEndProlog(StringRef, SMLoc); |
| |
| bool IgnoreDirective(StringRef, SMLoc) { |
| while (!getLexer().is(AsmToken::EndOfStatement)) { |
| Lex(); |
| } |
| return false; |
| } |
| |
| void Initialize(MCAsmParser &Parser) override { |
| // Call the base implementation. |
| MCAsmParserExtension::Initialize(Parser); |
| |
| // x64 directives |
| addDirectiveHandler<&COFFMasmParser::ParseSEHDirectiveAllocStack>( |
| ".allocstack"); |
| addDirectiveHandler<&COFFMasmParser::ParseSEHDirectiveEndProlog>( |
| ".endprolog"); |
| |
| // Code label directives |
| // label |
| // org |
| |
| // Conditional control flow directives |
| // .break |
| // .continue |
| // .else |
| // .elseif |
| // .endif |
| // .endw |
| // .if |
| // .repeat |
| // .until |
| // .untilcxz |
| // .while |
| |
| // Data allocation directives |
| // align |
| // even |
| // mmword |
| // tbyte |
| // xmmword |
| // ymmword |
| |
| // Listing control directives |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".cref"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".list"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".listall"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".listif"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".listmacro"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".listmacroall"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".nocref"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".nolist"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".nolistif"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".nolistmacro"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>("page"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>("subtitle"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".tfcond"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>("title"); |
| |
| // Macro directives |
| // goto |
| |
| // Miscellaneous directives |
| addDirectiveHandler<&COFFMasmParser::ParseDirectiveAlias>("alias"); |
| // assume |
| // .fpo |
| addDirectiveHandler<&COFFMasmParser::ParseDirectiveIncludelib>( |
| "includelib"); |
| addDirectiveHandler<&COFFMasmParser::ParseDirectiveOption>("option"); |
| // popcontext |
| // pushcontext |
| // .safeseh |
| |
| // Procedure directives |
| addDirectiveHandler<&COFFMasmParser::ParseDirectiveEndProc>("endp"); |
| // invoke (32-bit only) |
| addDirectiveHandler<&COFFMasmParser::ParseDirectiveProc>("proc"); |
| // proto |
| |
| // Processor directives; all ignored |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".386"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".386p"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".387"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".486"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".486p"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".586"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".586p"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".686"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".686p"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".k3d"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".mmx"); |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".xmm"); |
| |
| // Scope directives |
| // comm |
| // externdef |
| |
| // Segment directives |
| // .alpha (32-bit only, order segments alphabetically) |
| // .dosseg (32-bit only, order segments in DOS convention) |
| // .seq (32-bit only, order segments sequentially) |
| addDirectiveHandler<&COFFMasmParser::ParseDirectiveSegmentEnd>("ends"); |
| // group (32-bit only) |
| addDirectiveHandler<&COFFMasmParser::ParseDirectiveSegment>("segment"); |
| |
| // Simplified segment directives |
| addDirectiveHandler<&COFFMasmParser::ParseSectionDirectiveCode>(".code"); |
| // .const |
| addDirectiveHandler< |
| &COFFMasmParser::ParseSectionDirectiveInitializedData>(".data"); |
| addDirectiveHandler< |
| &COFFMasmParser::ParseSectionDirectiveUninitializedData>(".data?"); |
| // .exit |
| // .fardata |
| // .fardata? |
| addDirectiveHandler<&COFFMasmParser::IgnoreDirective>(".model"); |
| // .stack |
| // .startup |
| |
| // String directives, written <name> <directive> <params> |
| // catstr (equivalent to <name> TEXTEQU <params>) |
| // instr (equivalent to <name> = @InStr(<params>)) |
| // sizestr (equivalent to <name> = @SizeStr(<params>)) |
| // substr (equivalent to <name> TEXTEQU @SubStr(<params>)) |
| |
| // Structure and record directives |
| // record |
| // typedef |
| } |
| |
| bool ParseSectionDirectiveCode(StringRef, SMLoc) { |
| return ParseSectionSwitch(".text", |
| COFF::IMAGE_SCN_CNT_CODE |
| | COFF::IMAGE_SCN_MEM_EXECUTE |
| | COFF::IMAGE_SCN_MEM_READ, |
| SectionKind::getText()); |
| } |
| |
| bool ParseSectionDirectiveInitializedData(StringRef, SMLoc) { |
| return ParseSectionSwitch(".data", |
| COFF::IMAGE_SCN_CNT_INITIALIZED_DATA |
| | COFF::IMAGE_SCN_MEM_READ |
| | COFF::IMAGE_SCN_MEM_WRITE, |
| SectionKind::getData()); |
| } |
| |
| bool ParseSectionDirectiveUninitializedData(StringRef, SMLoc) { |
| return ParseSectionSwitch(".bss", |
| COFF::IMAGE_SCN_CNT_UNINITIALIZED_DATA |
| | COFF::IMAGE_SCN_MEM_READ |
| | COFF::IMAGE_SCN_MEM_WRITE, |
| SectionKind::getBSS()); |
| } |
| |
| /// Stack of active procedure definitions. |
| SmallVector<StringRef, 1> CurrentProcedures; |
| SmallVector<bool, 1> CurrentProceduresFramed; |
| |
| public: |
| COFFMasmParser() = default; |
| }; |
| |
| } // end anonymous namespace. |
| |
| bool COFFMasmParser::ParseSectionSwitch(StringRef SectionName, |
| unsigned Characteristics, |
| SectionKind Kind) { |
| return ParseSectionSwitch(SectionName, Characteristics, Kind, "", |
| (COFF::COMDATType)0, Align(16)); |
| } |
| |
| bool COFFMasmParser::ParseSectionSwitch( |
| StringRef SectionName, unsigned Characteristics, SectionKind Kind, |
| StringRef COMDATSymName, COFF::COMDATType Type, Align Alignment) { |
| if (getLexer().isNot(AsmToken::EndOfStatement)) |
| return TokError("unexpected token in section switching directive"); |
| Lex(); |
| |
| MCSection *Section = getContext().getCOFFSection(SectionName, Characteristics, |
| Kind, COMDATSymName, Type); |
| Section->setAlignment(Alignment); |
| getStreamer().switchSection(Section); |
| |
| return false; |
| } |
| |
| bool COFFMasmParser::ParseDirectiveSegment(StringRef Directive, SMLoc Loc) { |
| StringRef SegmentName; |
| if (!getLexer().is(AsmToken::Identifier)) |
| return TokError("expected identifier in directive"); |
| SegmentName = getTok().getIdentifier(); |
| Lex(); |
| |
| StringRef SectionName = SegmentName; |
| SmallVector<char, 247> SectionNameVector; |
| |
| StringRef Class; |
| if (SegmentName == "_TEXT" || SegmentName.startswith("_TEXT$")) { |
| if (SegmentName.size() == 5) { |
| SectionName = ".text"; |
| } else { |
| SectionName = |
| (".text$" + SegmentName.substr(6)).toStringRef(SectionNameVector); |
| } |
| Class = "CODE"; |
| } |
| |
| // Parse all options to end of statement. |
| // Alignment defaults to PARA if unspecified. |
| int64_t Alignment = 16; |
| // Default flags are used only if no characteristics are set. |
| bool DefaultCharacteristics = true; |
| unsigned Flags = 0; |
| // "obsolete" according to the documentation, but still supported. |
| bool Readonly = false; |
| while (getLexer().isNot(AsmToken::EndOfStatement)) { |
| switch (getTok().getKind()) { |
| default: |
| break; |
| case AsmToken::String: { |
| // Class identifier; overrides Kind. |
| Class = getTok().getStringContents(); |
| Lex(); |
| break; |
| } |
| case AsmToken::Identifier: { |
| SMLoc KeywordLoc = getTok().getLoc(); |
| StringRef Keyword; |
| if (getParser().parseIdentifier(Keyword)) { |
| llvm_unreachable("failed to parse identifier at an identifier token"); |
| } |
| if (Keyword.equals_insensitive("byte")) { |
| Alignment = 1; |
| } else if (Keyword.equals_insensitive("word")) { |
| Alignment = 2; |
| } else if (Keyword.equals_insensitive("dword")) { |
| Alignment = 4; |
| } else if (Keyword.equals_insensitive("para")) { |
| Alignment = 16; |
| } else if (Keyword.equals_insensitive("page")) { |
| Alignment = 256; |
| } else if (Keyword.equals_insensitive("align")) { |
| if (getParser().parseToken(AsmToken::LParen) || |
| getParser().parseIntToken(Alignment, |
| "Expected integer alignment") || |
| getParser().parseToken(AsmToken::RParen)) { |
| return Error(getTok().getLoc(), |
| "Expected (n) following ALIGN in SEGMENT directive"); |
| } |
| if (!isPowerOf2_64(Alignment) || Alignment > 8192) { |
| return Error(KeywordLoc, |
| "ALIGN argument must be a power of 2 from 1 to 8192"); |
| } |
| } else if (Keyword.equals_insensitive("alias")) { |
| if (getParser().parseToken(AsmToken::LParen) || |
| !getTok().is(AsmToken::String)) |
| return Error( |
| getTok().getLoc(), |
| "Expected (string) following ALIAS in SEGMENT directive"); |
| SectionName = getTok().getStringContents(); |
| Lex(); |
| if (getParser().parseToken(AsmToken::RParen)) |
| return Error( |
| getTok().getLoc(), |
| "Expected (string) following ALIAS in SEGMENT directive"); |
| } else if (Keyword.equals_insensitive("readonly")) { |
| Readonly = true; |
| } else { |
| unsigned Characteristic = |
| StringSwitch<unsigned>(Keyword) |
| .CaseLower("info", COFF::IMAGE_SCN_LNK_INFO) |
| .CaseLower("read", COFF::IMAGE_SCN_MEM_READ) |
| .CaseLower("write", COFF::IMAGE_SCN_MEM_WRITE) |
| .CaseLower("execute", COFF::IMAGE_SCN_MEM_EXECUTE) |
| .CaseLower("shared", COFF::IMAGE_SCN_MEM_SHARED) |
| .CaseLower("nopage", COFF::IMAGE_SCN_MEM_NOT_PAGED) |
| .CaseLower("nocache", COFF::IMAGE_SCN_MEM_NOT_CACHED) |
| .CaseLower("discard", COFF::IMAGE_SCN_MEM_DISCARDABLE) |
| .Default(-1); |
| if (Characteristic == static_cast<unsigned>(-1)) { |
| return Error(KeywordLoc, |
| "Expected characteristic in SEGMENT directive; found '" + |
| Keyword + "'"); |
| } |
| Flags |= Characteristic; |
| DefaultCharacteristics = false; |
| } |
| } |
| } |
| } |
| |
| SectionKind Kind = StringSwitch<SectionKind>(Class) |
| .CaseLower("data", SectionKind::getData()) |
| .CaseLower("code", SectionKind::getText()) |
| .CaseLower("const", SectionKind::getReadOnly()) |
| .Default(SectionKind::getData()); |
| if (Kind.isText()) { |
| if (DefaultCharacteristics) { |
| Flags |= COFF::IMAGE_SCN_MEM_EXECUTE | COFF::IMAGE_SCN_MEM_READ; |
| } |
| Flags |= COFF::IMAGE_SCN_CNT_CODE; |
| } else { |
| if (DefaultCharacteristics) { |
| Flags |= COFF::IMAGE_SCN_MEM_READ | COFF::IMAGE_SCN_MEM_WRITE; |
| } |
| Flags |= COFF::IMAGE_SCN_CNT_INITIALIZED_DATA; |
| } |
| if (Readonly) { |
| Flags &= ~COFF::IMAGE_SCN_MEM_WRITE; |
| } |
| |
| MCSection *Section = getContext().getCOFFSection(SectionName, Flags, Kind, "", |
| (COFF::COMDATType)(0)); |
| if (Alignment != 0) { |
| Section->setAlignment(Align(Alignment)); |
| } |
| getStreamer().switchSection(Section); |
| return false; |
| } |
| |
| /// ParseDirectiveSegmentEnd |
| /// ::= identifier "ends" |
| bool COFFMasmParser::ParseDirectiveSegmentEnd(StringRef Directive, SMLoc Loc) { |
| StringRef SegmentName; |
| if (!getLexer().is(AsmToken::Identifier)) |
| return TokError("expected identifier in directive"); |
| SegmentName = getTok().getIdentifier(); |
| |
| // Ignore; no action necessary. |
| Lex(); |
| return false; |
| } |
| |
| /// ParseDirectiveIncludelib |
| /// ::= "includelib" identifier |
| bool COFFMasmParser::ParseDirectiveIncludelib(StringRef Directive, SMLoc Loc) { |
| StringRef Lib; |
| if (getParser().parseIdentifier(Lib)) |
| return TokError("expected identifier in includelib directive"); |
| |
| unsigned Flags = COFF::IMAGE_SCN_MEM_PRELOAD | COFF::IMAGE_SCN_MEM_16BIT; |
| SectionKind Kind = SectionKind::getData(); |
| getStreamer().pushSection(); |
| getStreamer().switchSection(getContext().getCOFFSection( |
| ".drectve", Flags, Kind, "", (COFF::COMDATType)(0))); |
| getStreamer().emitBytes("/DEFAULTLIB:"); |
| getStreamer().emitBytes(Lib); |
| getStreamer().emitBytes(" "); |
| getStreamer().popSection(); |
| return false; |
| } |
| |
| /// ParseDirectiveOption |
| /// ::= "option" option-list |
| bool COFFMasmParser::ParseDirectiveOption(StringRef Directive, SMLoc Loc) { |
| auto parseOption = [&]() -> bool { |
| StringRef Option; |
| if (getParser().parseIdentifier(Option)) |
| return TokError("expected identifier for option name"); |
| if (Option.equals_insensitive("prologue")) { |
| StringRef MacroId; |
| if (parseToken(AsmToken::Colon) || getParser().parseIdentifier(MacroId)) |
| return TokError("expected :macroId after OPTION PROLOGUE"); |
| if (MacroId.equals_insensitive("none")) { |
| // Since we currently don't implement prologues/epilogues, NONE is our |
| // default. |
| return false; |
| } |
| return TokError("OPTION PROLOGUE is currently unsupported"); |
| } |
| if (Option.equals_insensitive("epilogue")) { |
| StringRef MacroId; |
| if (parseToken(AsmToken::Colon) || getParser().parseIdentifier(MacroId)) |
| return TokError("expected :macroId after OPTION EPILOGUE"); |
| if (MacroId.equals_insensitive("none")) { |
| // Since we currently don't implement prologues/epilogues, NONE is our |
| // default. |
| return false; |
| } |
| return TokError("OPTION EPILOGUE is currently unsupported"); |
| } |
| return TokError("OPTION '" + Option + "' is currently unsupported"); |
| }; |
| |
| if (parseMany(parseOption)) |
| return addErrorSuffix(" in OPTION directive"); |
| return false; |
| } |
| |
| /// ParseDirectiveProc |
| /// TODO(epastor): Implement parameters and other attributes. |
| /// ::= label "proc" [[distance]] |
| /// statements |
| /// label "endproc" |
| bool COFFMasmParser::ParseDirectiveProc(StringRef Directive, SMLoc Loc) { |
| StringRef Label; |
| if (getParser().parseIdentifier(Label)) |
| return Error(Loc, "expected identifier for procedure"); |
| if (getLexer().is(AsmToken::Identifier)) { |
| StringRef nextVal = getTok().getString(); |
| SMLoc nextLoc = getTok().getLoc(); |
| if (nextVal.equals_insensitive("far")) { |
| // TODO(epastor): Handle far procedure definitions. |
| Lex(); |
| return Error(nextLoc, "far procedure definitions not yet supported"); |
| } else if (nextVal.equals_insensitive("near")) { |
| Lex(); |
| nextVal = getTok().getString(); |
| nextLoc = getTok().getLoc(); |
| } |
| } |
| MCSymbolCOFF *Sym = cast<MCSymbolCOFF>(getContext().getOrCreateSymbol(Label)); |
| |
| // Define symbol as simple external function |
| Sym->setExternal(true); |
| Sym->setType(COFF::IMAGE_SYM_DTYPE_FUNCTION << COFF::SCT_COMPLEX_TYPE_SHIFT); |
| |
| bool Framed = false; |
| if (getLexer().is(AsmToken::Identifier) && |
| getTok().getString().equals_insensitive("frame")) { |
| Lex(); |
| Framed = true; |
| getStreamer().emitWinCFIStartProc(Sym, Loc); |
| } |
| getStreamer().emitLabel(Sym, Loc); |
| |
| CurrentProcedures.push_back(Label); |
| CurrentProceduresFramed.push_back(Framed); |
| return false; |
| } |
| bool COFFMasmParser::ParseDirectiveEndProc(StringRef Directive, SMLoc Loc) { |
| StringRef Label; |
| SMLoc LabelLoc = getTok().getLoc(); |
| if (getParser().parseIdentifier(Label)) |
| return Error(LabelLoc, "expected identifier for procedure end"); |
| |
| if (CurrentProcedures.empty()) |
| return Error(Loc, "endp outside of procedure block"); |
| else if (!CurrentProcedures.back().equals_insensitive(Label)) |
| return Error(LabelLoc, "endp does not match current procedure '" + |
| CurrentProcedures.back() + "'"); |
| |
| if (CurrentProceduresFramed.back()) { |
| getStreamer().emitWinCFIEndProc(Loc); |
| } |
| CurrentProcedures.pop_back(); |
| CurrentProceduresFramed.pop_back(); |
| return false; |
| } |
| |
| bool COFFMasmParser::ParseDirectiveAlias(StringRef Directive, SMLoc Loc) { |
| std::string AliasName, ActualName; |
| if (getTok().isNot(AsmToken::Less) || |
| getParser().parseAngleBracketString(AliasName)) |
| return Error(getTok().getLoc(), "expected <aliasName>"); |
| if (getParser().parseToken(AsmToken::Equal)) |
| return addErrorSuffix(" in " + Directive + " directive"); |
| if (getTok().isNot(AsmToken::Less) || |
| getParser().parseAngleBracketString(ActualName)) |
| return Error(getTok().getLoc(), "expected <actualName>"); |
| |
| MCSymbol *Alias = getContext().getOrCreateSymbol(AliasName); |
| MCSymbol *Actual = getContext().getOrCreateSymbol(ActualName); |
| |
| getStreamer().emitWeakReference(Alias, Actual); |
| |
| return false; |
| } |
| |
| bool COFFMasmParser::ParseSEHDirectiveAllocStack(StringRef Directive, |
| SMLoc Loc) { |
| int64_t Size; |
| SMLoc SizeLoc = getTok().getLoc(); |
| if (getParser().parseAbsoluteExpression(Size)) |
| return Error(SizeLoc, "expected integer size"); |
| if (Size % 8 != 0) |
| return Error(SizeLoc, "stack size must be a multiple of 8"); |
| getStreamer().emitWinCFIAllocStack(static_cast<unsigned>(Size), Loc); |
| return false; |
| } |
| |
| bool COFFMasmParser::ParseSEHDirectiveEndProlog(StringRef Directive, |
| SMLoc Loc) { |
| getStreamer().emitWinCFIEndProlog(Loc); |
| return false; |
| } |
| |
| namespace llvm { |
| |
| MCAsmParserExtension *createCOFFMasmParser() { return new COFFMasmParser; } |
| |
| } // end namespace llvm |