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