| 1 | //===- MapFile.cpp --------------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file implements the /map option in the same format as link.exe |
| 10 | // (based on observations) |
| 11 | // |
| 12 | // Header (program name, timestamp info, preferred load address) |
| 13 | // |
| 14 | // Section list (Start = Section index:Base address): |
| 15 | // Start Length Name Class |
| 16 | // 0001:00001000 00000015H .text CODE |
| 17 | // |
| 18 | // Symbols list: |
| 19 | // Address Publics by Value Rva + Base Lib:Object |
| 20 | // 0001:00001000 main 0000000140001000 main.obj |
| 21 | // 0001:00001300 ?__scrt_common_main@@YAHXZ 0000000140001300 libcmt:exe_main.obj |
| 22 | // |
| 23 | // entry point at 0001:00000360 |
| 24 | // |
| 25 | // Static symbols |
| 26 | // |
| 27 | // 0000:00000000 __guard_fids__ 0000000140000000 libcmt : exe_main.obj |
| 28 | //===----------------------------------------------------------------------===// |
| 29 | |
| 30 | #include "MapFile.h" |
| 31 | #include "COFFLinkerContext.h" |
| 32 | #include "SymbolTable.h" |
| 33 | #include "Symbols.h" |
| 34 | #include "Writer.h" |
| 35 | #include "lld/Common/Timer.h" |
| 36 | #include "llvm/Support/Parallel.h" |
| 37 | #include "llvm/Support/Path.h" |
| 38 | #include "llvm/Support/TimeProfiler.h" |
| 39 | #include "llvm/Support/raw_ostream.h" |
| 40 | |
| 41 | using namespace llvm; |
| 42 | using namespace llvm::object; |
| 43 | using namespace lld; |
| 44 | using namespace lld::coff; |
| 45 | |
| 46 | // Print out the first two columns of a line. |
| 47 | static void (raw_ostream &os, uint32_t sec, uint64_t addr) { |
| 48 | os << format(Fmt: " %04x:%08llx" , Vals: sec, Vals: addr); |
| 49 | } |
| 50 | |
| 51 | // Write the time stamp with the format used by link.exe |
| 52 | // It seems identical to strftime with "%c" on msvc build, but we need a |
| 53 | // locale-agnostic version. |
| 54 | static void writeFormattedTimestamp(raw_ostream &os, time_t tds) { |
| 55 | constexpr const char *const days[7] = {"Sun" , "Mon" , "Tue" , "Wed" , |
| 56 | "Thu" , "Fri" , "Sat" }; |
| 57 | constexpr const char *const months[12] = {"Jan" , "Feb" , "Mar" , "Apr" , |
| 58 | "May" , "Jun" , "Jul" , "Aug" , |
| 59 | "Sep" , "Oct" , "Nov" , "Dec" }; |
| 60 | tm *time = localtime(timer: &tds); |
| 61 | os << format(Fmt: "%s %s %2d %02d:%02d:%02d %d" , Vals: days[time->tm_wday], |
| 62 | Vals: months[time->tm_mon], Vals: time->tm_mday, Vals: time->tm_hour, Vals: time->tm_min, |
| 63 | Vals: time->tm_sec, Vals: time->tm_year + 1900); |
| 64 | } |
| 65 | |
| 66 | static void sortUniqueSymbols(std::vector<Defined *> &syms, |
| 67 | uint64_t imageBase) { |
| 68 | // Build helper vector |
| 69 | using SortEntry = std::pair<Defined *, size_t>; |
| 70 | std::vector<SortEntry> v; |
| 71 | v.resize(new_size: syms.size()); |
| 72 | for (size_t i = 0, e = syms.size(); i < e; ++i) |
| 73 | v[i] = SortEntry(syms[i], i); |
| 74 | |
| 75 | // Remove duplicate symbol pointers |
| 76 | parallelSort(R&: v, Comp: std::less<SortEntry>()); |
| 77 | auto end = llvm::unique(R&: v, P: [](const SortEntry &a, const SortEntry &b) { |
| 78 | return a.first == b.first; |
| 79 | }); |
| 80 | v.erase(first: end, last: v.end()); |
| 81 | |
| 82 | // Sort by RVA then original order |
| 83 | parallelSort(R&: v, Comp: [imageBase](const SortEntry &a, const SortEntry &b) { |
| 84 | // Add config.imageBase to avoid comparing "negative" RVAs. |
| 85 | // This can happen with symbols of Absolute kind |
| 86 | uint64_t rvaa = imageBase + a.first->getRVA(); |
| 87 | uint64_t rvab = imageBase + b.first->getRVA(); |
| 88 | return rvaa < rvab || (rvaa == rvab && a.second < b.second); |
| 89 | }); |
| 90 | |
| 91 | syms.resize(new_size: v.size()); |
| 92 | for (size_t i = 0, e = v.size(); i < e; ++i) |
| 93 | syms[i] = v[i].first; |
| 94 | } |
| 95 | |
| 96 | // Returns the lists of all symbols that we want to print out. |
| 97 | static void getSymbols(const COFFLinkerContext &ctx, |
| 98 | std::vector<Defined *> &syms, |
| 99 | std::vector<Defined *> &staticSyms) { |
| 100 | |
| 101 | for (ObjFile *file : ctx.objFileInstances) |
| 102 | for (Symbol *b : file->getSymbols()) { |
| 103 | if (!b || !b->isLive()) |
| 104 | continue; |
| 105 | if (auto *sym = dyn_cast<DefinedCOFF>(Val: b)) { |
| 106 | COFFSymbolRef symRef = sym->getCOFFSymbol(); |
| 107 | if (!symRef.isSectionDefinition() && |
| 108 | symRef.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL) { |
| 109 | if (symRef.getStorageClass() == COFF::IMAGE_SYM_CLASS_STATIC) |
| 110 | staticSyms.push_back(x: sym); |
| 111 | else |
| 112 | syms.push_back(x: sym); |
| 113 | } |
| 114 | } else if (auto *sym = dyn_cast<Defined>(Val: b)) { |
| 115 | syms.push_back(x: sym); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | for (ImportFile *file : ctx.importFileInstances) { |
| 120 | if (!file->live) |
| 121 | continue; |
| 122 | |
| 123 | if (file->impSym) |
| 124 | syms.push_back(x: file->impSym); |
| 125 | if (file->thunkSym && file->thunkSym->isLive()) |
| 126 | syms.push_back(x: file->thunkSym); |
| 127 | if (file->auxThunkSym && file->auxThunkSym->isLive()) |
| 128 | syms.push_back(x: file->auxThunkSym); |
| 129 | if (file->impchkThunk) |
| 130 | syms.push_back(x: file->impchkThunk->sym); |
| 131 | if (file->impECSym) |
| 132 | syms.push_back(x: file->impECSym); |
| 133 | if (file->auxImpCopySym) |
| 134 | syms.push_back(x: file->auxImpCopySym); |
| 135 | } |
| 136 | |
| 137 | sortUniqueSymbols(syms, imageBase: ctx.config.imageBase); |
| 138 | sortUniqueSymbols(syms&: staticSyms, imageBase: ctx.config.imageBase); |
| 139 | } |
| 140 | |
| 141 | // Construct a map from symbols to their stringified representations. |
| 142 | static DenseMap<Defined *, std::string> |
| 143 | getSymbolStrings(const COFFLinkerContext &ctx, ArrayRef<Defined *> syms) { |
| 144 | std::vector<std::string> str(syms.size()); |
| 145 | parallelFor(Begin: (size_t)0, End: syms.size(), Fn: [&](size_t i) { |
| 146 | raw_string_ostream os(str[i]); |
| 147 | Defined *sym = syms[i]; |
| 148 | |
| 149 | uint16_t sectionIdx = 0; |
| 150 | uint64_t address = 0; |
| 151 | SmallString<128> fileDescr; |
| 152 | |
| 153 | if (auto *absSym = dyn_cast<DefinedAbsolute>(Val: sym)) { |
| 154 | address = absSym->getVA(); |
| 155 | fileDescr = "<absolute>" ; |
| 156 | } else if (isa<DefinedSynthetic>(Val: sym)) { |
| 157 | fileDescr = "<linker-defined>" ; |
| 158 | } else if (isa<DefinedCommon>(Val: sym)) { |
| 159 | fileDescr = "<common>" ; |
| 160 | } else if (Chunk *chunk = sym->getChunk()) { |
| 161 | address = sym->getRVA(); |
| 162 | if (OutputSection *sec = ctx.getOutputSection(c: chunk)) |
| 163 | address -= sec->header.VirtualAddress; |
| 164 | |
| 165 | sectionIdx = chunk->getOutputSectionIdx(); |
| 166 | |
| 167 | InputFile *file; |
| 168 | if (auto *impSym = dyn_cast<DefinedImportData>(Val: sym)) |
| 169 | file = impSym->file; |
| 170 | else if (auto *thunkSym = dyn_cast<DefinedImportThunk>(Val: sym)) |
| 171 | file = thunkSym->wrappedSym->file; |
| 172 | else |
| 173 | file = sym->getFile(); |
| 174 | |
| 175 | if (file) { |
| 176 | if (!file->parentName.empty()) { |
| 177 | fileDescr = sys::path::filename(path: file->parentName); |
| 178 | sys::path::replace_extension(path&: fileDescr, extension: "" ); |
| 179 | fileDescr += ":" ; |
| 180 | } |
| 181 | fileDescr += sys::path::filename(path: file->getName()); |
| 182 | } |
| 183 | } |
| 184 | writeHeader(os, sec: sectionIdx, addr: address); |
| 185 | os << " " ; |
| 186 | os << left_justify(Str: sym->getName(), Width: 26); |
| 187 | os << " " ; |
| 188 | os << format_hex_no_prefix(N: (ctx.config.imageBase + sym->getRVA()), Width: 16); |
| 189 | if (!fileDescr.empty()) { |
| 190 | os << " " ; // FIXME : Handle "f" and "i" flags sometimes generated |
| 191 | // by link.exe in those spaces |
| 192 | os << fileDescr; |
| 193 | } |
| 194 | }); |
| 195 | |
| 196 | DenseMap<Defined *, std::string> ret; |
| 197 | for (size_t i = 0, e = syms.size(); i < e; ++i) |
| 198 | ret[syms[i]] = std::move(str[i]); |
| 199 | return ret; |
| 200 | } |
| 201 | |
| 202 | void lld::coff::writeMapFile(COFFLinkerContext &ctx) { |
| 203 | if (ctx.config.mapFile.empty()) |
| 204 | return; |
| 205 | |
| 206 | llvm::TimeTraceScope timeScope("Map file" ); |
| 207 | std::error_code ec; |
| 208 | raw_fd_ostream os(ctx.config.mapFile, ec, sys::fs::OF_None); |
| 209 | if (ec) |
| 210 | Fatal(ctx) << "cannot open " << ctx.config.mapFile << ": " << ec.message(); |
| 211 | |
| 212 | ScopedTimer t1(ctx.totalMapTimer); |
| 213 | |
| 214 | // Collect symbol info that we want to print out. |
| 215 | ScopedTimer t2(ctx.symbolGatherTimer); |
| 216 | std::vector<Defined *> syms; |
| 217 | std::vector<Defined *> staticSyms; |
| 218 | getSymbols(ctx, syms, staticSyms); |
| 219 | t2.stop(); |
| 220 | |
| 221 | ScopedTimer t3(ctx.symbolStringsTimer); |
| 222 | DenseMap<Defined *, std::string> symStr = getSymbolStrings(ctx, syms); |
| 223 | DenseMap<Defined *, std::string> staticSymStr = |
| 224 | getSymbolStrings(ctx, syms: staticSyms); |
| 225 | t3.stop(); |
| 226 | |
| 227 | ScopedTimer t4(ctx.writeTimer); |
| 228 | SmallString<128> AppName = sys::path::filename(path: ctx.config.outputFile); |
| 229 | sys::path::replace_extension(path&: AppName, extension: "" ); |
| 230 | |
| 231 | // Print out the file header |
| 232 | os << " " << AppName << "\n" ; |
| 233 | os << "\n" ; |
| 234 | |
| 235 | os << " Timestamp is " << format_hex_no_prefix(N: ctx.config.timestamp, Width: 8) |
| 236 | << " (" ; |
| 237 | if (ctx.config.repro) { |
| 238 | os << "Repro mode" ; |
| 239 | } else { |
| 240 | writeFormattedTimestamp(os, tds: ctx.config.timestamp); |
| 241 | } |
| 242 | os << ")\n" ; |
| 243 | |
| 244 | os << "\n" ; |
| 245 | os << " Preferred load address is " |
| 246 | << format_hex_no_prefix(N: ctx.config.imageBase, Width: 16) << "\n" ; |
| 247 | os << "\n" ; |
| 248 | |
| 249 | // Print out section table. |
| 250 | os << " Start Length Name Class\n" ; |
| 251 | |
| 252 | for (OutputSection *sec : ctx.outputSections) { |
| 253 | // Merge display of chunks with same sectionName |
| 254 | std::vector<std::pair<SectionChunk *, SectionChunk *>> ChunkRanges; |
| 255 | for (Chunk *c : sec->chunks) { |
| 256 | auto *sc = dyn_cast<SectionChunk>(Val: c); |
| 257 | if (!sc) |
| 258 | continue; |
| 259 | |
| 260 | if (ChunkRanges.empty() || |
| 261 | c->getSectionName() != ChunkRanges.back().first->getSectionName()) { |
| 262 | ChunkRanges.emplace_back(args&: sc, args&: sc); |
| 263 | } else { |
| 264 | ChunkRanges.back().second = sc; |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | const bool isCodeSection = |
| 269 | (sec->header.Characteristics & COFF::IMAGE_SCN_CNT_CODE) && |
| 270 | (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_READ) && |
| 271 | (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_EXECUTE); |
| 272 | StringRef SectionClass = (isCodeSection ? "CODE" : "DATA" ); |
| 273 | |
| 274 | for (auto &cr : ChunkRanges) { |
| 275 | size_t size = |
| 276 | cr.second->getRVA() + cr.second->getSize() - cr.first->getRVA(); |
| 277 | |
| 278 | auto address = cr.first->getRVA() - sec->header.VirtualAddress; |
| 279 | writeHeader(os, sec: sec->sectionIndex, addr: address); |
| 280 | os << " " << format_hex_no_prefix(N: size, Width: 8) << "H" ; |
| 281 | os << " " << left_justify(Str: cr.first->getSectionName(), Width: 23); |
| 282 | os << " " << SectionClass; |
| 283 | os << '\n'; |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | // Print out the symbols table (without static symbols) |
| 288 | os << "\n" ; |
| 289 | os << " Address Publics by Value Rva+Base" |
| 290 | " Lib:Object\n" ; |
| 291 | os << "\n" ; |
| 292 | for (Defined *sym : syms) |
| 293 | os << symStr[sym] << '\n'; |
| 294 | |
| 295 | // Print out the entry point. |
| 296 | os << "\n" ; |
| 297 | |
| 298 | uint16_t entrySecIndex = 0; |
| 299 | uint64_t entryAddress = 0; |
| 300 | |
| 301 | if (!ctx.config.noEntry) { |
| 302 | Defined *entry = dyn_cast_or_null<Defined>(Val: ctx.symtab.entry); |
| 303 | if (entry) { |
| 304 | Chunk *chunk = entry->getChunk(); |
| 305 | entrySecIndex = chunk->getOutputSectionIdx(); |
| 306 | entryAddress = |
| 307 | entry->getRVA() - ctx.getOutputSection(c: chunk)->header.VirtualAddress; |
| 308 | } |
| 309 | } |
| 310 | os << " entry point at " ; |
| 311 | os << format(Fmt: "%04x:%08llx" , Vals: entrySecIndex, Vals: entryAddress); |
| 312 | os << "\n" ; |
| 313 | |
| 314 | // Print out the static symbols |
| 315 | os << "\n" ; |
| 316 | os << " Static symbols\n" ; |
| 317 | os << "\n" ; |
| 318 | for (Defined *sym : staticSyms) |
| 319 | os << staticSymStr[sym] << '\n'; |
| 320 | |
| 321 | // Print out the exported functions |
| 322 | if (ctx.config.mapInfo) { |
| 323 | os << "\n" ; |
| 324 | os << " Exports\n" ; |
| 325 | os << "\n" ; |
| 326 | os << " ordinal name\n\n" ; |
| 327 | for (Export &e : ctx.symtab.exports) { |
| 328 | os << format(Fmt: " %7d" , Vals: e.ordinal) << " " << e.name << "\n" ; |
| 329 | if (!e.extName.empty() && e.extName != e.name) |
| 330 | os << " exported name: " << e.extName << "\n" ; |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | t4.stop(); |
| 335 | t1.stop(); |
| 336 | } |
| 337 | |