diff --git a/lld/COFF/CMakeLists.txt b/lld/COFF/CMakeLists.txt --- a/lld/COFF/CMakeLists.txt +++ b/lld/COFF/CMakeLists.txt @@ -16,6 +16,7 @@ InputFiles.cpp LTO.cpp MapFile.cpp + MapFileMS.cpp MarkLive.cpp MinGW.cpp PDB.cpp diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -184,6 +184,9 @@ // Used for /lldmap. std::string mapFile; + // Used for /map. + std::string mapFileMS; + // Used for /thinlto-index-only: llvm::StringRef thinLTOIndexOnlyArg; diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -715,6 +715,18 @@ assert(arg->getOption().getID() == OPT_lldmap); StringRef outFile = config->outputFile; + return (outFile.substr(0, outFile.rfind('.')) + ".lldmap").str(); +} + +static std::string getMapFileMS(const opt::InputArgList &args) { + auto *arg = args.getLastArg(OPT_map, OPT_map_file); + if (!arg) + return ""; + if (arg->getOption().getID() == OPT_map_file) + return arg->getValue(); + + assert(arg->getOption().getID() == OPT_map); + StringRef outFile = config->outputFile; return (outFile.substr(0, outFile.rfind('.')) + ".map").str(); } @@ -1558,6 +1570,7 @@ config->warnLongSectionNames = false; config->mapFile = getMapFile(args); + config->mapFileMS = getMapFileMS(args); if (config->incremental && args.hasArg(OPT_profile)) { warn("ignoring '/incremental' due to '/profile' specification"); diff --git a/lld/COFF/MapFileMS.h b/lld/COFF/MapFileMS.h new file mode 100644 --- /dev/null +++ b/lld/COFF/MapFileMS.h @@ -0,0 +1,21 @@ +//===- MapFileMS.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_MAPFILE_MS_H +#define LLD_COFF_MAPFILE_MS_H + +#include "llvm/ADT/ArrayRef.h" + +namespace lld { +namespace coff { +class OutputSection; +void writeMapFileMS(llvm::ArrayRef outputSections); +} +} + +#endif diff --git a/lld/COFF/MapFileMS.cpp b/lld/COFF/MapFileMS.cpp new file mode 100644 --- /dev/null +++ b/lld/COFF/MapFileMS.cpp @@ -0,0 +1,221 @@ +//===- MapFileMS.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 /map option in a format close to the one generated +// by link.exe (based on observations) +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "MapFileMS.h" +#include "SymbolTable.h" +#include "Symbols.h" +#include "Writer.h" +#include "lld/Common/ErrorHandler.h" +#include "lld/Common/Threads.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace llvm::object; + +using namespace lld; +using namespace lld::coff; + +using SymbolMapTy = DenseMap>; + +static const std::string indent7 = " "; // 7 spaces + +// Print out the first three columns of a line. +static void writeHeader(raw_ostream &os, uint32_t sec, uint64_t addr) { + os << format(" %04x:%08llx", sec, addr); +} + +// 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); + + for (ImportFile *file : ImportFile::instances) { + if (!file->live) + continue; + + if (!file->thunkSym) + continue; + + if (!file->thunkLive) + continue; + + auto *thunkSym = dyn_cast(file->thunkSym); + if (thunkSym) + v.push_back(thunkSym); + + auto *impSym = dyn_cast_or_null(file->impSym); + if (impSym) + v.push_back(impSym); + } + + return v; +} + +// Returns a map from sections to their symbols. +static SymbolMapTy getSectionSyms(ArrayRef syms) { + SymbolMapTy ret; + for (Defined *s : syms) + ret[s->getChunk()].push_back(s); + + // Sort symbols by address then by name, and remove duplicates. + for (auto &it : ret) { + SmallVectorImpl &v = it.second; + std::sort(v.begin(), v.end(), [](Defined *a, Defined *b) { + if (a->getRVA() < b->getRVA()) + return true; + else + return (a->getRVA() == b->getRVA() && a->getName() < b->getName()); + }); + + auto newend = std::unique(v.begin(), v.end()); + if (newend != v.end()) + v.resize(std::distance(v.begin(), newend)); + } + 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]); + Defined *sym = syms[i]; + writeHeader(os, sym->getChunk()->getOutputSectionIdx(), sym->getRVA()); + os << indent7 << left_justify(sym->getName(), 27); + os << " " << format_hex_no_prefix((config->imageBase + sym->getRVA()), 16); + + // Extract file + 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 != nullptr) { + os << " "; // FIXME : Handle "f" and "i" flags sometimes generated by + // link.exe in those spaces + if (!file->parentName.empty()) { + SmallString<128> s = sys::path::filename(file->parentName); + sys::path::replace_extension(s, ""); + os << s << ":"; + } + os << sys::path::filename(file->getName()); + } + }); + + DenseMap ret; + for (size_t i = 0, e = syms.size(); i < e; ++i) + ret[syms[i]] = std::move(str[i]); + return ret; +} + +void coff::writeMapFileMS(ArrayRef outputSections) { + if (config->mapFileMS.empty()) + return; + + std::error_code ec; + raw_fd_ostream os(config->mapFileMS, ec, sys::fs::F_None); + if (ec) + fatal("cannot open " + config->mapFileMS + ": " + ec.message()); + + // Collect symbol info that we want to print out. + std::vector syms = getSymbols(); + SymbolMapTy sectionSyms = getSectionSyms(syms); + DenseMap symStr = getSymbolStrings(syms); + + 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; + + if (ChunkRanges.empty() || + c->getSectionName() != ChunkRanges.back().first->getSectionName()) { + ChunkRanges.emplace_back(sc, sc); + } else { + ChunkRanges.back().second = sc; + } + } + + StringRef SectionClass = (sec->name.startswith(".text") ? "CODE" : "DATA"); + for (auto &cr : ChunkRanges) { + size_t size = + cr.second->getRVA() + cr.second->getSize() - cr.first->getRVA(); + + writeHeader(os, sec->sectionIndex, cr.first->getRVA()); + os << " " << format_hex_no_prefix(size, 8) << "H"; + os << " " << left_justify(cr.first->getSectionName(), 23); + os << " " << SectionClass; + os << '\n'; + } + } + + // Print out the symbols table. + os << "\n"; + os << " Address Publics by Value Rva+Base" + " Lib:Object\n"; + os << "\n"; + + for (OutputSection *sec : outputSections) { + for (Chunk *c : sec->chunks) { + for (Defined *sym : sectionSyms[c]) + os << symStr[sym] << '\n'; + } + } +} diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td --- a/lld/COFF/Options.td +++ b/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">; diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -11,6 +11,7 @@ #include "DLL.h" #include "InputFiles.h" #include "MapFile.h" +#include "MapFileMS.h" #include "PDB.h" #include "SymbolTable.h" #include "Symbols.h" @@ -623,6 +624,7 @@ writeBuildId(); writeMapFile(outputSections); + writeMapFileMS(outputSections); if (errorCount()) return; diff --git a/lld/test/COFF/lldmap.test b/lld/test/COFF/lldmap.test deleted file mode 100644 --- a/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 diff --git a/lld/test/COFF/map.test b/lld/test/COFF/map.test new file mode 100644 --- /dev/null +++ b/lld/test/COFF/map.test @@ -0,0 +1,34 @@ +# 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-ms.map /lldmap:%T/foo-lld.map +# RUN: FileCheck -check-prefix=MAP -strict-whitespace %s < %T/foo-ms.map +# RUN: FileCheck -check-prefix=LLDMAP -strict-whitespace %s < %T/foo-lld.map +# RUN: lld-link /out:%t.exe /entry:main %t.obj %t-dll.lib /map /lldmap +# RUN: FileCheck -check-prefix=MAP -strict-whitespace %s < %t.map +# RUN: FileCheck -check-prefix=LLDMAP -strict-whitespace %s < %t.lldmap + +# 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:00001000 00000008H .text CODE +# MAP-EMPTY: +# MAP-NEXT: Address Publics by Value Rva+Base Lib:Object +# MAP-EMPTY: +# MAP-NEXT: 0001:00001000 main 0000000140001000 map.test.tmp.obj +# MAP-NEXT: 0001:00001010 exportfn1 0000000140001010 map.test.tmp-dll:map.test.tmp.dll +# MAP-NEXT: 0001:00001020 exportfn2 0000000140001020 map.test.tmp-dll:map.test.tmp.dll +# MAP-NEXT: 0002:00002040 __imp_exportfn1 0000000140002040 map.test.tmp-dll:map.test.tmp.dll +# MAP-NEXT: 0002:00002048 __imp_exportfn2 0000000140002048 map.test.tmp-dll:map.test.tmp.dll + + + +# 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