blob: 34aa5bf2ae39c3eb0064e95fbf84f0276c70cfce [file] [log] [blame]
//===- 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