Index: lld/COFF/CMakeLists.txt =================================================================== --- lld/COFF/CMakeLists.txt +++ lld/COFF/CMakeLists.txt @@ -14,6 +14,7 @@ DriverUtils.cpp ICF.cpp InputFiles.cpp + LLDMapFile.cpp LTO.cpp MapFile.cpp MarkLive.cpp Index: lld/COFF/Config.h =================================================================== --- lld/COFF/Config.h +++ lld/COFF/Config.h @@ -182,6 +182,9 @@ llvm::StringMap order; // Used for /lldmap. + std::string lldmapFile; + + // Used for /map. std::string mapFile; // Used for /thinlto-index-only: Index: lld/COFF/Driver.cpp =================================================================== --- lld/COFF/Driver.cpp +++ lld/COFF/Driver.cpp @@ -706,14 +706,15 @@ return debugTypes; } -static std::string getMapFile(const opt::InputArgList &args) { - auto *arg = args.getLastArg(OPT_lldmap, OPT_lldmap_file); +static std::string getMapFile(const opt::InputArgList &args, + opt::OptSpecifier os, opt::OptSpecifier osFile) { + auto *arg = args.getLastArg(os, osFile); if (!arg) return ""; - if (arg->getOption().getID() == OPT_lldmap_file) + if (arg->getOption().getID() == osFile.getID()) return arg->getValue(); - assert(arg->getOption().getID() == OPT_lldmap); + assert(arg->getOption().getID() == os.getID()); StringRef outFile = config->outputFile; return (outFile.substr(0, outFile.rfind('.')) + ".map").str(); } @@ -1557,7 +1558,15 @@ if (config->mingw || config->debugDwarf) config->warnLongSectionNames = false; - config->mapFile = getMapFile(args); + config->lldmapFile = getMapFile(args, OPT_lldmap, OPT_lldmap_file); + config->mapFile = getMapFile(args, OPT_map, OPT_map_file); + + if (!config->lldmapFile.empty() && !config->mapFile.empty() && + config->lldmapFile == config->mapFile) { + warn("/lldmap and /map have the same output file '" + config->mapFile + + "'.\n>>> ignoring /lldmap"); + config->lldmapFile.clear(); + } if (config->incremental && args.hasArg(OPT_profile)) { warn("ignoring '/incremental' due to '/profile' specification"); Index: lld/COFF/LLDMapFile.h =================================================================== --- /dev/null +++ lld/COFF/LLDMapFile.h @@ -0,0 +1,21 @@ +//===- LLDMapFile.h ---------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_COFF_LLDMAPFILE_H +#define LLD_COFF_LLDMAPFILE_H + +#include "llvm/ADT/ArrayRef.h" + +namespace lld { +namespace coff { +class OutputSection; +void writeLLDMapFile(llvm::ArrayRef outputSections); +} +} + +#endif Index: lld/COFF/LLDMapFile.cpp =================================================================== --- /dev/null +++ lld/COFF/LLDMapFile.cpp @@ -0,0 +1,127 @@ +//===- LLDMapFile.cpp -----------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the /lldmap option. It shows lists in order and +// hierarchically the output sections, input sections, input files and +// symbol: +// +// Address Size Align Out File Symbol +// 00201000 00000015 4 .text +// 00201000 0000000e 4 test.o:(.text) +// 0020100e 00000000 0 local +// 00201005 00000000 0 f(int) +// +//===----------------------------------------------------------------------===// + +#include "LLDMapFile.h" +#include "SymbolTable.h" +#include "Symbols.h" +#include "Writer.h" +#include "lld/Common/ErrorHandler.h" +#include "lld/Common/Threads.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace llvm::object; + +namespace lld { +namespace coff { + +using SymbolMapTy = + DenseMap>; + +static constexpr char indent8[] = " "; // 8 spaces +static constexpr char indent16[] = " "; // 16 spaces + +// Print out the first three columns of a line. +static void writeHeader(raw_ostream &os, uint64_t addr, uint64_t size, + uint64_t align) { + os << format("%08llx %08llx %5lld ", addr, size, align); +} + +// Returns a list of all symbols that we want to print out. +static std::vector getSymbols() { + std::vector v; + for (ObjFile *file : ObjFile::instances) + for (Symbol *b : file->getSymbols()) + if (auto *sym = dyn_cast_or_null(b)) + if (sym && !sym->getCOFFSymbol().isSectionDefinition()) + v.push_back(sym); + return v; +} + +// Returns a map from sections to their symbols. +static SymbolMapTy getSectionSyms(ArrayRef syms) { + SymbolMapTy ret; + for (DefinedRegular *s : syms) + ret[s->getChunk()].push_back(s); + + // Sort symbols by address. + for (auto &it : ret) { + SmallVectorImpl &v = it.second; + std::sort(v.begin(), v.end(), [](DefinedRegular *a, DefinedRegular *b) { + return a->getRVA() < b->getRVA(); + }); + } + return ret; +} + +// Construct a map from symbols to their stringified representations. +static DenseMap +getSymbolStrings(ArrayRef syms) { + std::vector str(syms.size()); + parallelForEachN((size_t)0, syms.size(), [&](size_t i) { + raw_string_ostream os(str[i]); + writeHeader(os, syms[i]->getRVA(), 0, 0); + os << indent16 << toString(*syms[i]); + }); + + DenseMap ret; + for (size_t i = 0, e = syms.size(); i < e; ++i) + ret[syms[i]] = std::move(str[i]); + return ret; +} + +void writeLLDMapFile(ArrayRef outputSections) { + if (config->lldmapFile.empty()) + return; + + std::error_code ec; + raw_fd_ostream os(config->lldmapFile, ec, sys::fs::F_None); + if (ec) + fatal("cannot open " + config->lldmapFile + ": " + ec.message()); + + // Collect symbol info that we want to print out. + std::vector syms = getSymbols(); + SymbolMapTy sectionSyms = getSectionSyms(syms); + DenseMap symStr = getSymbolStrings(syms); + + // Print out the header line. + os << "Address Size Align Out In Symbol\n"; + + // Print out file contents. + for (OutputSection *sec : outputSections) { + writeHeader(os, sec->getRVA(), sec->getVirtualSize(), /*align=*/pageSize); + os << sec->name << '\n'; + + for (Chunk *c : sec->chunks) { + auto *sc = dyn_cast(c); + if (!sc) + continue; + + writeHeader(os, sc->getRVA(), sc->getSize(), sc->getAlignment()); + os << indent8 << sc->file->getName() << ":(" << sc->getSectionName() + << ")\n"; + for (DefinedRegular *sym : sectionSyms[sc]) + os << symStr[sym] << '\n'; + } + } +} + +} // namespace coff +} // namespace lld \ No newline at end of file Index: lld/COFF/MapFile.cpp =================================================================== --- lld/COFF/MapFile.cpp +++ lld/COFF/MapFile.cpp @@ -6,16 +6,25 @@ // //===----------------------------------------------------------------------===// // -// This file implements the /lldmap option. It shows lists in order and -// hierarchically the output sections, input sections, input files and -// symbol: +// This file implements the /map option in the same format as link.exe +// (based on observations) // -// Address Size Align Out File Symbol -// 00201000 00000015 4 .text -// 00201000 0000000e 4 test.o:(.text) -// 0020100e 00000000 0 local -// 00201005 00000000 0 f(int) +// Header (program name, timestamp info, preferred load address) // +// Section list (Start = Section index:Base address): +// Start Length Name Class +// 0001:00001000 00000015H .text CODE +// +// Symbols list: +// Address Publics by Value Rva + Base Lib:Object +// 0001:00001000 main 0000000140001000 main.obj +// 0001:00001300 ?__scrt_common_main@@YAHXZ 0000000140001300 libcmt:exe_main.obj +// +// entry point at 0001:00000360 +// +// Static symbols +// +// 0000:00000000 __guard_fids__ 0000000140000000 libcmt : exe_main.obj //===----------------------------------------------------------------------===// #include "MapFile.h" @@ -24,6 +33,8 @@ #include "Writer.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Threads.h" +#include "lld/Common/Timer.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; @@ -32,56 +43,144 @@ namespace lld { namespace coff { -using SymbolMapTy = - DenseMap>; +static Timer totalMapTimer("MAP emission (Cumulative)", Timer::root()); +static Timer symbolGatherTimer("Gather symbols", totalMapTimer); +static Timer symbolStringsTimer("Build symbol strings", totalMapTimer); +static Timer writeTimer("Write to file", totalMapTimer); -static constexpr char indent8[] = " "; // 8 spaces -static constexpr char indent16[] = " "; // 16 spaces +// Print out the first two columns of a line. +static void writeHeader(raw_ostream &os, uint32_t sec, uint64_t addr) { + os << format(" %04x:%08llx", sec, addr); +} + +static void sortUniqueSymbols(std::vector &syms) { + // Build helper vector + using SortEntry = std::pair; + std::vector v; + v.resize(syms.size()); + for (size_t i = 0, e = syms.size(); i < e; ++i) + v[i] = SortEntry(syms[i], i); -// Print out the first three columns of a line. -static void writeHeader(raw_ostream &os, uint64_t addr, uint64_t size, - uint64_t align) { - os << format("%08llx %08llx %5lld ", addr, size, align); + // Remove duplicate symbol pointers + parallelSort(v, [](const auto &a, const auto &b) { + return a.first < b.first || (a.first == b.first && a.second < b.second); + }); + auto end = std::unique(v.begin(), v.end(), [](const auto &a, const auto &b) { + return a.first == b.first; + }); + v.resize(std::distance(v.begin(), end)); + + // Sort by RVA then original order + parallelSort(v, [](const auto &a, const auto &b) { + // Add config->imageBase to avoid comparing "negative" RVAs. + // This can happen with symbols of Absolute kind + uint64_t rvaa = config->imageBase + a.first->getRVA(); + uint64_t rvab = config->imageBase + b.first->getRVA(); + return rvaa < rvab || (rvaa == rvab && a.second < b.second); + }); + + syms.resize(v.size()); + for (size_t i = 0, e = v.size(); i < e; ++i) + syms[i] = v[i].first; } -// Returns a list of all symbols that we want to print out. -static std::vector getSymbols() { - std::vector v; +// Returns the lists of all symbols that we want to print out. +static void getSymbols(std::vector &syms, + std::vector &staticSyms) { + for (ObjFile *file : ObjFile::instances) - for (Symbol *b : file->getSymbols()) - if (auto *sym = dyn_cast_or_null(b)) - if (sym && !sym->getCOFFSymbol().isSectionDefinition()) - v.push_back(sym); - return v; -} + for (Symbol *b : file->getSymbols()) { + if (!b || !b->isLive()) + continue; + if (auto *sym = dyn_cast(b)) { + COFFSymbolRef symRef = sym->getCOFFSymbol(); + if (!symRef.isSectionDefinition() && + symRef.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL) { + if (symRef.getStorageClass() == COFF::IMAGE_SYM_CLASS_STATIC) + staticSyms.push_back(sym); + else + syms.push_back(sym); + } + } else if (auto *sym = dyn_cast(b)) { + syms.push_back(sym); + } + } + + for (ImportFile *file : ImportFile::instances) { + if (!file->live) + continue; + + if (!file->thunkSym) + continue; + + if (!file->thunkLive) + continue; -// Returns a map from sections to their symbols. -static SymbolMapTy getSectionSyms(ArrayRef syms) { - SymbolMapTy ret; - for (DefinedRegular *s : syms) - ret[s->getChunk()].push_back(s); - - // Sort symbols by address. - for (auto &it : ret) { - SmallVectorImpl &v = it.second; - std::sort(v.begin(), v.end(), [](DefinedRegular *a, DefinedRegular *b) { - return a->getRVA() < b->getRVA(); - }); + if (auto *thunkSym = dyn_cast(file->thunkSym)) + syms.push_back(thunkSym); + + if (auto *impSym = dyn_cast_or_null(file->impSym)) + syms.push_back(impSym); } - return ret; + + sortUniqueSymbols(syms); + sortUniqueSymbols(staticSyms); } // Construct a map from symbols to their stringified representations. -static DenseMap -getSymbolStrings(ArrayRef syms) { +static DenseMap +getSymbolStrings(ArrayRef syms) { std::vector str(syms.size()); parallelForEachN((size_t)0, syms.size(), [&](size_t i) { raw_string_ostream os(str[i]); - writeHeader(os, syms[i]->getRVA(), 0, 0); - os << indent16 << toString(*syms[i]); + Defined *sym = syms[i]; + + uint16_t sectionIdx = 0; + uint64_t address = 0; + SmallString<128> fileDescr; + + if (auto *absSym = dyn_cast(sym)) { + address = absSym->getVA(); + fileDescr = ""; + } else if (isa(sym)) { + fileDescr = ""; + } else if (isa(sym)) { + fileDescr = ""; + } else if (Chunk *chunk = sym->getChunk()) { + address = sym->getRVA(); + if (OutputSection *sec = chunk->getOutputSection()) + address -= sec->header.VirtualAddress; + + sectionIdx = chunk->getOutputSectionIdx(); + + InputFile *file; + if (auto *impSym = dyn_cast(sym)) + file = impSym->file; + else if (auto *thunkSym = dyn_cast(sym)) + file = thunkSym->wrappedSym->file; + else + file = sym->getFile(); + + if (!file->parentName.empty()) { + fileDescr = sys::path::filename(file->parentName); + sys::path::replace_extension(fileDescr, ""); + fileDescr += ":"; + } + fileDescr += sys::path::filename(file->getName()); + } + writeHeader(os, sectionIdx, address); + os << " "; + os << left_justify(sym->getName(), 26); + os << " "; + os << format_hex_no_prefix((config->imageBase + sym->getRVA()), 16); + if (!fileDescr.empty()) { + os << " "; // FIXME : Handle "f" and "i" flags sometimes generated + // by link.exe in those spaces + os << fileDescr; + } }); - DenseMap ret; + DenseMap ret; for (size_t i = 0, e = syms.size(); i < e; ++i) ret[syms[i]] = std::move(str[i]); return ret; @@ -96,31 +195,117 @@ if (ec) fatal("cannot open " + config->mapFile + ": " + ec.message()); + ScopedTimer t1(totalMapTimer); + // Collect symbol info that we want to print out. - std::vector syms = getSymbols(); - SymbolMapTy sectionSyms = getSectionSyms(syms); - DenseMap symStr = getSymbolStrings(syms); + ScopedTimer t2(symbolGatherTimer); + std::vector syms; + std::vector staticSyms; + getSymbols(syms, staticSyms); + t2.stop(); - // Print out the header line. - os << "Address Size Align Out In Symbol\n"; + ScopedTimer t3(symbolStringsTimer); + DenseMap symStr = getSymbolStrings(syms); + DenseMap staticSymStr = getSymbolStrings(staticSyms); + t3.stop(); - // Print out file contents. - for (OutputSection *sec : outputSections) { - writeHeader(os, sec->getRVA(), sec->getVirtualSize(), /*align=*/pageSize); - os << sec->name << '\n'; + ScopedTimer t4(writeTimer); + SmallString<128> AppName = sys::path::filename(config->outputFile); + sys::path::replace_extension(AppName, ""); + // Print out the file header + os << " " << AppName << "\n"; + os << "\n"; + + os << " Timestamp is " << format_hex_no_prefix(config->timestamp, 8); + if (config->repro) { + os << " (Repro mode)\n"; + } else { + time_t TDS = config->timestamp; + char FormattedTime[64] = {}; + strftime(FormattedTime, 64, "%c", localtime(&TDS)); + os << " (" << FormattedTime << ")\n"; + } + + os << "\n"; + os << " Preferred load address is " + << format_hex_no_prefix(config->imageBase, 16) << "\n"; + os << "\n"; + + // Print out section table. + os << " Start Length Name Class\n"; + + for (OutputSection *sec : outputSections) { + // Merge display of chunks with same sectionName + std::vector> ChunkRanges; for (Chunk *c : sec->chunks) { auto *sc = dyn_cast(c); if (!sc) continue; - writeHeader(os, sc->getRVA(), sc->getSize(), sc->getAlignment()); - os << indent8 << sc->file->getName() << ":(" << sc->getSectionName() - << ")\n"; - for (DefinedRegular *sym : sectionSyms[sc]) - os << symStr[sym] << '\n'; + if (ChunkRanges.empty() || + c->getSectionName() != ChunkRanges.back().first->getSectionName()) { + ChunkRanges.emplace_back(sc, sc); + } else { + ChunkRanges.back().second = sc; + } + } + + const bool isCodeSection = + (sec->header.Characteristics & COFF::IMAGE_SCN_CNT_CODE) && + (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_READ) && + (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_EXECUTE); + StringRef SectionClass = (isCodeSection ? "CODE" : "DATA"); + + for (auto &cr : ChunkRanges) { + size_t size = + cr.second->getRVA() + cr.second->getSize() - cr.first->getRVA(); + + auto address = cr.first->getRVA() - sec->header.VirtualAddress; + writeHeader(os, sec->sectionIndex, address); + os << " " << format_hex_no_prefix(size, 8) << "H"; + os << " " << left_justify(cr.first->getSectionName(), 23); + os << " " << SectionClass; + os << '\n'; + } + } + + // Print out the symbols table (without static symbols) + os << "\n"; + os << " Address Publics by Value Rva+Base" + " Lib:Object\n"; + os << "\n"; + for (Defined *sym : syms) + os << symStr[sym] << '\n'; + + // Print out the entry point. + os << "\n"; + + uint16_t entrySecIndex = 0; + uint64_t entryAddress = 0; + + if (!config->noEntry) { + Defined *entry = dyn_cast_or_null(config->entry); + if (entry) { + Chunk *chunk = entry->getChunk(); + entrySecIndex = chunk->getOutputSectionIdx(); + entryAddress = + entry->getRVA() - chunk->getOutputSection()->header.VirtualAddress; } } + os << " entry point at "; + os << format("%04x:%08llx", entrySecIndex, entryAddress); + os << "\n"; + + // Print out the static symbols + os << "\n"; + os << " Static symbols\n"; + os << "\n"; + for (Defined *sym : staticSyms) + os << staticSymStr[sym] << '\n'; + + t4.stop(); + t1.stop(); } } // namespace coff Index: lld/COFF/Options.td =================================================================== --- lld/COFF/Options.td +++ lld/COFF/Options.td @@ -223,6 +223,8 @@ // Flags for debugging def lldmap : F<"lldmap">; def lldmap_file : Joined<["/", "-", "/?", "-?"], "lldmap:">; +def map : F<"map">; +def map_file : Joined<["/", "-", "/?", "-?"], "map:">; def show_timing : F<"time">; def summary : F<"summary">; Index: lld/COFF/Symbols.h =================================================================== --- lld/COFF/Symbols.h +++ lld/COFF/Symbols.h @@ -227,6 +227,7 @@ } uint64_t getRVA() { return va - config->imageBase; } + uint64_t getVA() { return va; } void setVA(uint64_t v) { va = v; } // Section index relocations against absolute symbols resolve to Index: lld/COFF/Writer.cpp =================================================================== --- lld/COFF/Writer.cpp +++ lld/COFF/Writer.cpp @@ -10,6 +10,7 @@ #include "Config.h" #include "DLL.h" #include "InputFiles.h" +#include "LLDMapFile.h" #include "MapFile.h" #include "PDB.h" #include "SymbolTable.h" @@ -622,6 +623,7 @@ } writeBuildId(); + writeLLDMapFile(outputSections); writeMapFile(outputSections); if (errorCount()) Index: lld/test/COFF/lldmap.test =================================================================== --- lld/test/COFF/lldmap.test +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: yaml2obj < %p/Inputs/ret42.yaml > %t.obj -# RUN: lld-link /out:%t.exe /entry:main /lldmap:%T/foo.map %t.obj -# RUN: FileCheck -strict-whitespace %s < %T/foo.map -# RUN: lld-link /out:%T/bar.exe /entry:main /lldmap %t.obj -# RUN: FileCheck -strict-whitespace %s < %T/bar.map - -# CHECK: Address Size Align Out In Symbol -# CHECK-NEXT: 00001000 00000006 4096 .text -# CHECK-NEXT: 00001000 00000006 16 {{.*}}lldmap.test.tmp.obj:(.text$mn) -# CHECK-NEXT: 00001000 00000000 0 main Index: lld/test/COFF/map.test =================================================================== --- /dev/null +++ lld/test/COFF/map.test @@ -0,0 +1,37 @@ +# RUN: yaml2obj < %p/Inputs/export.yaml > %t-dll.obj +# RUN: lld-link /out:%t.dll /dll %t-dll.obj /implib:%t-dll.lib \ +# RUN: /export:exportfn1 /export:exportfn2 +# RUN: yaml2obj < %p/Inputs/import.yaml > %t.obj +# RUN: lld-link /out:%t.exe /entry:main %t.obj %t-dll.lib /map:%T/foo.map /lldmap +# RUN: FileCheck -check-prefix=MAP -strict-whitespace %s < %T/foo.map +# RUN: FileCheck -check-prefix=LLDMAP -strict-whitespace %s < %t.map +# RUN: lld-link /out:%t.exe /entry:main %t.obj %t-dll.lib /map /lldmap:%T/foo-lld.map +# RUN: FileCheck -check-prefix=MAP -strict-whitespace %s < %t.map +# RUN: FileCheck -check-prefix=LLDMAP -strict-whitespace %s < %T/foo-lld.map + +# MAP: {{.*}} +# MAP-EMPTY: +# MAP-NEXT: Timestamp is {{.*}} +# MAP-EMPTY: +# MAP-NEXT: Preferred load address is 0000000140000000 +# MAP-EMPTY: +# MAP-NEXT: Start Length Name Class +# MAP-NEXT: 0001:00000000 00000008H .text CODE +# MAP-EMPTY: +# MAP-NEXT: Address Publics by Value Rva+Base Lib:Object +# MAP-EMPTY: +# MAP-NEXT: 0001:00000000 main 0000000140001000 map.test.tmp.obj +# MAP-NEXT: 0001:00000010 exportfn1 0000000140001010 map.test.tmp-dll:map.test.tmp.dll +# MAP-NEXT: 0001:00000020 exportfn2 0000000140001020 map.test.tmp-dll:map.test.tmp.dll +# MAP-NEXT: 0002:00000040 __imp_exportfn1 0000000140002040 map.test.tmp-dll:map.test.tmp.dll +# MAP-NEXT: 0002:00000048 __imp_exportfn2 0000000140002048 map.test.tmp-dll:map.test.tmp.dll +# MAP-EMPTY: +# MAP-NEXT: entry point at 0001:00000000 +# MAP-EMPTY: +# MAP-NEXT: Static symbols + + +# LLDMAP: Address Size Align Out In Symbol +# LLDMAP-NEXT: 00001000 00000026 4096 .text +# LLDMAP-NEXT: 00001000 00000008 4 {{.*}}map.test.tmp.obj:(.text) +# LLDMAP-NEXT: 00001000 00000000 0 main