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 | |
42 | using namespace llvm; |
43 | using namespace llvm::object; |
44 | using namespace lld; |
45 | using namespace lld::coff; |
46 | |
47 | // Print out the first two columns of a line. |
48 | static void (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. |
55 | static 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 | |
67 | static 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. |
99 | static 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. |
143 | static DenseMap<Defined *, std::string> |
144 | getSymbolStrings(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 | |
203 | void 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 | |