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 | |