blob: ef80507e69f4e053107f3d216f43b80ed8dda056 [file] [log] [blame]
//===- tools/lld/lld-core.cpp - Linker Core Test Driver -----------------===//
//
// The LLVM Linker
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "lld/Core/Atom.h"
#include "lld/Core/LLVM.h"
#include "lld/Core/NativeReader.h"
#include "lld/Core/NativeWriter.h"
#include "lld/Core/Pass.h"
#include "lld/Core/Resolver.h"
#include "lld/Core/YamlReader.h"
#include "lld/Core/YamlWriter.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/system_error.h"
#include <vector>
using namespace lld;
static void error(Twine message) {
llvm::errs() << "lld-core: " << message << ".\n";
}
static bool error(error_code ec) {
if (ec) {
error(ec.message());
return true;
}
return false;
}
namespace {
//
// Simple atom created by the stubs pass.
//
class TestingStubAtom : public DefinedAtom {
public:
TestingStubAtom(const File& f, const Atom& shlib) :
_file(f), _shlib(shlib) {
static uint32_t lastOrdinal = 0;
_ordinal = lastOrdinal++;
}
virtual const File& file() const {
return _file;
}
virtual StringRef name() const {
return StringRef();
}
virtual uint64_t ordinal() const {
return _ordinal;
}
virtual uint64_t size() const {
return 0;
}
virtual Scope scope() const {
return DefinedAtom::scopeLinkageUnit;
}
virtual Interposable interposable() const {
return DefinedAtom::interposeNo;
}
virtual Merge merge() const {
return DefinedAtom::mergeNo;
}
virtual ContentType contentType() const {
return DefinedAtom::typeStub;
}
virtual Alignment alignment() const {
return Alignment(0,0);
}
virtual SectionChoice sectionChoice() const {
return DefinedAtom::sectionBasedOnContent;
}
virtual StringRef customSectionName() const {
return StringRef();
}
virtual DeadStripKind deadStrip() const {
return DefinedAtom::deadStripNormal;
}
virtual ContentPermissions permissions() const {
return DefinedAtom::permR_X;
}
virtual bool isThumb() const {
return false;
}
virtual bool isAlias() const {
return false;
}
virtual ArrayRef<uint8_t> rawContent() const {
return ArrayRef<uint8_t>();
}
virtual reference_iterator begin() const {
return reference_iterator(*this, nullptr);
}
virtual reference_iterator end() const {
return reference_iterator(*this, nullptr);
}
virtual const Reference* derefIterator(const void* iter) const {
return nullptr;
}
virtual void incrementIterator(const void*& iter) const {
}
private:
const File& _file;
const Atom& _shlib;
uint32_t _ordinal;
};
//
// Simple atom created by the GOT pass.
//
class TestingGOTAtom : public DefinedAtom {
public:
TestingGOTAtom(const File& f, const Atom& shlib) :
_file(f), _shlib(shlib) {
static uint32_t lastOrdinal = 0;
_ordinal = lastOrdinal++;
}
virtual const File& file() const {
return _file;
}
virtual StringRef name() const {
return StringRef();
}
virtual uint64_t ordinal() const {
return _ordinal;
}
virtual uint64_t size() const {
return 0;
}
virtual Scope scope() const {
return DefinedAtom::scopeLinkageUnit;
}
virtual Interposable interposable() const {
return DefinedAtom::interposeNo;
}
virtual Merge merge() const {
return DefinedAtom::mergeNo;
}
virtual ContentType contentType() const {
return DefinedAtom::typeGOT;
}
virtual Alignment alignment() const {
return Alignment(3,0);
}
virtual SectionChoice sectionChoice() const {
return DefinedAtom::sectionBasedOnContent;
}
virtual StringRef customSectionName() const {
return StringRef();
}
virtual DeadStripKind deadStrip() const {
return DefinedAtom::deadStripNormal;
}
virtual ContentPermissions permissions() const {
return DefinedAtom::permRW_;
}
virtual bool isThumb() const {
return false;
}
virtual bool isAlias() const {
return false;
}
virtual ArrayRef<uint8_t> rawContent() const {
return ArrayRef<uint8_t>();
}
virtual reference_iterator begin() const {
return reference_iterator(*this, nullptr);
}
virtual reference_iterator end() const {
return reference_iterator(*this, nullptr);
}
virtual const Reference* derefIterator(const void* iter) const {
return nullptr;
}
virtual void incrementIterator(const void*& iter) const {
}
private:
const File& _file;
const Atom& _shlib;
uint32_t _ordinal;
};
//
// A simple platform for testing.
//
class TestingPlatform : public Platform {
public:
virtual void initialize() { }
// tell platform object another file has been added
virtual void fileAdded(const File &file) { }
// tell platform object another atom has been added
virtual void atomAdded(const Atom &file) { }
// give platform a chance to change each atom's scope
virtual void adjustScope(const DefinedAtom &atom) { }
// if specified atom needs alternate names, return AliasAtom(s)
virtual bool getAliasAtoms(const Atom &atom,
std::vector<const DefinedAtom *>&) {
return false;
}
// give platform a chance to resolve platform-specific undefs
virtual bool getPlatformAtoms(StringRef undefined,
std::vector<const DefinedAtom *>&) {
return false;
}
// resolver should remove unreferenced atoms
virtual bool deadCodeStripping() {
return false;
}
// atom must be kept so should be root of dead-strip graph
virtual bool isDeadStripRoot(const Atom &atom) {
return false;
}
// if target must have some atoms, denote here
virtual bool getImplicitDeadStripRoots(std::vector<const DefinedAtom *>&) {
return false;
}
// return entry point for output file (e.g. "main") or nullptr
virtual StringRef entryPointName() {
return StringRef();
}
// for iterating must-be-defined symbols ("main" or -u command line option)
typedef StringRef const *UndefinesIterator;
virtual UndefinesIterator initialUndefinesBegin() const {
return nullptr;
}
virtual UndefinesIterator initialUndefinesEnd() const {
return nullptr;
}
// if platform wants resolvers to search libraries for overrides
virtual bool searchArchivesToOverrideTentativeDefinitions() {
return false;
}
virtual bool searchSharedLibrariesToOverrideTentativeDefinitions() {
return false;
}
// if platform allows symbol to remain undefined (e.g. -r)
virtual bool allowUndefinedSymbol(StringRef name) {
return true;
}
// for debugging dead code stripping, -why_live
virtual bool printWhyLive(StringRef name) {
return false;
}
virtual const Atom& handleMultipleDefinitions(const Atom& def1,
const Atom& def2) {
llvm::report_fatal_error("symbol '"
+ Twine(def1.name())
+ "' multiply defined");
}
// print out undefined symbol error messages in platform specific way
virtual void errorWithUndefines(const std::vector<const Atom *> &undefs,
const std::vector<const Atom *> &all) {}
// print out undefined can-be-null mismatches
virtual void undefineCanBeNullMismatch(const UndefinedAtom& undef1,
const UndefinedAtom& undef2,
bool& useUndef2) { }
// print out shared library mismatches
virtual void sharedLibrarylMismatch(const SharedLibraryAtom& shLib1,
const SharedLibraryAtom& shLib2,
bool& useShlib2) { }
// last chance for platform to tweak atoms
virtual void postResolveTweaks(std::vector<const Atom *> &all) {}
struct KindMapping {
const char* string;
Reference::Kind value;
bool isBranch;
bool isGotLoad;
bool isGotUse;
};
static const KindMapping _s_kindMappings[];
virtual Reference::Kind kindFromString(StringRef kindName) {
for (const KindMapping* p = _s_kindMappings; p->string != nullptr; ++p) {
if ( kindName.equals(p->string) )
return p->value;
}
int k;
if (kindName.getAsInteger(0, k))
k = 0;
return k;
}
virtual StringRef kindToString(Reference::Kind value) {
for (const KindMapping* p = _s_kindMappings; p->string != nullptr; ++p) {
if ( value == p->value)
return p->string;
}
return StringRef("???");
}
virtual bool noTextRelocs() {
return true;
}
virtual bool isCallSite(Reference::Kind kind) {
for (const KindMapping* p = _s_kindMappings; p->string != nullptr; ++p) {
if ( kind == p->value )
return p->isBranch;
}
return false;
}
virtual bool isGOTAccess(Reference::Kind kind, bool& canBypassGOT) {
for (const KindMapping* p = _s_kindMappings; p->string != nullptr; ++p) {
if ( kind == p->value ) {
canBypassGOT = p->isGotLoad;
return p->isGotUse;
}
}
return false;
}
virtual void updateReferenceToGOT(const Reference* ref, bool targetIsNowGOT) {
if ( targetIsNowGOT )
(const_cast<Reference*>(ref))->setKind(kindFromString("pcrel32"));
else
(const_cast<Reference*>(ref))->setKind(kindFromString("lea32wasGot"));
}
virtual const DefinedAtom *getStub(const Atom& shlibAtom, File& file) {
const DefinedAtom *result = new TestingStubAtom(file, shlibAtom);
_stubs.push_back(result);
return result;
}
virtual const DefinedAtom* makeGOTEntry(const Atom& shlibAtom, File& file) {
return new TestingGOTAtom(file, shlibAtom);
}
virtual void addStubAtoms(File &file) {
for (const DefinedAtom *stub : _stubs) {
file.addAtom(*stub);
}
}
virtual void writeExecutable(const lld::File &, raw_ostream &out) {
}
private:
std::vector<const DefinedAtom*> _stubs;
};
//
// Table of fixup kinds in YAML documents used for testing
//
const TestingPlatform::KindMapping TestingPlatform::_s_kindMappings[] = {
{ "call32", 1, true, false, false},
{ "pcrel32", 2, false, false, false },
{ "gotLoad32", 3, false, true, true },
{ "gotUse32", 4, false, false, true },
{ "lea32wasGot", 5, false, false, false },
{ nullptr, 0, false, false, false }
};
//
// A simple input files wrapper for testing.
//
class TestingInputFiles : public InputFiles {
public:
TestingInputFiles(std::vector<const File*>& f) : _files(f) { }
// InputFiles interface
virtual void forEachInitialAtom(InputFiles::Handler& handler) const {
for ( const File *file : _files ) {
handler.doFile(*file);
for( const DefinedAtom *atom : file->defined() ) {
handler.doDefinedAtom(*atom);
}
for( const UndefinedAtom *undefAtom : file->undefined() ) {
handler.doUndefinedAtom(*undefAtom);
}
for( const SharedLibraryAtom *shlibAtom : file->sharedLibrary() ) {
handler.doSharedLibraryAtom(*shlibAtom);
}
for( const AbsoluteAtom *absAtom : file->absolute() ) {
handler.doAbsoluteAtom(*absAtom);
}
}
}
virtual bool searchLibraries(StringRef name, bool searchDylibs,
bool searchArchives, bool dataSymbolOnly,
InputFiles::Handler &) const {
return false;
}
private:
std::vector<const File*>& _files;
};
}
llvm::cl::opt<std::string>
gInputFilePath(llvm::cl::Positional,
llvm::cl::desc("<input file>"),
llvm::cl::init("-"));
llvm::cl::opt<std::string>
gOutputFilePath("o",
llvm::cl::desc("Specify output filename"),
llvm::cl::value_desc("filename"));
llvm::cl::opt<bool>
gDoStubsPass("stubs_pass",
llvm::cl::desc("Run pass to create stub atoms"));
llvm::cl::opt<bool>
gDoGotPass("got_pass",
llvm::cl::desc("Run pass to create GOT atoms"));
enum PlatformChoice {
platformTesting, platformDarwin
};
llvm::cl::opt<PlatformChoice>
platformSelected("platform",
llvm::cl::desc("Select platform"),
llvm::cl::values(
clEnumValN(platformTesting, "none", "link for testing"),
clEnumValN(platformDarwin, "darwin", "link as darwin would"),
clEnumValEnd));
int main(int argc, char *argv[]) {
// Print a stack trace if we signal out.
llvm::sys::PrintStackTraceOnErrorSignal();
llvm::PrettyStackTraceProgram X(argc, argv);
llvm::llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
// parse options
llvm::cl::ParseCommandLineOptions(argc, argv);
// create platform for testing
Platform* platform = NULL;
switch ( platformSelected ) {
case platformTesting:
platform = new TestingPlatform();
break;
case platformDarwin:
platform = createDarwinPlatform();
break;
}
// read input YAML doc into object file(s)
std::vector<const File *> files;
if (error(yaml::parseObjectTextFileOrSTDIN(gInputFilePath,
*platform, files))) {
return 1;
}
// create object to mange input files
TestingInputFiles inputFiles(files);
// merge all atom graphs
Resolver resolver(*platform, inputFiles);
resolver.resolve();
// run passes
if ( gDoGotPass ) {
GOTPass addGot(resolver.resultFile(), *platform);
addGot.perform();
}
if ( gDoStubsPass ) {
StubsPass addStubs(resolver.resultFile(), *platform);
addStubs.perform();
}
// write new atom graph out as YAML doc
std::string errorInfo;
const char* outPath = gOutputFilePath.empty() ? "-" : gOutputFilePath.c_str();
llvm::raw_fd_ostream out(outPath, errorInfo);
// yaml::writeObjectText(resolver.resultFile(), out);
// make unique temp .o file to put generated object file
int fd;
SmallString<128> tempPath;
llvm::sys::fs::unique_file("temp%%%%%.o", fd, tempPath);
llvm::raw_fd_ostream binaryOut(fd, /*shouldClose=*/true);
// write native file
writeNativeObjectFile(resolver.resultFile(), binaryOut);
binaryOut.close(); // manually close so that file can be read next
// out << "native file: " << tempPath.str() << "\n";
// read native file
std::unique_ptr<lld::File> natFile;
if ( error(parseNativeObjectFileOrSTDIN(tempPath, natFile)) )
return 1;
if ( platformSelected == platformTesting) {
// write new atom graph out as YAML doc
yaml::writeObjectText(resolver.resultFile() /* *natFile */, *platform, out);
}
else {
platform->writeExecutable(resolver.resultFile() /* *natFile */, out);
// HACK. I don't see any way to set the 'executable' bit on files
// in raw_fd_ostream or in llvm/Support.
#if HAVE_SYS_STAT_H
::chmod(outPath, 0777);
#endif
}
// delete temp .o file
bool existed;
llvm::sys::fs::remove(tempPath.str(), existed);
return 0;
}