Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -18,6 +18,7 @@ namespace elf2 { struct Configuration { + std::vector InputFiles; llvm::StringRef OutputFile; llvm::StringRef DynamicLinker; std::string RPath; Index: ELF/Driver.h =================================================================== --- ELF/Driver.h +++ ELF/Driver.h @@ -11,6 +11,7 @@ #define LLD_ELF_DRIVER_H #include "lld/Core/LLVM.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Option/ArgList.h" namespace lld { @@ -56,6 +57,12 @@ #undef OPTION }; +// Returns true if the file at Path seems to be a linker script. +bool isLinkerScript(llvm::StringRef Path); + +// Parses a linker script. Calling this function updates the Config +void readLinkerScript(llvm::StringRef Path); + } // namespace elf2 } // namespace lld Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -145,19 +145,19 @@ if (auto *Arg = Args.getLastArg(OPT_entry)) Config->Entry = Arg->getValue(); - // Create a list of input files. - std::vector Inputs; - for (auto *Arg : Args.filtered(OPT_l, OPT_INPUT)) { - StringRef Path = Arg->getValue(); - if (Arg->getOption().getID() == OPT_l) { - Inputs.push_back(openFile(searchLibrary(Path))); + std::string Path = Arg->getValue(); + if (Arg->getOption().getID() == OPT_l) + Path = searchLibrary(Path); + if (isLinkerScript(Path)) { + // readLinkerScript may add files to Config->InputFiles. + readLinkerScript(Path); continue; } - Inputs.push_back(openFile(Path)); + Config->InputFiles.push_back(Path); } - if (Inputs.empty()) + if (Config->InputFiles.empty()) error("no input files."); // Create a symbol table. @@ -165,7 +165,8 @@ // Parse all input files and put all symbols to the symbol table. // The symbol table will take care of name resolution. - for (MemoryBufferRef MB : Inputs) { + for (const std::string &Path : Config->InputFiles) { + MemoryBufferRef MB = openFile(Path); std::unique_ptr File = createFile(MB); Symtab.addFile(std::move(File)); } Index: ELF/DriverUtils.cpp =================================================================== --- ELF/DriverUtils.cpp +++ ELF/DriverUtils.cpp @@ -13,10 +13,13 @@ // //===----------------------------------------------------------------------===// +#include "Config.h" #include "Driver.h" #include "Error.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/StringSaver.h" using namespace llvm; @@ -75,3 +78,145 @@ return Args; } + +// Parser and evaluator of the linker script. +// Results are directly written to the Config object. +namespace { +class LinkerScript { +public: + LinkerScript(StringRef S) : Tokens(tokenize(S)) {} + void run(); + +private: + static std::vector tokenize(StringRef S); + static StringRef skipSpace(StringRef S); + StringRef next(); + bool atEOF() { return Tokens.size() == Pos; } + void expect(StringRef Expect); + + void readAsNeeded(); + void readGroup(); + void readOutputFormat(); + + std::vector Tokens; + size_t Pos = 0; +}; +} + +void LinkerScript::run() { + while (!atEOF()) { + StringRef Tok = next(); + if (Tok == "GROUP") { + readGroup(); + } else if (Tok == "OUTPUT_FORMAT") { + readOutputFormat(); + } else { + error("unknown directive: " + Tok); + } + } +} + +// Split S into linker script tokens. +std::vector LinkerScript::tokenize(StringRef S) { + std::vector Ret; + for (;;) { + S = skipSpace(S); + if (S.empty()) + return Ret; + size_t Pos = S.find_first_not_of( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789_.$/\\~=+[]*?-:"); + // A character that cannot start a word (which is usually a + // punctuation) forms a single character token. + if (Pos == 0) + Pos = 1; + Ret.push_back(S.substr(0, Pos)); + S = S.substr(Pos); + } +} + +// Skip leading whitespace characters or /**/-style comments. +StringRef LinkerScript::skipSpace(StringRef S) { + for (;;) { + if (S.startswith("/*")) { + size_t E = S.find("*/", 2); + if (E == StringRef::npos) + error("unclosed comment in a linker script"); + S = S.substr(E + 2); + continue; + } + size_t Size = S.size(); + S = S.ltrim(); + if (S.size() == Size) + return S; + } +} + +StringRef LinkerScript::next() { + if (Pos == Tokens.size()) + error("unexpected EOF"); + return Tokens[Pos++]; +} + +void LinkerScript::expect(StringRef Expect) { + StringRef Tok = next(); + if (Tok != Expect) + error(Expect + " expected, but got " + Tok); +} + +void LinkerScript::readAsNeeded() { + expect("("); + for (;;) { + StringRef Tok = next(); + if (Tok == ")") + return; + Config->InputFiles.push_back(Tok); + } +} + +void LinkerScript::readGroup() { + expect("("); + for (;;) { + StringRef Tok = next(); + if (Tok == ")") + return; + if (Tok == "AS_NEEDED") { + readAsNeeded(); + continue; + } + Config->InputFiles.push_back(Tok); + } +} + +void LinkerScript::readOutputFormat() { + // Error checking only for now. + expect("("); + next(); + expect(")"); +} + +namespace lld { +namespace elf2 { + +// Returns true if Path seems to be a linker script. +bool isLinkerScript(llvm::StringRef Path) { + // Because the linker script is just a text file that has no magic bytes, + // this function actually checks if Path doesn't seem an object file nor + // an archive file. + using namespace llvm::sys::fs; + ErrorOr> MBOrErr = MemoryBuffer::getFile(Path); + if (MBOrErr.getError()) + return false; + file_magic Magic = identify_magic((*MBOrErr)->getBuffer()); + return Magic == file_magic::unknown; +} + +// Parse a linker script. +void readLinkerScript(llvm::StringRef Path) { + ErrorOr> MBOrErr = MemoryBuffer::getFile(Path); + error(MBOrErr, Twine("cannot open ") + Path); + StringRef S = (*MBOrErr)->getBuffer(); + LinkerScript(S).run(); +} +} +} Index: test/elf2/basic.s =================================================================== --- test/elf2/basic.s +++ test/elf2/basic.s @@ -176,11 +176,26 @@ # CHECK-NEXT: } # CHECK-NEXT: ] + +# Test for the response file # RUN: echo " -o %t2" > %t.responsefile # RUN: lld -flavor gnu2 %t @%t.responsefile # RUN: llvm-readobj -file-headers -sections -program-headers -symbols %t2 \ # RUN: | FileCheck %s + +# Test for the linker script +# RUN: echo "GROUP(" %t ")" > %t.script +# RUN: lld -flavor gnu2 -o %t2 %t.script +# RUN: llvm-readobj -file-headers -sections -program-headers -symbols %t2 \ +# RUN: | FileCheck %s + +# RUN: echo "OUTPUT_FORMAT(elf64-x86-64) /*/*/ GROUP(" %t ")" > %t.script +# RUN: lld -flavor gnu2 -o %t2 %t.script +# RUN: llvm-readobj -file-headers -sections -program-headers -symbols %t2 \ +# RUN: | FileCheck %s + + # RUN: not lld -flavor gnu2 %t 2>&1 | FileCheck --check-prefix=NO_O %s # NO_O: -o must be specified. Index: test/elf2/linkerscript.test =================================================================== --- /dev/null +++ test/elf2/linkerscript.test @@ -0,0 +1,5 @@ +# RUN: echo "FOO(BAR)" > %t.script +# RUN: not lld -flavor gnu2 -o foo %t.script > %t.log 2>&1 +# RUN: FileCheck -check-prefix=ERR1 %s < %t.log + +ERR1: unknown directive: FOO