xref: /freebsd/contrib/llvm-project/lld/COFF/MapFile.cpp (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
10b57cec5SDimitry Andric //===- MapFile.cpp --------------------------------------------------------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
95ffd83dbSDimitry Andric // This file implements the /map option in the same format as link.exe
105ffd83dbSDimitry Andric // (based on observations)
110b57cec5SDimitry Andric //
125ffd83dbSDimitry Andric // Header (program name, timestamp info, preferred load address)
130b57cec5SDimitry Andric //
145ffd83dbSDimitry Andric // Section list (Start = Section index:Base address):
155ffd83dbSDimitry Andric // Start         Length     Name                   Class
165ffd83dbSDimitry Andric // 0001:00001000 00000015H .text                   CODE
175ffd83dbSDimitry Andric //
185ffd83dbSDimitry Andric // Symbols list:
195ffd83dbSDimitry Andric // Address        Publics by Value    Rva + Base          Lib:Object
205ffd83dbSDimitry Andric // 0001:00001000  main                 0000000140001000    main.obj
215ffd83dbSDimitry Andric // 0001:00001300  ?__scrt_common_main@@YAHXZ  0000000140001300 libcmt:exe_main.obj
225ffd83dbSDimitry Andric //
235ffd83dbSDimitry Andric // entry point at        0001:00000360
245ffd83dbSDimitry Andric //
255ffd83dbSDimitry Andric // Static symbols
265ffd83dbSDimitry Andric //
275ffd83dbSDimitry Andric // 0000:00000000  __guard_fids__       0000000140000000     libcmt : exe_main.obj
280b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
290b57cec5SDimitry Andric 
300b57cec5SDimitry Andric #include "MapFile.h"
31349cc55cSDimitry Andric #include "COFFLinkerContext.h"
320b57cec5SDimitry Andric #include "SymbolTable.h"
330b57cec5SDimitry Andric #include "Symbols.h"
340b57cec5SDimitry Andric #include "Writer.h"
350b57cec5SDimitry Andric #include "lld/Common/ErrorHandler.h"
365ffd83dbSDimitry Andric #include "lld/Common/Timer.h"
375ffd83dbSDimitry Andric #include "llvm/Support/Parallel.h"
385ffd83dbSDimitry Andric #include "llvm/Support/Path.h"
39*5f757f3fSDimitry Andric #include "llvm/Support/TimeProfiler.h"
400b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h"
410b57cec5SDimitry Andric 
420b57cec5SDimitry Andric using namespace llvm;
430b57cec5SDimitry Andric using namespace llvm::object;
445ffd83dbSDimitry Andric using namespace lld;
455ffd83dbSDimitry Andric using namespace lld::coff;
460b57cec5SDimitry Andric 
475ffd83dbSDimitry Andric // Print out the first two columns of a line.
485ffd83dbSDimitry Andric static void writeHeader(raw_ostream &os, uint32_t sec, uint64_t addr) {
495ffd83dbSDimitry Andric   os << format(" %04x:%08llx", sec, addr);
500b57cec5SDimitry Andric }
510b57cec5SDimitry Andric 
525ffd83dbSDimitry Andric // Write the time stamp with the format used by link.exe
535ffd83dbSDimitry Andric // It seems identical to strftime with "%c" on msvc build, but we need a
545ffd83dbSDimitry Andric // locale-agnostic version.
555ffd83dbSDimitry Andric static void writeFormattedTimestamp(raw_ostream &os, time_t tds) {
565ffd83dbSDimitry Andric   constexpr const char *const days[7] = {"Sun", "Mon", "Tue", "Wed",
575ffd83dbSDimitry Andric                                          "Thu", "Fri", "Sat"};
585ffd83dbSDimitry Andric   constexpr const char *const months[12] = {"Jan", "Feb", "Mar", "Apr",
595ffd83dbSDimitry Andric                                             "May", "Jun", "Jul", "Aug",
605ffd83dbSDimitry Andric                                             "Sep", "Oct", "Nov", "Dec"};
615ffd83dbSDimitry Andric   tm *time = localtime(&tds);
625ffd83dbSDimitry Andric   os << format("%s %s %2d %02d:%02d:%02d %d", days[time->tm_wday],
635ffd83dbSDimitry Andric                months[time->tm_mon], time->tm_mday, time->tm_hour, time->tm_min,
645ffd83dbSDimitry Andric                time->tm_sec, time->tm_year + 1900);
650b57cec5SDimitry Andric }
660b57cec5SDimitry Andric 
67bdd1243dSDimitry Andric static void sortUniqueSymbols(std::vector<Defined *> &syms,
68bdd1243dSDimitry Andric                               uint64_t imageBase) {
695ffd83dbSDimitry Andric   // Build helper vector
705ffd83dbSDimitry Andric   using SortEntry = std::pair<Defined *, size_t>;
715ffd83dbSDimitry Andric   std::vector<SortEntry> v;
725ffd83dbSDimitry Andric   v.resize(syms.size());
735ffd83dbSDimitry Andric   for (size_t i = 0, e = syms.size(); i < e; ++i)
745ffd83dbSDimitry Andric     v[i] = SortEntry(syms[i], i);
750b57cec5SDimitry Andric 
765ffd83dbSDimitry Andric   // Remove duplicate symbol pointers
775ffd83dbSDimitry Andric   parallelSort(v, std::less<SortEntry>());
785ffd83dbSDimitry Andric   auto end = std::unique(v.begin(), v.end(),
795ffd83dbSDimitry Andric                          [](const SortEntry &a, const SortEntry &b) {
805ffd83dbSDimitry Andric                            return a.first == b.first;
810b57cec5SDimitry Andric                          });
825ffd83dbSDimitry Andric   v.erase(end, v.end());
835ffd83dbSDimitry Andric 
845ffd83dbSDimitry Andric   // Sort by RVA then original order
85bdd1243dSDimitry Andric   parallelSort(v, [imageBase](const SortEntry &a, const SortEntry &b) {
86bdd1243dSDimitry Andric     // Add config.imageBase to avoid comparing "negative" RVAs.
875ffd83dbSDimitry Andric     // This can happen with symbols of Absolute kind
88bdd1243dSDimitry Andric     uint64_t rvaa = imageBase + a.first->getRVA();
89bdd1243dSDimitry Andric     uint64_t rvab = imageBase + b.first->getRVA();
905ffd83dbSDimitry Andric     return rvaa < rvab || (rvaa == rvab && a.second < b.second);
915ffd83dbSDimitry Andric   });
925ffd83dbSDimitry Andric 
935ffd83dbSDimitry Andric   syms.resize(v.size());
945ffd83dbSDimitry Andric   for (size_t i = 0, e = v.size(); i < e; ++i)
955ffd83dbSDimitry Andric     syms[i] = v[i].first;
960b57cec5SDimitry Andric }
975ffd83dbSDimitry Andric 
985ffd83dbSDimitry Andric // Returns the lists of all symbols that we want to print out.
99349cc55cSDimitry Andric static void getSymbols(const COFFLinkerContext &ctx,
100349cc55cSDimitry Andric                        std::vector<Defined *> &syms,
1015ffd83dbSDimitry Andric                        std::vector<Defined *> &staticSyms) {
1025ffd83dbSDimitry Andric 
103349cc55cSDimitry Andric   for (ObjFile *file : ctx.objFileInstances)
1045ffd83dbSDimitry Andric     for (Symbol *b : file->getSymbols()) {
1055ffd83dbSDimitry Andric       if (!b || !b->isLive())
1065ffd83dbSDimitry Andric         continue;
1075ffd83dbSDimitry Andric       if (auto *sym = dyn_cast<DefinedCOFF>(b)) {
1085ffd83dbSDimitry Andric         COFFSymbolRef symRef = sym->getCOFFSymbol();
1095ffd83dbSDimitry Andric         if (!symRef.isSectionDefinition() &&
1105ffd83dbSDimitry Andric             symRef.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL) {
1115ffd83dbSDimitry Andric           if (symRef.getStorageClass() == COFF::IMAGE_SYM_CLASS_STATIC)
1125ffd83dbSDimitry Andric             staticSyms.push_back(sym);
1135ffd83dbSDimitry Andric           else
1145ffd83dbSDimitry Andric             syms.push_back(sym);
1155ffd83dbSDimitry Andric         }
1165ffd83dbSDimitry Andric       } else if (auto *sym = dyn_cast<Defined>(b)) {
1175ffd83dbSDimitry Andric         syms.push_back(sym);
1185ffd83dbSDimitry Andric       }
1195ffd83dbSDimitry Andric     }
1205ffd83dbSDimitry Andric 
121349cc55cSDimitry Andric   for (ImportFile *file : ctx.importFileInstances) {
1225ffd83dbSDimitry Andric     if (!file->live)
1235ffd83dbSDimitry Andric       continue;
1245ffd83dbSDimitry Andric 
1255ffd83dbSDimitry Andric     if (!file->thunkSym)
1265ffd83dbSDimitry Andric       continue;
1275ffd83dbSDimitry Andric 
1285ffd83dbSDimitry Andric     if (!file->thunkLive)
1295ffd83dbSDimitry Andric       continue;
1305ffd83dbSDimitry Andric 
1315ffd83dbSDimitry Andric     if (auto *thunkSym = dyn_cast<Defined>(file->thunkSym))
1325ffd83dbSDimitry Andric       syms.push_back(thunkSym);
1335ffd83dbSDimitry Andric 
1345ffd83dbSDimitry Andric     if (auto *impSym = dyn_cast_or_null<Defined>(file->impSym))
1355ffd83dbSDimitry Andric       syms.push_back(impSym);
1365ffd83dbSDimitry Andric   }
1375ffd83dbSDimitry Andric 
138bdd1243dSDimitry Andric   sortUniqueSymbols(syms, ctx.config.imageBase);
139bdd1243dSDimitry Andric   sortUniqueSymbols(staticSyms, ctx.config.imageBase);
1400b57cec5SDimitry Andric }
1410b57cec5SDimitry Andric 
1420b57cec5SDimitry Andric // Construct a map from symbols to their stringified representations.
1435ffd83dbSDimitry Andric static DenseMap<Defined *, std::string>
144349cc55cSDimitry Andric getSymbolStrings(const COFFLinkerContext &ctx, ArrayRef<Defined *> syms) {
1450b57cec5SDimitry Andric   std::vector<std::string> str(syms.size());
14681ad6265SDimitry Andric   parallelFor((size_t)0, syms.size(), [&](size_t i) {
1470b57cec5SDimitry Andric     raw_string_ostream os(str[i]);
1485ffd83dbSDimitry Andric     Defined *sym = syms[i];
1495ffd83dbSDimitry Andric 
1505ffd83dbSDimitry Andric     uint16_t sectionIdx = 0;
1515ffd83dbSDimitry Andric     uint64_t address = 0;
1525ffd83dbSDimitry Andric     SmallString<128> fileDescr;
1535ffd83dbSDimitry Andric 
1545ffd83dbSDimitry Andric     if (auto *absSym = dyn_cast<DefinedAbsolute>(sym)) {
1555ffd83dbSDimitry Andric       address = absSym->getVA();
1565ffd83dbSDimitry Andric       fileDescr = "<absolute>";
1575ffd83dbSDimitry Andric     } else if (isa<DefinedSynthetic>(sym)) {
1585ffd83dbSDimitry Andric       fileDescr = "<linker-defined>";
1595ffd83dbSDimitry Andric     } else if (isa<DefinedCommon>(sym)) {
1605ffd83dbSDimitry Andric       fileDescr = "<common>";
1615ffd83dbSDimitry Andric     } else if (Chunk *chunk = sym->getChunk()) {
1625ffd83dbSDimitry Andric       address = sym->getRVA();
163349cc55cSDimitry Andric       if (OutputSection *sec = ctx.getOutputSection(chunk))
1645ffd83dbSDimitry Andric         address -= sec->header.VirtualAddress;
1655ffd83dbSDimitry Andric 
1665ffd83dbSDimitry Andric       sectionIdx = chunk->getOutputSectionIdx();
1675ffd83dbSDimitry Andric 
1685ffd83dbSDimitry Andric       InputFile *file;
1695ffd83dbSDimitry Andric       if (auto *impSym = dyn_cast<DefinedImportData>(sym))
1705ffd83dbSDimitry Andric         file = impSym->file;
1715ffd83dbSDimitry Andric       else if (auto *thunkSym = dyn_cast<DefinedImportThunk>(sym))
1725ffd83dbSDimitry Andric         file = thunkSym->wrappedSym->file;
1735ffd83dbSDimitry Andric       else
1745ffd83dbSDimitry Andric         file = sym->getFile();
1755ffd83dbSDimitry Andric 
1765ffd83dbSDimitry Andric       if (file) {
1775ffd83dbSDimitry Andric         if (!file->parentName.empty()) {
1785ffd83dbSDimitry Andric           fileDescr = sys::path::filename(file->parentName);
1795ffd83dbSDimitry Andric           sys::path::replace_extension(fileDescr, "");
1805ffd83dbSDimitry Andric           fileDescr += ":";
1815ffd83dbSDimitry Andric         }
1825ffd83dbSDimitry Andric         fileDescr += sys::path::filename(file->getName());
1835ffd83dbSDimitry Andric       }
1845ffd83dbSDimitry Andric     }
1855ffd83dbSDimitry Andric     writeHeader(os, sectionIdx, address);
1865ffd83dbSDimitry Andric     os << "       ";
1875ffd83dbSDimitry Andric     os << left_justify(sym->getName(), 26);
1885ffd83dbSDimitry Andric     os << " ";
189bdd1243dSDimitry Andric     os << format_hex_no_prefix((ctx.config.imageBase + sym->getRVA()), 16);
1905ffd83dbSDimitry Andric     if (!fileDescr.empty()) {
1915ffd83dbSDimitry Andric       os << "     "; // FIXME : Handle "f" and "i" flags sometimes generated
1925ffd83dbSDimitry Andric                      // by link.exe in those spaces
1935ffd83dbSDimitry Andric       os << fileDescr;
1945ffd83dbSDimitry Andric     }
1950b57cec5SDimitry Andric   });
1960b57cec5SDimitry Andric 
1975ffd83dbSDimitry Andric   DenseMap<Defined *, std::string> ret;
1980b57cec5SDimitry Andric   for (size_t i = 0, e = syms.size(); i < e; ++i)
1990b57cec5SDimitry Andric     ret[syms[i]] = std::move(str[i]);
2000b57cec5SDimitry Andric   return ret;
2010b57cec5SDimitry Andric }
2020b57cec5SDimitry Andric 
203349cc55cSDimitry Andric void lld::coff::writeMapFile(COFFLinkerContext &ctx) {
204bdd1243dSDimitry Andric   if (ctx.config.mapFile.empty())
2050b57cec5SDimitry Andric     return;
2060b57cec5SDimitry Andric 
207*5f757f3fSDimitry Andric   llvm::TimeTraceScope timeScope("Map file");
2080b57cec5SDimitry Andric   std::error_code ec;
209bdd1243dSDimitry Andric   raw_fd_ostream os(ctx.config.mapFile, ec, sys::fs::OF_None);
2100b57cec5SDimitry Andric   if (ec)
211bdd1243dSDimitry Andric     fatal("cannot open " + ctx.config.mapFile + ": " + ec.message());
2120b57cec5SDimitry Andric 
213349cc55cSDimitry Andric   ScopedTimer t1(ctx.totalMapTimer);
2145ffd83dbSDimitry Andric 
2150b57cec5SDimitry Andric   // Collect symbol info that we want to print out.
216349cc55cSDimitry Andric   ScopedTimer t2(ctx.symbolGatherTimer);
2175ffd83dbSDimitry Andric   std::vector<Defined *> syms;
2185ffd83dbSDimitry Andric   std::vector<Defined *> staticSyms;
219349cc55cSDimitry Andric   getSymbols(ctx, syms, staticSyms);
2205ffd83dbSDimitry Andric   t2.stop();
2210b57cec5SDimitry Andric 
222349cc55cSDimitry Andric   ScopedTimer t3(ctx.symbolStringsTimer);
223349cc55cSDimitry Andric   DenseMap<Defined *, std::string> symStr = getSymbolStrings(ctx, syms);
224349cc55cSDimitry Andric   DenseMap<Defined *, std::string> staticSymStr =
225349cc55cSDimitry Andric       getSymbolStrings(ctx, staticSyms);
2265ffd83dbSDimitry Andric   t3.stop();
2270b57cec5SDimitry Andric 
228349cc55cSDimitry Andric   ScopedTimer t4(ctx.writeTimer);
229bdd1243dSDimitry Andric   SmallString<128> AppName = sys::path::filename(ctx.config.outputFile);
2305ffd83dbSDimitry Andric   sys::path::replace_extension(AppName, "");
2315ffd83dbSDimitry Andric 
2325ffd83dbSDimitry Andric   // Print out the file header
2335ffd83dbSDimitry Andric   os << " " << AppName << "\n";
2345ffd83dbSDimitry Andric   os << "\n";
2355ffd83dbSDimitry Andric 
236bdd1243dSDimitry Andric   os << " Timestamp is " << format_hex_no_prefix(ctx.config.timestamp, 8)
237bdd1243dSDimitry Andric      << " (";
238bdd1243dSDimitry Andric   if (ctx.config.repro) {
2395ffd83dbSDimitry Andric     os << "Repro mode";
2405ffd83dbSDimitry Andric   } else {
241bdd1243dSDimitry Andric     writeFormattedTimestamp(os, ctx.config.timestamp);
2425ffd83dbSDimitry Andric   }
2435ffd83dbSDimitry Andric   os << ")\n";
2445ffd83dbSDimitry Andric 
2455ffd83dbSDimitry Andric   os << "\n";
2465ffd83dbSDimitry Andric   os << " Preferred load address is "
247bdd1243dSDimitry Andric      << format_hex_no_prefix(ctx.config.imageBase, 16) << "\n";
2485ffd83dbSDimitry Andric   os << "\n";
2495ffd83dbSDimitry Andric 
2505ffd83dbSDimitry Andric   // Print out section table.
2515ffd83dbSDimitry Andric   os << " Start         Length     Name                   Class\n";
2525ffd83dbSDimitry Andric 
253349cc55cSDimitry Andric   for (OutputSection *sec : ctx.outputSections) {
2545ffd83dbSDimitry Andric     // Merge display of chunks with same sectionName
2555ffd83dbSDimitry Andric     std::vector<std::pair<SectionChunk *, SectionChunk *>> ChunkRanges;
2560b57cec5SDimitry Andric     for (Chunk *c : sec->chunks) {
2570b57cec5SDimitry Andric       auto *sc = dyn_cast<SectionChunk>(c);
2580b57cec5SDimitry Andric       if (!sc)
2590b57cec5SDimitry Andric         continue;
2600b57cec5SDimitry Andric 
2615ffd83dbSDimitry Andric       if (ChunkRanges.empty() ||
2625ffd83dbSDimitry Andric           c->getSectionName() != ChunkRanges.back().first->getSectionName()) {
2635ffd83dbSDimitry Andric         ChunkRanges.emplace_back(sc, sc);
2645ffd83dbSDimitry Andric       } else {
2655ffd83dbSDimitry Andric         ChunkRanges.back().second = sc;
2660b57cec5SDimitry Andric       }
2670b57cec5SDimitry Andric     }
26885868e8aSDimitry Andric 
2695ffd83dbSDimitry Andric     const bool isCodeSection =
2705ffd83dbSDimitry Andric         (sec->header.Characteristics & COFF::IMAGE_SCN_CNT_CODE) &&
2715ffd83dbSDimitry Andric         (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_READ) &&
2725ffd83dbSDimitry Andric         (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_EXECUTE);
2735ffd83dbSDimitry Andric     StringRef SectionClass = (isCodeSection ? "CODE" : "DATA");
2745ffd83dbSDimitry Andric 
2755ffd83dbSDimitry Andric     for (auto &cr : ChunkRanges) {
2765ffd83dbSDimitry Andric       size_t size =
2775ffd83dbSDimitry Andric           cr.second->getRVA() + cr.second->getSize() - cr.first->getRVA();
2785ffd83dbSDimitry Andric 
2795ffd83dbSDimitry Andric       auto address = cr.first->getRVA() - sec->header.VirtualAddress;
2805ffd83dbSDimitry Andric       writeHeader(os, sec->sectionIndex, address);
2815ffd83dbSDimitry Andric       os << " " << format_hex_no_prefix(size, 8) << "H";
2825ffd83dbSDimitry Andric       os << " " << left_justify(cr.first->getSectionName(), 23);
2835ffd83dbSDimitry Andric       os << " " << SectionClass;
2845ffd83dbSDimitry Andric       os << '\n';
2855ffd83dbSDimitry Andric     }
2865ffd83dbSDimitry Andric   }
2875ffd83dbSDimitry Andric 
2885ffd83dbSDimitry Andric   // Print out the symbols table (without static symbols)
2895ffd83dbSDimitry Andric   os << "\n";
2905ffd83dbSDimitry Andric   os << "  Address         Publics by Value              Rva+Base"
2915ffd83dbSDimitry Andric         "               Lib:Object\n";
2925ffd83dbSDimitry Andric   os << "\n";
2935ffd83dbSDimitry Andric   for (Defined *sym : syms)
2945ffd83dbSDimitry Andric     os << symStr[sym] << '\n';
2955ffd83dbSDimitry Andric 
2965ffd83dbSDimitry Andric   // Print out the entry point.
2975ffd83dbSDimitry Andric   os << "\n";
2985ffd83dbSDimitry Andric 
2995ffd83dbSDimitry Andric   uint16_t entrySecIndex = 0;
3005ffd83dbSDimitry Andric   uint64_t entryAddress = 0;
3015ffd83dbSDimitry Andric 
302bdd1243dSDimitry Andric   if (!ctx.config.noEntry) {
303bdd1243dSDimitry Andric     Defined *entry = dyn_cast_or_null<Defined>(ctx.config.entry);
3045ffd83dbSDimitry Andric     if (entry) {
3055ffd83dbSDimitry Andric       Chunk *chunk = entry->getChunk();
3065ffd83dbSDimitry Andric       entrySecIndex = chunk->getOutputSectionIdx();
3075ffd83dbSDimitry Andric       entryAddress =
308349cc55cSDimitry Andric           entry->getRVA() - ctx.getOutputSection(chunk)->header.VirtualAddress;
3095ffd83dbSDimitry Andric     }
3105ffd83dbSDimitry Andric   }
3115ffd83dbSDimitry Andric   os << " entry point at         ";
3125ffd83dbSDimitry Andric   os << format("%04x:%08llx", entrySecIndex, entryAddress);
3135ffd83dbSDimitry Andric   os << "\n";
3145ffd83dbSDimitry Andric 
3155ffd83dbSDimitry Andric   // Print out the static symbols
3165ffd83dbSDimitry Andric   os << "\n";
3175ffd83dbSDimitry Andric   os << " Static symbols\n";
3185ffd83dbSDimitry Andric   os << "\n";
3195ffd83dbSDimitry Andric   for (Defined *sym : staticSyms)
3205ffd83dbSDimitry Andric     os << staticSymStr[sym] << '\n';
3215ffd83dbSDimitry Andric 
322bdd1243dSDimitry Andric   // Print out the exported functions
323bdd1243dSDimitry Andric   if (ctx.config.mapInfo) {
324bdd1243dSDimitry Andric     os << "\n";
325bdd1243dSDimitry Andric     os << " Exports\n";
326bdd1243dSDimitry Andric     os << "\n";
327bdd1243dSDimitry Andric     os << "  ordinal    name\n\n";
328bdd1243dSDimitry Andric     for (Export &e : ctx.config.exports) {
329bdd1243dSDimitry Andric       os << format("  %7d", e.ordinal) << "    " << e.name << "\n";
330bdd1243dSDimitry Andric       if (!e.extName.empty() && e.extName != e.name)
331bdd1243dSDimitry Andric         os << "               exported name: " << e.extName << "\n";
332bdd1243dSDimitry Andric     }
333bdd1243dSDimitry Andric   }
334bdd1243dSDimitry Andric 
3355ffd83dbSDimitry Andric   t4.stop();
3365ffd83dbSDimitry Andric   t1.stop();
3375ffd83dbSDimitry Andric }
338